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
+15
View File
@@ -0,0 +1,15 @@
/dist/
/downloads/
/sources/
/work/
/armbian/.work/
/media/out/
# Public keys are source inputs; private credentials never belong in Git.
/keys/*
!/keys/*.pub
*.pem
*.key
.env
.env.*
!.env.example
+105
View File
@@ -0,0 +1,105 @@
# Physical acceptance test
The image has passed structural checks. Run this checklist on the RK1 attached
to the actual carrier and TV before treating it as the permanent media center.
## 1. Provisioning and services
SSH over Ethernet and wait for provisioning to finish:
```bash
ssh [email protected]
sudo systemctl is-active rk1-media-identity.service
sudo systemctl is-active rk1-media-provision.service
sudo systemctl is-active kodi-rk.service
sudo journalctl -u rk1-media-provision.service -u kodi-rk.service -b --no-pager
```
All three should be active/successful and Kodi should be visible. The first
boot may remain on a console while packages install.
## 2. One-command hardware qualification
```bash
sudo rk1-media-selftest
sudo rk1-media-selftest --strict --json | tee /tmp/rk1-selftest.json
```
The normal run performs short Vulkan, hardware encode, and NPU workloads when
applicable. Strict mode treats a missing/disconnected advertised device as a
failure. Confirm Panthor, an HDMI connector with EDID/modes, RKMPP decoder and
encoder bindings, RGA2/RGA3, RKNPU, HDMI ALSA, Ethernet, eMMC, and NVMe.
Run the NPU directly across all three cores:
```bash
rknn-inference-test --core all --iterations 10
```
## 3. 4K media acceptance
Use media you legally possess, with filenames containing `h264`, `hevc`,
`vp9`, and `av1` so the self-test can select them:
```bash
sudo rk1-media-selftest --media-dir /path/to/test-clips
ffmpeg-rk -hide_banner -decoders | grep rkmpp
ffmpeg-rk -hide_banner -encoders | grep rkmpp
ffmpeg-rk -hide_banner -filters | grep rkrga
```
In Kodi, test at least 20 minutes each of 4K60 SDR H.264/HEVC, VP9, and AV1,
plus seeking, pause/resume, subtitles, and HDMI PCM audio. Watch CPU usage and
temperature; hardware-decoded playback should not pin all CPU cores. Also test
CEC with `cec-client -l` and the intended remote/IR receiver.
HDR, lossless audio passthrough, HDCP/streaming-service DRM, and unusual
10/12-bit chroma formats are separate acceptance items; this build does not
claim them merely because 4K60 SDR succeeds.
## 4. Blank-HDMI decision tree
From SSH:
```bash
for connector in /sys/class/drm/card*-HDMI-A-*; do
printf '%s: ' "$connector"
cat "$connector/status"
cat "$connector/modes"
done
sudo journalctl -b -k | grep -Ei 'drm|hdmi|edid|vop|hdptx|panthor'
sudo journalctl -b -u kodi-rk.service --no-pager
```
- No HDMI connector: DTB/kernel probe issue; collect diagnostics.
- Connector says `disconnected`: focus on carrier HPD/DDC, cable, TV input, and
the early-RK1 hardware note below—not Kodi.
- `connected` but no modes: EDID/DDC or sink/cable issue.
- Modes exist but Kodi loops: inspect DRM permissions/master and Kodi logs at
`/var/lib/kodi/.kodi/temp/kodi.log`.
- Kodi runs but the screen is black: stop Kodi and test direct KMS/Vulkan, then
collect a bundle.
```bash
sudo systemctl stop kodi-rk.service
sudo kmscube
sudo systemctl start kodi-rk.service
sudo rk1-media-diagnostics
```
The diagnostic command writes locally and does not upload anything.
## Carrier/module caveat
The supplied Amazon ASIN is marketed as a KLAYERS Jetson **Orin** Nano/NX HDMI
base board, rather than NVIDIAs older Jetson Nano carrier. Its advertised
layout matches the relevant Jetson-compatible RK1 HDMI lane/control pins, but a
public KLAYERS schematic was not available, so physical validation is still
required.
Turing documents that modules from its initial pool of 100 RK1s can fail HDMI
with some Jetson-compatible carriers and may need two solder bridges. Do not
guess the bridge locations: verify the module batch and obtain Turings exact
hardware guidance before modifying it:
https://docs.turingpi.com/docs/turing-rk1-flashing-os
+65
View File
@@ -0,0 +1,65 @@
# Design and source lock
## Production artifact inputs
- Armbian Community 26.11.0-trunk.1 Turing RK1 Trixie minimal image, vendor
kernel 6.1.115: SHA-256
`cbbd16d90786331fbfbbb695614a53f5e87ea441ea37b425d986334318190cf6`.
- Repacked Kodi/RKMPP appliance package: SHA-256
`dd0ca68696aa35b1998f9c9e131fdf8f0d1ef4ba6f58a36d0d99f0625d47f430`.
- RKNN Toolkit2 v2.3.2 commit
`42aa1d426c0a9e0869b6374edba009f7208a1926`, with per-asset checksums.
- 140 Debian Trixie dependency packages locked by filename, size, SHA-256, and
signed-index URL in `packages/debian-packages.lock.tsv`.
The upstream Kodi release recipe followed moving branches, so its immutable
release asset SHA-256 is the authoritative input for the fast repack path. A
fully source-pinned native build recipe is retained with these commits:
- MPP `a9380ef333102ac318628f83b5f7a460d377749e`
- RGA `1d330cc28551943bed3380261a5a9c6fbd58ff53`
- FFmpeg-Rockchip `d90e3a1c18d7929383cf88c1b3da2e2d1c966cbf`
- dav1d `b546257f770768b2c88258c533da38b91a06f737`
- libdisplay-info `f1b75310181e364f97a16464efcd92bc82e57ccc`
- Kodi `9c56bf593441a6c246fdc6931177e71f349ee2bc`
The source-level Armbian base recipe separately pins:
- Armbian framework `ea18947bed789c829a260df129e815c353c14908`
- Rockchip kernel 6.1.115 `5280f9b4336199c4025c8eed894d2b4e2268dcc6`
- U-Boot `ece349ade2973e220f524ce59e59711cc919263f`
- RKBin `452f49a987097d7ca675811e51a873b42ebd101f`
- Armbian firmware `d9846710f54da5e4383e2d67311819659ac2cf5c`
## Trust and update boundaries
The vendor kernel, Rockchip binary boot components, RKNN runtime, and upstream
Kodi media integration are trusted community/vendor inputs, not Debian or
LibreELEC-supported RK1 components. Each is pinned and isolated, but pins do not
turn proprietary firmware/runtime blobs into auditable source.
Debian security updates run automatically. Kernel, DTB, U-Boot/BSP, Mesa/media,
and RKNN changes are deliberately excluded from unattended upgrades because
they must move together and pass the physical acceptance suite. Rebuild and
requalify the image to update those layers.
The open GPU and proprietary GPU stacks are mutually exclusive in practice:
Panthor/Panfrost/PanVK is selected for KMS, OpenGL, Vulkan, and Kodi stability;
Mali kbase/libMali/OpenCL is not installed. NPU compute is provided separately
by RKNN.
## Security posture
- No default or image-time password remains; roots shadow field is exactly
`!` before the raw image is emitted.
- SSH accepts public keys only, refuses root, and is ordered after unique host
key generation. The identity unit creates SSH's `/run/sshd` runtime directory
before validating the daemon configuration; `ssh.service` recreates it for
its own lifetime. Armbians later first-run key regeneration is disabled.
- The build rejects private-key input and authorized-key options.
- Kodi runs under a locked system user with device groups and systemd sandboxing.
- Package and injected-directory modes are normalized and asserted so a
collaborative host umask cannot make `/`, `/etc`, `/usr`, or `/opt`
group-writable.
- First-boot package installation is fully offline; APT simulation proves its
dependency closure before image creation.
+83
View File
@@ -0,0 +1,83 @@
# Flashing the image to RK1 eMMC
Flashing destroys the selected target. Boot the RK1 from another device first
(for example NVMe), or attach the eMMC through an appropriate loader/USB mode.
Never overwrite the device that currently contains `/`.
Identify every disk by path, size, model, and serial:
```bash
findmnt /
lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,TRAN,TYPE,MOUNTPOINTS
```
The eMMC will usually be a whole `/dev/mmcblkN` device, but the number is not a
promise. The helper deliberately refuses partitions such as `/dev/mmcblk0p1`,
non-MMC disks, mounted descendants, and active swap. Repeat the resolved target
path in `--confirm`:
```bash
cd /home/ubuntu/rk1-media-image
sudo ./scripts/flash-emmc.sh \
--image dist/rk1-media-20260817-r4-trixie-vendor-6.1.115-turing-rk1-emmc.img.xz \
--target /dev/mmcblkN \
--confirm /dev/mmcblkN
```
The helper performs four checks around the destructive write:
1. validates the XZ stream and adjacent SHA-256 file;
2. checks target type, capacity, mounts, and swap;
3. streams the 4 GiB raw image with a flushed direct write;
4. decompresses it again while reading the target back byte-for-byte.
Only remove power after it reports that read-back verification passed. Then
shut down, remove or reprioritize the temporary boot disk, attach Ethernet and
HDMI, turn on the TV/input, and boot the eMMC.
If the carrier exposes RK1 eMMC through a different flashing workflow, the
`.img.xz` is a normal whole-disk image. A trusted imager can use it directly;
retain the same checksum and target-identification discipline.
## Keep boot on eMMC and move root to NVMe
The RK1 still needs its boot payload and `/boot` on eMMC. Once the appliance has
finished first-boot provisioning, identify the NVMe by its exact model and
serial, then run the helper included in revision r4:
```bash
findmnt -no SOURCE,FSTYPE /
lsblk -e7 -o NAME,PATH,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL,SERIAL
sudo test -e /var/lib/rk1-media/provisioned && echo PROVISIONED
sudo rk1-media-migrate-root-to-nvme \
--target /dev/nvme0n1 \
--serial EXACT_SERIAL_FROM_LSBLK
```
This destroys every existing partition on only the confirmed target NVMe. It
does not repartition the eMMC or rewrite U-Boot. The helper stops Kodi and
package-update jobs, performs the Armbian `sd`-mode root copy, checks the new
filesystem and boot configuration independently, and prints
`MIGRATION_VALIDATED` only after the rollback-sensitive writes have synced.
Reboot immediately after that result:
```bash
sudo systemctl reboot
findmnt -no SOURCE,FSTYPE /
findmnt /boot
findmnt /media/boot-media
```
To return to the intact eMMC root while the NVMe-rooted system is still
reachable:
```bash
sudo cp -a /boot/armbianEnv.txt.pre-nvme /boot/armbianEnv.txt
sudo cmp /boot/armbianEnv.txt.pre-nvme /boot/armbianEnv.txt
sudo sync
sudo systemctl reboot
```
Never use `armbian-install --boot split-emmc` for this rollback-preserving
migration; that mode repartitions both devices.
+147
View File
@@ -0,0 +1,147 @@
# Turing RK1 media-center image
This project now produces a complete, flashable home-theater image for a
Turing RK1 on a Jetson Orin Nano/NX-compatible HDMI carrier. It uses Debian 13
(Trixie) userspace on Armbian Community with the Rockchip vendor 6.1.115 kernel,
then starts Kodi directly on DRM/KMS through GBM—there is no desktop or display
manager in the way.
The intentionally non-obvious choice is the kernel. Armbian also offers newer
mainline kernels, but the vendor 6.1 branch is currently the coherent path for
RK3588 HDMI, RKMPP VPU, RGA, and RKNPU support. “Newest kernel number” and “most
complete RK3588 appliance” are not the same target.
## Ready artifact
- `dist/rk1-media-20260817-r4-trixie-vendor-6.1.115-turing-rk1-emmc.img.xz`
- SHA-256: `bb4fc337f1293fb2b2423b34e8fff370ed69e9ec8709c7da87fdfeea2a516f04`
- Compressed size: 591,529,876 bytes; raw image: 4 GiB
- Initial target: whole eMMC (the included guarded helper can then move `/`
to NVMe while retaining RK1 U-Boot and `/boot` on eMMC)
- Login: `rkadmin` using the sole public key whose fingerprint is recorded in
the adjacent manifest
- Hostname after first boot: `rk1-media.local`
There is no password login and no root login. The image contains no SSH private
key and no image-time SSH host key. Unique host keys are generated before SSH
can start. `rkadmin` has passwordless sudo because its only authentication path
is a supplied public key.
Read [FLASHING.md](FLASHING.md) before writing eMMC. The included flasher
requires the target path twice, rejects mounted targets, verifies the compressed
image, and reads the written bytes back.
## Hardware stack
- GPU: Panthor kernel DRM plus Mesa Panfrost/PanVK (OpenGL ES/OpenGL/Vulkan).
- Video: matched RKMPP decode/encode and RGA zero-copy/scaling stack isolated
under `/opt/rkmedia`; system FFmpeg libraries are not replaced.
- Kodi: direct GBM/GLES appliance service on tty1, running as a locked `kodi`
user rather than root.
- Decode: H.264, HEVC, VP9, AV1, and MJPEG RKMPP paths.
- Encode: H.264, HEVC, and MJPEG RKMPP paths.
- NPU: pinned RKNN Runtime 2.3.2, RK3588 model, per-core inference test, and
local diagnostics.
- HDMI support: exact `rk3588-turing-rk1.dtb`, HDMI PHY/audio nodes, Panthor
overlay, ALSA, CEC utilities, and IR keymap support.
The open Panthor/Mesa GPU route does not provide Mali OpenCL. Proprietary
libMali/OpenCL requires the mutually incompatible Mali kbase stack; it is not
mixed into this media image. The RK3588 NPU remains available through RKNN.
## Build it again
On an ARM64 build host:
```bash
cd /home/ubuntu/rk1-media-image
./build.sh --ssh-public-key /absolute/path/to/authorized_keys
```
The key file can hold multiple plain OpenSSH public-key lines. Private keys and
authorized-key options are rejected. The build needs no root access, mounts, or
loop devices. It verifies/downloads locked inputs, verifies 140 locked Debian
dependency packages, reproducibly repacks the Kodi bundle, installs the RKNN
runtime into the filesystem, expands the GPT/ext4 image, and emits checksums and
a manifest under `dist/`.
Validation:
```bash
./tests/run-static.sh
./tests/validate-image.sh \
dist/rk1-media-20260817-r4-trixie-vendor-6.1.115-turing-rk1-emmc.img
```
The produced image uses a checksum-locked Armbian release asset. `armbian/`
also contains a source-level base-image recipe pinned to the Armbian framework,
kernel, U-Boot, RKBin, and firmware commits. Archive package versions are
captured separately; true byte-for-byte source rebuilds additionally require a
package-repository snapshot.
## First boot
Connect Ethernet and HDMI before powering on. Armbian expands the root
filesystem, creates the unique machine/SSH identity, and makes SSH available.
An offline one-shot service then installs the 141-package graphics/media bundle,
reloads device permissions, enables Avahi, and starts Kodi. This can take a few
minutes; do not remove power while `rk1-media-provision.service` is active.
```bash
ssh [email protected]
systemctl status rk1-media-provision.service kodi-rk.service
journalctl -u rk1-media-provision.service -u kodi-rk.service -b
```
Revision r4 includes the SSH runtime-directory correction introduced in r2,
fixes first-boot offline APT acquisition and Kodi service ordering, and contains
only the dedicated `rk1-media` public key. Its fingerprint is
`SHA256:12b/wcqo8/yLfZ2OnLYga9baOt6tIlOSVZWrnjXtuN4`. The superseded
unrevisioned image could leave port 22 closed, while r3 could fail during its
offline package transaction and then deadlock while starting Kodi. Neither
should be used for a new flash; the repaired live r3 system does not need to be
reflashed.
The provisioner retries on the next boot if it does not reach its success
marker. Platform kernel/DTB/U-Boot packages are held; Debian security updates
are automatic, while platform/media upgrades require an intentionally tested
image rebuild.
Continue with [ACCEPTANCE.md](ACCEPTANCE.md). Static validation cannot prove
the carriers physical HPD/DDC/TMDS path, the TVs EDID, DRM-master acquisition,
or sustained 4K acceleration.
## Move the root filesystem to NVMe
Flash and boot the image from eMMC first. After first-boot provisioning has
completed, the image includes a guarded migration helper that keeps U-Boot and
`/boot` on eMMC, creates a fresh ext4 filesystem on the selected NVMe, copies
the live root, and validates the UUIDs, filesystem, `fstab`, and boot environment
before allowing a reboot:
```bash
findmnt -no SOURCE,FSTYPE /
lsblk -e7 -o NAME,PATH,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL,SERIAL
sudo rk1-media-migrate-root-to-nvme \
--target /dev/nvme0n1 \
--serial EXACT_SERIAL_FROM_LSBLK
sudo systemctl reboot
```
The selected NVMe is erased. Do not use Armbian's `split-emmc` mode for this
workflow: the eMMC is the boot medium and rollback copy. After reboot,
`findmnt /` should report the NVMe partition while `/boot` and
`/media/boot-media` resolve to eMMC. See [FLASHING.md](FLASHING.md) for the
rollback command.
## Project layout
- `image/`: rootless raw-image composition and first-boot services.
- `media/`: deterministic release repack and fully pinned native source recipe.
- `runtime/`: RKNN runtime installer, NPU test, hardware self-test, diagnostics.
- `packages/`: exact Debian dependency integrity lock and fetcher.
- `armbian/`: source-pinned Armbian base-image recipe.
- `tests/`: package, dependency, runtime, and loopless image validation.
- `scripts/`: locked input fetcher and guarded eMMC flasher.
See [DESIGN.md](DESIGN.md) for pins, trust boundaries, and tradeoffs.
+2
View File
@@ -0,0 +1,2 @@
.work/
+57
View File
@@ -0,0 +1,57 @@
# Armbian base image for Turing RK1
This directory builds the minimal, remotely manageable base for the RK1 media
appliance. It targets Debian 13 (Trixie), Armbian's Rockchip vendor kernel, and
the `rk3588-turing-rk1.dtb`. Kodi and the pinned RKMPP/RGA media stack are added
by the higher-level project layers.
## Build
Use a dedicated administrator public key. Password and root login are disabled
in the resulting image.
```bash
./build-image.sh --ssh-public-key /absolute/path/to/id_ed25519.pub
```
Useful non-building checks:
```bash
./tests/lint.sh
./build-image.sh --ssh-public-key /absolute/path/to/id_ed25519.pub --prepare-only
./build-image.sh --ssh-public-key /absolute/path/to/id_ed25519.pub --config-dump
```
The framework checkout, source caches, build logs, and images live under
`.work/armbian-build/`; completed images are in its `output/images/` directory.
The build command creates an image and checksum but never flashes a device.
## What is locked
`userpatches/source-lock.env` pins the Armbian framework, the 6.1.115 Rockchip
kernel source, U-Boot, Rockchip's required binary boot components, and Armbian
firmware by commit hash. The custom `rkbin-tools` extension and framework
`git_sources.json` override prevent the two stock moving `master` inputs from
silently changing.
Debian and Armbian archive packages are resolved when the image is built, so
byte-for-byte reproduction also requires retaining the generated package
manifest and a snapshot of the package repositories. Every built image records
the resolved package versions in `/usr/share/rk1-media/package-manifest.tsv`.
## First boot and updates
- `rkadmin` is created with the supplied SSH public key and passwordless sudo.
- Root and user password hashes are replaced with an unusable value.
- SSH password, keyboard-interactive, and root authentication are disabled.
- Image-time SSH host keys are erased and regenerated before SSH starts.
- Ethernet uses Armbian's systemd-networkd DHCP configuration.
- Avahi advertises the fixed hostname `rk1-media.local`.
- `overlays=panthor-gpu` selects Mesa's Panthor DRM path instead of libMali.
- Armbian's independent filesystem-resize service remains enabled.
- Debian security updates are automatic; kernel, boot, BSP, and media packages
remain held until a rebuilt image passes hardware acceptance tests.
The raw Armbian image is suitable for eMMC, NVMe, or removable-media flashing.
Always identify the destination by model, size, and serial immediately before a
write; the build wrapper deliberately contains no flashing operation.
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
readonly PROJECT_USERPATCHES="${SCRIPT_DIR}/userpatches"
readonly SOURCE_LOCK="${PROJECT_USERPATCHES}/source-lock.env"
readonly FRAMEWORK_OVERRIDES="${SCRIPT_DIR}/framework-overrides"
die() {
echo "build-image.sh: $*" >&2
exit 1
}
usage() {
cat <<'EOF'
Usage: build-image.sh --ssh-public-key PATH [OPTIONS]
Build the source-locked Armbian Debian Trixie image for Turing RK1.
Options:
--ssh-public-key PATH Required OpenSSH public-key/authorized_keys file.
--work-dir PATH Managed framework/cache directory (default: .work).
--prepare-only Clone/stage inputs, but do not invoke Armbian.
--config-dump Stage inputs and run Armbian's non-building config dump.
-h, --help Show this help.
The default action performs the full image build. It may ask for sudo through
Armbian's normal native-build launcher. No image is flashed by this command.
EOF
}
validate_public_keys() {
local key_file="${1}"
[[ -f "${key_file}" ]] || die "public-key file does not exist: ${key_file}"
[[ -s "${key_file}" ]] || die "public-key file is empty: ${key_file}"
command -v ssh-keygen >/dev/null || die "ssh-keygen is required to validate the public key"
awk '
/^[[:space:]]*($|#)/ { next }
$1 !~ /^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)|[email protected]|[email protected])$/ { exit 1 }
{ count++ }
END { if (count < 1) exit 1 }
' "${key_file}" || die "use plain OpenSSH public keys without authorized_keys options"
ssh-keygen -l -f "${key_file}" >/dev/null || die "ssh-keygen rejected the supplied public-key file"
}
prepare_framework() {
local work_root="${1}"
local framework_dir="${work_root}/armbian-build"
local origin
install -d -m 0755 "${work_root}"
work_root="$(cd -- "${work_root}" && pwd -P)"
framework_dir="${work_root}/armbian-build"
[[ "${work_root}" != "/" ]] || die "refusing to use / as the work directory"
if [[ ! -e "${framework_dir}" ]]; then
git init "${framework_dir}" >/dev/null
git -C "${framework_dir}" remote add origin "${RK1_MEDIA_ARMBIAN_BUILD_URL}"
install -m 0600 /dev/null "${framework_dir}/.rk1-media-build-root"
fi
[[ -d "${framework_dir}/.git" ]] || die "work path is not a Git checkout: ${framework_dir}"
[[ -f "${framework_dir}/.rk1-media-build-root" ]] || die "work checkout is not managed by this script: ${framework_dir}"
origin="$(git -C "${framework_dir}" remote get-url origin)"
[[ "${origin}" == "${RK1_MEDIA_ARMBIAN_BUILD_URL}" ]] || die "unexpected Armbian origin: ${origin}"
if git -C "${framework_dir}" rev-parse --verify HEAD >/dev/null 2>&1; then
git -C "${framework_dir}" diff --quiet || die "tracked modifications exist in ${framework_dir}"
git -C "${framework_dir}" diff --cached --quiet || die "staged modifications exist in ${framework_dir}"
fi
if ! git -C "${framework_dir}" cat-file -e "${RK1_MEDIA_ARMBIAN_BUILD_COMMIT}^{commit}" 2>/dev/null; then
git -C "${framework_dir}" fetch --depth=1 origin "${RK1_MEDIA_ARMBIAN_BUILD_COMMIT}"
fi
git -C "${framework_dir}" checkout --detach "${RK1_MEDIA_ARMBIAN_BUILD_COMMIT}" >/dev/null
printf '%s\n' "${framework_dir}"
}
stage_userpatches() {
local framework_dir="${1}"
local public_key="${2}"
local destination="${framework_dir}/userpatches"
[[ -f "${framework_dir}/.rk1-media-build-root" ]] || die "refusing to stage into an unmanaged checkout"
[[ "${destination}" == "${framework_dir}/userpatches" ]] || die "internal userpatch path check failed"
[[ ! -L "${destination}" ]] || die "refusing to replace a symlinked userpatches directory"
if [[ -e "${destination}" ]]; then
[[ -f "${destination}/.rk1-media-managed" ]] || die "existing userpatches are not managed by this script"
rm -rf -- "${destination}"
fi
install -d -m 0755 "${destination}"
cp -a "${PROJECT_USERPATCHES}/." "${destination}/"
install -m 0600 /dev/null "${destination}/.rk1-media-managed"
install -d -m 0700 "${destination}/overlay/build-input"
install -m 0600 "${public_key}" "${destination}/overlay/build-input/authorized_keys"
install -m 0644 "${SOURCE_LOCK}" "${destination}/overlay/build-input/source-lock.env"
# Armbian supports immutable resolutions for otherwise moving branch inputs
# through config/sources/git_sources.json. This pins its firmware artifact.
cp -a "${FRAMEWORK_OVERRIDES}/." "${framework_dir}/"
}
main() {
local public_key=""
local work_dir="${SCRIPT_DIR}/.work"
local mode="build"
local framework_dir
while (($#)); do
case "${1}" in
--ssh-public-key)
(($# >= 2)) || die "--ssh-public-key requires a path"
public_key="${2}"
shift 2
;;
--work-dir)
(($# >= 2)) || die "--work-dir requires a path"
work_dir="${2}"
shift 2
;;
--prepare-only)
mode="prepare"
shift
;;
--config-dump)
mode="config-dump"
shift
;;
-h|--help)
usage
exit 0
;;
*) die "unknown option: ${1}" ;;
esac
done
[[ -n "${public_key}" ]] || die "--ssh-public-key is required; the image never enables password login"
[[ -r "${SOURCE_LOCK}" ]] || die "source lock is missing: ${SOURCE_LOCK}"
# shellcheck source=userpatches/source-lock.env
source "${SOURCE_LOCK}"
validate_public_keys "${public_key}"
framework_dir="$(prepare_framework "${work_dir}")"
stage_userpatches "${framework_dir}" "${public_key}"
echo "Prepared Armbian ${RK1_MEDIA_ARMBIAN_BUILD_COMMIT} in ${framework_dir}"
case "${mode}" in
prepare)
echo "Configuration staged; no build was started."
;;
config-dump)
(cd "${framework_dir}" && CONFIG_DEFS_ONLY=yes ./compile.sh rk1-media config-dump)
;;
build)
(cd "${framework_dir}" && ./compile.sh rk1-media build)
echo "Images and checksums: ${framework_dir}/output/images"
;;
esac
}
main "$@"
@@ -0,0 +1,7 @@
[
{
"source": "https://github.com/armbian/firmware",
"branch": "master",
"sha1": "d9846710f54da5e4383e2d67311819659ac2cf5c"
}
]
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -Eeuo pipefail
readonly ARMBIAN_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)"
bash -n \
"${ARMBIAN_DIR}/build-image.sh" \
"${ARMBIAN_DIR}/userpatches/config-rk1-media.conf" \
"${ARMBIAN_DIR}/userpatches/customize-image.sh" \
"${ARMBIAN_DIR}/userpatches/extensions/rkbin-tools.sh" \
"${ARMBIAN_DIR}/userpatches/overlay/rootfs/usr/local/sbin/rk1-media-first-boot"
# shellcheck source=../userpatches/source-lock.env
source "${ARMBIAN_DIR}/userpatches/source-lock.env"
for revision in \
"${RK1_MEDIA_ARMBIAN_BUILD_COMMIT}" \
"${RK1_MEDIA_KERNEL_COMMIT}" \
"${RK1_MEDIA_UBOOT_COMMIT}" \
"${RK1_MEDIA_RKBIN_COMMIT}" \
"${RK1_MEDIA_FIRMWARE_COMMIT}"; do
[[ "${revision}" =~ ^[0-9a-f]{40}$ ]] || { echo "Invalid pinned revision: ${revision}" >&2; exit 1; }
done
jq -e --arg revision "${RK1_MEDIA_FIRMWARE_COMMIT}" \
'. == [{"source":"https://github.com/armbian/firmware","branch":"master","sha1":$revision}]' \
"${ARMBIAN_DIR}/framework-overrides/config/sources/git_sources.json" >/dev/null
[[ ! -e "${ARMBIAN_DIR}/userpatches/overlay/build-input/authorized_keys" ]] || {
echo "A staged administrator key must not be committed to userpatches/overlay/build-input" >&2
exit 1
}
if command -v apt-config >/dev/null; then
apt_dump="$(apt-config -c "${ARMBIAN_DIR}/userpatches/overlay/rootfs/etc/apt/apt.conf.d/52rk1-media-unattended-upgrades" dump)"
[[ "$(grep -c '^Unattended-Upgrade::Origins-Pattern:: ' <<< "${apt_dump}")" == "1" ]] || {
echo "Unattended upgrades must contain exactly one allowed origin" >&2
exit 1
}
grep -q 'codename=${distro_codename}-security' <<< "${apt_dump}" || {
echo "Debian security origin is missing from unattended-upgrades policy" >&2
exit 1
}
fi
if command -v shellcheck >/dev/null; then
shellcheck --exclude=SC1091,SC2154 \
"${ARMBIAN_DIR}/build-image.sh" \
"${ARMBIAN_DIR}/userpatches/config-rk1-media.conf" \
"${ARMBIAN_DIR}/userpatches/customize-image.sh" \
"${ARMBIAN_DIR}/userpatches/extensions/rkbin-tools.sh" \
"${ARMBIAN_DIR}/userpatches/overlay/rootfs/usr/local/sbin/rk1-media-first-boot"
fi
echo "Armbian scaffolding checks passed."
+2
View File
@@ -0,0 +1,2 @@
0.1.0
+72
View File
@@ -0,0 +1,72 @@
# shellcheck shell=bash
# Armbian image identity and target.
BOARD="turing-rk1"
BRANCH="vendor"
RELEASE="trixie"
BUILD_MINIMAL="yes"
BUILD_DESKTOP="no"
KERNEL_CONFIGURE="no"
UBOOT_CONFIGURE="no"
INSTALL_HEADERS="no"
EXPERT="yes" # Turing RK1 is an Armbian community-supported board.
# Image layout and deterministic build choices.
ROOTFS_TYPE="ext4"
EXTRA_ROOTFS_MIB_SIZE="1536"
COMPRESS_OUTPUTIMAGE="sha,xz"
IMAGE_XZ_COMPRESSION_RATIO="6"
NETWORKING_STACK="systemd-networkd"
SHARE_LOGS="no"
BETA="no"
USE_CCACHE="no"
# Runtime defaults. The temporary build password is overwritten with an
# unusable shadow value by customize-image.sh; no password works in the image.
HOST="rk1-media"
VENDOR="RK1 Media"
VENDORURL="https://github.com/armbian/build"
ROOTPWD="RK1_IMAGE_LOGIN_DISABLED"
CONSOLE_AUTOLOGIN="no"
DESKTOP_AUTOLOGIN="no"
OPENSSHD_REGENERATE_HOST_KEYS="false"
# This file is copied with the rest of userpatches by build-image.sh.
# shellcheck source=source-lock.env
source "${BASH_SOURCE%/*}/source-lock.env"
# The board and family definitions intentionally use moving branches. Override
# them after those definitions have run while retaining Armbian's matching
# vendor patch directories.
function post_family_config__999_rk1_media_pin_boot_sources() {
[[ "${BOARD}" == "turing-rk1" ]] || exit_with_error "RK1 media config used with the wrong board" "${BOARD}"
[[ "${BRANCH}" == "vendor" ]] || exit_with_error "RK1 media config requires BRANCH=vendor" "${BRANCH}"
declare -g KERNELSOURCE="${RK1_MEDIA_KERNEL_URL}"
declare -g KERNELBRANCH="commit:${RK1_MEDIA_KERNEL_COMMIT}"
declare -g KERNEL_MAJOR_MINOR="${RK1_MEDIA_KERNEL_SERIES}"
declare -g KERNELPATCHDIR="${RK1_MEDIA_KERNEL_PATCHSET}"
declare -g BOOTSOURCE="${RK1_MEDIA_UBOOT_URL}"
declare -g BOOTBRANCH="commit:${RK1_MEDIA_UBOOT_COMMIT}"
declare -g BOOTPATCHDIR="${RK1_MEDIA_UBOOT_PATCHSET}"
}
function user_config__200_rk1_media_base_packages() {
[[ "${RELEASE}" == "trixie" ]] || exit_with_error "RK1 media config requires Debian Trixie" "${RELEASE}"
add_packages_to_image \
openssh-server \
sudo \
avahi-daemon \
libnss-mdns \
unattended-upgrades \
ca-certificates \
curl \
jq
# Do not inherit the build host's locale/time zone.
declare -g DEST_LANG="en_US.UTF-8"
declare -g TZDATA="Etc/UTC"
}
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
set -Eeuo pipefail
RELEASE="${1:?missing Armbian release}"
LINUXFAMILY="${2:?missing Linux family}"
BOARD="${3:?missing board}"
BUILD_DESKTOP="${4:?missing desktop flag}"
ARCH="${5:?missing architecture}"
readonly RELEASE LINUXFAMILY BOARD BUILD_DESKTOP ARCH
readonly OVERLAY_ROOT="/tmp/overlay/rootfs"
readonly AUTHORIZED_KEYS_INPUT="/tmp/overlay/build-input/authorized_keys"
readonly SOURCE_LOCK_INPUT="/tmp/overlay/build-input/source-lock.env"
fail() {
echo "rk1-media customize: $*" >&2
exit 1
}
configure_identity() {
printf '%s\n' "rk1-media" > /etc/hostname
if grep -Eq '^127\.0\.1\.1[[:space:]]' /etc/hosts; then
sed -Ei 's/^127\.0\.1\.1[[:space:]].*$/127.0.1.1 rk1-media/' /etc/hosts
else
printf '%s\n' '127.0.1.1 rk1-media' >> /etc/hosts
fi
}
configure_boot() {
local env_file="/boot/armbianEnv.txt"
local overlays
[[ -f "${env_file}" ]] || fail "Armbian boot environment is missing: ${env_file}"
if grep -q '^overlays=' "${env_file}"; then
overlays="$(sed -n 's/^overlays=//p' "${env_file}" | head -n 1)"
case " ${overlays} " in
*" panthor-gpu "*) ;;
*) sed -i "0,/^overlays=.*/s//overlays=${overlays} panthor-gpu/" "${env_file}" ;;
esac
else
printf '%s\n' 'overlays=panthor-gpu' >> "${env_file}"
fi
}
configure_admin() {
if ! getent group rkadmin >/dev/null; then
groupadd rkadmin
fi
if ! id rkadmin >/dev/null 2>&1; then
useradd --create-home --gid rkadmin --shell /bin/bash --comment "RK1 administrator" rkadmin
else
usermod --gid rkadmin rkadmin
fi
local group groups_csv
local -a groups=(sudo adm systemd-journal audio video render input)
local -a available_groups=()
for group in "${groups[@]}"; do
getent group "${group}" >/dev/null && available_groups+=("${group}")
done
groups_csv="$(IFS=,; echo "${available_groups[*]}")"
[[ -z "${groups_csv}" ]] || usermod --append --groups "${groups_csv}" rkadmin
install -d -o rkadmin -g rkadmin -m 0700 /home/rkadmin/.ssh
install -o rkadmin -g rkadmin -m 0600 "${AUTHORIZED_KEYS_INPUT}" /home/rkadmin/.ssh/authorized_keys
install -m 0440 /dev/null /etc/sudoers.d/90-rkadmin
printf '%s\n' 'rkadmin ALL=(ALL:ALL) NOPASSWD: ALL' > /etc/sudoers.d/90-rkadmin
visudo --check --file=/etc/sudoers.d/90-rkadmin >/dev/null
# Replace (rather than merely prefix) the build-time password hashes.
usermod --password '!' root
usermod --password '!' rkadmin
rm -f /root/.not_logged_in_yet
}
hold_platform_packages() {
local package
local -a held=()
while IFS= read -r package; do
case "${package}" in
linux-image-*|linux-dtb-*|linux-u-boot-*|armbian-bsp-*)
apt-mark hold "${package}" >/dev/null
held+=("${package}")
;;
esac
done < <(dpkg-query --show --showformat='${binary:Package}\n')
printf '%s\n' "${held[@]}" | LC_ALL=C sort -u > /usr/share/rk1-media/held-packages.txt
}
configure_services() {
install -d -m 0755 /var/lib/rk1-media
install -m 0600 /dev/null /var/lib/rk1-media/first-boot.pending
# Validate the complete SSH configuration, then remove generated host keys so
# every flashed module creates a unique set before ssh.service can start.
ssh-keygen -A
/usr/sbin/sshd -t
rm -f /etc/ssh/ssh_host_*
systemctl enable rk1-media-first-boot.service
systemctl enable ssh.service
systemctl enable avahi-daemon.service
systemctl enable apt-daily.timer
systemctl enable apt-daily-upgrade.timer
}
write_build_manifest() {
install -d -m 0755 /usr/share/rk1-media
install -m 0644 "${SOURCE_LOCK_INPUT}" /usr/share/rk1-media/source-lock.env
dpkg-query --show --showformat='${binary:Package}\t${Version}\t${Architecture}\n' \
| LC_ALL=C sort > /usr/share/rk1-media/package-manifest.tsv
}
main() {
[[ "${RELEASE}" == "trixie" ]] || fail "unexpected release: ${RELEASE}"
[[ "${BOARD}" == "turing-rk1" ]] || fail "unexpected board: ${BOARD}"
[[ "${BUILD_DESKTOP}" == "no" ]] || fail "desktop images are not supported by this customization"
[[ "${ARCH}" == "arm64" ]] || fail "unexpected architecture: ${ARCH}"
[[ -d "${OVERLAY_ROOT}" ]] || fail "rootfs overlay is missing"
[[ -s "${AUTHORIZED_KEYS_INPUT}" ]] || fail "authorized_keys build input is missing"
[[ -s "${SOURCE_LOCK_INPUT}" ]] || fail "source lock build input is missing"
cp -a "${OVERLAY_ROOT}/." /
configure_identity
configure_boot
configure_admin
write_build_manifest
hold_platform_packages
configure_services
# systemd creates a unique machine ID on the first real boot.
: > /etc/machine-id
rm -f /var/lib/dbus/machine-id
}
main "$@"
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Source-locked replacement for Armbian's rkbin-tools extension. The upstream
# extension follows branch:master, which makes otherwise pinned builds drift.
function fetch_sources_tools__rkbin_tools() {
: "${RK1_MEDIA_RKBIN_URL:?source-lock.env did not set RK1_MEDIA_RKBIN_URL}"
: "${RK1_MEDIA_RKBIN_COMMIT:?source-lock.env did not set RK1_MEDIA_RKBIN_COMMIT}"
fetch_from_repo "${RK1_MEDIA_RKBIN_URL}" "rkbin-tools" "commit:${RK1_MEDIA_RKBIN_COMMIT}"
}
function build_host_tools__install_rkbin_tools() {
cd "${SRC}/cache/sources/rkbin-tools" || exit
if [[ ! -f .commit_id || "$(improved_git rev-parse @ 2>/dev/null)" != "$(< .commit_id)" || ! -f /usr/local/bin/loaderimage ]]; then
display_alert "Installing" "source-locked rkbin tools" "info"
mkdir -p /usr/local/bin/
install -m 0755 tools/loaderimage /usr/local/bin/
install -m 0755 tools/trust_merger /usr/local/bin/
improved_git rev-parse @ 2>/dev/null > .commit_id
fi
}
@@ -0,0 +1,3 @@
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
@@ -0,0 +1,22 @@
// Security fixes may install automatically. Platform/media packages remain
// pinned until a rebuilt image passes the RK1 hardware acceptance suite.
#clear Unattended-Upgrade::Allowed-Origins;
#clear Unattended-Upgrade::Origins-Pattern;
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};
Unattended-Upgrade::Package-Blacklist {
"^linux-image-.*$";
"^linux-dtb-.*$";
"^linux-u-boot-.*$";
"^armbian-bsp-.*$";
"^mesa-.*$";
"^kodi.*$";
"^rknn.*$";
"^rockchip.*$";
};
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "false";
@@ -0,0 +1,8 @@
# Generated by rk1-media-image. Administrative access is SSH-key-only.
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitEmptyPasswords no
@@ -0,0 +1,14 @@
[Unit]
Description=RK1 media image first-boot identity setup
After=local-fs.target
Before=ssh.service sshd.service
ConditionPathExists=/var/lib/rk1-media/first-boot.pending
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rk1-media-first-boot
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,4 @@
[Unit]
Requires=rk1-media-first-boot.service
After=rk1-media-first-boot.service
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
readonly MARKER="/var/lib/rk1-media/first-boot.pending"
[[ -e "${MARKER}" ]] || exit 0
rm -f /etc/ssh/ssh_host_*
ssh-keygen -A
found_key="no"
for private_key in /etc/ssh/ssh_host_*_key; do
if [[ -s "${private_key}" ]]; then
found_key="yes"
break
fi
done
[[ "${found_key}" == "yes" ]] || { echo "No SSH host key was generated" >&2; exit 1; }
/usr/sbin/sshd -t
rm -f "${MARKER}"
sync
+23
View File
@@ -0,0 +1,23 @@
# Source revisions resolved on 2026-08-16.
#
# This file is sourced by both build-image.sh and Armbian's user configuration.
# Keep values shell-safe (no command substitutions or unquoted whitespace).
RK1_MEDIA_ARMBIAN_BUILD_URL="https://github.com/armbian/build.git"
RK1_MEDIA_ARMBIAN_BUILD_COMMIT="ea18947bed789c829a260df129e815c353c14908"
RK1_MEDIA_KERNEL_URL="https://github.com/armbian/linux-rockchip.git"
RK1_MEDIA_KERNEL_COMMIT="5280f9b4336199c4025c8eed894d2b4e2268dcc6"
RK1_MEDIA_KERNEL_VERSION="6.1.115"
RK1_MEDIA_KERNEL_SERIES="6.1"
RK1_MEDIA_KERNEL_PATCHSET="rk35xx-vendor-6.1"
RK1_MEDIA_UBOOT_URL="https://github.com/u-boot/u-boot.git"
RK1_MEDIA_UBOOT_COMMIT="ece349ade2973e220f524ce59e59711cc919263f"
RK1_MEDIA_UBOOT_PATCHSET="v2026.07"
RK1_MEDIA_RKBIN_URL="https://github.com/armbian/rkbin.git"
RK1_MEDIA_RKBIN_COMMIT="452f49a987097d7ca675811e51a873b42ebd101f"
RK1_MEDIA_FIRMWARE_URL="https://github.com/armbian/firmware"
RK1_MEDIA_FIRMWARE_COMMIT="d9846710f54da5e4383e2d67311819659ac2cf5c"
Executable
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Assemble the complete RK1 media-center image from locked binary inputs.
set -Eeuo pipefail
umask 022
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
SSH_PUBLIC_KEY=""
IMAGE_SIZE_MIB=4096
IMAGE_REVISION="r4"
FORCE_REPACK=0
usage() {
cat <<'EOF'
Usage: ./build.sh --ssh-public-key PATH [--image-size-mib N] [--revision NAME] [--force-repack]
Builds a flashable Turing RK1 image without root, loop devices, or mounts.
The key file may contain one or more plain OpenSSH public keys.
EOF
}
while (($#)); do
case "$1" in
--ssh-public-key) SSH_PUBLIC_KEY=${2:?}; shift 2 ;;
--image-size-mib) IMAGE_SIZE_MIB=${2:?}; shift 2 ;;
--revision) IMAGE_REVISION=${2:?}; shift 2 ;;
--force-repack) FORCE_REPACK=1; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
esac
done
[[ -n "$SSH_PUBLIC_KEY" ]] || { usage >&2; exit 2; }
"$SCRIPT_DIR/scripts/fetch-inputs.sh"
upstream_deb="$SCRIPT_DIR/downloads/kodi-rockchip-gbm_20260721-1741_trixie_arm64.deb"
media_deb="$SCRIPT_DIR/media/out/rk1-media-stack_20260721.1741+rk1.1_arm64.deb"
if ((FORCE_REPACK)) || [[ ! -s "$media_deb" ]]; then
"$SCRIPT_DIR/media/scripts/repack-upstream-deb.sh" \
--deb "$upstream_deb" --output-dir "$SCRIPT_DIR/media/out"
fi
[[ -s "$media_deb.sha256" ]] || { echo "Missing media checksum" >&2; exit 1; }
(
cd "$(dirname -- "$media_deb")"
sha256sum --check --status "$(basename -- "$media_deb.sha256")"
) || { echo "Repacked media checksum failed" >&2; exit 1; }
"$SCRIPT_DIR/image/build-image.sh" \
--ssh-public-key "$SSH_PUBLIC_KEY" \
--kodi-deb "$media_deb" \
--image-size-mib "$IMAGE_SIZE_MIB" \
--revision "$IMAGE_REVISION"
+325
View File
@@ -0,0 +1,325 @@
#!/usr/bin/env bash
set -Eeuo pipefail
umask 022
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
PROJECT_DIR=$(cd -- "${SCRIPT_DIR}/.." && pwd)
BASE_XZ="${PROJECT_DIR}/downloads/Armbian_community_26.11.0-trunk.1_Turing-rk1_trixie_vendor_6.1.115_minimal.img.xz"
BASE_SHA256="cbbd16d90786331fbfbbb695614a53f5e87ea441ea37b425d986334318190cf6"
KODI_DEB="${PROJECT_DIR}/media/out/rk1-media-stack_20260721.1741+rk1.1_arm64.deb"
DEB_DIR="${PROJECT_DIR}/work/apt/archives"
RUNTIME_DIR="${PROJECT_DIR}/runtime"
RKNN_SOURCE_DIR="${PROJECT_DIR}/downloads/rknn-toolkit2-v2.3.2-minimal"
SSH_PUBLIC_KEY=""
OUTPUT_DIR="${PROJECT_DIR}/dist"
IMAGE_SIZE_MIB=4096
IMAGE_REVISION="r4"
usage() {
cat <<'EOF'
Usage: build-image.sh --ssh-public-key PATH [options]
Options:
--base-xz PATH Verified Armbian .img.xz base
--kodi-deb PATH Repacked RKMPP Kodi arm64 package
--deb-dir PATH Offline Debian dependency directory
--runtime-dir PATH RKNN/self-test runtime project
--rknn-source-dir PATH Offline RKNN Toolkit2 v2.3.2 asset bundle
--output-dir PATH Artifact directory
--image-size-mib N Expanded image size (default: 4096)
--revision NAME Artifact revision suffix (default: r4)
EOF
}
while (($#)); do
case "$1" in
--ssh-public-key) SSH_PUBLIC_KEY=${2:?}; shift 2 ;;
--base-xz) BASE_XZ=${2:?}; shift 2 ;;
--kodi-deb) KODI_DEB=${2:?}; shift 2 ;;
--deb-dir) DEB_DIR=${2:?}; shift 2 ;;
--runtime-dir) RUNTIME_DIR=${2:?}; shift 2 ;;
--rknn-source-dir) RKNN_SOURCE_DIR=${2:?}; shift 2 ;;
--output-dir) OUTPUT_DIR=${2:?}; shift 2 ;;
--image-size-mib) IMAGE_SIZE_MIB=${2:?}; shift 2 ;;
--revision) IMAGE_REVISION=${2:?}; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
esac
done
for command_name in debugfs e2fsck install jq resize2fs sfdisk sgdisk sha256sum ssh-keygen xz; do
command -v "$command_name" >/dev/null || { echo "Missing command: $command_name" >&2; exit 1; }
done
[[ -n "$SSH_PUBLIC_KEY" && -s "$SSH_PUBLIC_KEY" ]] || {
echo "--ssh-public-key must name a non-empty public-key file" >&2
exit 2
}
grep -Eq -- '-----BEGIN .*PRIVATE KEY-----' "$SSH_PUBLIC_KEY" && {
echo "Refusing a private key: $SSH_PUBLIC_KEY" >&2
exit 2
}
awk '
/^[[:space:]]*($|#)/ { next }
$1 !~ /^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)|[email protected]|[email protected])$/ { exit 1 }
NF < 2 { exit 1 }
{ count++ }
END { if (count < 1) exit 1 }
' "$SSH_PUBLIC_KEY" || {
echo "Use plain OpenSSH public-key lines without authorized_keys options" >&2
exit 2
}
ssh-keygen -lf "$SSH_PUBLIC_KEY" >/dev/null || {
echo "ssh-keygen rejected the public-key file" >&2
exit 2
}
[[ -s "$BASE_XZ" ]] || { echo "Missing base image: $BASE_XZ" >&2; exit 1; }
[[ -s "$KODI_DEB" ]] || { echo "Missing Kodi package: $KODI_DEB" >&2; exit 1; }
[[ -d "$DEB_DIR" ]] || { echo "Missing dependency directory: $DEB_DIR" >&2; exit 1; }
[[ -x "$RUNTIME_DIR/install-rknn-runtime.sh" ]] || {
echo "Missing RKNN installer below: $RUNTIME_DIR" >&2
exit 1
}
[[ -d "$RKNN_SOURCE_DIR" ]] || {
echo "Missing offline RKNN source bundle: $RKNN_SOURCE_DIR" >&2
exit 1
}
[[ "$IMAGE_SIZE_MIB" =~ ^[0-9]+$ ]] && ((IMAGE_SIZE_MIB >= 3072)) || {
echo "Image size must be an integer of at least 3072 MiB" >&2
exit 2
}
[[ "$IMAGE_REVISION" =~ ^[a-z0-9][a-z0-9._-]*$ ]] || {
echo "Image revision contains unsafe characters: $IMAGE_REVISION" >&2
exit 2
}
actual_base_sha=$(sha256sum "$BASE_XZ" | awk '{print $1}')
[[ "$actual_base_sha" == "$BASE_SHA256" ]] || {
echo "Base image checksum mismatch: $actual_base_sha" >&2
exit 1
}
repair_ext4() {
local status
if e2fsck -fy "$1" >/dev/null; then
return 0
else
status=$?
fi
# fsck bit 0 means errors were corrected; this is expected after debugfs.
((status == 1)) || {
echo "e2fsck failed for $1 with status $status" >&2
return "$status"
}
}
BUILD_ID="$(date -u +%Y%m%d)-${IMAGE_REVISION}-trixie-vendor-6.1.115"
WORK_DIR="${PROJECT_DIR}/work/image-${BUILD_ID}"
STAGE_DIR="${WORK_DIR}/overlay"
RAW_IMAGE="${WORK_DIR}/rk1-media-${BUILD_ID}.img"
ROOTFS_IMAGE="${WORK_DIR}/rootfs.ext4"
OUTPUT_BASENAME="rk1-media-${BUILD_ID}-turing-rk1-emmc"
rm -rf -- "$WORK_DIR"
mkdir -p "$STAGE_DIR" "$OUTPUT_DIR"
cp -a "${SCRIPT_DIR}/overlay/." "$STAGE_DIR/"
mkdir -p "$STAGE_DIR/opt/rk1-seed/debs" "$STAGE_DIR/usr/share/doc/rk1-media-image"
install -D -m 0755 "$PROJECT_DIR/scripts/live-migrate-root-to-nvme.sh" \
"$STAGE_DIR/usr/local/sbin/rk1-media-migrate-root-to-nvme"
cp -a "$DEB_DIR"/*.deb "$STAGE_DIR/opt/rk1-seed/debs/"
cp -a "$KODI_DEB" "$STAGE_DIR/opt/rk1-seed/debs/"
cp -a "$SSH_PUBLIC_KEY" "$STAGE_DIR/opt/rk1-seed/authorized_keys"
(
cd "$STAGE_DIR/opt/rk1-seed/debs"
sha256sum -- *.deb >../debs.sha256
)
cp -a "$RUNTIME_DIR/README.md" "$RUNTIME_DIR/rknn-version.env" \
"$STAGE_DIR/usr/share/doc/rk1-media-image/"
"$RUNTIME_DIR/install-rknn-runtime.sh" \
--rootfs "$STAGE_DIR" --source-dir "$RKNN_SOURCE_DIR"
xz --decompress --stdout "$BASE_XZ" >"$RAW_IMAGE"
partition_json=$(sfdisk --json "$RAW_IMAGE")
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_uuid=$(jq -r '.partitiontable.partitions[0].uuid' <<<"$partition_json")
[[ "$partition_start" == "32768" ]] || {
echo "Unexpected base partition start: $partition_start" >&2
exit 1
}
dd if="$RAW_IMAGE" of="$ROOTFS_IMAGE" bs=512 skip="$partition_start" count="$partition_size" status=none
repair_ext4 "$ROOTFS_IMAGE"
firstrun_file="${WORK_DIR}/armbian-firstrun"
debugfs -R 'cat /etc/default/armbian-firstrun' "$ROOTFS_IMAGE" \
2>/dev/null >"$firstrun_file"
if grep -q '^OPENSSHD_REGENERATE_HOST_KEYS=' "$firstrun_file"; then
sed -i 's/^OPENSSHD_REGENERATE_HOST_KEYS=.*/OPENSSHD_REGENERATE_HOST_KEYS=false/' \
"$firstrun_file"
else
printf '\nOPENSSHD_REGENERATE_HOST_KEYS=false\n' >>"$firstrun_file"
fi
mkdir -p "$STAGE_DIR/etc/default"
cp "$firstrun_file" "$STAGE_DIR/etc/default/armbian-firstrun"
# Remove the generic base image's root password hash before the image exists.
shadow_file="${WORK_DIR}/shadow"
debugfs -R 'cat /etc/shadow' "$ROOTFS_IMAGE" 2>/dev/null \
| awk 'BEGIN { FS=OFS=":" } $1 == "root" { $2="!"; found=1 } { print } END { if (!found) exit 1 }' \
>"$shadow_file"
mkdir -p "$STAGE_DIR/etc"
cp "$shadow_file" "$STAGE_DIR/etc/shadow"
truncate -s "${IMAGE_SIZE_MIB}M" "$RAW_IMAGE"
sgdisk --move-second-header "$RAW_IMAGE" >/dev/null
sgdisk --delete=1 "$RAW_IMAGE" >/dev/null
sgdisk --new="1:${partition_start}:0" --typecode="1:${partition_type}" --change-name='1:rootfs' "$RAW_IMAGE" >/dev/null
sgdisk --partition-guid="1:${partition_uuid}" "$RAW_IMAGE" >/dev/null
partition_json=$(sfdisk --json "$RAW_IMAGE")
new_partition_size=$(jq -r '.partitiontable.partitions[0].size' <<<"$partition_json")
truncate -s "$((new_partition_size * 512))" "$ROOTFS_IMAGE"
resize2fs "$ROOTFS_IMAGE" >/dev/null
env_file="${WORK_DIR}/armbianEnv.txt"
debugfs -R 'cat /boot/armbianEnv.txt' "$ROOTFS_IMAGE" 2>/dev/null >"$env_file"
if sed -n 's/^overlays=//p' "$env_file" | tr '[:space:]' '\n' | grep -Fxq panthor-gpu; then
:
elif grep -q '^overlays=' "$env_file"; then
sed -i '/^overlays=/ s/$/ panthor-gpu/' "$env_file"
else
printf '\noverlays=panthor-gpu\n' >>"$env_file"
fi
mkdir -p "$STAGE_DIR/boot"
cp "$env_file" "$STAGE_DIR/boot/armbianEnv.txt"
# Host umasks must not leak group-writable modes into /etc or systemd paths.
find "$STAGE_DIR" -type d -exec chmod 0755 {} +
find "$STAGE_DIR" -type f ! -perm /0111 -exec chmod 0644 {} +
find "$STAGE_DIR" -type f -perm /0111 -exec chmod 0755 {} +
chmod 0640 "$STAGE_DIR/etc/shadow"
"${SCRIPT_DIR}/inject-ext4-tree.sh" "$ROOTFS_IMAGE" "$STAGE_DIR"
debugfs -w -R 'set_inode_field /etc/shadow gid 42' "$ROOTFS_IMAGE" >/dev/null
debugfs -w -R 'set_inode_field /etc/shadow mode 0100640' "$ROOTFS_IMAGE" >/dev/null
# Replace the overlay's portable placeholder files with real enablement links.
debugfs -w -R 'rm /etc/systemd/system/sysinit.target.wants/rk1-media-identity.service' "$ROOTFS_IMAGE" >/dev/null 2>&1 || true
debugfs -w -R 'symlink /etc/systemd/system/sysinit.target.wants/rk1-media-identity.service ../rk1-media-identity.service' "$ROOTFS_IMAGE" >/dev/null
debugfs -w -R 'rm /etc/systemd/system/multi-user.target.wants/rk1-media-provision.service' "$ROOTFS_IMAGE" >/dev/null 2>&1 || true
debugfs -w -R 'symlink /etc/systemd/system/multi-user.target.wants/rk1-media-provision.service ../rk1-media-provision.service' "$ROOTFS_IMAGE" >/dev/null
for stale_path in \
/root/.not_logged_in_yet \
/root/.ssh/authorized_keys \
/etc/ssh/ssh_host_ecdsa_key /etc/ssh/ssh_host_ecdsa_key.pub \
/etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_ed25519_key.pub \
/etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_rsa_key.pub; do
debugfs -w -R "rm ${stale_path}" "$ROOTFS_IMAGE" >/dev/null 2>&1 || true
done
ext4_stat() {
debugfs -R "stat $1" "$ROOTFS_IMAGE" 2>/dev/null
}
ext4_exists() {
ext4_stat "$1" | grep -q '^Inode:'
}
ext4_missing() {
! ext4_exists "$1"
}
assert_ext4_contains() {
local path=$1 pattern=$2
debugfs -R "cat $path" "$ROOTFS_IMAGE" 2>/dev/null | grep -Eq "$pattern" || {
echo "Image assertion failed for $path: $pattern" >&2
exit 1
}
}
assert_ext4_contains /boot/armbianEnv.txt '^fdtfile=rockchip/rk3588-turing-rk1\.dtb$'
assert_ext4_contains /boot/armbianEnv.txt '^overlays=.*panthor-gpu'
assert_ext4_contains /etc/default/armbian-firstrun '^OPENSSHD_REGENERATE_HOST_KEYS=false$'
assert_ext4_contains /etc/ssh/sshd_config.d/90-rk1-media.conf '^AuthenticationMethods publickey$'
assert_ext4_contains /etc/shadow '^root:!:'
assert_ext4_contains /usr/local/sbin/rk1-media-provision \
'^seed_archives=/opt/rk1-seed/debs$'
assert_ext4_contains /usr/local/sbin/rk1-media-provision \
'Dir::Cache::archives=.*seed_archives'
assert_ext4_contains /usr/local/sbin/rk1-media-provision \
'^systemctl --no-block start .*kodi-rk\.service$'
assert_ext4_contains /usr/local/sbin/rk1-media-provision \
'^udevadm trigger --action=add --subsystem-match=misc'
ext4_exists /opt/rknn/2.3.2/lib/librknnrt.so || { echo 'RKNN runtime is absent' >&2; exit 1; }
ext4_exists /usr/local/sbin/rk1-media-migrate-root-to-nvme || {
echo 'Guarded NVMe migration helper is absent' >&2
exit 1
}
ext4_exists /opt/rk1-seed/debs/"$(basename -- "$KODI_DEB")" || { echo 'Kodi package is absent' >&2; exit 1; }
expected_deb_count=$(find "$STAGE_DIR/opt/rk1-seed/debs" -maxdepth 1 -type f -name '*.deb' | wc -l)
actual_deb_count=$(debugfs -R 'ls -p /opt/rk1-seed/debs' "$ROOTFS_IMAGE" 2>/dev/null | grep -c '/.*\.deb/' || true)
[[ "$actual_deb_count" == "$expected_deb_count" ]] || {
echo "Offline package bundle is incomplete ($actual_deb_count/$expected_deb_count)" >&2
exit 1
}
ext4_stat /etc | grep -Eq 'Mode:[[:space:]]+0755' || { echo '/etc mode is unsafe' >&2; exit 1; }
ext4_stat /etc/systemd/system/sysinit.target.wants/rk1-media-identity.service \
| grep -Fq 'Fast link dest: "../rk1-media-identity.service"' || { echo 'Identity service is not enabled' >&2; exit 1; }
ext4_stat /etc/systemd/system/multi-user.target.wants/rk1-media-provision.service \
| grep -Fq 'Fast link dest: "../rk1-media-provision.service"' || { echo 'Provisioning service is not enabled' >&2; exit 1; }
for forbidden_path in /root/.not_logged_in_yet /root/.ssh/authorized_keys \
/etc/ssh/ssh_host_ecdsa_key /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key; do
ext4_missing "$forbidden_path" || { echo "Forbidden image-time credential: $forbidden_path" >&2; exit 1; }
done
repair_ext4 "$ROOTFS_IMAGE"
dd if="$ROOTFS_IMAGE" of="$RAW_IMAGE" bs=512 seek="$partition_start" conv=notrunc status=none
sgdisk --verify "$RAW_IMAGE" >/dev/null
cp --reflink=auto "$RAW_IMAGE" "${OUTPUT_DIR}/${OUTPUT_BASENAME}.img"
xz --threads=0 -3 --keep --force "${OUTPUT_DIR}/${OUTPUT_BASENAME}.img"
(
cd "$OUTPUT_DIR"
sha256sum "${OUTPUT_BASENAME}.img.xz" >"${OUTPUT_BASENAME}.img.xz.sha256"
)
raw_sha=$(sha256sum "${OUTPUT_DIR}/${OUTPUT_BASENAME}.img" | awk '{print $1}')
compressed_sha=$(sha256sum "${OUTPUT_DIR}/${OUTPUT_BASENAME}.img.xz" | awk '{print $1}')
fingerprints_json=$(ssh-keygen -lf "$SSH_PUBLIC_KEY" | awk '{print $2}' \
| jq -Rsc 'split("\n") | map(select(length > 0))')
jq -n \
--arg build_id "$BUILD_ID" \
--arg base_file "$(basename -- "$BASE_XZ")" \
--arg base_sha256 "$actual_base_sha" \
--arg media_deb "$(basename -- "$KODI_DEB")" \
--arg media_deb_sha256 "$(sha256sum "$KODI_DEB" | awk '{print $1}')" \
--arg rknn_version 2.3.2 \
--arg rknn_commit 42aa1d426c0a9e0869b6374edba009f7208a1926 \
--arg inputs_lock_sha256 "$(sha256sum "$PROJECT_DIR/inputs.lock.json" | awk '{print $1}')" \
--arg debian_lock_sha256 "$(sha256sum "$PROJECT_DIR/packages/debian-packages.lock.tsv" | awk '{print $1}')" \
--arg raw_sha256 "$raw_sha" \
--arg compressed_sha256 "$compressed_sha" \
--argjson image_size_mib "$IMAGE_SIZE_MIB" \
--argjson dependency_package_count "$expected_deb_count" \
--argjson ssh_key_fingerprints "$fingerprints_json" \
'{
schema_version: 1,
build_id: $build_id,
board: "turing-rk1",
release: "debian-trixie-13.6",
kernel: "6.1.115-vendor-rk35xx",
gpu_stack: "panthor+mesa-panfrost+panvk",
base: {file: $base_file, sha256: $base_sha256},
media_package: {file: $media_deb, sha256: $media_deb_sha256},
rknn: {version: $rknn_version, commit: $rknn_commit},
locks: {inputs_sha256: $inputs_lock_sha256, debian_packages_sha256: $debian_lock_sha256},
artifacts: {
raw_sha256: $raw_sha256,
compressed_sha256: $compressed_sha256,
image_size_mib: $image_size_mib
},
dependency_package_count: $dependency_package_count,
ssh_key_fingerprints: $ssh_key_fingerprints
}' >"${OUTPUT_DIR}/${OUTPUT_BASENAME}.manifest.json"
echo "Built ${OUTPUT_DIR}/${OUTPUT_BASENAME}.img.xz"
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -Eeuo pipefail
[[ $# -eq 2 ]] || { echo "Usage: $0 ROOTFS.ext4 TREE" >&2; exit 2; }
ROOTFS=$1
TREE=$2
[[ -f "$ROOTFS" && -d "$TREE" ]] || { echo "Invalid rootfs or tree" >&2; exit 2; }
COMMANDS=$(mktemp)
trap 'rm -f -- "$COMMANDS"' EXIT
escape_debugfs() {
local value=$1
value=${value//\\/\\\\}
value=${value//\"/\\\"}
printf '%s' "$value"
}
while IFS= read -r -d '' source_path; do
relative_path=${source_path#"$TREE"/}
target_path="/${relative_path}"
quoted_target=$(escape_debugfs "$target_path")
if [[ -d "$source_path" && ! -L "$source_path" ]]; then
printf 'mkdir "%s"\n' "$quoted_target" >>"$COMMANDS"
fi
done < <(find "$TREE" -mindepth 1 -type d -print0 | sort -z)
while IFS= read -r -d '' source_path; do
relative_path=${source_path#"$TREE"/}
target_path="/${relative_path}"
quoted_source=$(escape_debugfs "$source_path")
quoted_target=$(escape_debugfs "$target_path")
if [[ -L "$source_path" ]]; then
printf 'rm "%s"\n' "$quoted_target" >>"$COMMANDS"
printf 'symlink "%s" "%s"\n' "$quoted_target" "$(escape_debugfs "$(readlink "$source_path")")" >>"$COMMANDS"
elif [[ -f "$source_path" ]]; then
printf 'rm "%s"\n' "$quoted_target" >>"$COMMANDS"
printf 'write "%s" "%s"\n' "$quoted_source" "$quoted_target" >>"$COMMANDS"
fi
done < <(find "$TREE" -mindepth 1 \( -type f -o -type l \) -print0 | sort -z)
while IFS= read -r -d '' source_path; do
relative_path=${source_path#"$TREE"/}
target_path="/${relative_path}"
quoted_target=$(escape_debugfs "$target_path")
permissions=$(stat -c '%a' "$source_path")
if [[ -d "$source_path" && ! -L "$source_path" ]]; then
file_type=040000
elif [[ -L "$source_path" ]]; then
file_type=0120000
else
file_type=0100000
fi
printf 'set_inode_field "%s" uid 0\n' "$quoted_target" >>"$COMMANDS"
printf 'set_inode_field "%s" gid 0\n' "$quoted_target" >>"$COMMANDS"
printf 'set_inode_field "%s" mode 0%o\n' "$quoted_target" "$((file_type | 8#$permissions))" >>"$COMMANDS"
done < <(find "$TREE" -mindepth 1 -print0 | sort -z)
# debugfs reports benign "already exists" and "file not found" diagnostics for
# idempotent mkdir/rm operations. The caller performs explicit postconditions.
debugfs -w -f "$COMMANDS" "$ROOTFS" >/dev/null 2>&1
@@ -0,0 +1,2 @@
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
@@ -0,0 +1,17 @@
#clear Unattended-Upgrade::Allowed-Origins;
#clear Unattended-Upgrade::Origins-Pattern;
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};
Unattended-Upgrade::Package-Blacklist {
"^linux-image-.*$";
"^linux-dtb-.*$";
"^linux-u-boot-.*$";
"^armbian-bsp-.*$";
"^mesa-.*$";
"^kodi.*$";
"^rknn.*$";
"^rockchip.*$";
};
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "false";
+1
View File
@@ -0,0 +1 @@
rk1-media
@@ -0,0 +1,7 @@
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitEmptyPasswords no
AllowUsers rkadmin
@@ -0,0 +1 @@
../rk1-media-provision.service
@@ -0,0 +1,15 @@
[Unit]
Description=RK1 media appliance identity and SSH hardening
DefaultDependencies=no
After=local-fs.target systemd-remount-fs.service
Before=ssh.service sshd.service getty.target serial-getty.target multi-user.target
ConditionPathExists=!/var/lib/rk1-media/identity-ready
[Service]
Type=oneshot
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755
ExecStart=/usr/local/sbin/rk1-media-identity
[Install]
WantedBy=sysinit.target
@@ -0,0 +1,14 @@
[Unit]
Description=Install the offline RK1 media appliance payload
After=local-fs.target systemd-udev-settle.service rk1-media-identity.service
Before=apt-daily.service apt-daily-upgrade.service kodi-rk.service
ConditionPathExists=!/var/lib/rk1-media/provisioned
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rk1-media-provision
RemainAfterExit=yes
TimeoutStartSec=20min
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,3 @@
[Unit]
Requires=rk1-media-identity.service
After=rk1-media-identity.service
@@ -0,0 +1 @@
../../rk1-media-identity.service
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -Eeuo pipefail
install -d -m 0755 /var/lib/rk1-media
hostnamectl set-hostname rk1-media 2>/dev/null || printf 'rk1-media\n' >/etc/hostname
for group_name in audio video render input; do
getent group "$group_name" >/dev/null || groupadd --system "$group_name"
done
getent group rkadmin >/dev/null || groupadd rkadmin
if ! id rkadmin >/dev/null 2>&1; then
useradd --create-home --gid rkadmin --shell /bin/bash --groups sudo,adm,systemd-journal,video,render,audio,input rkadmin
fi
usermod --password '!' rkadmin
install -d -o rkadmin -g rkadmin -m 0700 /home/rkadmin/.ssh
install -o rkadmin -g rkadmin -m 0600 /opt/rk1-seed/authorized_keys /home/rkadmin/.ssh/authorized_keys
install -d -m 0755 /etc/sudoers.d
printf 'rkadmin ALL=(ALL:ALL) NOPASSWD: ALL\n' >/etc/sudoers.d/90-rkadmin
chmod 0440 /etc/sudoers.d/90-rkadmin
visudo --check --file=/etc/sudoers.d/90-rkadmin >/dev/null
usermod --password '!' root
if grep -q '^127\.0\.1\.1[[:space:]]' /etc/hosts; then
sed -i 's/^127\.0\.1\.1[[:space:]].*/127.0.1.1\trk1-media/' /etc/hosts
else
printf '127.0.1.1\trk1-media\n' >>/etc/hosts
fi
rm -f /root/.not_logged_in_yet /etc/ssh/ssh_host_*
ssh-keygen -A
# ssh.service normally creates this through RuntimeDirectory=sshd. This
# validation runs before ssh.service, so it must provide the directory itself.
install -d -o root -g root -m 0755 /run/sshd
/usr/sbin/sshd -t
touch /var/lib/rk1-media/identity-ready
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -Eeuo pipefail
exec > >(tee -a /var/log/rk1-media-provision.log) 2>&1
export DEBIAN_FRONTEND=noninteractive
install -d -m 0755 /var/lib/rk1-media
seed_archives=/opt/rk1-seed/debs
# APT's --no-download mode only accepts dependency archives from its configured
# cache, even when every package is also named as an absolute local path. Make
# the immutable image seed that cache rather than copying it into /var/cache.
install -d -o root -g root -m 0755 "$seed_archives"
install -d -o _apt -g root -m 0700 "$seed_archives/partial"
mapfile -d '' debs < <(find "$seed_archives" -maxdepth 1 -type f -name '*.deb' -print0 | sort -z)
((${#debs[@]} > 0)) || { echo 'Offline package bundle is empty' >&2; exit 1; }
apt-get \
-o Dir::Cache::archives="$seed_archives" \
-o APT::Keep-Downloaded-Packages=true \
--no-download --no-install-recommends --yes install "${debs[@]}"
ldconfig
udevadm control --reload-rules
# MODE/GROUP assignments for device nodes are applied on add events. The misc
# trigger covers the vendor MPP, RGA, and RKNN control nodes.
udevadm trigger --action=add --subsystem-match=drm || true
udevadm trigger --action=add --subsystem-match=dma_heap || true
udevadm trigger --action=add --subsystem-match=misc || true
udevadm settle
install -d -m 0755 /usr/share/rk1-media
held_packages=()
while IFS= read -r package_name; do
case "$package_name" in
linux-image-*|linux-dtb-*|linux-u-boot-*|armbian-bsp-*)
apt-mark hold "$package_name" >/dev/null
held_packages+=("$package_name")
;;
esac
done < <(dpkg-query --show --showformat='${binary:Package}\n')
printf '%s\n' "${held_packages[@]}" | LC_ALL=C sort -u \
>/usr/share/rk1-media/held-packages.txt
dpkg-query --show --showformat='${binary:Package}\t${Version}\t${Architecture}\n' \
| LC_ALL=C sort >/usr/share/rk1-media/package-manifest.tsv
systemctl disable --now kodi-pulse.service pulseaudio.service 2>/dev/null || true
systemctl daemon-reload
systemctl enable avahi-daemon.service kodi-rk.service
# kodi-rk is ordered after this oneshot. Queue it without waiting so systemd
# can start it as soon as provisioning exits instead of deadlocking here.
systemctl --no-block start avahi-daemon.service kodi-rk.service
touch /var/lib/rk1-media/provisioned
+24
View File
@@ -0,0 +1,24 @@
{
"schema_version": 1,
"locked_at": "2026-08-16T20:32:07Z",
"base_image": {
"description": "Armbian Community Debian 13 minimal image for Turing RK1",
"url": "https://github.com/armbian/community/releases/download/26.11.0-trunk.1/Armbian_community_26.11.0-trunk.1_Turing-rk1_trixie_vendor_6.1.115_minimal.img.xz",
"output_name": "Armbian_community_26.11.0-trunk.1_Turing-rk1_trixie_vendor_6.1.115_minimal.img.xz",
"size": 322369692,
"sha256": "cbbd16d90786331fbfbbb695614a53f5e87ea441ea37b425d986334318190cf6"
},
"kodi_upstream_deb": {
"description": "armsurvivors Kodi GBM/RKMPP Debian Trixie release asset",
"url": "https://github.com/armsurvivors/kodi-rockchip-deb/releases/download/20260721-1741/kodi-rockchip-gbm_arm64_kodi_master_ffmpeg_81_trixie.deb",
"output_name": "kodi-rockchip-gbm_20260721-1741_trixie_arm64.deb",
"size": 147977956,
"sha256": "f9eed4c02d80f12b443604026fb00c4a6ff5ddd1fe5e6c554d81852a849d261a"
},
"rknn_toolkit2": {
"repository": "https://github.com/airockchip/rknn-toolkit2.git",
"tag": "v2.3.2",
"commit": "42aa1d426c0a9e0869b6374edba009f7208a1926",
"bundle_directory": "rknn-toolkit2-v2.3.2-minimal"
}
}
+1
View File
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG9jdtdu7zOd98m+tTreOwXk5b5QB5wA4zZV3f/mIb/v rk1-media
+2
View File
@@ -0,0 +1,2 @@
/out/
+108
View File
@@ -0,0 +1,108 @@
# RK1 media stack
This directory builds an ARM64-only Kodi/GBM appliance stack for an RK3588
running Armbian Debian 13 (Trixie) with the Rockchip vendor 6.1 kernel. All
third-party inputs are immutable Git commits recorded in `sources.lock.json`.
The result is a Debian package whose custom libraries and programs live under
`/opt/rkmedia`; it does not replace Debian's FFmpeg or Kodi packages.
## Why the compatibility MPP and RGA branches are pinned
The current `kodi-rockchip-deb` recipe uses nyanmisaka's `jellyfin-mpp` and
`jellyfin-rga` branches. FFmpeg-Rockchip 8.1 checks for MPP package version
1.3.9 and the `mpp_buffer_sync_begin_f` symbol, and checks the RGA APIs
`c_RkRgaBlit` and `querystring`. These exact branches satisfy that interface.
The official Rockchip MPP/RGA heads inspected when this lock was generated are
recorded as reference metadata, but are deliberately not substituted into the
matched build without a complete hardware regression run.
## Build
Run this on an aarch64 Debian Trixie installation. The native build is large and
can take several hours.
```sh
sudo ./scripts/install-build-deps.sh --install
./scripts/build-media.sh
```
Outputs are written to `out/` by default:
- `rk1-media-stack_<version>_arm64.deb`
- an unpacked `stage/` tree for inspection
- cloned, detached source trees and component build directories
Useful options:
```sh
./scripts/fetch-sources.sh --dest /path/to/sources
./scripts/build-media.sh --source-dir /path/to/sources --work-dir /path/to/work --output-dir /path/to/output --jobs 8
./scripts/build-media.sh --no-fetch
```
Set `RKMEDIA_ALLOW_UNSUPPORTED_HOST=1` only for development. Packages built on
anything other than Trixie are unsupported because their generated shared
library dependencies will describe the build host, not the target image.
## Fast candidate repack
For image composition and hardware validation, the pinned July 2026 upstream
Trixie package can be deterministically repacked in a few minutes instead of
performing the multi-hour native compile:
```sh
./scripts/repack-upstream-deb.sh
```
The script downloads the exact asset in `upstream-deb.lock.json`, verifies its
size and SHA-256 before extraction, removes approximately 270 MB of static
FFmpeg archives and all development pkg-config files, and emits the same
`rk1-media-stack` package format. Kodi remains at `/usr/local`, its compiled
prefix. FFmpeg, FFprobe, MPP, RGA, dav1d, and display-info move under
`/opt/rkmedia`; the launcher supplies the corresponding library path.
Use `--deb PATH` to repack an already-downloaded copy without network access.
The prebuilt asset is reproducible as an input but its original recipe used
moving source branches; its verified asset digest, rather than reconstructed
Git commit guesses, is the provenance boundary.
## Install and operate
Install with APT so runtime dependencies are resolved:
```sh
sudo apt install ./rk1-media-stack_<version>_arm64.deb
sudo systemctl enable --now kodi-rk.service
```
The package creates a locked `kodi` system account, grants it only the existing
`video`, `render`, `audio`, and `input` group memberships, and gives it persistent
state under `/var/lib/kodi`. Kodi takes DRM master on tty1, so a display manager
or another program holding the KMS device must not be active.
The service is enabled during package configuration but is not started inside
an image-build chroot. Edit `/etc/rkmedia/kodi.env` for supported environment
overrides, then restart `kodi-rk.service`.
The following commands are exposed without replacing system FFmpeg:
```sh
ffmpeg-rk -hide_banner -decoders
ffmpeg-rk -hide_banner -encoders
ffmpeg-rk -hide_banner -filters
ffprobe-rk media-file.mkv
```
Expected hardware entries include `h264_rkmpp`, `hevc_rkmpp`, `vp9_rkmpp`,
`av1_rkmpp`, `mjpeg_rkmpp`, and the `scale_rkrga`, `vpp_rkrga`, and
`overlay_rkrga` filters. Runtime access is provided through conservative udev
rules for the DRM, dma-heap, RGA, IEP, VPU, and MPP device nodes.
## Boundaries
- The package does not install or select the kernel, device tree, Panthor
overlay, Mesa, firmware, or bootloader. Those belong to the parent image.
- The package does not claim HDMI link, HDR, passthrough, or codec acceptance;
those require testing on the actual RK1/carrier/TV combination.
- Kodi is pinned to a mainline commit because the current Rockchip GBM work is
newer than a stable Kodi release. Update the lock only as a tested set.
+2
View File
@@ -0,0 +1,2 @@
20260816.1
+15
View File
@@ -0,0 +1,15 @@
Package: rk1-media-stack
Version: @VERSION@
Section: video
Priority: optional
Architecture: arm64
Maintainer: RK1 Media Image Builder <root@localhost>
Installed-Size: @INSTALLED_SIZE@
Depends: @DEPENDS@
Recommends: armbian-firmware, libgl1-mesa-dri, mesa-vulkan-drivers
Homepage: https://github.com/armsurvivors/kodi-rockchip-deb
X-RKMedia-Provenance-SHA256: @LOCK_SHA256@
Description: isolated RK3588 Kodi GBM and FFmpeg hardware media stack
Kodi for direct GBM/GLES rendering plus a matched FFmpeg-Rockchip, MPP,
RGA, dav1d, and libdisplay-info runtime. Custom software is isolated under
/opt/rkmedia and does not replace the distribution FFmpeg packages.
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
set -e
if ! getent group kodi >/dev/null 2>&1; then
addgroup --system kodi
fi
if ! getent passwd kodi >/dev/null 2>&1; then
adduser --system --ingroup kodi --home /var/lib/kodi --no-create-home \
--disabled-login --shell /usr/sbin/nologin kodi
fi
for group in video render audio input; do
if getent group "${group}" >/dev/null 2>&1; then
adduser kodi "${group}" >/dev/null
fi
done
systemd-tmpfiles --create rkmedia.conf >/dev/null 2>&1 || true
udevadm control --reload-rules >/dev/null 2>&1 || true
udevadm trigger --action=change --subsystem-match=drm >/dev/null 2>&1 || true
udevadm trigger --action=change --subsystem-match=dma_heap >/dev/null 2>&1 || true
udevadm trigger --action=change --subsystem-match=misc >/dev/null 2>&1 || true
udevadm settle >/dev/null 2>&1 || true
if command -v deb-systemd-helper >/dev/null 2>&1; then
deb-systemd-helper unmask kodi-rk.service >/dev/null || true
deb-systemd-helper enable kodi-rk.service >/dev/null || true
fi
if [ -d /run/systemd/system ]; then
systemctl daemon-reload >/dev/null || true
fi
exit 0
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
set -e
if command -v deb-systemd-helper >/dev/null 2>&1; then
if [ "$1" = purge ]; then
deb-systemd-helper purge kodi-rk.service >/dev/null || true
fi
fi
if [ -d /run/systemd/system ]; then
systemctl daemon-reload >/dev/null || true
fi
# Deliberately retain /var/lib/kodi and the locked account on removal. The
# media database and settings must never be deleted by a package uninstall.
exit 0
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
set -e
if [ "$1" = remove ] && [ -d /run/systemd/system ]; then
systemctl --no-reload stop kodi-rk.service >/dev/null 2>&1 || true
fi
exit 0
@@ -0,0 +1,8 @@
# Environment loaded by kodi-rk.service. This file is preserved across package
# upgrades. Kodi command-line arguments live in the unit so values here never
# undergo unsafe shell word splitting.
KODI_DATA=/var/lib/kodi/.kodi
CRASHLOG_DIR=/var/lib/kodi
XDG_CACHE_HOME=/var/cache/kodi
MESA_SHADER_CACHE_DIR=/var/cache/kodi/mesa_shader_cache
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
set -eu
prefix=/opt/rkmedia
export LD_LIBRARY_PATH="${prefix}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
exec "${prefix}/bin/ffmpeg" "$@"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
set -eu
prefix=/opt/rkmedia
export LD_LIBRARY_PATH="${prefix}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
exec "${prefix}/bin/ffprobe" "$@"
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
set -eu
prefix=/opt/rkmedia
data_dir="${KODI_DATA:-${HOME}/.kodi}"
userdata_dir="${data_dir}/userdata"
export PATH="${prefix}/bin:${PATH}"
export LD_LIBRARY_PATH="${prefix}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
mkdir -p "${userdata_dir}"
if [ ! -e "${userdata_dir}/guisettings.xml" ]; then
cp /usr/share/rkmedia/kodi/guisettings.xml "${userdata_dir}/guisettings.xml"
fi
if [ -x "${prefix}/bin/kodi" ]; then
kodi_launcher="${prefix}/bin/kodi"
elif [ -x /usr/local/bin/kodi ]; then
kodi_launcher=/usr/local/bin/kodi
else
printf 'kodi-rk: no Kodi launcher found under /opt/rkmedia or /usr/local\n' >&2
exit 127
fi
exec "${kodi_launcher}" "$@"
@@ -0,0 +1,51 @@
[Unit]
Description=Kodi RK3588 GBM appliance
Documentation=https://github.com/armsurvivors/kodi-rockchip-deb
Wants=network-online.target
After=systemd-user-sessions.service network-online.target sound.target
Conflicts=display-manager.service [email protected]
[Service]
Type=simple
User=kodi
Group=kodi
SupplementaryGroups=video render audio input
Environment=HOME=/var/lib/kodi
Environment=XDG_RUNTIME_DIR=/run/kodi
Environment=LD_LIBRARY_PATH=/opt/rkmedia/lib
EnvironmentFile=-/etc/rkmedia/kodi.env
RuntimeDirectory=kodi
RuntimeDirectoryMode=0700
StateDirectory=kodi
StateDirectoryMode=0750
CacheDirectory=kodi
CacheDirectoryMode=0750
PAMName=login
UtmpIdentifier=tty1
UtmpMode=user
StandardInput=tty
TTYPath=/dev/tty1
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
ExecStart=/opt/rkmedia/bin/kodi-rk --standalone --logging=console --windowing=gbm --audio-backend=alsa
Restart=always
RestartSec=2s
TimeoutStopSec=15s
LimitNOFILE=16384
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictNamespaces=yes
RestrictSUIDSGID=yes
LockPersonality=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
CapabilityBoundingSet=CAP_SYS_TTY_CONFIG
AmbientCapabilities=CAP_SYS_TTY_CONFIG
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,4 @@
d /var/lib/kodi 0750 kodi kodi -
d /var/lib/kodi/.kodi 0750 kodi kodi -
d /var/cache/kodi 0750 kodi kodi -
@@ -0,0 +1,22 @@
# Primary and render DRM nodes used by Kodi GBM and DRM PRIME.
SUBSYSTEM=="drm", KERNEL=="card[0-9]*", GROUP="video", MODE="0660", TAG+="uaccess"
SUBSYSTEM=="drm", KERNEL=="renderD[0-9]*", GROUP="render", MODE="0660", TAG+="uaccess"
# Rockchip vendor-kernel media accelerators. Optional compatibility names are
# included because their exact node names vary across BSP kernel revisions.
SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660"
KERNEL=="rga", GROUP="video", MODE="0660"
KERNEL=="iep", GROUP="video", MODE="0660"
KERNEL=="mpp_service", GROUP="video", MODE="0660"
KERNEL=="mpp-service", GROUP="video", MODE="0660"
KERNEL=="vpu_service", GROUP="video", MODE="0660"
KERNEL=="vpu-service", GROUP="video", MODE="0660"
KERNEL=="hevc_service", GROUP="video", MODE="0660"
KERNEL=="hevc-service", GROUP="video", MODE="0660"
KERNEL=="rkvdec", GROUP="video", MODE="0660"
KERNEL=="rkvenc", GROUP="video", MODE="0660"
KERNEL=="vepu", GROUP="video", MODE="0660"
KERNEL=="h265e", GROUP="video", MODE="0660"
# Rockchip NPU control/render compatibility nodes used by RKNN diagnostics.
KERNEL=="rknpu*", GROUP="render", MODE="0660"
@@ -0,0 +1,5 @@
<settings version="2">
<setting id="videoplayer.useprimedecoder">true</setting>
<setting id="videoplayer.useprimedecoderforhw" default="true">true</setting>
<setting id="videoplayer.useprimerenderer">0</setting>
</settings>
+226
View File
@@ -0,0 +1,226 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
LOCK_FILE="${RKMEDIA_ROOT}/sources.lock.json"
WORK_DIR="${RKMEDIA_ROOT}/out/work"
SOURCE_DIR="${RKMEDIA_ROOT}/out/sources"
OUTPUT_DIR="${RKMEDIA_ROOT}/out"
JOBS="$(nproc)"
FETCH=1
PACKAGE_VERSION="$(tr -d '[:space:]' < "${RKMEDIA_ROOT}/VERSION")"
RUNTIME_PREFIX=/opt/rkmedia
usage() {
cat <<'EOF'
Usage: build-media.sh [options]
--source-dir DIR Locked source checkouts (default: out/sources)
--work-dir DIR Build work directory (default: out/work)
--output-dir DIR Package output directory (default: out)
--jobs N Parallel jobs (default: nproc)
--no-fetch Require sources to exist; never access the network
--version VERSION Debian package version (default: VERSION file)
EOF
}
while (($#)); do
case "$1" in
--source-dir) [[ $# -ge 2 ]] || die "--source-dir requires a directory"; SOURCE_DIR="$2"; shift 2 ;;
--work-dir) [[ $# -ge 2 ]] || die "--work-dir requires a directory"; WORK_DIR="$2"; shift 2 ;;
--output-dir) [[ $# -ge 2 ]] || die "--output-dir requires a directory"; OUTPUT_DIR="$2"; shift 2 ;;
--jobs) [[ $# -ge 2 ]] || die "--jobs requires a number"; JOBS="$2"; shift 2 ;;
--no-fetch) FETCH=0; shift ;;
--version) [[ $# -ge 2 ]] || die "--version requires a value"; PACKAGE_VERSION="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
[[ "${JOBS}" =~ ^[1-9][0-9]*$ ]] || die "--jobs must be a positive integer"
[[ "${PACKAGE_VERSION}" =~ ^[0-9A-Za-z.+:~-]+$ ]] || die "invalid Debian package version: ${PACKAGE_VERSION}"
require_arm64_trixie
for command_name in cmake git jq meson ninja pkg-config make patch readelf strip; do
need_command "${command_name}"
done
SOURCE_DIR="$(absolute_path "${SOURCE_DIR}")"
WORK_DIR="$(absolute_path "${WORK_DIR}")"
OUTPUT_DIR="$(absolute_path "${OUTPUT_DIR}")"
mkdir -p -- "${SOURCE_DIR}" "${WORK_DIR}" "${OUTPUT_DIR}"
if ((FETCH)); then
"${SCRIPT_DIR}/fetch-sources.sh" --lock "${LOCK_FILE}" --dest "${SOURCE_DIR}"
fi
while IFS=$'\t' read -r name commit; do
dir="${SOURCE_DIR}/${name}"
[[ -d "${dir}/.git" ]] || die "missing locked source: ${dir}"
[[ "$(git -C "${dir}" rev-parse HEAD)" == "${commit}" ]] || die "source commit mismatch: ${name}"
done < <(jq -r '.sources[] | select(.build == true) | [.name, .commit] | @tsv' "${LOCK_FILE}")
BUILD_ROOT="${WORK_DIR}/build"
BUILD_PREFIX="${WORK_DIR}/prefix"
STAGE_DIR="${WORK_DIR}/stage"
reset_child_dir "${WORK_DIR}" "${BUILD_ROOT}"
reset_child_dir "${WORK_DIR}" "${BUILD_PREFIX}"
reset_child_dir "${WORK_DIR}" "${STAGE_DIR}"
export SOURCE_DATE_EPOCH
SOURCE_DATE_EPOCH="$(jq -er '.source_date_epoch' "${LOCK_FILE}")"
export CFLAGS="${CFLAGS:--O2 -pipe -ffile-prefix-map=${WORK_DIR}=. -fdebug-prefix-map=${WORK_DIR}=.}"
export CXXFLAGS="${CXXFLAGS:--O2 -pipe -ffile-prefix-map=${WORK_DIR}=. -fdebug-prefix-map=${WORK_DIR}=.}"
export LDFLAGS="${LDFLAGS:-}"
log "building MPP"
cmake -S "${SOURCE_DIR}/mpp" -B "${BUILD_ROOT}/mpp" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${BUILD_PREFIX}" \
-DCMAKE_INSTALL_LIBDIR=lib \
-DBUILD_SHARED_LIBS=ON \
-DBUILD_TEST=OFF
cmake --build "${BUILD_ROOT}/mpp" --parallel "${JOBS}"
cmake --install "${BUILD_ROOT}/mpp"
log "building RGA"
meson setup "${BUILD_ROOT}/rga" "${SOURCE_DIR}/rga" \
--prefix="${BUILD_PREFIX}" \
--libdir=lib \
--buildtype=release \
--default-library=shared \
-Dcpp_args=-fpermissive \
-Dlibdrm=false \
-Dlibrga_demo=false
meson compile -C "${BUILD_ROOT}/rga" -j "${JOBS}"
meson install -C "${BUILD_ROOT}/rga"
log "building dav1d"
meson setup "${BUILD_ROOT}/dav1d" "${SOURCE_DIR}/dav1d" \
--prefix="${BUILD_PREFIX}" \
--libdir=lib \
--buildtype=release \
--default-library=shared \
-Denable_tools=false \
-Denable_tests=false \
-Denable_examples=false \
-Denable_docs=false
meson compile -C "${BUILD_ROOT}/dav1d" -j "${JOBS}"
meson install -C "${BUILD_ROOT}/dav1d"
export PKG_CONFIG_PATH="${BUILD_PREFIX}/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
export LD_LIBRARY_PATH="${BUILD_PREFIX}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
log "building FFmpeg-Rockchip"
mkdir -p -- "${BUILD_ROOT}/ffmpeg"
pushd "${BUILD_ROOT}/ffmpeg" >/dev/null
"${SOURCE_DIR}/ffmpeg-rockchip/configure" \
--prefix="${BUILD_PREFIX}" \
--libdir="${BUILD_PREFIX}/lib" \
--enable-shared \
--disable-static \
--disable-debug \
--disable-doc \
--enable-pic \
--enable-gpl \
--enable-version3 \
--enable-libdrm \
--enable-rkmpp \
--enable-rkrga \
--enable-libdav1d \
--extra-cflags="-I${BUILD_PREFIX}/include" \
--extra-ldflags="-L${BUILD_PREFIX}/lib -Wl,-rpath,${RUNTIME_PREFIX}/lib"
make -j "${JOBS}"
make install
popd >/dev/null
FFMPEG_BUILD="${BUILD_PREFIX}/bin/ffmpeg"
for decoder in h264_rkmpp hevc_rkmpp vp9_rkmpp av1_rkmpp mjpeg_rkmpp; do
"${FFMPEG_BUILD}" -hide_banner -decoders 2>/dev/null | awk '{print $2}' | grep -Fxq "${decoder}" \
|| die "FFmpeg is missing decoder ${decoder}"
done
for encoder in h264_rkmpp hevc_rkmpp mjpeg_rkmpp; do
"${FFMPEG_BUILD}" -hide_banner -encoders 2>/dev/null | awk '{print $2}' | grep -Fxq "${encoder}" \
|| die "FFmpeg is missing encoder ${encoder}"
done
for filter in scale_rkrga vpp_rkrga overlay_rkrga; do
"${FFMPEG_BUILD}" -hide_banner -filters 2>/dev/null | awk '{print $2}' | grep -Fxq "${filter}" \
|| die "FFmpeg is missing filter ${filter}"
done
log "building libdisplay-info"
meson setup "${BUILD_ROOT}/libdisplay-info" "${SOURCE_DIR}/libdisplay-info" \
--prefix="${BUILD_PREFIX}" \
--libdir=lib \
--buildtype=release \
--default-library=shared
meson compile -C "${BUILD_ROOT}/libdisplay-info" -j "${JOBS}"
meson install -C "${BUILD_ROOT}/libdisplay-info"
log "building Kodi for GBM/GLES with external FFmpeg-Rockchip"
cmake -S "${SOURCE_DIR}/kodi" -B "${BUILD_ROOT}/kodi" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${RUNTIME_PREFIX}" \
-DCMAKE_INSTALL_RPATH="${RUNTIME_PREFIX}/lib" \
-DCMAKE_BUILD_RPATH="${BUILD_PREFIX}/lib" \
-DCMAKE_PREFIX_PATH="${BUILD_PREFIX}" \
-DCORE_PLATFORM_NAME=gbm \
-DAPP_RENDER_SYSTEM=gles \
-DENABLE_INTERNAL_FFMPEG=OFF \
-DFFMPEG_PATH="${BUILD_PREFIX}" \
-DENABLE_INTERNAL_FMT=ON \
-DENABLE_INTERNAL_FLATBUFFERS=ON
cmake --build "${BUILD_ROOT}/kodi" --parallel "${JOBS}"
mkdir -p -- "${STAGE_DIR}${RUNTIME_PREFIX}"
cp -a -- "${BUILD_PREFIX}/." "${STAGE_DIR}${RUNTIME_PREFIX}/"
DESTDIR="${STAGE_DIR}" cmake --install "${BUILD_ROOT}/kodi"
log "adding runtime integration and source provenance"
cp -a -- "${RKMEDIA_ROOT}/packaging/rootfs/." "${STAGE_DIR}/"
mkdir -p -- "${STAGE_DIR}/usr/bin" "${STAGE_DIR}/usr/share/doc/rk1-media-stack/licenses"
ln -sfn /opt/rkmedia/bin/ffmpeg-rk "${STAGE_DIR}/usr/bin/ffmpeg-rk"
ln -sfn /opt/rkmedia/bin/ffprobe-rk "${STAGE_DIR}/usr/bin/ffprobe-rk"
ln -sfn /opt/rkmedia/bin/kodi-rk "${STAGE_DIR}/usr/bin/kodi-rk"
install -m 0644 "${LOCK_FILE}" "${STAGE_DIR}/usr/share/doc/rk1-media-stack/sources.lock.json"
while IFS=$'\t' read -r name license_path; do
source_license="${SOURCE_DIR}/${name}/${license_path}"
destination="${STAGE_DIR}/usr/share/doc/rk1-media-stack/licenses/${name}"
[[ -e "${source_license}" ]] || die "locked license path is missing: ${name}/${license_path}"
mkdir -p -- "${destination}"
cp -a -- "${source_license}" "${destination}/"
done < <(jq -r '.sources[] | select(.build == true) as $source | $source.license_files[] | [$source.name, .] | @tsv' "${LOCK_FILE}")
rm -rf -- \
"${STAGE_DIR}${RUNTIME_PREFIX}/include" \
"${STAGE_DIR}${RUNTIME_PREFIX}/lib/pkgconfig" \
"${STAGE_DIR}${RUNTIME_PREFIX}/share/pkgconfig"
find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f \( -name '*.a' -o -name '*.la' \) -delete
while IFS= read -r -d '' candidate; do
if file --brief "${candidate}" | grep -q '^ELF '; then
strip --strip-unneeded "${candidate}" || die "failed to strip ${candidate}"
fi
done < <(find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f -print0)
for required in \
"${STAGE_DIR}${RUNTIME_PREFIX}/bin/ffmpeg" \
"${STAGE_DIR}${RUNTIME_PREFIX}/bin/ffprobe" \
"${STAGE_DIR}${RUNTIME_PREFIX}/bin/kodi" \
"${STAGE_DIR}/usr/lib/systemd/system/kodi-rk.service"; do
[[ -e "${required}" ]] || die "staged runtime file is missing: ${required}"
done
if find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f -exec grep -IlF "${WORK_DIR}" {} + | grep -q .; then
die "build path leaked into a staged text file"
fi
"${SCRIPT_DIR}/package-media.sh" \
--stage "${STAGE_DIR}" \
--output-dir "${OUTPUT_DIR}" \
--version "${PACKAGE_VERSION}"
log "media build complete"
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
LOCK_FILE="${RKMEDIA_ROOT}/sources.lock.json"
DEST_DIR="${RKMEDIA_ROOT}/out/sources"
usage() {
cat <<'EOF'
Usage: fetch-sources.sh [--lock FILE] [--dest DIRECTORY]
Fetch every build input at the exact commit recorded in the source lock.
Existing repositories are accepted only when their origin and HEAD match.
EOF
}
while (($#)); do
case "$1" in
--lock)
[[ $# -ge 2 ]] || die "--lock requires a file"
LOCK_FILE="$2"
shift 2
;;
--dest)
[[ $# -ge 2 ]] || die "--dest requires a directory"
DEST_DIR="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*) die "unknown argument: $1" ;;
esac
done
need_command git
need_command jq
LOCK_FILE="$(absolute_path "${LOCK_FILE}")"
DEST_DIR="$(absolute_path "${DEST_DIR}")"
[[ -f "${LOCK_FILE}" ]] || die "source lock not found: ${LOCK_FILE}"
jq -e '.schema_version == 1 and (.sources | type == "array")' "${LOCK_FILE}" >/dev/null \
|| die "invalid source lock: ${LOCK_FILE}"
mkdir -p -- "${DEST_DIR}"
verify_checkout() {
local dir="$1" expected_repo="$2" expected_commit="$3"
local actual_repo actual_commit
[[ -d "${dir}/.git" ]] || return 1
actual_repo="$(git -C "${dir}" remote get-url origin 2>/dev/null || true)"
actual_commit="$(git -C "${dir}" rev-parse HEAD 2>/dev/null || true)"
[[ "${actual_repo}" == "${expected_repo}" && "${actual_commit}" == "${expected_commit}" ]]
}
while IFS=$'\t' read -r name repository ref commit; do
[[ "${name}" =~ ^[a-z0-9][a-z0-9._-]*$ ]] || die "unsafe source name in lock: ${name}"
[[ "${commit}" =~ ^[0-9a-f]{40}$ ]] || die "invalid commit for ${name}: ${commit}"
source_dir="${DEST_DIR}/${name}"
if verify_checkout "${source_dir}" "${repository}" "${commit}"; then
log "source already verified: ${name} ${commit}"
continue
fi
if [[ -e "${source_dir}" ]]; then
die "${source_dir} exists but does not match the lock; move it aside and retry"
fi
log "fetching ${name} at ${commit}"
mkdir -p -- "${source_dir}"
git -C "${source_dir}" init --quiet
git -C "${source_dir}" remote add origin "${repository}"
if ! git -C "${source_dir}" fetch --quiet --depth=1 origin "${commit}"; then
log "direct commit fetch was unavailable; fetching history from ${ref}"
git -C "${source_dir}" fetch --quiet --filter=blob:none origin "${ref}"
fi
git -C "${source_dir}" checkout --quiet --detach "${commit}"
verify_checkout "${source_dir}" "${repository}" "${commit}" \
|| die "checkout verification failed for ${name}"
done < <(jq -r '.sources[] | select(.build == true) | [.name, .repository, .ref, .commit] | @tsv' "${LOCK_FILE}")
log "all locked sources are present in ${DEST_DIR}"
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
MODE=check
case "${1:---check}" in
--check) MODE=check ;;
--install) MODE=install ;;
-h|--help)
printf 'Usage: install-build-deps.sh [--check|--install]\n'
exit 0
;;
*) die "unknown argument: $1" ;;
esac
require_arm64_trixie
need_command dpkg-query
packages=(
adduser autoconf automake autopoint autotools-dev build-essential ca-certificates
cmake default-jre devscripts dpkg-dev file gawk gcc g++ gdc gettext git gperf
hwdata jq libasound2-dev libass-dev libavahi-client-dev libavahi-common-dev
libbluetooth-dev libbluray-dev libbz2-dev libcdio++-dev libcdio-dev libcec-dev
libcrossguid-dev libcwiid-dev libcurl4-openssl-dev libdbus-1-dev libdrm-dev
libegl1-mesa-dev libenca-dev libexiv2-dev libflac-dev libfmt-dev
libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgbm-dev
libgcrypt20-dev libgif-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev
libgnutls28-dev libgpg-error-dev libgtest-dev libinput-dev libiso9660-dev
libjpeg-dev liblcms2-dev liblirc-dev libltdl-dev liblzo2-dev libmariadb-dev
libmicrohttpd-dev libnfs-dev libogg-dev libp8-platform-dev libpcre2-dev
libplist-dev libpng-dev libpulse-dev libshairplay-dev libsmbclient-dev
libspdlog-dev libsqlite3-dev libssl-dev libtag1-dev libtiff-dev
libtinyxml2-dev libtinyxml-dev libtool libudev-dev libunistring-dev
libvorbis-dev libxkbcommon-dev libxslt1-dev libxt-dev lsb-release meson nasm
ninja-build nlohmann-json3-dev patch pkg-config python3-dev python3-pil
python3-pip rapidjson-dev swig unzip uuid-dev zip zlib1g-dev
)
missing=()
for package in "${packages[@]}"; do
if ! dpkg-query -W -f='${db:Status-Abbrev}' "${package}" 2>/dev/null | grep -q '^ii '; then
missing+=("${package}")
fi
done
if ((${#missing[@]} == 0)); then
log "all build dependencies are installed"
exit 0
fi
if [[ "${MODE}" == "check" ]]; then
printf 'Missing build packages:\n'
printf ' %s\n' "${missing[@]}"
exit 1
fi
[[ "${EUID}" -eq 0 ]] || die "--install must run as root (use sudo)"
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends "${missing[@]}"
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -Eeuo pipefail
RKMEDIA_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
RKMEDIA_ROOT="$(cd -- "${RKMEDIA_SCRIPT_DIR}/.." && pwd -P)"
log() {
printf '[rkmedia] %s\n' "$*" >&2
}
die() {
printf '[rkmedia] ERROR: %s\n' "$*" >&2
exit 1
}
need_command() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}
absolute_path() {
realpath -m -- "$1"
}
assert_child_path() {
local parent child
parent="$(absolute_path "$1")"
child="$(absolute_path "$2")"
case "${child}" in
"${parent}"/*) ;;
*) die "refusing operation outside ${parent}: ${child}" ;;
esac
[[ "${child}" != "${parent}" ]] || die "refusing operation on parent directory itself: ${parent}"
}
reset_child_dir() {
local parent="$1"
local child="$2"
assert_child_path "${parent}" "${child}"
rm -rf -- "${child}"
mkdir -p -- "${child}"
}
require_arm64_trixie() {
local arch codename
arch="$(dpkg --print-architecture 2>/dev/null || true)"
codename=""
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
codename="${VERSION_CODENAME:-}"
fi
if [[ "${arch}" != "arm64" || "$(uname -m)" != "aarch64" || "${codename}" != "trixie" ]]; then
if [[ "${RKMEDIA_ALLOW_UNSUPPORTED_HOST:-0}" == "1" ]]; then
log "warning: unsupported build host (dpkg=${arch}, uname=$(uname -m), suite=${codename:-unknown})"
return
fi
die "native Debian Trixie arm64 is required (set RKMEDIA_ALLOW_UNSUPPORTED_HOST=1 only for development)"
fi
}
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
set -Eeuo pipefail
umask 022
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
STAGE_DIR=""
OUTPUT_DIR="${RKMEDIA_ROOT}/out"
PACKAGE_VERSION="$(tr -d '[:space:]' < "${RKMEDIA_ROOT}/VERSION")"
DEPENDS_FROM_DEB=""
usage() {
printf 'Usage: package-media.sh --stage DIRECTORY [--output-dir DIRECTORY] [--version VERSION] [--depends-from-deb FILE]\n'
}
while (($#)); do
case "$1" in
--stage) [[ $# -ge 2 ]] || die "--stage requires a directory"; STAGE_DIR="$2"; shift 2 ;;
--output-dir) [[ $# -ge 2 ]] || die "--output-dir requires a directory"; OUTPUT_DIR="$2"; shift 2 ;;
--version) [[ $# -ge 2 ]] || die "--version requires a value"; PACKAGE_VERSION="$2"; shift 2 ;;
--depends-from-deb) [[ $# -ge 2 ]] || die "--depends-from-deb requires a file"; DEPENDS_FROM_DEB="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
[[ -n "${STAGE_DIR}" ]] || die "--stage is required"
[[ "${PACKAGE_VERSION}" =~ ^[0-9A-Za-z.+:~-]+$ ]] || die "invalid Debian package version: ${PACKAGE_VERSION}"
for command_name in dpkg-deb dpkg-shlibdeps file jq md5sum sed sha256sum; do
need_command "${command_name}"
done
STAGE_DIR="$(absolute_path "${STAGE_DIR}")"
OUTPUT_DIR="$(absolute_path "${OUTPUT_DIR}")"
[[ -x "${STAGE_DIR}/opt/rkmedia/bin/ffmpeg" ]] || die "stage does not contain the RKMedia runtime"
mkdir -p -- "${OUTPUT_DIR}"
PACKAGE_ROOT="${OUTPUT_DIR}/package-root"
reset_child_dir "${OUTPUT_DIR}" "${PACKAGE_ROOT}"
cp -a -- "${STAGE_DIR}/." "${PACKAGE_ROOT}/"
mkdir -p -- "${PACKAGE_ROOT}/DEBIAN" "${PACKAGE_ROOT}/usr/share/doc/rk1-media-stack"
cp -- "${RKMEDIA_ROOT}/packaging/postinst" "${PACKAGE_ROOT}/DEBIAN/postinst"
cp -- "${RKMEDIA_ROOT}/packaging/prerm" "${PACKAGE_ROOT}/DEBIAN/prerm"
cp -- "${RKMEDIA_ROOT}/packaging/postrm" "${PACKAGE_ROOT}/DEBIAN/postrm"
chmod 0755 "${PACKAGE_ROOT}/DEBIAN/postinst" "${PACKAGE_ROOT}/DEBIAN/prerm" "${PACKAGE_ROOT}/DEBIAN/postrm"
printf '/etc/rkmedia/kodi.env\n' > "${PACKAGE_ROOT}/DEBIAN/conffiles"
# A collaborative workspace or permissive host umask must never make core
# target directories such as /etc, /usr, or /opt group-writable via dpkg.
find "${PACKAGE_ROOT}" -type d -exec chmod 0755 {} +
find "${PACKAGE_ROOT}" -type f ! -perm /0111 -exec chmod 0644 {} +
find "${PACKAGE_ROOT}" -type f -perm /0111 -exec chmod 0755 {} +
if find "${PACKAGE_ROOT}" \( -type d -o -type f \) -perm /0022 -print -quit \
| grep -q .; then
die "package contains a group/world-writable path"
fi
if [[ -n "${DEPENDS_FROM_DEB}" ]]; then
DEPENDS_FROM_DEB="$(absolute_path "${DEPENDS_FROM_DEB}")"
[[ -f "${DEPENDS_FROM_DEB}" ]] || die "dependency source package not found: ${DEPENDS_FROM_DEB}"
shlib_depends="$(dpkg-deb --field "${DEPENDS_FROM_DEB}" Depends)"
[[ -n "${shlib_depends}" ]] || die "upstream package has an empty Depends field"
else
analysis_root="${OUTPUT_DIR}/shlibdeps"
reset_child_dir "${OUTPUT_DIR}" "${analysis_root}"
mkdir -p -- "${analysis_root}/debian"
cat > "${analysis_root}/debian/control" <<'EOF'
Source: rk1-media-stack
Section: video
Priority: optional
Package: rk1-media-stack
Architecture: arm64
Description: temporary metadata for dpkg-shlibdeps
EOF
elf_files=()
while IFS= read -r -d '' candidate; do
if file --brief "${candidate}" | grep -q '^ELF '; then
elf_files+=("${candidate}")
fi
done < <(find "${PACKAGE_ROOT}/opt/rkmedia" -type f -print0)
((${#elf_files[@]} > 0)) || die "no ELF files found in media stage"
pushd "${analysis_root}" >/dev/null
shlib_output="$(dpkg-shlibdeps --ignore-missing-info -O \
-l"${PACKAGE_ROOT}/opt/rkmedia/lib" "${elf_files[@]}")"
popd >/dev/null
shlib_depends="${shlib_output#shlibs:Depends=}"
[[ -n "${shlib_depends}" && "${shlib_depends}" != "${shlib_output}" ]] \
|| die "dpkg-shlibdeps did not produce runtime dependencies"
fi
depends="adduser, systemd, udev, ${shlib_depends}"
installed_size="$(du -sk "${PACKAGE_ROOT}" | awk '{print $1}')"
if [[ -f "${PACKAGE_ROOT}/usr/share/doc/rk1-media-stack/upstream-deb.lock.json" ]]; then
provenance_file="${PACKAGE_ROOT}/usr/share/doc/rk1-media-stack/upstream-deb.lock.json"
else
provenance_file="${PACKAGE_ROOT}/usr/share/doc/rk1-media-stack/sources.lock.json"
fi
lock_sha256="$(sha256sum "${provenance_file}" | awk '{print $1}')"
sed \
-e "s/@VERSION@/${PACKAGE_VERSION}/g" \
-e "s/@INSTALLED_SIZE@/${installed_size}/g" \
-e "s/@LOCK_SHA256@/${lock_sha256}/g" \
-e "s/@DEPENDS@/${depends}/g" \
"${RKMEDIA_ROOT}/packaging/control.in" > "${PACKAGE_ROOT}/DEBIAN/control"
pushd "${PACKAGE_ROOT}" >/dev/null
find etc opt usr -type f -print0 \
| sort -z \
| xargs -0 md5sum > DEBIAN/md5sums
popd >/dev/null
if find "${PACKAGE_ROOT}" \( -type d -o -type f \) -perm /0022 -print -quit \
| grep -q .; then
die "package contains a group/world-writable path after metadata generation"
fi
output_deb="${OUTPUT_DIR}/rk1-media-stack_${PACKAGE_VERSION}_arm64.deb"
rm -f -- "${output_deb}"
SOURCE_DATE_EPOCH="$(jq -er '.source_date_epoch' "${RKMEDIA_ROOT}/sources.lock.json")" \
dpkg-deb --root-owner-group --build "${PACKAGE_ROOT}" "${output_deb}"
(
cd "${OUTPUT_DIR}"
sha256sum "$(basename -- "${output_deb}")" \
>"$(basename -- "${output_deb}").sha256"
)
dpkg-deb --info "${output_deb}" >/dev/null
log "created ${output_deb}"
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
LOCK_FILE="${RKMEDIA_ROOT}/upstream-deb.lock.json"
WORK_DIR="${RKMEDIA_ROOT}/out/repack-work"
OUTPUT_DIR="${RKMEDIA_ROOT}/out"
CACHE_DIR="${RKMEDIA_ROOT}/out/downloads"
INPUT_DEB=""
PACKAGE_VERSION="20260721.1741+rk1.1"
usage() {
cat <<'EOF'
Usage: repack-upstream-deb.sh [options]
--deb FILE Use an existing upstream .deb instead of downloading
--work-dir DIR Temporary extraction directory
--output-dir DIR Repacked package output directory
--cache-dir DIR Download cache directory
--version VERSION Output Debian package version
EOF
}
while (($#)); do
case "$1" in
--deb) [[ $# -ge 2 ]] || die "--deb requires a file"; INPUT_DEB="$2"; shift 2 ;;
--work-dir) [[ $# -ge 2 ]] || die "--work-dir requires a directory"; WORK_DIR="$2"; shift 2 ;;
--output-dir) [[ $# -ge 2 ]] || die "--output-dir requires a directory"; OUTPUT_DIR="$2"; shift 2 ;;
--cache-dir) [[ $# -ge 2 ]] || die "--cache-dir requires a directory"; CACHE_DIR="$2"; shift 2 ;;
--version) [[ $# -ge 2 ]] || die "--version requires a value"; PACKAGE_VERSION="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
for command_name in curl dpkg-deb file jq readelf sha256sum stat; do
need_command "${command_name}"
done
[[ -f "${LOCK_FILE}" ]] || die "upstream asset lock is missing: ${LOCK_FILE}"
jq -e '.schema_version == 1 and (.asset_sha256 | test("^[0-9a-f]{64}$"))' "${LOCK_FILE}" >/dev/null \
|| die "invalid upstream asset lock"
WORK_DIR="$(absolute_path "${WORK_DIR}")"
OUTPUT_DIR="$(absolute_path "${OUTPUT_DIR}")"
CACHE_DIR="$(absolute_path "${CACHE_DIR}")"
mkdir -p -- "${WORK_DIR}" "${OUTPUT_DIR}" "${CACHE_DIR}"
asset_name="$(jq -er '.asset_name' "${LOCK_FILE}")"
asset_url="$(jq -er '.asset_url' "${LOCK_FILE}")"
expected_size="$(jq -er '.asset_size' "${LOCK_FILE}")"
expected_sha256="$(jq -er '.asset_sha256' "${LOCK_FILE}")"
if [[ -z "${INPUT_DEB}" ]]; then
INPUT_DEB="${CACHE_DIR}/${asset_name}"
if [[ ! -f "${INPUT_DEB}" ]]; then
partial="${INPUT_DEB}.partial"
rm -f -- "${partial}"
log "downloading pinned upstream Kodi package"
curl -L --fail --show-error --output "${partial}" "${asset_url}"
mv -- "${partial}" "${INPUT_DEB}"
fi
fi
INPUT_DEB="$(absolute_path "${INPUT_DEB}")"
[[ -f "${INPUT_DEB}" ]] || die "upstream package not found: ${INPUT_DEB}"
actual_size="$(stat -c '%s' "${INPUT_DEB}")"
actual_sha256="$(sha256sum "${INPUT_DEB}" | awk '{print $1}')"
[[ "${actual_size}" == "${expected_size}" ]] || die "upstream package size mismatch"
[[ "${actual_sha256}" == "${expected_sha256}" ]] || die "upstream package SHA-256 mismatch"
[[ "$(dpkg-deb --field "${INPUT_DEB}" Package)" == "$(jq -er '.package' "${LOCK_FILE}")" ]] \
|| die "upstream package name mismatch"
[[ "$(dpkg-deb --field "${INPUT_DEB}" Version)" == "$(jq -er '.package_version' "${LOCK_FILE}")" ]] \
|| die "upstream package version mismatch"
[[ "$(dpkg-deb --field "${INPUT_DEB}" Architecture)" == "arm64" ]] \
|| die "upstream package is not arm64"
STAGE_DIR="${WORK_DIR}/stage"
reset_child_dir "${WORK_DIR}" "${STAGE_DIR}"
dpkg-deb --extract "${INPUT_DEB}" "${STAGE_DIR}"
local_bin="${STAGE_DIR}/usr/local/bin"
local_lib="${STAGE_DIR}/usr/local/lib"
runtime_bin="${STAGE_DIR}/opt/rkmedia/bin"
runtime_lib="${STAGE_DIR}/opt/rkmedia/lib"
mkdir -p -- "${runtime_bin}" "${runtime_lib}"
for program in ffmpeg ffprobe; do
[[ -x "${local_bin}/${program}" ]] || die "upstream package is missing ${program}"
mv -- "${local_bin}/${program}" "${runtime_bin}/${program}"
done
if [[ -x "${local_bin}/di-edid-decode" ]]; then
mv -- "${local_bin}/di-edid-decode" "${runtime_bin}/di-edid-decode"
fi
shopt -s nullglob
runtime_libraries=(
"${local_lib}"/libdav1d.so*
"${local_lib}"/librga.so*
"${local_lib}"/librockchip_mpp.so*
"${local_lib}"/librockchip_vpu.so*
"${local_lib}"/aarch64-linux-gnu/libdisplay-info.so*
)
((${#runtime_libraries[@]} > 0)) || die "no Rockchip runtime libraries found in upstream package"
mv -- "${runtime_libraries[@]}" "${runtime_lib}/"
shopt -u nullglob
find "${local_lib}" -type f \( -name '*.a' -o -name '*.la' \) -delete
rm -rf -- "${local_lib}/pkgconfig" "${local_lib}/aarch64-linux-gnu/pkgconfig"
rm -f -- \
"${STAGE_DIR}/usr/lib/systemd/system/kodi.service" \
"${STAGE_DIR}/usr/lib/systemd/system/kodi-pulse.service" \
"${STAGE_DIR}/usr/lib/systemd/system/pulseaudio.service"
cp -a -- "${RKMEDIA_ROOT}/packaging/rootfs/." "${STAGE_DIR}/"
mkdir -p -- "${STAGE_DIR}/usr/bin" "${STAGE_DIR}/usr/share/doc/rk1-media-stack"
ln -sfn /opt/rkmedia/bin/ffmpeg-rk "${STAGE_DIR}/usr/bin/ffmpeg-rk"
ln -sfn /opt/rkmedia/bin/ffprobe-rk "${STAGE_DIR}/usr/bin/ffprobe-rk"
ln -sfn /opt/rkmedia/bin/kodi-rk "${STAGE_DIR}/usr/bin/kodi-rk"
install -m 0644 "${LOCK_FILE}" "${STAGE_DIR}/usr/share/doc/rk1-media-stack/upstream-deb.lock.json"
[[ -x "${STAGE_DIR}/usr/local/bin/kodi" ]] || die "Kodi launcher was not retained at its compiled prefix"
[[ -x "${STAGE_DIR}/usr/local/lib/kodi/kodi-gbm" ]] || die "Kodi GBM binary is missing"
for soname in librga.so.2 libdav1d.so.7 librockchip_mpp.so.1 libdisplay-info.so.4; do
[[ -e "${runtime_lib}/${soname}" ]] || die "relocated runtime is missing ${soname}"
readelf -d "${STAGE_DIR}/usr/local/lib/kodi/kodi-gbm" | grep -Fq "Shared library: [${soname}]" \
|| die "Kodi binary does not declare expected dependency ${soname}"
done
if find "${STAGE_DIR}" -type f -name '*.a' -print -quit | grep -q .; then
die "static library remained after repack"
fi
if find "${STAGE_DIR}/usr/local/lib" -type d -name pkgconfig -print -quit | grep -q .; then
die "pkg-config development directory remained after repack"
fi
"${SCRIPT_DIR}/package-media.sh" \
--stage "${STAGE_DIR}" \
--output-dir "${OUTPUT_DIR}" \
--version "${PACKAGE_VERSION}" \
--depends-from-deb "${INPUT_DEB}"
log "repack complete; authoritative input SHA-256: ${actual_sha256}"
+98
View File
@@ -0,0 +1,98 @@
{
"schema_version": 1,
"generated_at": "2026-08-16T16:00:00Z",
"source_date_epoch": 1786886768,
"target": {
"architecture": "arm64",
"gnu_machine": "aarch64-linux-gnu",
"distribution": "debian",
"suite": "trixie",
"install_prefix": "/opt/rkmedia"
},
"sources": [
{
"name": "mpp",
"description": "Rockchip MPP compatibility branch used by ffmpeg-rockchip and kodi-rockchip-deb",
"repository": "https://github.com/nyanmisaka/mpp.git",
"ref": "refs/heads/jellyfin-mpp",
"commit": "a9380ef333102ac318628f83b5f7a460d377749e",
"committed_at": "2025-12-26T12:59:40Z",
"build": true,
"license_files": ["LICENSES"]
},
{
"name": "rga",
"description": "Rockchip RGA compatibility branch used by ffmpeg-rockchip and kodi-rockchip-deb",
"repository": "https://github.com/nyanmisaka/rk-mirrors.git",
"ref": "refs/heads/jellyfin-rga",
"commit": "1d330cc28551943bed3380261a5a9c6fbd58ff53",
"committed_at": "2025-10-19T08:11:31Z",
"build": true,
"license_files": ["COPYING"]
},
{
"name": "dav1d",
"description": "AV1 software fallback required by the matched FFmpeg/Kodi build",
"repository": "https://code.videolan.org/videolan/dav1d.git",
"ref": "refs/tags/1.5.3",
"commit": "b546257f770768b2c88258c533da38b91a06f737",
"committed_at": "2025-12-31T14:50:45Z",
"build": true,
"license_files": ["COPYING"]
},
{
"name": "ffmpeg-rockchip",
"description": "FFmpeg 8.1 with RKMPP decoders/encoders and RKRGA filters",
"repository": "https://github.com/nyanmisaka/ffmpeg-rockchip.git",
"ref": "refs/heads/8.1",
"commit": "d90e3a1c18d7929383cf88c1b3da2e2d1c966cbf",
"committed_at": "2026-08-10T08:42:00Z",
"build": true,
"license_files": ["COPYING.GPLv2", "COPYING.GPLv3", "COPYING.LGPLv2.1", "COPYING.LGPLv3"]
},
{
"name": "libdisplay-info",
"description": "EDID and DisplayID parser required by Kodi's GBM platform",
"repository": "https://gitlab.freedesktop.org/emersion/libdisplay-info.git",
"ref": "refs/heads/main",
"commit": "f1b75310181e364f97a16464efcd92bc82e57ccc",
"committed_at": "2026-07-27T10:12:35Z",
"build": true,
"license_files": ["LICENSE"]
},
{
"name": "kodi",
"description": "Kodi mainline with current GBM DRM PRIME support",
"repository": "https://github.com/xbmc/xbmc.git",
"ref": "refs/heads/master",
"commit": "9c56bf593441a6c246fdc6931177e71f349ee2bc",
"committed_at": "2026-08-16T13:26:08Z",
"build": true,
"license_files": ["LICENSE.md"]
}
],
"reference_heads": [
{
"name": "kodi-rockchip-deb",
"repository": "https://github.com/armsurvivors/kodi-rockchip-deb.git",
"ref": "refs/heads/main",
"commit": "8540dbd24f180aa43cf2d6e55f2ad17bd4f12530",
"observed_at": "2026-08-16T16:00:00Z"
},
{
"name": "rockchip-linux-mpp-official",
"repository": "https://github.com/rockchip-linux/mpp.git",
"ref": "refs/heads/develop",
"commit": "c08762ebfadeb4e986d2fed993bc7a54862d3ebe",
"observed_at": "2026-08-16T16:00:00Z"
},
{
"name": "airockchip-librga-official",
"repository": "https://github.com/airockchip/librga.git",
"ref": "refs/heads/main",
"commit": "2b32edcb97b601b25683e2941d888c8515da6d55",
"observed_at": "2026-08-16T16:00:00Z"
}
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"schema_version": 1,
"release": "20260721-1741",
"release_url": "https://github.com/armsurvivors/kodi-rockchip-deb/releases/tag/20260721-1741",
"recipe_repository": "https://github.com/armsurvivors/kodi-rockchip-deb.git",
"recipe_commit": "4ceb39b453dbbb1de13bd355e2be2542427e2b1e",
"asset_id": 484995753,
"asset_name": "kodi-rockchip-gbm_arm64_kodi_master_ffmpeg_81_trixie.deb",
"asset_url": "https://github.com/armsurvivors/kodi-rockchip-deb/releases/download/20260721-1741/kodi-rockchip-gbm_arm64_kodi_master_ffmpeg_81_trixie.deb",
"asset_size": 147977956,
"asset_sha256": "f9eed4c02d80f12b443604026fb00c4a6ff5ddd1fe5e6c554d81852a849d261a",
"package": "kodi-rockchip-gbm",
"package_version": "20260721-1741-kodi-master-ffmpeg-81",
"architecture": "arm64",
"embedded_ffmpeg_revision": "388741a",
"notes": "The release recipe cloned moving branches without commit locks. The GitHub asset SHA-256 is therefore the authoritative immutable input."
}
+141
View File
@@ -0,0 +1,141 @@
# filename size sha256 url
avahi-daemon_0.8-16_arm64.deb 80636 c88a5d29eefafecdfe52f1357898e0f44e9c0b0a88b62df8311fe3c836b836cd https://deb.debian.org/debian/pool/main/a/avahi/avahi-daemon_0.8-16_arm64.deb
cec-utils_7.0.0-1+b1_arm64.deb 31016 516d5d5df5002dd470d6cea1ea49626765be697815c08c9083ccbc9040f1fe46 https://deb.debian.org/debian/pool/main/libc/libcec/cec-utils_7.0.0-1+b1_arm64.deb
cifs-utils_2%3a7.4-1_arm64.deb 98004 b80ad8586843b5ca439829f95d37201a13c80ed73a0d7bdfffebd42683c38ace https://deb.debian.org/debian/pool/main/c/cifs-utils/cifs-utils_7.4-1_arm64.deb
distro-info-data_0.66+deb13u2_all.deb 6792 91bf35d6efa2a12e0d81abea1819e87fef2d292899f41c40a7ee1085e92cba60 https://deb.debian.org/debian/pool/main/d/distro-info-data/distro-info-data_0.66+deb13u2_all.deb
fontconfig-config_2.15.0-2.3_arm64.deb 318304 10f643f467b51cf25fc18d1945bc61246dd8d8d1fe6bc712a2b1712c74ebc4d6 https://deb.debian.org/debian/pool/main/f/fontconfig/fontconfig-config_2.15.0-2.3_arm64.deb
fonts-dejavu-core_2.37-8_all.deb 840460 86635b3d25b3655fc11cb3ecc3af59f0bf19643b02b94f2de48bd10253cdba12 https://deb.debian.org/debian/pool/main/f/fonts-dejavu/fonts-dejavu-core_2.37-8_all.deb
fonts-dejavu-mono_2.37-8_all.deb 488808 3003e98a5debfdeadc7040a7f715fe9fe6fb67f68deacf6049b54e30f07fc014 https://deb.debian.org/debian/pool/main/f/fonts-dejavu/fonts-dejavu-mono_2.37-8_all.deb
ir-keytable_1.30.1-1_arm64.deb 156240 f70c3afc00fb2fcea6c4fabe24135f110b3642a195e09764202759bfa948e1d7 https://deb.debian.org/debian/pool/main/v/v4l-utils/ir-keytable_1.30.1-1_arm64.deb
iso-codes_4.18.0-1_all.deb 3124584 969582afb02fd3f0833a666b2112931a999c4f6b23e7fe514f8051dfd7672a45 https://deb.debian.org/debian/pool/main/i/iso-codes/iso-codes_4.18.0-1_all.deb
keyutils_1.6.3-6_arm64.deb 54264 21a2ceadbb2f42d9a176646164e573035bcbcb43c1bdfff5a496decd189b459f https://deb.debian.org/debian/pool/main/k/keyutils/keyutils_1.6.3-6_arm64.deb
kmscube_0.0.0~git20210103-1+b3_arm64.deb 75800 5a3549ae3a3b24ed0307b7b8e5ac05878447ae4053d25941bc224fa43b4cdab1 https://deb.debian.org/debian/pool/main/k/kmscube/kmscube_0.0.0~git20210103-1+b3_arm64.deb
libass9_1%3a0.17.3-1+deb13u1_arm64.deb 101336 849fbb86d5fa8b3cac38b80ff577ff3d1f85e6fdcb95313db4639593069843e2 https://deb.debian.org/debian/pool/main/liba/libass/libass9_0.17.3-1+deb13u1_arm64.deb
libasyncns0_0.8-6+b5_arm64.deb 11672 818bbcaa7ada536a1bdd5b1c26b77608606403f5c15557691e83d0b4433b89ef https://deb.debian.org/debian/pool/main/liba/libasyncns/libasyncns0_0.8-6+b5_arm64.deb
libavahi-client3_0.8-16_arm64.deb 46740 aad7c1a4f0e8b9131881ebdd61150bb9e15f11534aafe4877e61fefc266f1586 https://deb.debian.org/debian/pool/main/a/avahi/libavahi-client3_0.8-16_arm64.deb
libavahi-common-data_0.8-16_arm64.deb 112288 75d80488f931f6cc577aaf366b1a17099bd4d0c179dfd33d014f564094255da9 https://deb.debian.org/debian/pool/main/a/avahi/libavahi-common-data_0.8-16_arm64.deb
libavahi-common3_0.8-16_arm64.deb 43340 495d3502dbaed9e71c54cd0030fd135d62140b7c780620cc14a0a8a615adda14 https://deb.debian.org/debian/pool/main/a/avahi/libavahi-common3_0.8-16_arm64.deb
libavahi-core7_0.8-16_arm64.deb 98280 1d60d72c3c4c3a59d86c9b681dbbb71f1ea80c1ea754327a41c96a7288a01f5d https://deb.debian.org/debian/pool/main/a/avahi/libavahi-core7_0.8-16_arm64.deb
libbluetooth3_5.82-1.1_arm64.deb 103028 67395cec3551163c302889cf2eeea12a11de6c069aff8e7a1b9348770a24e180 https://deb.debian.org/debian/pool/main/b/bluez/libbluetooth3_5.82-1.1_arm64.deb
libbluray2_1%3a1.3.4-1+b2_arm64.deb 125560 14da09203931bdbff17b129d447850daecc8c72b87b296e3f7d0e0beb56e9f50 https://deb.debian.org/debian/pool/main/libb/libbluray/libbluray2_1.3.4-1+b2_arm64.deb
libcdio19t64_2.2.0-4.1~deb13u1_arm64.deb 59316 3c76de181f7018ca14e29f75ba6992cf5ec9099e3c3ee304d9d64454af48062f https://deb.debian.org/debian/pool/main/libc/libcdio/libcdio19t64_2.2.0-4.1~deb13u1_arm64.deb
libcec7_7.0.0-1+b1_arm64.deb 189744 f9a9042af0fe627eabf4a4d4a8467a6b0bbeceae1b772f86b5027364f9bae6cf https://deb.debian.org/debian/pool/main/libc/libcec/libcec7_7.0.0-1+b1_arm64.deb
libdaemon0_0.14-7.1+b3_arm64.deb 13152 a685244841cd0840b269da6eeac476410c454b74f1c265823073d27c91a4cefb https://deb.debian.org/debian/pool/main/libd/libdaemon/libdaemon0_0.14-7.1+b3_arm64.deb
libdecor-0-0_0.2.2-2_arm64.deb 15204 34c0db59390767ef7b1cb5594f78f142b0673a5e7788e068cdd323f9c16f9207 https://deb.debian.org/debian/pool/main/libd/libdecor-0/libdecor-0-0_0.2.2-2_arm64.deb
libdrm-amdgpu1_2.4.124-2_arm64.deb 21828 e36c72774cc215818b48ee2174c7ad1d9a2bf89a09cdee7a7a128d288d8eef43 https://deb.debian.org/debian/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.124-2_arm64.deb
libdrm-common_2.4.124-2_all.deb 8288 9a8a6c65c165e9964f106fb4ac710959b5d33e0790227e3ab6b27c4742d1254a https://deb.debian.org/debian/pool/main/libd/libdrm/libdrm-common_2.4.124-2_all.deb
libdrm2_2.4.124-2_arm64.deb 38304 b535506630bb6a9a616bc07caf3b29915f980d8c3437852fb96ea64fb1a8a3f8 https://deb.debian.org/debian/pool/main/libd/libdrm/libdrm2_2.4.124-2_arm64.deb
libegl-mesa0_25.0.7-2+deb13u1_arm64.deb 120960 e344e0c87657d91ebfcb059bee3a3c87ba05871e2918079c3f9393665d4472af https://deb.debian.org/debian/pool/main/m/mesa/libegl-mesa0_25.0.7-2+deb13u1_arm64.deb
libegl1_1.7.0-1+b2_arm64.deb 33952 b663ad1777df7ed8e320f897bbf678c2baef67f5458fe4a43195ac9b444c7afb https://deb.debian.org/debian/pool/main/libg/libglvnd/libegl1_1.7.0-1+b2_arm64.deb
libevdev2_1.13.4+dfsg-1_arm64.deb 30356 abdb9ca2f0a27d88ec16b480f346d1d6c183c59e3d071734ba8d8b9794aacfc6 https://deb.debian.org/debian/pool/main/libe/libevdev/libevdev2_1.13.4+dfsg-1_arm64.deb
libevent-core-2.1-7t64_2.1.12-stable-10+b1_arm64.deb 125752 84a27a752d520df78bab24a499f66db21d5274803eb9a2e4d7f88554210f7dea https://deb.debian.org/debian/pool/main/libe/libevent/libevent-core-2.1-7t64_2.1.12-stable-10+b1_arm64.deb
libexiv2-28_0.28.5+dfsg-1_arm64.deb 820348 62a590d7d09f4ef23acb53abe5f0be2027f71f73cda8c869e812f3fd17f8a1b7 https://deb.debian.org/debian/pool/main/e/exiv2/libexiv2-28_0.28.5+dfsg-1_arm64.deb
libexiv2-data_0.28.5+dfsg-1_all.deb 1775296 afc70f25e5223489d00347bc28f7eb05cf56f6b3faaf05a506ce6d0efbd5fa61 https://deb.debian.org/debian/pool/main/e/exiv2/libexiv2-data_0.28.5+dfsg-1_all.deb
libflac14_1.5.0+ds-2_arm64.deb 179768 11800827306e56fcbd1a65ea67d9d13126fc02bf87fdfecd35e0f48a145b3c59 https://deb.debian.org/debian/pool/main/f/flac/libflac14_1.5.0+ds-2_arm64.deb
libfontconfig1_2.15.0-2.3_arm64.deb 387092 872781cf62926b68c34a4302a3caae334ff8d4d83a2b2477f8fb454460aadc99 https://deb.debian.org/debian/pool/main/f/fontconfig/libfontconfig1_2.15.0-2.3_arm64.deb
libfreetype6_2.13.3+dfsg-1+deb13u1_arm64.deb 422284 0c426e83f1af816b9a4df9056fd2238463d1cd07969ee640965da9fb6ecaf107 https://deb.debian.org/debian/pool/main/f/freetype/libfreetype6_2.13.3+dfsg-1+deb13u1_arm64.deb
libfribidi0_1.0.16-1_arm64.deb 26460 5f5bfdf6ef126b0a38f8ebb8751cbbb845fe2cd4e1dd5258d241be243e09897f https://deb.debian.org/debian/pool/main/f/fribidi/libfribidi0_1.0.16-1_arm64.deb
libfstrcmp0_0.7.D001-5_arm64.deb 7964 bf36ecf2c4d18a1e8cd55d5a9db5f495bd4ef482778d686a49ddaa2c0cdadc60 https://deb.debian.org/debian/pool/main/f/fstrcmp/libfstrcmp0_0.7.D001-5_arm64.deb
libgbm1_25.0.7-2+deb13u1_arm64.deb 44144 2beb8d20ca715a01b7b2352c1167c0d7097c9d8a486704a9a966f95d4077cdfc https://deb.debian.org/debian/pool/main/m/mesa/libgbm1_25.0.7-2+deb13u1_arm64.deb
libgif7_5.2.2-1+deb13u1_arm64.deb 44120 658318d55d788a628836d35d994f60057d2655cb81b355648fbe2dc3003ddeb9 https://deb.debian.org/debian/pool/main/g/giflib/libgif7_5.2.2-1+deb13u1_arm64.deb
libgl1-mesa-dri_25.0.7-2+deb13u1_arm64.deb 45852 628245ff9d8e4f11edb66c0854915018402853e0eb588343d472111f6c4fb582 https://deb.debian.org/debian/pool/main/m/mesa/libgl1-mesa-dri_25.0.7-2+deb13u1_arm64.deb
libgl1_1.7.0-1+b2_arm64.deb 90876 7943f99962aa3316515760bf44e66f0c15a3c813ad750b784b43e319fa2e6003 https://deb.debian.org/debian/pool/main/libg/libglvnd/libgl1_1.7.0-1+b2_arm64.deb
libgles2_1.7.0-1+b2_arm64.deb 18036 c727fa1746ed3e343d1a2917932363d71e961eacca0443a1e2296587e03c47fb https://deb.debian.org/debian/pool/main/libg/libglvnd/libgles2_1.7.0-1+b2_arm64.deb
libglvnd0_1.7.0-1+b2_arm64.deb 41588 29ec7b8cf1c9a47055de643eb0a1921854e762b4789a7f3bc3b0dea498fd4da9 https://deb.debian.org/debian/pool/main/libg/libglvnd/libglvnd0_1.7.0-1+b2_arm64.deb
libglx-mesa0_25.0.7-2+deb13u1_arm64.deb 142320 18c02afcdd88f547ba66d3ae9290f93ab155381bf8d96b0127f9028374acfc64 https://deb.debian.org/debian/pool/main/m/mesa/libglx-mesa0_25.0.7-2+deb13u1_arm64.deb
libglx0_1.7.0-1+b2_arm64.deb 31080 0245aafaac5e68ca9511f23c5bebf010327e63c65790c03dbb80bf14d485956d https://deb.debian.org/debian/pool/main/libg/libglvnd/libglx0_1.7.0-1+b2_arm64.deb
libgraphite2-3_1.3.14-2+deb13u1_arm64.deb 70348 b0c3414b113de62be1dc28fa2c899ac0856256e2f094d92c36c3275a5053311a https://deb.debian.org/debian/pool/main/g/graphite2/libgraphite2-3_1.3.14-2+deb13u1_arm64.deb
libgstreamer-plugins-base1.0-0_1.26.2-1+deb13u1_arm64.deb 897352 bc3205ea9fd35448b2cb6171da0e24bd3af893fb8a01f8eb797cbfb4d1fe7b0e https://deb.debian.org/debian/pool/main/g/gst-plugins-base1.0/libgstreamer-plugins-base1.0-0_1.26.2-1+deb13u1_arm64.deb
libgstreamer1.0-0_1.26.2-2_arm64.deb 1294408 77d44e8bc93793f815ea7b8cdb9c1b0b0bf6fd5797e2e69a7ef95bb37f249a86 https://deb.debian.org/debian/pool/main/g/gstreamer1.0/libgstreamer1.0-0_1.26.2-2_arm64.deb
libgudev-1.0-0_238-6_arm64.deb 13720 f0fec02f1bbc0803c9d044413609d3cd0fde24bfe6a5141a38aabfa86c292b21 https://deb.debian.org/debian/pool/main/libg/libgudev/libgudev-1.0-0_238-6_arm64.deb
libharfbuzz0b_10.2.0-1+deb13u1_arm64.deb 441932 3ff928f981b54b491ee12e4cf68558cc5d61c652bdab276cd4c4c3fbbcee16ca https://deb.debian.org/debian/pool/main/h/harfbuzz/libharfbuzz0b_10.2.0-1+deb13u1_arm64.deb
libicu76_76.1-4_arm64.deb 9523672 62eb15577c881b7d02aa861e3eb860902335630cc8174daf525b48c2fa39298f https://deb.debian.org/debian/pool/main/i/icu/libicu76_76.1-4_arm64.deb
libinih1_59-1_arm64.deb 6932 fcbc936af8fdd3db56b2a8fc4891b27955ce48c7639cbd6905fd292ae20061fa https://deb.debian.org/debian/pool/main/libi/libinih/libinih1_59-1_arm64.deb
libinireader0_59-1_arm64.deb 12188 40937f22b74c8ac9bdb5f286b53ea7b81eb543d1d0497854412d0dd664e0c626 https://deb.debian.org/debian/pool/main/libi/libinih/libinireader0_59-1_arm64.deb
libinput-bin_1.28.1-1+deb13u1_arm64.deb 26788 9da373d042dd8e0c45e34a5a0dc361cb386fd5ff4f066b7b3e64e1ea66de0835 https://deb.debian.org/debian/pool/main/libi/libinput/libinput-bin_1.28.1-1+deb13u1_arm64.deb
libinput10_1.28.1-1+deb13u1_arm64.deb 129616 a2acd04f3b1e9e91d22a26b63650087a179b6b3dbaddadc92d89edfbb2075ae1 https://deb.debian.org/debian/pool/main/libi/libinput/libinput10_1.28.1-1+deb13u1_arm64.deb
libjansson4_2.14-2+b3_arm64.deb 39228 7938472b1ddfa8b0c8f58d5f44406ae7a77a342dbb502a02f6bf292b4f853ab0 https://deb.debian.org/debian/pool/main/j/jansson/libjansson4_2.14-2+b3_arm64.deb
libjpeg62-turbo_1%3a2.1.5-4_arm64.deb 172784 e4989073bb0bac8a6ec043c7adb80e1dfe601d8552233da48bb24ab45d1a1d4a https://deb.debian.org/debian/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.1.5-4_arm64.deb
liblcms2-2_2.16-2+deb13u2_arm64.deb 151540 6636997fc488327a8d0951a038df6e5178a52b3f5e5cc2386d42655621f5672d https://deb.debian.org/debian/pool/main/l/lcms2/liblcms2-2_2.16-2+deb13u2_arm64.deb
libldb2_2%3a2.11.0+samba4.22.10+dfsg-0+deb13u2_arm64.deb 172588 b3017ddb7b56ca01c3501f6a25c663bb45747e01e5de5c8341f853fb42d80cf1 https://security.debian.org/debian-security/pool/updates/main/s/samba/libldb2_2.11.0+samba4.22.10+dfsg-0+deb13u2_arm64.deb
liblirc-client0t64_0.10.2-0.10_arm64.deb 30864 ddde026178705ec2227c603b93121bd8f35d2d8231c9db269de3127df98075b1 https://deb.debian.org/debian/pool/main/l/lirc/liblirc-client0t64_0.10.2-0.10_arm64.deb
libllvm19_1%3a19.1.7-3+b1_arm64.deb 23285344 8a48e1a49f7fcc3be74e697767d72902f4b03041401d1529bcfad1849df39148 https://deb.debian.org/debian/pool/main/l/llvm-toolchain-19/libllvm19_19.1.7-3+b1_arm64.deb
liblmdb0_0.9.31-1+b2_arm64.deb 43504 aaef40a5569a13235e4b067b07e170130fc50c5ff21d685e8961290323baff1d https://deb.debian.org/debian/pool/main/l/lmdb/liblmdb0_0.9.31-1+b2_arm64.deb
libmariadb3_1%3a11.8.6-0+deb13u1_arm64.deb 175040 29acd0080b3ea32440a8ebd0445bb13e88ff564c408faf2bfb70c129c2048503 https://deb.debian.org/debian/pool/main/m/mariadb/libmariadb3_11.8.6-0+deb13u1_arm64.deb
libmicrohttpd12t64_1.0.1-4_arm64.deb 147824 c6822d190185155f25eec4ab42613b47d68ee76811415c563e88ce3a35fcf179 https://deb.debian.org/debian/pool/main/libm/libmicrohttpd/libmicrohttpd12t64_1.0.1-4_arm64.deb
libmp3lame0_3.100-6+b3_arm64.deb 354760 ed3563722129ffa4def03b7af0b589fa20eef972bdcc4599202ddb1ac654c58e https://deb.debian.org/debian/pool/main/l/lame/libmp3lame0_3.100-6+b3_arm64.deb
libmpg123-0t64_1.32.10-1+deb13u1_arm64.deb 143208 d284df39ff3b64f1cdf274352613c52534c55eed82c3a2ff0fc7ba154acd7bf0 https://deb.debian.org/debian/pool/main/m/mpg123/libmpg123-0t64_1.32.10-1+deb13u1_arm64.deb
libmtdev1t64_1.1.7-1_arm64.deb 22456 3433f77a9f96d0dc537e870db734a4dbb50528836782c86709845736e8897b47 https://deb.debian.org/debian/pool/main/m/mtdev/libmtdev1t64_1.1.7-1_arm64.deb
libncurses6_6.5+20250216-2_arm64.deb 96112 259fe5736ebb105df7c6a7b5cc6231a8743666f5f57885f6c7989babe2fc0082 https://deb.debian.org/debian/pool/main/n/ncurses/libncurses6_6.5+20250216-2_arm64.deb
libnfs14_5.0.2-1+b2_arm64.deb 102472 1929f3b75a32c99582e55039a91c4be93561be9bcf652122e602d5098fe51221 https://deb.debian.org/debian/pool/main/libn/libnfs/libnfs14_5.0.2-1+b2_arm64.deb
libnfsidmap1_1%3a2.8.3-1_arm64.deb 55728 861f226ebe6d1ea4c7951b92e900b1a92902d8c897c902535a67579877c4ef54 https://deb.debian.org/debian/pool/main/n/nfs-utils/libnfsidmap1_2.8.3-1_arm64.deb
libogg0_1.3.5-3+b2_arm64.deb 23580 d59a83ab352bf0c16ce9513fdf264f87214813ca8c628c652801ef476ece8286 https://deb.debian.org/debian/pool/main/libo/libogg/libogg0_1.3.5-3+b2_arm64.deb
libopus0_1.5.2-2_arm64.deb 2812952 1d980e8a4717074805a98dbe07671cb3bee068c25709315f3de836980c629836 https://deb.debian.org/debian/pool/main/o/opus/libopus0_1.5.2-2_arm64.deb
liborc-0.4-0t64_1%3a0.4.41-1_arm64.deb 170796 e69ee68606246657ff19bfe13058a9aef177e686b97638728f9e0e911af07680 https://deb.debian.org/debian/pool/main/o/orc/liborc-0.4-0t64_0.4.41-1_arm64.deb
libp8-platform2_2.1.0.1+dfsg1-4+b3_arm64.deb 20216 d3e7af8fd96a2532600260f35421f05564fcde3a2aaf6355d2500cd212c03e20 https://deb.debian.org/debian/pool/main/p/p8-platform/libp8-platform2_2.1.0.1+dfsg1-4+b3_arm64.deb
libpci3_1%3a3.13.0-2_arm64.deb 71808 fde73536f93fcd930362531506f8431b2e959bfbdeb41e772100c1cfc1f83953 https://deb.debian.org/debian/pool/main/p/pciutils/libpci3_3.13.0-2_arm64.deb
libplist-2.0-4_2.6.0-2+b1_arm64.deb 46824 45d585abe7d0ba46abda543513d18869a14d21e4abba266b851dd71f2f69882b https://deb.debian.org/debian/pool/main/libp/libplist/libplist-2.0-4_2.6.0-2+b1_arm64.deb
libpng16-16t64_1.6.48-1+deb13u5_arm64.deb 275192 11096ad43504ca24e8044bcc89e922d932a2812ff25568613b28d22235190ba5 https://deb.debian.org/debian/pool/main/libp/libpng1.6/libpng16-16t64_1.6.48-1+deb13u5_arm64.deb
libpulse0_17.0+dfsg1-2+b1_arm64.deb 236376 2de5610e3affe69cfb11110f890375261b47c9e0fc1a81eef7ecc4d6d2dc9a6d https://deb.debian.org/debian/pool/main/p/pulseaudio/libpulse0_17.0+dfsg1-2+b1_arm64.deb
libpython3.13_3.13.5-2+deb13u4_arm64.deb 1977636 d8f0a0d8d829988605ad66aff280c8c9770060b21868aae14a45e8ef872589ec https://deb.debian.org/debian/pool/main/p/python3.13/libpython3.13_3.13.5-2+deb13u4_arm64.deb
libsensors-config_1%3a3.6.2-2_all.deb 16236 3056da80c7d963af795dab480ab6f6f4b154ad4ac39f522dc52d17c834fea253 https://deb.debian.org/debian/pool/main/l/lm-sensors/libsensors-config_3.6.2-2_all.deb
libsensors5_1%3a3.6.2-2_arm64.deb 36444 7469f1ccce8afdba33d3b11847d0ecdf2221805a0694dee21ee4d622e8f72145 https://deb.debian.org/debian/pool/main/l/lm-sensors/libsensors5_3.6.2-2_arm64.deb
libshairplay0_0.9.0~git20180824.096b61a+dfsg1-3+b2_arm64.deb 100452 753f16da9a7953b3cec3e66ae5a379e72ed5e82ad7bb94443d13ec5f678c141d https://deb.debian.org/debian/pool/main/s/shairplay/libshairplay0_0.9.0~git20180824.096b61a+dfsg1-3+b2_arm64.deb
libsmbclient0_2%3a4.22.10+dfsg-0+deb13u2_arm64.deb 95392 db717af217d11808f02cdce5f4c12a4196a8dbff8d8713746e67265e5aff48c1 https://security.debian.org/debian-security/pool/updates/main/s/samba/libsmbclient0_4.22.10+dfsg-0+deb13u2_arm64.deb
libsndfile1_1.2.2-2+deb13u1_arm64.deb 185528 6c94f17e1f02778f98b96bb9ae19ff30085662e96685898a018c26bfd4bfc3f2 https://deb.debian.org/debian/pool/main/libs/libsndfile/libsndfile1_1.2.2-2+deb13u1_arm64.deb
libtag2_2.0.2-2_arm64.deb 310408 7fcf13ab2eb9f7671ea1692bb78c168e712e17f16cbd1e39e7c8dcc89dd077a7 https://deb.debian.org/debian/pool/main/t/taglib/libtag2_2.0.2-2_arm64.deb
libtalloc2_2%3a2.4.3+samba4.22.10+dfsg-0+deb13u2_arm64.deb 62316 20a23a60ef4030207d6f22e2f5bb20e57bb42a090fefb1a5606f23d34aba0510 https://security.debian.org/debian-security/pool/updates/main/s/samba/libtalloc2_2.4.3+samba4.22.10+dfsg-0+deb13u2_arm64.deb
libtdb1_2%3a1.4.13+samba4.22.10+dfsg-0+deb13u2_arm64.deb 82104 ba86a85fee628c394480248ea226cca10545b527b4ebf8799a1142c09265f327 https://security.debian.org/debian-security/pool/updates/main/s/samba/libtdb1_1.4.13+samba4.22.10+dfsg-0+deb13u2_arm64.deb
libtevent0t64_2%3a0.16.2+samba4.22.10+dfsg-0+deb13u2_arm64.deb 76128 2383d7e65a90114761f80c7ed801c516c3e09f74c78b92c6288a8be32ec2fce1 https://security.debian.org/debian-security/pool/updates/main/s/samba/libtevent0t64_0.16.2+samba4.22.10+dfsg-0+deb13u2_arm64.deb
libtinyxml2-11_11.0.0+dfsg-1+b1_arm64.deb 29808 596a10c41466c126c61ecfeed5d31f515066abccf5b2a2a6587e85473540eca9 https://deb.debian.org/debian/pool/main/t/tinyxml2/libtinyxml2-11_11.0.0+dfsg-1+b1_arm64.deb
libtinyxml2.6.2v5_2.6.2-7_arm64.deb 36500 6d6122c8011043f7f7bbd8f4e93c70f2dd191bd57a596cff61e2f6411c3f55ed https://deb.debian.org/debian/pool/main/t/tinyxml/libtinyxml2.6.2v5_2.6.2-7_arm64.deb
libudfread0_1.1.2-1+b2_arm64.deb 17312 badab108ac953987ced64c4b25a38c75ac391e6f4815cca4880619f313b4da22 https://deb.debian.org/debian/pool/main/libu/libudfread/libudfread0_1.1.2-1+b2_arm64.deb
libunibreak6_6.1-3_arm64.deb 21464 8689f3761d5ac0dd9119f187561dd61dbb8b273d3a684fc57fbcc3de9daac9ed https://deb.debian.org/debian/pool/main/libu/libunibreak/libunibreak6_6.1-3_arm64.deb
libvorbis0a_1.3.7-3_arm64.deb 83772 6e8e64ebe692dd2d019af7f914f2e3b81bf13d4b1ce2516fb4248a0310ef7cf6 https://deb.debian.org/debian/pool/main/libv/libvorbis/libvorbis0a_1.3.7-3_arm64.deb
libvorbisenc2_1.3.7-3_arm64.deb 75532 4a477b3ae19ba50a288f036c8176d61cd9f3836ba65472f88b95e3f6b3bf2dd3 https://deb.debian.org/debian/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-3_arm64.deb
libvulkan1_1.4.309.0-1_arm64.deb 126808 f5f576d8b9e5703702a9f3254c8c5d130bef17da9db4aecd46ba86cec7a08930 https://deb.debian.org/debian/pool/main/v/vulkan-loader/libvulkan1_1.4.309.0-1_arm64.deb
libwacom-common_2.14.0-1_all.deb 102748 214956bd9b26600c4d5ac0d60c1685be6ef650644e36b712620c56fccd4d4f58 https://deb.debian.org/debian/pool/main/libw/libwacom/libwacom-common_2.14.0-1_all.deb
libwacom9_2.14.0-1_arm64.deb 24192 8fb8419c43fcc166f996c30cc262fcc86457bdd6f82656dad8619c88dc00b013 https://deb.debian.org/debian/pool/main/libw/libwacom/libwacom9_2.14.0-1_arm64.deb
libwayland-client0_1.23.1-3_arm64.deb 26140 8abdc649e46470548c545f827e16f52e1bc16d05d4a3794e6dedb2d74ce58c9a https://deb.debian.org/debian/pool/main/w/wayland/libwayland-client0_1.23.1-3_arm64.deb
libwayland-egl1_1.23.1-3_arm64.deb 5952 fe01fb2f3c5d5b54784563d9826fa505fab61dc30333d71343352bddd3812b18 https://deb.debian.org/debian/pool/main/w/wayland/libwayland-egl1_1.23.1-3_arm64.deb
libwayland-server0_1.23.1-3_arm64.deb 33656 062702d57cf07b42ab7f0fa1aac8bc3f605eb47f2f0b9ecb1614674140d79f1b https://deb.debian.org/debian/pool/main/w/wayland/libwayland-server0_1.23.1-3_arm64.deb
libwbclient0_2%3a4.22.10+dfsg-0+deb13u2_arm64.deb 70980 885cd7c331d6ffb0da72aeb76809deac82a8949ef415ed5c742a466d7fe1be96 https://security.debian.org/debian-security/pool/updates/main/s/samba/libwbclient0_4.22.10+dfsg-0+deb13u2_arm64.deb
libx11-6_2%3a1.8.12-1_arm64.deb 794952 646f2d3f2165c8eebceb7f4aaca31e97a818bb11a045964833a9f764b738bac2 https://deb.debian.org/debian/pool/main/libx/libx11/libx11-6_1.8.12-1_arm64.deb
libx11-data_2%3a1.8.12-1_all.deb 343072 c54f87069888f80ba4da586da6147d74c7598ccdd8b90906dbc4271fa414c738 https://deb.debian.org/debian/pool/main/libx/libx11/libx11-data_1.8.12-1_all.deb
libx11-xcb1_2%3a1.8.12-1_arm64.deb 247336 1eafaa4295e129d6a81e742ab8da43194daa6b916199923c8cef8ad358985894 https://deb.debian.org/debian/pool/main/libx/libx11/libx11-xcb1_1.8.12-1_arm64.deb
libxau6_1%3a1.0.11-1_arm64.deb 20576 ac1061728670f4626adaa1288953a0e6fb801c9cae72ee1c3231e63e2609d23a https://deb.debian.org/debian/pool/main/libx/libxau/libxau6_1.0.11-1_arm64.deb
libxcb-dri3-0_1.17.0-2+b1_arm64.deb 107068 247bf758dd78d6c285fdd47942ddacf4a3189fd50de4f0891c9bd9aea12da3a1 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-dri3-0_1.17.0-2+b1_arm64.deb
libxcb-glx0_1.17.0-2+b1_arm64.deb 122608 a0d96ab5faea8fa1f3e095d6236597622a6b763f061f8f9fa23b77733608fdbc https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-glx0_1.17.0-2+b1_arm64.deb
libxcb-present0_1.17.0-2+b1_arm64.deb 105784 73521d715554a2230901d1d480465d6358b3083a5ce8801639595ffde6f31441 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-present0_1.17.0-2+b1_arm64.deb
libxcb-randr0_1.17.0-2+b1_arm64.deb 116736 dbeb78d1d032959fe65548758ab12bfc6bb5919ec2984c3d871496b52aef929a https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-randr0_1.17.0-2+b1_arm64.deb
libxcb-shm0_1.17.0-2+b1_arm64.deb 105480 e9e871dc5d2f9265eb5d3028f0d0545cb6f8b748a68997b4c43f5a9416c7ead7 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-shm0_1.17.0-2+b1_arm64.deb
libxcb-sync1_1.17.0-2+b1_arm64.deb 108792 eb428ac77daac9e5eea8a4e9ba48d1d78cc8ca400e415c8e4628cc729c6f4749 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-sync1_1.17.0-2+b1_arm64.deb
libxcb-xfixes0_1.17.0-2+b1_arm64.deb 109644 19cdb8502827bc010993e69eaba09c670024976f7edd35b2b504bd646b141b0a https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-xfixes0_1.17.0-2+b1_arm64.deb
libxcb-xkb1_1.17.0-2+b1_arm64.deb 128572 739b023ad82b4c9ac2c6cc9e6cc8602a17a05f7f5387d45a5a60eafdc17bf0e5 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-xkb1_1.17.0-2+b1_arm64.deb
libxcb1_1.17.0-2+b1_arm64.deb 143168 d0178198e80ed4cacdececabe2c112ec88c7a9258cc11a55b8e267ab14a90d82 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb1_1.17.0-2+b1_arm64.deb
libxdmcp6_1%3a1.1.5-1_arm64.deb 27752 e10bbb0802181992ecf091e9425171850eee16068729c109214ba4924f81fb52 https://deb.debian.org/debian/pool/main/libx/libxdmcp/libxdmcp6_1.1.5-1_arm64.deb
libxext6_2%3a1.3.4-1+b3_arm64.deb 49244 27cf208c6d2924b22ed3b9ceff304b992e1f07ef7b0ef00239585595581dac99 https://deb.debian.org/debian/pool/main/libx/libxext/libxext6_1.3.4-1+b3_arm64.deb
libxkbcommon-x11-0_1.7.0-2_arm64.deb 15168 414ec70596c8193b309279dc6d8a35f809ffe5047addc3587a6d52eb462bebc5 https://deb.debian.org/debian/pool/main/libx/libxkbcommon/libxkbcommon-x11-0_1.7.0-2_arm64.deb
libxkbcommon0_1.7.0-2_arm64.deb 106024 866888d3cfeb32388dd88a2615c4f78ebf8dd7b272821a83494cba260d8fa7d1 https://deb.debian.org/debian/pool/main/libx/libxkbcommon/libxkbcommon0_1.7.0-2_arm64.deb
libxml2_2.12.7+dfsg+really2.9.14-2.1+deb13u3_arm64.deb 631428 a66b9c960acef8ed9653223cbd00b155a92ebb758ea13a788c41609246c23bc7 https://deb.debian.org/debian/pool/main/libx/libxml2/libxml2_2.12.7+dfsg+really2.9.14-2.1+deb13u3_arm64.deb
libxshmfence1_1.3.3-1_arm64.deb 11104 34b494c83cebcf138d1f87c2cbdd5ee8efe9da2befbd69c2a025b0e214c64d2d https://deb.debian.org/debian/pool/main/libx/libxshmfence/libxshmfence1_1.3.3-1_arm64.deb
libxslt1.1_1.1.35-1.2+deb13u3_arm64.deb 222332 590a5d177dcfa2bf432f0d7db80b936ac7afadfebdb7f077d0c3305da20d1897 https://deb.debian.org/debian/pool/main/libx/libxslt/libxslt1.1_1.1.35-1.2+deb13u3_arm64.deb
libxxf86vm1_1%3a1.1.4-1+b4_arm64.deb 19176 9ed4bb25e311486207eeaa609decc3905aa9071c4c7f2a605829e3a79ec88b83 https://deb.debian.org/debian/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b4_arm64.deb
libz3-4_4.13.3-1_arm64.deb 7506868 29a7b924a5a38c9ac4e2a64aa11586a9c54f26b579b18e4f75adf8f7c1106e45 https://deb.debian.org/debian/pool/main/z/z3/libz3-4_4.13.3-1_arm64.deb
mariadb-common_1%3a11.8.6-0+deb13u1_all.deb 29544 b5116ff56a60d471332d45bfd4ec50391fdcf5a40747bf7fe23ec8551cb71f9c https://deb.debian.org/debian/pool/main/m/mariadb/mariadb-common_11.8.6-0+deb13u1_all.deb
mesa-libgallium_25.0.7-2+deb13u1_arm64.deb 8032536 a27576030704ff305adaef8bd143954b281680861d5bfe21aabd7d376a939149 https://deb.debian.org/debian/pool/main/m/mesa/mesa-libgallium_25.0.7-2+deb13u1_arm64.deb
mesa-utils-bin_9.0.0-2+b2_arm64.deb 192896 e7906035da53770d63ff477f626c0c7209a799caf781714b229263a3d75ec03c https://deb.debian.org/debian/pool/main/m/mesa-demos/mesa-utils-bin_9.0.0-2+b2_arm64.deb
mesa-utils_9.0.0-2+b2_arm64.deb 955092 aaa2276c9e679c811fb980c98e20acbd5ea93403a8d92792315f05f82a2e2bee https://deb.debian.org/debian/pool/main/m/mesa-demos/mesa-utils_9.0.0-2+b2_arm64.deb
mesa-vulkan-drivers_25.0.7-2+deb13u1_arm64.deb 11373712 b73e1b64f26d55c981339533f3eb64397fda4a8e86dd5593ed15e1c48711141d https://deb.debian.org/debian/pool/main/m/mesa/mesa-vulkan-drivers_25.0.7-2+deb13u1_arm64.deb
mysql-common_5.8+1.1.1_all.deb 6784 a4e36be39be45d6f5267ac71b92d7eecd40a15f25d52a0bfb9c5eaca1b90e534 https://deb.debian.org/debian/pool/main/m/mysql-defaults/mysql-common_5.8+1.1.1_all.deb
nfs-common_1%3a2.8.3-1_arm64.deb 260196 1c45d155086ecef6f531122a6d9a2dbf873f85a211eebf0fdf2841c3ee2f93d0 https://deb.debian.org/debian/pool/main/n/nfs-utils/nfs-common_2.8.3-1_arm64.deb
pci.ids_0.0~2025.06.09-1_all.deb 269172 132bc7520473e53939e277cbf4512261b8d73ae244d77c277f674ecaf174598c https://deb.debian.org/debian/pool/main/p/pci.ids/pci.ids_0.0~2025.06.09-1_all.deb
pciutils_1%3a3.13.0-2_arm64.deb 122800 e60b3a48ce93ed5634713226b6ca623a7e7a77007ac0de0a38698c84f36e1867 https://deb.debian.org/debian/pool/main/p/pciutils/pciutils_3.13.0-2_arm64.deb
python-apt-common_3.0.0_all.deb 68248 9aacb635fa04e01ab56a0af4869a034a94aadcd564d662a91048cd19fe7d6e0c https://deb.debian.org/debian/pool/main/p/python-apt/python-apt-common_3.0.0_all.deb
python3-apt_3.0.0_arm64.deb 157212 7f9c928895e7141ecea9d53ee93962cfb37d48a2015abc99d95a22e77535f8a7 https://deb.debian.org/debian/pool/main/p/python-apt/python3-apt_3.0.0_arm64.deb
python3-dbus_1.4.0-1_arm64.deb 96064 fe98d700adc6239a01ffa7441bb554a78b43bf7be4edf026a8be279266dde919 https://deb.debian.org/debian/pool/main/d/dbus-python/python3-dbus_1.4.0-1_arm64.deb
python3-distro-info_1.13_all.deb 7736 11848888a90676314dd977fd2788e2d1f11f5e471d879313b8158a28488dc062 https://deb.debian.org/debian/pool/main/d/distro-info/python3-distro-info_1.13_all.deb
rpcbind_1.2.7-1_arm64.deb 47016 1ff91ee1a472c5b3891653a2622d51c9bbb0d11f42e0bf62614d88c16e9d1003 https://deb.debian.org/debian/pool/main/r/rpcbind/rpcbind_1.2.7-1_arm64.deb
samba-libs_2%3a4.22.10+dfsg-0+deb13u2_arm64.deb 5646024 23dd06b3d384ff0ad2da844a369c45962eb2663ac66e7078edf01c811936cd16 https://security.debian.org/debian-security/pool/updates/main/s/samba/samba-libs_4.22.10+dfsg-0+deb13u2_arm64.deb
unattended-upgrades_2.12_all.deb 66912 a5e37d196598d2f2a7cb8c3ded760b77283d0ecf110d2f90d77d4821e3512eb6 https://deb.debian.org/debian/pool/main/u/unattended-upgrades/unattended-upgrades_2.12_all.deb
vulkan-tools_1.4.304.0+dfsg1-1_arm64.deb 261792 de3b21a2a8a2b2cf520cad433878589c70366050f52570a4fe37556b7c748647 https://deb.debian.org/debian/pool/main/v/vulkan-tools/vulkan-tools_1.4.304.0+dfsg1-1_arm64.deb
1 # filename size sha256 url
2 avahi-daemon_0.8-16_arm64.deb 80636 c88a5d29eefafecdfe52f1357898e0f44e9c0b0a88b62df8311fe3c836b836cd https://deb.debian.org/debian/pool/main/a/avahi/avahi-daemon_0.8-16_arm64.deb
3 cec-utils_7.0.0-1+b1_arm64.deb 31016 516d5d5df5002dd470d6cea1ea49626765be697815c08c9083ccbc9040f1fe46 https://deb.debian.org/debian/pool/main/libc/libcec/cec-utils_7.0.0-1+b1_arm64.deb
4 cifs-utils_2%3a7.4-1_arm64.deb 98004 b80ad8586843b5ca439829f95d37201a13c80ed73a0d7bdfffebd42683c38ace https://deb.debian.org/debian/pool/main/c/cifs-utils/cifs-utils_7.4-1_arm64.deb
5 distro-info-data_0.66+deb13u2_all.deb 6792 91bf35d6efa2a12e0d81abea1819e87fef2d292899f41c40a7ee1085e92cba60 https://deb.debian.org/debian/pool/main/d/distro-info-data/distro-info-data_0.66+deb13u2_all.deb
6 fontconfig-config_2.15.0-2.3_arm64.deb 318304 10f643f467b51cf25fc18d1945bc61246dd8d8d1fe6bc712a2b1712c74ebc4d6 https://deb.debian.org/debian/pool/main/f/fontconfig/fontconfig-config_2.15.0-2.3_arm64.deb
7 fonts-dejavu-core_2.37-8_all.deb 840460 86635b3d25b3655fc11cb3ecc3af59f0bf19643b02b94f2de48bd10253cdba12 https://deb.debian.org/debian/pool/main/f/fonts-dejavu/fonts-dejavu-core_2.37-8_all.deb
8 fonts-dejavu-mono_2.37-8_all.deb 488808 3003e98a5debfdeadc7040a7f715fe9fe6fb67f68deacf6049b54e30f07fc014 https://deb.debian.org/debian/pool/main/f/fonts-dejavu/fonts-dejavu-mono_2.37-8_all.deb
9 ir-keytable_1.30.1-1_arm64.deb 156240 f70c3afc00fb2fcea6c4fabe24135f110b3642a195e09764202759bfa948e1d7 https://deb.debian.org/debian/pool/main/v/v4l-utils/ir-keytable_1.30.1-1_arm64.deb
10 iso-codes_4.18.0-1_all.deb 3124584 969582afb02fd3f0833a666b2112931a999c4f6b23e7fe514f8051dfd7672a45 https://deb.debian.org/debian/pool/main/i/iso-codes/iso-codes_4.18.0-1_all.deb
11 keyutils_1.6.3-6_arm64.deb 54264 21a2ceadbb2f42d9a176646164e573035bcbcb43c1bdfff5a496decd189b459f https://deb.debian.org/debian/pool/main/k/keyutils/keyutils_1.6.3-6_arm64.deb
12 kmscube_0.0.0~git20210103-1+b3_arm64.deb 75800 5a3549ae3a3b24ed0307b7b8e5ac05878447ae4053d25941bc224fa43b4cdab1 https://deb.debian.org/debian/pool/main/k/kmscube/kmscube_0.0.0~git20210103-1+b3_arm64.deb
13 libass9_1%3a0.17.3-1+deb13u1_arm64.deb 101336 849fbb86d5fa8b3cac38b80ff577ff3d1f85e6fdcb95313db4639593069843e2 https://deb.debian.org/debian/pool/main/liba/libass/libass9_0.17.3-1+deb13u1_arm64.deb
14 libasyncns0_0.8-6+b5_arm64.deb 11672 818bbcaa7ada536a1bdd5b1c26b77608606403f5c15557691e83d0b4433b89ef https://deb.debian.org/debian/pool/main/liba/libasyncns/libasyncns0_0.8-6+b5_arm64.deb
15 libavahi-client3_0.8-16_arm64.deb 46740 aad7c1a4f0e8b9131881ebdd61150bb9e15f11534aafe4877e61fefc266f1586 https://deb.debian.org/debian/pool/main/a/avahi/libavahi-client3_0.8-16_arm64.deb
16 libavahi-common-data_0.8-16_arm64.deb 112288 75d80488f931f6cc577aaf366b1a17099bd4d0c179dfd33d014f564094255da9 https://deb.debian.org/debian/pool/main/a/avahi/libavahi-common-data_0.8-16_arm64.deb
17 libavahi-common3_0.8-16_arm64.deb 43340 495d3502dbaed9e71c54cd0030fd135d62140b7c780620cc14a0a8a615adda14 https://deb.debian.org/debian/pool/main/a/avahi/libavahi-common3_0.8-16_arm64.deb
18 libavahi-core7_0.8-16_arm64.deb 98280 1d60d72c3c4c3a59d86c9b681dbbb71f1ea80c1ea754327a41c96a7288a01f5d https://deb.debian.org/debian/pool/main/a/avahi/libavahi-core7_0.8-16_arm64.deb
19 libbluetooth3_5.82-1.1_arm64.deb 103028 67395cec3551163c302889cf2eeea12a11de6c069aff8e7a1b9348770a24e180 https://deb.debian.org/debian/pool/main/b/bluez/libbluetooth3_5.82-1.1_arm64.deb
20 libbluray2_1%3a1.3.4-1+b2_arm64.deb 125560 14da09203931bdbff17b129d447850daecc8c72b87b296e3f7d0e0beb56e9f50 https://deb.debian.org/debian/pool/main/libb/libbluray/libbluray2_1.3.4-1+b2_arm64.deb
21 libcdio19t64_2.2.0-4.1~deb13u1_arm64.deb 59316 3c76de181f7018ca14e29f75ba6992cf5ec9099e3c3ee304d9d64454af48062f https://deb.debian.org/debian/pool/main/libc/libcdio/libcdio19t64_2.2.0-4.1~deb13u1_arm64.deb
22 libcec7_7.0.0-1+b1_arm64.deb 189744 f9a9042af0fe627eabf4a4d4a8467a6b0bbeceae1b772f86b5027364f9bae6cf https://deb.debian.org/debian/pool/main/libc/libcec/libcec7_7.0.0-1+b1_arm64.deb
23 libdaemon0_0.14-7.1+b3_arm64.deb 13152 a685244841cd0840b269da6eeac476410c454b74f1c265823073d27c91a4cefb https://deb.debian.org/debian/pool/main/libd/libdaemon/libdaemon0_0.14-7.1+b3_arm64.deb
24 libdecor-0-0_0.2.2-2_arm64.deb 15204 34c0db59390767ef7b1cb5594f78f142b0673a5e7788e068cdd323f9c16f9207 https://deb.debian.org/debian/pool/main/libd/libdecor-0/libdecor-0-0_0.2.2-2_arm64.deb
25 libdrm-amdgpu1_2.4.124-2_arm64.deb 21828 e36c72774cc215818b48ee2174c7ad1d9a2bf89a09cdee7a7a128d288d8eef43 https://deb.debian.org/debian/pool/main/libd/libdrm/libdrm-amdgpu1_2.4.124-2_arm64.deb
26 libdrm-common_2.4.124-2_all.deb 8288 9a8a6c65c165e9964f106fb4ac710959b5d33e0790227e3ab6b27c4742d1254a https://deb.debian.org/debian/pool/main/libd/libdrm/libdrm-common_2.4.124-2_all.deb
27 libdrm2_2.4.124-2_arm64.deb 38304 b535506630bb6a9a616bc07caf3b29915f980d8c3437852fb96ea64fb1a8a3f8 https://deb.debian.org/debian/pool/main/libd/libdrm/libdrm2_2.4.124-2_arm64.deb
28 libegl-mesa0_25.0.7-2+deb13u1_arm64.deb 120960 e344e0c87657d91ebfcb059bee3a3c87ba05871e2918079c3f9393665d4472af https://deb.debian.org/debian/pool/main/m/mesa/libegl-mesa0_25.0.7-2+deb13u1_arm64.deb
29 libegl1_1.7.0-1+b2_arm64.deb 33952 b663ad1777df7ed8e320f897bbf678c2baef67f5458fe4a43195ac9b444c7afb https://deb.debian.org/debian/pool/main/libg/libglvnd/libegl1_1.7.0-1+b2_arm64.deb
30 libevdev2_1.13.4+dfsg-1_arm64.deb 30356 abdb9ca2f0a27d88ec16b480f346d1d6c183c59e3d071734ba8d8b9794aacfc6 https://deb.debian.org/debian/pool/main/libe/libevdev/libevdev2_1.13.4+dfsg-1_arm64.deb
31 libevent-core-2.1-7t64_2.1.12-stable-10+b1_arm64.deb 125752 84a27a752d520df78bab24a499f66db21d5274803eb9a2e4d7f88554210f7dea https://deb.debian.org/debian/pool/main/libe/libevent/libevent-core-2.1-7t64_2.1.12-stable-10+b1_arm64.deb
32 libexiv2-28_0.28.5+dfsg-1_arm64.deb 820348 62a590d7d09f4ef23acb53abe5f0be2027f71f73cda8c869e812f3fd17f8a1b7 https://deb.debian.org/debian/pool/main/e/exiv2/libexiv2-28_0.28.5+dfsg-1_arm64.deb
33 libexiv2-data_0.28.5+dfsg-1_all.deb 1775296 afc70f25e5223489d00347bc28f7eb05cf56f6b3faaf05a506ce6d0efbd5fa61 https://deb.debian.org/debian/pool/main/e/exiv2/libexiv2-data_0.28.5+dfsg-1_all.deb
34 libflac14_1.5.0+ds-2_arm64.deb 179768 11800827306e56fcbd1a65ea67d9d13126fc02bf87fdfecd35e0f48a145b3c59 https://deb.debian.org/debian/pool/main/f/flac/libflac14_1.5.0+ds-2_arm64.deb
35 libfontconfig1_2.15.0-2.3_arm64.deb 387092 872781cf62926b68c34a4302a3caae334ff8d4d83a2b2477f8fb454460aadc99 https://deb.debian.org/debian/pool/main/f/fontconfig/libfontconfig1_2.15.0-2.3_arm64.deb
36 libfreetype6_2.13.3+dfsg-1+deb13u1_arm64.deb 422284 0c426e83f1af816b9a4df9056fd2238463d1cd07969ee640965da9fb6ecaf107 https://deb.debian.org/debian/pool/main/f/freetype/libfreetype6_2.13.3+dfsg-1+deb13u1_arm64.deb
37 libfribidi0_1.0.16-1_arm64.deb 26460 5f5bfdf6ef126b0a38f8ebb8751cbbb845fe2cd4e1dd5258d241be243e09897f https://deb.debian.org/debian/pool/main/f/fribidi/libfribidi0_1.0.16-1_arm64.deb
38 libfstrcmp0_0.7.D001-5_arm64.deb 7964 bf36ecf2c4d18a1e8cd55d5a9db5f495bd4ef482778d686a49ddaa2c0cdadc60 https://deb.debian.org/debian/pool/main/f/fstrcmp/libfstrcmp0_0.7.D001-5_arm64.deb
39 libgbm1_25.0.7-2+deb13u1_arm64.deb 44144 2beb8d20ca715a01b7b2352c1167c0d7097c9d8a486704a9a966f95d4077cdfc https://deb.debian.org/debian/pool/main/m/mesa/libgbm1_25.0.7-2+deb13u1_arm64.deb
40 libgif7_5.2.2-1+deb13u1_arm64.deb 44120 658318d55d788a628836d35d994f60057d2655cb81b355648fbe2dc3003ddeb9 https://deb.debian.org/debian/pool/main/g/giflib/libgif7_5.2.2-1+deb13u1_arm64.deb
41 libgl1-mesa-dri_25.0.7-2+deb13u1_arm64.deb 45852 628245ff9d8e4f11edb66c0854915018402853e0eb588343d472111f6c4fb582 https://deb.debian.org/debian/pool/main/m/mesa/libgl1-mesa-dri_25.0.7-2+deb13u1_arm64.deb
42 libgl1_1.7.0-1+b2_arm64.deb 90876 7943f99962aa3316515760bf44e66f0c15a3c813ad750b784b43e319fa2e6003 https://deb.debian.org/debian/pool/main/libg/libglvnd/libgl1_1.7.0-1+b2_arm64.deb
43 libgles2_1.7.0-1+b2_arm64.deb 18036 c727fa1746ed3e343d1a2917932363d71e961eacca0443a1e2296587e03c47fb https://deb.debian.org/debian/pool/main/libg/libglvnd/libgles2_1.7.0-1+b2_arm64.deb
44 libglvnd0_1.7.0-1+b2_arm64.deb 41588 29ec7b8cf1c9a47055de643eb0a1921854e762b4789a7f3bc3b0dea498fd4da9 https://deb.debian.org/debian/pool/main/libg/libglvnd/libglvnd0_1.7.0-1+b2_arm64.deb
45 libglx-mesa0_25.0.7-2+deb13u1_arm64.deb 142320 18c02afcdd88f547ba66d3ae9290f93ab155381bf8d96b0127f9028374acfc64 https://deb.debian.org/debian/pool/main/m/mesa/libglx-mesa0_25.0.7-2+deb13u1_arm64.deb
46 libglx0_1.7.0-1+b2_arm64.deb 31080 0245aafaac5e68ca9511f23c5bebf010327e63c65790c03dbb80bf14d485956d https://deb.debian.org/debian/pool/main/libg/libglvnd/libglx0_1.7.0-1+b2_arm64.deb
47 libgraphite2-3_1.3.14-2+deb13u1_arm64.deb 70348 b0c3414b113de62be1dc28fa2c899ac0856256e2f094d92c36c3275a5053311a https://deb.debian.org/debian/pool/main/g/graphite2/libgraphite2-3_1.3.14-2+deb13u1_arm64.deb
48 libgstreamer-plugins-base1.0-0_1.26.2-1+deb13u1_arm64.deb 897352 bc3205ea9fd35448b2cb6171da0e24bd3af893fb8a01f8eb797cbfb4d1fe7b0e https://deb.debian.org/debian/pool/main/g/gst-plugins-base1.0/libgstreamer-plugins-base1.0-0_1.26.2-1+deb13u1_arm64.deb
49 libgstreamer1.0-0_1.26.2-2_arm64.deb 1294408 77d44e8bc93793f815ea7b8cdb9c1b0b0bf6fd5797e2e69a7ef95bb37f249a86 https://deb.debian.org/debian/pool/main/g/gstreamer1.0/libgstreamer1.0-0_1.26.2-2_arm64.deb
50 libgudev-1.0-0_238-6_arm64.deb 13720 f0fec02f1bbc0803c9d044413609d3cd0fde24bfe6a5141a38aabfa86c292b21 https://deb.debian.org/debian/pool/main/libg/libgudev/libgudev-1.0-0_238-6_arm64.deb
51 libharfbuzz0b_10.2.0-1+deb13u1_arm64.deb 441932 3ff928f981b54b491ee12e4cf68558cc5d61c652bdab276cd4c4c3fbbcee16ca https://deb.debian.org/debian/pool/main/h/harfbuzz/libharfbuzz0b_10.2.0-1+deb13u1_arm64.deb
52 libicu76_76.1-4_arm64.deb 9523672 62eb15577c881b7d02aa861e3eb860902335630cc8174daf525b48c2fa39298f https://deb.debian.org/debian/pool/main/i/icu/libicu76_76.1-4_arm64.deb
53 libinih1_59-1_arm64.deb 6932 fcbc936af8fdd3db56b2a8fc4891b27955ce48c7639cbd6905fd292ae20061fa https://deb.debian.org/debian/pool/main/libi/libinih/libinih1_59-1_arm64.deb
54 libinireader0_59-1_arm64.deb 12188 40937f22b74c8ac9bdb5f286b53ea7b81eb543d1d0497854412d0dd664e0c626 https://deb.debian.org/debian/pool/main/libi/libinih/libinireader0_59-1_arm64.deb
55 libinput-bin_1.28.1-1+deb13u1_arm64.deb 26788 9da373d042dd8e0c45e34a5a0dc361cb386fd5ff4f066b7b3e64e1ea66de0835 https://deb.debian.org/debian/pool/main/libi/libinput/libinput-bin_1.28.1-1+deb13u1_arm64.deb
56 libinput10_1.28.1-1+deb13u1_arm64.deb 129616 a2acd04f3b1e9e91d22a26b63650087a179b6b3dbaddadc92d89edfbb2075ae1 https://deb.debian.org/debian/pool/main/libi/libinput/libinput10_1.28.1-1+deb13u1_arm64.deb
57 libjansson4_2.14-2+b3_arm64.deb 39228 7938472b1ddfa8b0c8f58d5f44406ae7a77a342dbb502a02f6bf292b4f853ab0 https://deb.debian.org/debian/pool/main/j/jansson/libjansson4_2.14-2+b3_arm64.deb
58 libjpeg62-turbo_1%3a2.1.5-4_arm64.deb 172784 e4989073bb0bac8a6ec043c7adb80e1dfe601d8552233da48bb24ab45d1a1d4a https://deb.debian.org/debian/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_2.1.5-4_arm64.deb
59 liblcms2-2_2.16-2+deb13u2_arm64.deb 151540 6636997fc488327a8d0951a038df6e5178a52b3f5e5cc2386d42655621f5672d https://deb.debian.org/debian/pool/main/l/lcms2/liblcms2-2_2.16-2+deb13u2_arm64.deb
60 libldb2_2%3a2.11.0+samba4.22.10+dfsg-0+deb13u2_arm64.deb 172588 b3017ddb7b56ca01c3501f6a25c663bb45747e01e5de5c8341f853fb42d80cf1 https://security.debian.org/debian-security/pool/updates/main/s/samba/libldb2_2.11.0+samba4.22.10+dfsg-0+deb13u2_arm64.deb
61 liblirc-client0t64_0.10.2-0.10_arm64.deb 30864 ddde026178705ec2227c603b93121bd8f35d2d8231c9db269de3127df98075b1 https://deb.debian.org/debian/pool/main/l/lirc/liblirc-client0t64_0.10.2-0.10_arm64.deb
62 libllvm19_1%3a19.1.7-3+b1_arm64.deb 23285344 8a48e1a49f7fcc3be74e697767d72902f4b03041401d1529bcfad1849df39148 https://deb.debian.org/debian/pool/main/l/llvm-toolchain-19/libllvm19_19.1.7-3+b1_arm64.deb
63 liblmdb0_0.9.31-1+b2_arm64.deb 43504 aaef40a5569a13235e4b067b07e170130fc50c5ff21d685e8961290323baff1d https://deb.debian.org/debian/pool/main/l/lmdb/liblmdb0_0.9.31-1+b2_arm64.deb
64 libmariadb3_1%3a11.8.6-0+deb13u1_arm64.deb 175040 29acd0080b3ea32440a8ebd0445bb13e88ff564c408faf2bfb70c129c2048503 https://deb.debian.org/debian/pool/main/m/mariadb/libmariadb3_11.8.6-0+deb13u1_arm64.deb
65 libmicrohttpd12t64_1.0.1-4_arm64.deb 147824 c6822d190185155f25eec4ab42613b47d68ee76811415c563e88ce3a35fcf179 https://deb.debian.org/debian/pool/main/libm/libmicrohttpd/libmicrohttpd12t64_1.0.1-4_arm64.deb
66 libmp3lame0_3.100-6+b3_arm64.deb 354760 ed3563722129ffa4def03b7af0b589fa20eef972bdcc4599202ddb1ac654c58e https://deb.debian.org/debian/pool/main/l/lame/libmp3lame0_3.100-6+b3_arm64.deb
67 libmpg123-0t64_1.32.10-1+deb13u1_arm64.deb 143208 d284df39ff3b64f1cdf274352613c52534c55eed82c3a2ff0fc7ba154acd7bf0 https://deb.debian.org/debian/pool/main/m/mpg123/libmpg123-0t64_1.32.10-1+deb13u1_arm64.deb
68 libmtdev1t64_1.1.7-1_arm64.deb 22456 3433f77a9f96d0dc537e870db734a4dbb50528836782c86709845736e8897b47 https://deb.debian.org/debian/pool/main/m/mtdev/libmtdev1t64_1.1.7-1_arm64.deb
69 libncurses6_6.5+20250216-2_arm64.deb 96112 259fe5736ebb105df7c6a7b5cc6231a8743666f5f57885f6c7989babe2fc0082 https://deb.debian.org/debian/pool/main/n/ncurses/libncurses6_6.5+20250216-2_arm64.deb
70 libnfs14_5.0.2-1+b2_arm64.deb 102472 1929f3b75a32c99582e55039a91c4be93561be9bcf652122e602d5098fe51221 https://deb.debian.org/debian/pool/main/libn/libnfs/libnfs14_5.0.2-1+b2_arm64.deb
71 libnfsidmap1_1%3a2.8.3-1_arm64.deb 55728 861f226ebe6d1ea4c7951b92e900b1a92902d8c897c902535a67579877c4ef54 https://deb.debian.org/debian/pool/main/n/nfs-utils/libnfsidmap1_2.8.3-1_arm64.deb
72 libogg0_1.3.5-3+b2_arm64.deb 23580 d59a83ab352bf0c16ce9513fdf264f87214813ca8c628c652801ef476ece8286 https://deb.debian.org/debian/pool/main/libo/libogg/libogg0_1.3.5-3+b2_arm64.deb
73 libopus0_1.5.2-2_arm64.deb 2812952 1d980e8a4717074805a98dbe07671cb3bee068c25709315f3de836980c629836 https://deb.debian.org/debian/pool/main/o/opus/libopus0_1.5.2-2_arm64.deb
74 liborc-0.4-0t64_1%3a0.4.41-1_arm64.deb 170796 e69ee68606246657ff19bfe13058a9aef177e686b97638728f9e0e911af07680 https://deb.debian.org/debian/pool/main/o/orc/liborc-0.4-0t64_0.4.41-1_arm64.deb
75 libp8-platform2_2.1.0.1+dfsg1-4+b3_arm64.deb 20216 d3e7af8fd96a2532600260f35421f05564fcde3a2aaf6355d2500cd212c03e20 https://deb.debian.org/debian/pool/main/p/p8-platform/libp8-platform2_2.1.0.1+dfsg1-4+b3_arm64.deb
76 libpci3_1%3a3.13.0-2_arm64.deb 71808 fde73536f93fcd930362531506f8431b2e959bfbdeb41e772100c1cfc1f83953 https://deb.debian.org/debian/pool/main/p/pciutils/libpci3_3.13.0-2_arm64.deb
77 libplist-2.0-4_2.6.0-2+b1_arm64.deb 46824 45d585abe7d0ba46abda543513d18869a14d21e4abba266b851dd71f2f69882b https://deb.debian.org/debian/pool/main/libp/libplist/libplist-2.0-4_2.6.0-2+b1_arm64.deb
78 libpng16-16t64_1.6.48-1+deb13u5_arm64.deb 275192 11096ad43504ca24e8044bcc89e922d932a2812ff25568613b28d22235190ba5 https://deb.debian.org/debian/pool/main/libp/libpng1.6/libpng16-16t64_1.6.48-1+deb13u5_arm64.deb
79 libpulse0_17.0+dfsg1-2+b1_arm64.deb 236376 2de5610e3affe69cfb11110f890375261b47c9e0fc1a81eef7ecc4d6d2dc9a6d https://deb.debian.org/debian/pool/main/p/pulseaudio/libpulse0_17.0+dfsg1-2+b1_arm64.deb
80 libpython3.13_3.13.5-2+deb13u4_arm64.deb 1977636 d8f0a0d8d829988605ad66aff280c8c9770060b21868aae14a45e8ef872589ec https://deb.debian.org/debian/pool/main/p/python3.13/libpython3.13_3.13.5-2+deb13u4_arm64.deb
81 libsensors-config_1%3a3.6.2-2_all.deb 16236 3056da80c7d963af795dab480ab6f6f4b154ad4ac39f522dc52d17c834fea253 https://deb.debian.org/debian/pool/main/l/lm-sensors/libsensors-config_3.6.2-2_all.deb
82 libsensors5_1%3a3.6.2-2_arm64.deb 36444 7469f1ccce8afdba33d3b11847d0ecdf2221805a0694dee21ee4d622e8f72145 https://deb.debian.org/debian/pool/main/l/lm-sensors/libsensors5_3.6.2-2_arm64.deb
83 libshairplay0_0.9.0~git20180824.096b61a+dfsg1-3+b2_arm64.deb 100452 753f16da9a7953b3cec3e66ae5a379e72ed5e82ad7bb94443d13ec5f678c141d https://deb.debian.org/debian/pool/main/s/shairplay/libshairplay0_0.9.0~git20180824.096b61a+dfsg1-3+b2_arm64.deb
84 libsmbclient0_2%3a4.22.10+dfsg-0+deb13u2_arm64.deb 95392 db717af217d11808f02cdce5f4c12a4196a8dbff8d8713746e67265e5aff48c1 https://security.debian.org/debian-security/pool/updates/main/s/samba/libsmbclient0_4.22.10+dfsg-0+deb13u2_arm64.deb
85 libsndfile1_1.2.2-2+deb13u1_arm64.deb 185528 6c94f17e1f02778f98b96bb9ae19ff30085662e96685898a018c26bfd4bfc3f2 https://deb.debian.org/debian/pool/main/libs/libsndfile/libsndfile1_1.2.2-2+deb13u1_arm64.deb
86 libtag2_2.0.2-2_arm64.deb 310408 7fcf13ab2eb9f7671ea1692bb78c168e712e17f16cbd1e39e7c8dcc89dd077a7 https://deb.debian.org/debian/pool/main/t/taglib/libtag2_2.0.2-2_arm64.deb
87 libtalloc2_2%3a2.4.3+samba4.22.10+dfsg-0+deb13u2_arm64.deb 62316 20a23a60ef4030207d6f22e2f5bb20e57bb42a090fefb1a5606f23d34aba0510 https://security.debian.org/debian-security/pool/updates/main/s/samba/libtalloc2_2.4.3+samba4.22.10+dfsg-0+deb13u2_arm64.deb
88 libtdb1_2%3a1.4.13+samba4.22.10+dfsg-0+deb13u2_arm64.deb 82104 ba86a85fee628c394480248ea226cca10545b527b4ebf8799a1142c09265f327 https://security.debian.org/debian-security/pool/updates/main/s/samba/libtdb1_1.4.13+samba4.22.10+dfsg-0+deb13u2_arm64.deb
89 libtevent0t64_2%3a0.16.2+samba4.22.10+dfsg-0+deb13u2_arm64.deb 76128 2383d7e65a90114761f80c7ed801c516c3e09f74c78b92c6288a8be32ec2fce1 https://security.debian.org/debian-security/pool/updates/main/s/samba/libtevent0t64_0.16.2+samba4.22.10+dfsg-0+deb13u2_arm64.deb
90 libtinyxml2-11_11.0.0+dfsg-1+b1_arm64.deb 29808 596a10c41466c126c61ecfeed5d31f515066abccf5b2a2a6587e85473540eca9 https://deb.debian.org/debian/pool/main/t/tinyxml2/libtinyxml2-11_11.0.0+dfsg-1+b1_arm64.deb
91 libtinyxml2.6.2v5_2.6.2-7_arm64.deb 36500 6d6122c8011043f7f7bbd8f4e93c70f2dd191bd57a596cff61e2f6411c3f55ed https://deb.debian.org/debian/pool/main/t/tinyxml/libtinyxml2.6.2v5_2.6.2-7_arm64.deb
92 libudfread0_1.1.2-1+b2_arm64.deb 17312 badab108ac953987ced64c4b25a38c75ac391e6f4815cca4880619f313b4da22 https://deb.debian.org/debian/pool/main/libu/libudfread/libudfread0_1.1.2-1+b2_arm64.deb
93 libunibreak6_6.1-3_arm64.deb 21464 8689f3761d5ac0dd9119f187561dd61dbb8b273d3a684fc57fbcc3de9daac9ed https://deb.debian.org/debian/pool/main/libu/libunibreak/libunibreak6_6.1-3_arm64.deb
94 libvorbis0a_1.3.7-3_arm64.deb 83772 6e8e64ebe692dd2d019af7f914f2e3b81bf13d4b1ce2516fb4248a0310ef7cf6 https://deb.debian.org/debian/pool/main/libv/libvorbis/libvorbis0a_1.3.7-3_arm64.deb
95 libvorbisenc2_1.3.7-3_arm64.deb 75532 4a477b3ae19ba50a288f036c8176d61cd9f3836ba65472f88b95e3f6b3bf2dd3 https://deb.debian.org/debian/pool/main/libv/libvorbis/libvorbisenc2_1.3.7-3_arm64.deb
96 libvulkan1_1.4.309.0-1_arm64.deb 126808 f5f576d8b9e5703702a9f3254c8c5d130bef17da9db4aecd46ba86cec7a08930 https://deb.debian.org/debian/pool/main/v/vulkan-loader/libvulkan1_1.4.309.0-1_arm64.deb
97 libwacom-common_2.14.0-1_all.deb 102748 214956bd9b26600c4d5ac0d60c1685be6ef650644e36b712620c56fccd4d4f58 https://deb.debian.org/debian/pool/main/libw/libwacom/libwacom-common_2.14.0-1_all.deb
98 libwacom9_2.14.0-1_arm64.deb 24192 8fb8419c43fcc166f996c30cc262fcc86457bdd6f82656dad8619c88dc00b013 https://deb.debian.org/debian/pool/main/libw/libwacom/libwacom9_2.14.0-1_arm64.deb
99 libwayland-client0_1.23.1-3_arm64.deb 26140 8abdc649e46470548c545f827e16f52e1bc16d05d4a3794e6dedb2d74ce58c9a https://deb.debian.org/debian/pool/main/w/wayland/libwayland-client0_1.23.1-3_arm64.deb
100 libwayland-egl1_1.23.1-3_arm64.deb 5952 fe01fb2f3c5d5b54784563d9826fa505fab61dc30333d71343352bddd3812b18 https://deb.debian.org/debian/pool/main/w/wayland/libwayland-egl1_1.23.1-3_arm64.deb
101 libwayland-server0_1.23.1-3_arm64.deb 33656 062702d57cf07b42ab7f0fa1aac8bc3f605eb47f2f0b9ecb1614674140d79f1b https://deb.debian.org/debian/pool/main/w/wayland/libwayland-server0_1.23.1-3_arm64.deb
102 libwbclient0_2%3a4.22.10+dfsg-0+deb13u2_arm64.deb 70980 885cd7c331d6ffb0da72aeb76809deac82a8949ef415ed5c742a466d7fe1be96 https://security.debian.org/debian-security/pool/updates/main/s/samba/libwbclient0_4.22.10+dfsg-0+deb13u2_arm64.deb
103 libx11-6_2%3a1.8.12-1_arm64.deb 794952 646f2d3f2165c8eebceb7f4aaca31e97a818bb11a045964833a9f764b738bac2 https://deb.debian.org/debian/pool/main/libx/libx11/libx11-6_1.8.12-1_arm64.deb
104 libx11-data_2%3a1.8.12-1_all.deb 343072 c54f87069888f80ba4da586da6147d74c7598ccdd8b90906dbc4271fa414c738 https://deb.debian.org/debian/pool/main/libx/libx11/libx11-data_1.8.12-1_all.deb
105 libx11-xcb1_2%3a1.8.12-1_arm64.deb 247336 1eafaa4295e129d6a81e742ab8da43194daa6b916199923c8cef8ad358985894 https://deb.debian.org/debian/pool/main/libx/libx11/libx11-xcb1_1.8.12-1_arm64.deb
106 libxau6_1%3a1.0.11-1_arm64.deb 20576 ac1061728670f4626adaa1288953a0e6fb801c9cae72ee1c3231e63e2609d23a https://deb.debian.org/debian/pool/main/libx/libxau/libxau6_1.0.11-1_arm64.deb
107 libxcb-dri3-0_1.17.0-2+b1_arm64.deb 107068 247bf758dd78d6c285fdd47942ddacf4a3189fd50de4f0891c9bd9aea12da3a1 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-dri3-0_1.17.0-2+b1_arm64.deb
108 libxcb-glx0_1.17.0-2+b1_arm64.deb 122608 a0d96ab5faea8fa1f3e095d6236597622a6b763f061f8f9fa23b77733608fdbc https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-glx0_1.17.0-2+b1_arm64.deb
109 libxcb-present0_1.17.0-2+b1_arm64.deb 105784 73521d715554a2230901d1d480465d6358b3083a5ce8801639595ffde6f31441 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-present0_1.17.0-2+b1_arm64.deb
110 libxcb-randr0_1.17.0-2+b1_arm64.deb 116736 dbeb78d1d032959fe65548758ab12bfc6bb5919ec2984c3d871496b52aef929a https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-randr0_1.17.0-2+b1_arm64.deb
111 libxcb-shm0_1.17.0-2+b1_arm64.deb 105480 e9e871dc5d2f9265eb5d3028f0d0545cb6f8b748a68997b4c43f5a9416c7ead7 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-shm0_1.17.0-2+b1_arm64.deb
112 libxcb-sync1_1.17.0-2+b1_arm64.deb 108792 eb428ac77daac9e5eea8a4e9ba48d1d78cc8ca400e415c8e4628cc729c6f4749 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-sync1_1.17.0-2+b1_arm64.deb
113 libxcb-xfixes0_1.17.0-2+b1_arm64.deb 109644 19cdb8502827bc010993e69eaba09c670024976f7edd35b2b504bd646b141b0a https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-xfixes0_1.17.0-2+b1_arm64.deb
114 libxcb-xkb1_1.17.0-2+b1_arm64.deb 128572 739b023ad82b4c9ac2c6cc9e6cc8602a17a05f7f5387d45a5a60eafdc17bf0e5 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb-xkb1_1.17.0-2+b1_arm64.deb
115 libxcb1_1.17.0-2+b1_arm64.deb 143168 d0178198e80ed4cacdececabe2c112ec88c7a9258cc11a55b8e267ab14a90d82 https://deb.debian.org/debian/pool/main/libx/libxcb/libxcb1_1.17.0-2+b1_arm64.deb
116 libxdmcp6_1%3a1.1.5-1_arm64.deb 27752 e10bbb0802181992ecf091e9425171850eee16068729c109214ba4924f81fb52 https://deb.debian.org/debian/pool/main/libx/libxdmcp/libxdmcp6_1.1.5-1_arm64.deb
117 libxext6_2%3a1.3.4-1+b3_arm64.deb 49244 27cf208c6d2924b22ed3b9ceff304b992e1f07ef7b0ef00239585595581dac99 https://deb.debian.org/debian/pool/main/libx/libxext/libxext6_1.3.4-1+b3_arm64.deb
118 libxkbcommon-x11-0_1.7.0-2_arm64.deb 15168 414ec70596c8193b309279dc6d8a35f809ffe5047addc3587a6d52eb462bebc5 https://deb.debian.org/debian/pool/main/libx/libxkbcommon/libxkbcommon-x11-0_1.7.0-2_arm64.deb
119 libxkbcommon0_1.7.0-2_arm64.deb 106024 866888d3cfeb32388dd88a2615c4f78ebf8dd7b272821a83494cba260d8fa7d1 https://deb.debian.org/debian/pool/main/libx/libxkbcommon/libxkbcommon0_1.7.0-2_arm64.deb
120 libxml2_2.12.7+dfsg+really2.9.14-2.1+deb13u3_arm64.deb 631428 a66b9c960acef8ed9653223cbd00b155a92ebb758ea13a788c41609246c23bc7 https://deb.debian.org/debian/pool/main/libx/libxml2/libxml2_2.12.7+dfsg+really2.9.14-2.1+deb13u3_arm64.deb
121 libxshmfence1_1.3.3-1_arm64.deb 11104 34b494c83cebcf138d1f87c2cbdd5ee8efe9da2befbd69c2a025b0e214c64d2d https://deb.debian.org/debian/pool/main/libx/libxshmfence/libxshmfence1_1.3.3-1_arm64.deb
122 libxslt1.1_1.1.35-1.2+deb13u3_arm64.deb 222332 590a5d177dcfa2bf432f0d7db80b936ac7afadfebdb7f077d0c3305da20d1897 https://deb.debian.org/debian/pool/main/libx/libxslt/libxslt1.1_1.1.35-1.2+deb13u3_arm64.deb
123 libxxf86vm1_1%3a1.1.4-1+b4_arm64.deb 19176 9ed4bb25e311486207eeaa609decc3905aa9071c4c7f2a605829e3a79ec88b83 https://deb.debian.org/debian/pool/main/libx/libxxf86vm/libxxf86vm1_1.1.4-1+b4_arm64.deb
124 libz3-4_4.13.3-1_arm64.deb 7506868 29a7b924a5a38c9ac4e2a64aa11586a9c54f26b579b18e4f75adf8f7c1106e45 https://deb.debian.org/debian/pool/main/z/z3/libz3-4_4.13.3-1_arm64.deb
125 mariadb-common_1%3a11.8.6-0+deb13u1_all.deb 29544 b5116ff56a60d471332d45bfd4ec50391fdcf5a40747bf7fe23ec8551cb71f9c https://deb.debian.org/debian/pool/main/m/mariadb/mariadb-common_11.8.6-0+deb13u1_all.deb
126 mesa-libgallium_25.0.7-2+deb13u1_arm64.deb 8032536 a27576030704ff305adaef8bd143954b281680861d5bfe21aabd7d376a939149 https://deb.debian.org/debian/pool/main/m/mesa/mesa-libgallium_25.0.7-2+deb13u1_arm64.deb
127 mesa-utils-bin_9.0.0-2+b2_arm64.deb 192896 e7906035da53770d63ff477f626c0c7209a799caf781714b229263a3d75ec03c https://deb.debian.org/debian/pool/main/m/mesa-demos/mesa-utils-bin_9.0.0-2+b2_arm64.deb
128 mesa-utils_9.0.0-2+b2_arm64.deb 955092 aaa2276c9e679c811fb980c98e20acbd5ea93403a8d92792315f05f82a2e2bee https://deb.debian.org/debian/pool/main/m/mesa-demos/mesa-utils_9.0.0-2+b2_arm64.deb
129 mesa-vulkan-drivers_25.0.7-2+deb13u1_arm64.deb 11373712 b73e1b64f26d55c981339533f3eb64397fda4a8e86dd5593ed15e1c48711141d https://deb.debian.org/debian/pool/main/m/mesa/mesa-vulkan-drivers_25.0.7-2+deb13u1_arm64.deb
130 mysql-common_5.8+1.1.1_all.deb 6784 a4e36be39be45d6f5267ac71b92d7eecd40a15f25d52a0bfb9c5eaca1b90e534 https://deb.debian.org/debian/pool/main/m/mysql-defaults/mysql-common_5.8+1.1.1_all.deb
131 nfs-common_1%3a2.8.3-1_arm64.deb 260196 1c45d155086ecef6f531122a6d9a2dbf873f85a211eebf0fdf2841c3ee2f93d0 https://deb.debian.org/debian/pool/main/n/nfs-utils/nfs-common_2.8.3-1_arm64.deb
132 pci.ids_0.0~2025.06.09-1_all.deb 269172 132bc7520473e53939e277cbf4512261b8d73ae244d77c277f674ecaf174598c https://deb.debian.org/debian/pool/main/p/pci.ids/pci.ids_0.0~2025.06.09-1_all.deb
133 pciutils_1%3a3.13.0-2_arm64.deb 122800 e60b3a48ce93ed5634713226b6ca623a7e7a77007ac0de0a38698c84f36e1867 https://deb.debian.org/debian/pool/main/p/pciutils/pciutils_3.13.0-2_arm64.deb
134 python-apt-common_3.0.0_all.deb 68248 9aacb635fa04e01ab56a0af4869a034a94aadcd564d662a91048cd19fe7d6e0c https://deb.debian.org/debian/pool/main/p/python-apt/python-apt-common_3.0.0_all.deb
135 python3-apt_3.0.0_arm64.deb 157212 7f9c928895e7141ecea9d53ee93962cfb37d48a2015abc99d95a22e77535f8a7 https://deb.debian.org/debian/pool/main/p/python-apt/python3-apt_3.0.0_arm64.deb
136 python3-dbus_1.4.0-1_arm64.deb 96064 fe98d700adc6239a01ffa7441bb554a78b43bf7be4edf026a8be279266dde919 https://deb.debian.org/debian/pool/main/d/dbus-python/python3-dbus_1.4.0-1_arm64.deb
137 python3-distro-info_1.13_all.deb 7736 11848888a90676314dd977fd2788e2d1f11f5e471d879313b8158a28488dc062 https://deb.debian.org/debian/pool/main/d/distro-info/python3-distro-info_1.13_all.deb
138 rpcbind_1.2.7-1_arm64.deb 47016 1ff91ee1a472c5b3891653a2622d51c9bbb0d11f42e0bf62614d88c16e9d1003 https://deb.debian.org/debian/pool/main/r/rpcbind/rpcbind_1.2.7-1_arm64.deb
139 samba-libs_2%3a4.22.10+dfsg-0+deb13u2_arm64.deb 5646024 23dd06b3d384ff0ad2da844a369c45962eb2663ac66e7078edf01c811936cd16 https://security.debian.org/debian-security/pool/updates/main/s/samba/samba-libs_4.22.10+dfsg-0+deb13u2_arm64.deb
140 unattended-upgrades_2.12_all.deb 66912 a5e37d196598d2f2a7cb8c3ded760b77283d0ecf110d2f90d77d4821e3512eb6 https://deb.debian.org/debian/pool/main/u/unattended-upgrades/unattended-upgrades_2.12_all.deb
141 vulkan-tools_1.4.304.0+dfsg1-1_arm64.deb 261792 de3b21a2a8a2b2cf520cad433878589c70366050f52570a4fe37556b7c748647 https://deb.debian.org/debian/pool/main/v/vulkan-tools/vulkan-tools_1.4.304.0+dfsg1-1_arm64.deb
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Fetch the exact Debian package payload described by the integrity lock.
set -Eeuo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
LOCK_FILE=${1:-"$SCRIPT_DIR/debian-packages.lock.tsv"}
OUTPUT_DIR=${2:-"$PROJECT_DIR/work/apt/archives"}
[[ -s "$LOCK_FILE" ]] || { echo "Missing package lock: $LOCK_FILE" >&2; exit 1; }
mkdir -p "$OUTPUT_DIR"
while IFS=$'\t' read -r filename expected_size expected_sha url; do
[[ "$filename" == '# filename' ]] && continue
[[ -n "$filename" && "$expected_sha" =~ ^[0-9a-f]{64}$ ]] || {
echo "Malformed package lock row: $filename" >&2
exit 1
}
output="$OUTPUT_DIR/$filename"
if [[ -f "$output" ]] &&
[[ "$(stat -c '%s' "$output")" == "$expected_size" ]] &&
[[ "$(sha256sum "$output" | awk '{print $1}')" == "$expected_sha" ]]; then
continue
fi
partial="$output.partial"
rm -f -- "$partial"
curl --fail --location --show-error --output "$partial" "$url"
[[ "$(stat -c '%s' "$partial")" == "$expected_size" ]] || {
echo "Size mismatch for $filename" >&2
exit 1
}
echo "$expected_sha $partial" | sha256sum --check --status || {
echo "Checksum mismatch for $filename" >&2
exit 1
}
mv -- "$partial" "$output"
done <"$LOCK_FILE"
echo "Verified locked package bundle: $OUTPUT_DIR"
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# Generate an integrity lock for an already-resolved Debian package bundle.
set -Eeuo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
ARCHIVE_DIR=${1:-"$PROJECT_DIR/work/apt/archives"}
LIST_DIR=${2:-"$PROJECT_DIR/work/apt/lists"}
OUTPUT=${3:-"$SCRIPT_DIR/debian-packages.lock.tsv"}
[[ -d "$ARCHIVE_DIR" && -d "$LIST_DIR" ]] || {
echo "Usage: $0 [ARCHIVE-DIR LIST-DIR OUTPUT]" >&2
exit 2
}
INDEX=$(mktemp)
OUTPUT_TMP=$(mktemp)
trap 'rm -f -- "$INDEX" "$OUTPUT_TMP"' EXIT
for package_list in "$LIST_DIR"/*_Packages; do
[[ -f "$package_list" ]] || continue
case "$(basename -- "$package_list")" in
security.debian.org_*) base_url=https://security.debian.org/debian-security ;;
deb.debian.org_*) base_url=https://deb.debian.org/debian ;;
*) continue ;;
esac
awk -v base_url="$base_url" '
/^Filename: / { filename=substr($0, 11) }
/^Size: / { size=substr($0, 7) }
/^SHA256: / { sha=substr($0, 9) }
/^$/ {
if (filename != "" && size != "" && sha != "") {
print sha "\t" size "\t" base_url "/" filename
}
filename=size=sha=""
}
END {
if (filename != "" && size != "" && sha != "") {
print sha "\t" size "\t" base_url "/" filename
}
}
' "$package_list" >>"$INDEX"
done
LC_ALL=C sort -u -o "$INDEX" "$INDEX"
printf '# filename\tsize\tsha256\turl\n' >"$OUTPUT_TMP"
while IFS= read -r -d '' package_path; do
filename=$(basename -- "$package_path")
size=$(stat -c '%s' "$package_path")
sha=$(sha256sum "$package_path" | awk '{print $1}')
match=$(awk -F '\t' -v sha="$sha" -v size="$size" \
'$1 == sha && $2 == size { print; exit }' "$INDEX")
[[ -n "$match" ]] || {
echo "No signed package-index record for $filename" >&2
exit 1
}
url=${match#*$'\t'}
url=${url#*$'\t'}
printf '%s\t%s\t%s\t%s\n' "$filename" "$size" "$sha" "$url" \
>>"$OUTPUT_TMP"
done < <(find "$ARCHIVE_DIR" -maxdepth 1 -type f -name '*.deb' -print0 | sort -z)
install -m 0644 "$OUTPUT_TMP" "$OUTPUT"
echo "Locked $(($(wc -l <"$OUTPUT") - 1)) Debian packages in $OUTPUT"
+100
View File
@@ -0,0 +1,100 @@
# RK1 runtime and hardware diagnostics
This directory installs the local RKNN C runtime and supplies the two commands
used to qualify an RK3588 media image. It does not install RKNN Toolkit's model
conversion environment, Python wheels, or `rknn_server`.
## Pinned RKNN runtime
`rknn-version.env` locks RKNN Toolkit2 v2.3.2 to commit
`42aa1d426c0a9e0869b6374edba009f7208a1926`. The installer verifies the SHA-256
of the ARM64 runtime, C header, RK3588 MobileNet model, demo image, and license
before installing anything. A tag move or damaged download therefore fails the
image build.
Run the installer inside the target chroot:
```sh
runtime/install-rknn-runtime.sh
```
Or install into a mounted root filesystem from an ARM64 build host:
```sh
runtime/install-rknn-runtime.sh --rootfs /path/to/rootfs
```
For an offline/reproducible build, provide the pinned checkout explicitly:
```sh
runtime/install-rknn-runtime.sh \
--rootfs /path/to/rootfs \
--source-dir /path/to/rknn-toolkit2-v2.3.2
```
The build host needs Bash, Git when downloading, an ARM64 C compiler,
`coreutils`, and standard install utilities. Native compilation on the RK1 is
the supported default; a cross-build can select a compiler with `--cc`.
Files are installed below `/opt/rknn/2.3.2`, with `/opt/rknn/current` as the
stable link. The runtime path is registered in `/etc/ld.so.conf.d/rknn.conf`,
and the inference test also embeds that path as an ELF rpath. The test uses a
deterministic synthetic tensor and verifies model initialization, selected-core
submission, and finite output. It is a hardware/runtime smoke test, not a
MobileNet accuracy benchmark.
## Hardware self-test
The installed command inventories HDMI/EDID, Panthor or Mali, DRM render
nodes, RKMPP decoder and encoder bindings, AV1, RGA2/RGA3, RKNN, HDMI ALSA,
Ethernet link state, eMMC, NVMe, and the root filesystem:
```sh
rk1-media-selftest
rk1-media-selftest --quick
rk1-media-selftest --json
```
The normal run adds short Vulkan, H.264/HEVC/MJPEG encode, and NPU core
0/1/2/combined workloads when their tools are installed. `--quick` performs no
active workloads. To test 4K decoding and RKMPP-to-RGA zero-copy scaling, place
licensed samples in a directory with names containing `h264`, `hevc`, `vp9`,
and `av1`, then run:
```sh
rk1-media-selftest --media-dir /usr/share/rk1-media/samples
```
Missing tools and tests that cannot apply are `SKIP`; absent or disconnected
hardware is normally `WARN`; an advertised capability whose active workload
fails is `FAIL`. The exit status is zero unless a check fails. `--strict` also
makes warnings return status 1. Invocation errors return status 2.
## Diagnostic bundle
Create a local archive suitable for troubleshooting a blank display or missing
accelerator:
```sh
sudo rk1-media-diagnostics
sudo rk1-media-diagnostics --include-active-tests \
--media-dir /usr/share/rk1-media/samples
```
The collector records the JSON quick test, DRM connectors, decoded EDID,
drivers, filtered kernel messages, relevant packages, ALSA devices, link state,
and storage topology. It does not upload anything. It omits raw EDID, disk
serials and UUIDs, addresses, user files, SSH material, and unfiltered journals,
and applies basic redaction; review the bundle before sharing it.
## Tests
```sh
runtime/tests/run-tests.sh
RKNN_SOURCE_DIR=/path/to/rknn-toolkit2-v2.3.2 \
runtime/tests/run-tests.sh
```
The first form tests syntax, absent-hardware handling, JSON, synthetic sysfs,
and diagnostic collection. Supplying `RKNN_SOURCE_DIR` also compiles and stages
the pinned runtime into a temporary root filesystem.
+209
View File
@@ -0,0 +1,209 @@
#!/usr/bin/env bash
# Install the pinned RKNN C runtime, test model, and RK1 diagnostic tools.
set -Eeuo pipefail
umask 022
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
# shellcheck source=rknn-version.env
source "$SCRIPT_DIR/rknn-version.env"
ROOTFS="/"
SOURCE_DIR=""
CC_BIN=${CC:-cc}
KEEP_WORK=0
WORK_DIR=""
usage() {
cat <<'EOF'
Usage: install-rknn-runtime.sh [OPTIONS]
Options:
--rootfs PATH Install below PATH (default: /).
--source-dir PATH Use an existing rknn-toolkit2 v2.3.2 checkout.
--cc COMMAND C compiler for the target (default: $CC or cc).
--keep-work Preserve a downloaded checkout for inspection.
-h, --help Show this help.
Without --source-dir, the script fetches only the required paths from the
immutable upstream commit. Network access and git are then required.
EOF
}
die() {
printf 'install-rknn-runtime: error: %s\n' "$*" >&2
exit 1
}
note() {
printf 'install-rknn-runtime: %s\n' "$*" >&2
}
while (($#)); do
case "$1" in
--rootfs)
(($# >= 2)) || die "--rootfs requires a path"
ROOTFS=$2
shift 2
;;
--source-dir)
(($# >= 2)) || die "--source-dir requires a path"
SOURCE_DIR=$2
shift 2
;;
--cc)
(($# >= 2)) || die "--cc requires a compiler command"
CC_BIN=$2
shift 2
;;
--keep-work)
KEEP_WORK=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
[[ -d "$ROOTFS" ]] || die "rootfs is not a directory: $ROOTFS"
ROOTFS=$(readlink -f -- "$ROOTFS")
[[ -n "$ROOTFS" ]] || die "could not resolve rootfs"
if [[ "$ROOTFS" == "/" && ${EUID:-$(id -u)} -ne 0 ]]; then
die "installing into / requires root"
fi
for tool in install ln readlink sha256sum "$CC_BIN"; do
command -v "$tool" >/dev/null 2>&1 || die "required command not found: $tool"
done
cleanup() {
if [[ -n "$WORK_DIR" && -d "$WORK_DIR" && $KEEP_WORK -eq 0 ]]; then
rm -rf -- "$WORK_DIR"
elif [[ -n "$WORK_DIR" && -d "$WORK_DIR" ]]; then
note "preserved work directory: $WORK_DIR"
fi
}
trap cleanup EXIT
verify_sha256() {
local expected=$1
local path=$2
local actual
[[ -f "$path" ]] || die "required upstream asset is missing: $path"
actual=$(sha256sum -- "$path")
actual=${actual%% *}
[[ "$actual" == "$expected" ]] ||
die "checksum mismatch for $path (expected $expected, got $actual)"
}
if [[ -z "$SOURCE_DIR" ]]; then
command -v git >/dev/null 2>&1 || die "git is required without --source-dir"
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rknn-v232.XXXXXXXX")
SOURCE_DIR="$WORK_DIR/source"
note "fetching RKNN Toolkit2 commit $RKNN_COMMIT"
git init -q "$SOURCE_DIR"
git -C "$SOURCE_DIR" remote add origin "$RKNN_REPOSITORY"
git -C "$SOURCE_DIR" config remote.origin.promisor true
git -C "$SOURCE_DIR" config remote.origin.partialclonefilter blob:none
git -C "$SOURCE_DIR" sparse-checkout init --cone
git -C "$SOURCE_DIR" sparse-checkout set \
rknpu2/runtime/Linux/librknn_api \
rknpu2/examples/rknn_api_demo/model
git -C "$SOURCE_DIR" fetch -q --depth 1 --filter=blob:none origin \
"refs/tags/$RKNN_TAG"
git -C "$SOURCE_DIR" checkout -q --detach FETCH_HEAD
fi
SOURCE_DIR=$(readlink -f -- "$SOURCE_DIR")
[[ -d "$SOURCE_DIR" ]] || die "source directory is not a directory"
if [[ -d "$SOURCE_DIR/.git" ]]; then
source_commit=$(git -C "$SOURCE_DIR" rev-parse HEAD)
[[ "$source_commit" == "$RKNN_COMMIT" ]] ||
die "source checkout is $source_commit, expected $RKNN_COMMIT"
fi
HEADER="$SOURCE_DIR/rknpu2/runtime/Linux/librknn_api/include/rknn_api.h"
RUNTIME_SO="$SOURCE_DIR/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so"
MODEL="$SOURCE_DIR/rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn"
DEMO_IMAGE="$SOURCE_DIR/rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg"
LICENSE_FILE="$SOURCE_DIR/LICENSE"
verify_sha256 "$RKNN_HEADER_SHA256" "$HEADER"
verify_sha256 "$RKNN_RUNTIME_AARCH64_SHA256" "$RUNTIME_SO"
verify_sha256 "$RKNN_MOBILENET_RK3588_SHA256" "$MODEL"
verify_sha256 "$RKNN_DEMO_IMAGE_SHA256" "$DEMO_IMAGE"
verify_sha256 "$RKNN_LICENSE_SHA256" "$LICENSE_FILE"
if [[ -z "$WORK_DIR" ]]; then
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rknn-v232-build.XXXXXXXX")
fi
TEST_BINARY="$WORK_DIR/rknn-inference-test"
note "building the ARM64 RKNN smoke test with $CC_BIN"
"$CC_BIN" \
-std=c11 -D_POSIX_C_SOURCE=200809L -O2 -Wall -Wextra -Wpedantic \
-fstack-protector-strong -D_FORTIFY_SOURCE=2 \
-I"$(dirname -- "$HEADER")" \
"$SCRIPT_DIR/rknn-inference-test.c" \
-L"$(dirname -- "$RUNTIME_SO")" -Wl,--as-needed \
-Wl,-z,relro,-z,now -Wl,-rpath,/opt/rknn/current/lib \
-lrknnrt -lm -o "$TEST_BINARY"
VERSION_ROOT="$ROOTFS/opt/rknn/$RKNN_VERSION"
install -d -m 0755 \
"$VERSION_ROOT/bin" \
"$VERSION_ROOT/include" \
"$VERSION_ROOT/lib" \
"$VERSION_ROOT/share/models/rk3588" \
"$VERSION_ROOT/share/demo" \
"$VERSION_ROOT/share/licenses" \
"$ROOTFS/etc/ld.so.conf.d" \
"$ROOTFS/usr/local/bin" \
"$ROOTFS/usr/local/sbin"
install -m 0755 "$TEST_BINARY" "$VERSION_ROOT/bin/rknn-inference-test"
install -m 0644 "$HEADER" "$VERSION_ROOT/include/rknn_api.h"
install -m 0644 "$RUNTIME_SO" "$VERSION_ROOT/lib/librknnrt.so"
ln -sfn librknnrt.so "$VERSION_ROOT/lib/librknn_api.so"
install -m 0644 "$MODEL" \
"$VERSION_ROOT/share/models/rk3588/mobilenet_v1.rknn"
install -m 0644 "$DEMO_IMAGE" "$VERSION_ROOT/share/demo/dog_224x224.jpg"
install -m 0644 "$LICENSE_FILE" \
"$VERSION_ROOT/share/licenses/rknn-toolkit2-LICENSE"
cat >"$VERSION_ROOT/manifest.env" <<EOF
RKNN_VERSION=$RKNN_VERSION
RKNN_TAG=$RKNN_TAG
RKNN_COMMIT=$RKNN_COMMIT
RKNN_REPOSITORY=$RKNN_REPOSITORY
RKNN_RUNTIME_AARCH64_SHA256=$RKNN_RUNTIME_AARCH64_SHA256
RKNN_MOBILENET_RK3588_SHA256=$RKNN_MOBILENET_RK3588_SHA256
RKNN_DEMO_IMAGE_SHA256=$RKNN_DEMO_IMAGE_SHA256
EOF
chmod 0644 "$VERSION_ROOT/manifest.env"
ln -sfn "$RKNN_VERSION" "$ROOTFS/opt/rknn/current"
ln -sfn /opt/rknn/current/bin/rknn-inference-test \
"$ROOTFS/usr/local/bin/rknn-inference-test"
printf '%s\n' '/opt/rknn/current/lib' >"$ROOTFS/etc/ld.so.conf.d/rknn.conf"
chmod 0644 "$ROOTFS/etc/ld.so.conf.d/rknn.conf"
install -m 0755 "$SCRIPT_DIR/rk1-media-selftest" \
"$ROOTFS/usr/local/bin/rk1-media-selftest"
install -m 0755 "$SCRIPT_DIR/rk1-media-diagnostics" \
"$ROOTFS/usr/local/sbin/rk1-media-diagnostics"
if command -v ldconfig >/dev/null 2>&1 &&
[[ -e "$ROOTFS/etc/ld.so.conf" && -d "$ROOTFS/lib" ]]; then
if ! ldconfig -r "$ROOTFS"; then
note "warning: ldconfig failed; the smoke test still has an embedded rpath"
fi
fi
note "installed RKNN Runtime $RKNN_VERSION below $ROOTFS/opt/rknn/current"
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Extract only the immutable RKNN assets needed by install-rknn-runtime.sh.
set -Eeuo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
# shellcheck source=rknn-version.env
source "$SCRIPT_DIR/rknn-version.env"
[[ $# -eq 2 ]] || {
echo "Usage: $0 PINNED-RKNN-CHECKOUT OUTPUT-DIRECTORY" >&2
exit 2
}
SOURCE_DIR=$(readlink -f -- "$1")
OUTPUT_DIR=$2
[[ -d "$SOURCE_DIR/.git" ]] || {
echo "Source must be a Git checkout: $SOURCE_DIR" >&2
exit 1
}
[[ "$(git -C "$SOURCE_DIR" rev-parse HEAD)" == "$RKNN_COMMIT" ]] || {
echo "RKNN checkout is not pinned commit $RKNN_COMMIT" >&2
exit 1
}
[[ ! -e "$OUTPUT_DIR" ]] || {
echo "Refusing to replace existing output: $OUTPUT_DIR" >&2
exit 1
}
declare -a assets=(
"rknpu2/runtime/Linux/librknn_api/include/rknn_api.h:$RKNN_HEADER_SHA256"
"rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so:$RKNN_RUNTIME_AARCH64_SHA256"
"rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn:$RKNN_MOBILENET_RK3588_SHA256"
"rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg:$RKNN_DEMO_IMAGE_SHA256"
"LICENSE:$RKNN_LICENSE_SHA256"
)
for entry in "${assets[@]}"; do
relative_path=${entry%%:*}
expected_sha=${entry##*:}
actual_sha=$(sha256sum "$SOURCE_DIR/$relative_path" | awk '{print $1}')
[[ "$actual_sha" == "$expected_sha" ]] || {
echo "Checksum mismatch: $relative_path" >&2
exit 1
}
install -D -m 0644 "$SOURCE_DIR/$relative_path" "$OUTPUT_DIR/$relative_path"
done
cat >"$OUTPUT_DIR/SOURCE.env" <<EOF
RKNN_VERSION=$RKNN_VERSION
RKNN_TAG=$RKNN_TAG
RKNN_COMMIT=$RKNN_COMMIT
RKNN_REPOSITORY=$RKNN_REPOSITORY
EOF
chmod 0644 "$OUTPUT_DIR/SOURCE.env"
echo "Created verified offline RKNN bundle: $OUTPUT_DIR"
+437
View File
@@ -0,0 +1,437 @@
#!/usr/bin/env bash
# Collect a privacy-conscious, read-only RK1 media diagnostic bundle.
set -Eeuo pipefail
export LC_ALL=C
umask 077
OUTPUT=""
DIRECTORY_OUTPUT=0
ACTIVE_TESTS=0
MEDIA_DIR=""
TEMP_DIR=""
SYS_ROOT=${RK1_SYSFS_ROOT:-/sys}
PROC_ROOT=${RK1_PROCFS_ROOT:-/proc}
DEV_ROOT=${RK1_DEV_ROOT:-/dev}
RKNN_HOME=${RKNN_HOME:-/opt/rknn/current}
usage() {
cat <<'EOF'
Usage: rk1-media-diagnostics [OPTIONS]
Options:
--output PATH Output archive or directory path.
--directory Write an unpacked directory instead of a .tar.gz.
--include-active-tests Include short Vulkan, encode, and NPU workloads.
--media-dir PATH Pass media samples to active decode/RGA tests.
-h, --help Show this help.
The bundle is local only. It deliberately excludes environment variables,
home-directory contents, SSH material, disk serials/UUIDs, MAC addresses,
IP addresses, raw EDID, and unfiltered system journals. Review it before
sharing it with anyone.
EOF
}
die() {
printf 'rk1-media-diagnostics: %s\n' "$*" >&2
exit 2
}
while (($#)); do
case "$1" in
--output)
(($# >= 2)) || die "--output requires a path"
OUTPUT=$2
shift 2
;;
--directory)
DIRECTORY_OUTPUT=1
shift
;;
--include-active-tests)
ACTIVE_TESTS=1
shift
;;
--media-dir)
(($# >= 2)) || die "--media-dir requires a path"
MEDIA_DIR=$2
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
if [[ -n "$MEDIA_DIR" && ! -d "$MEDIA_DIR" ]]; then
die "media directory is not a directory: $MEDIA_DIR"
fi
safe_host=$(hostname 2>/dev/null || printf rk1)
safe_host=${safe_host//[^A-Za-z0-9._-]/_}
timestamp=$(date -u +%Y%m%dT%H%M%SZ)
if [[ -z "$OUTPUT" ]]; then
if ((DIRECTORY_OUTPUT)); then
OUTPUT="$PWD/rk1-media-diagnostics-${safe_host}-${timestamp}"
else
OUTPUT="$PWD/rk1-media-diagnostics-${safe_host}-${timestamp}.tar.gz"
fi
fi
output_parent=$(dirname -- "$OUTPUT")
[[ -d "$output_parent" ]] || die "output parent does not exist: $output_parent"
output_parent=$(cd -- "$output_parent" && pwd -P)
OUTPUT="$output_parent/$(basename -- "$OUTPUT")"
[[ ! -e "$OUTPUT" && ! -L "$OUTPUT" ]] || die "refusing to overwrite $OUTPUT"
TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rk1-media-diagnostics.XXXXXXXX")
REPORT="$TEMP_DIR/report"
mkdir -p "$REPORT"
cleanup() {
if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
rm -rf -- "$TEMP_DIR"
fi
}
trap cleanup EXIT
sanitize_stream() {
# Redact common MAC, IPv4, root-device, and static-IP command-line forms.
sed -E \
-e 's/([[:xdigit:]]{2}:){5}[[:xdigit:]]{2}/<mac-redacted>/g' \
-e 's/([[:space:]=]|^)([0-9]{1,3}\.){3}[0-9]{1,3}([[:space:]\/:]|$)/\1<ip-redacted>\3/g' \
-e 's/(root=)[^[:space:]]+/\1<root-device-redacted>/g' \
-e 's/(ip=)[^[:space:]]+/\1<ip-config-redacted>/g' \
-e 's/(UUID|PARTUUID)=[A-Za-z0-9-]+/\1=<redacted>/g' \
-e 's/([Ss]erial([ _-]?[Nn]umber)?[=:][[:space:]]*)[^[:space:],;]+/\1<redacted>/g'
}
run_capture() {
local destination=$1
shift
{
printf '$'
printf ' %q' "$@"
printf '\n'
if command -v "$1" >/dev/null 2>&1; then
timeout 45s "$@" 2>&1 ||
printf '[command exited %d]\n' "$?"
else
printf '[command unavailable: %s]\n' "$1"
fi
} | sanitize_stream >"$REPORT/$destination"
}
append_command() {
local destination=$1
shift
{
printf '\n$'
printf ' %q' "$@"
printf '\n'
if command -v "$1" >/dev/null 2>&1; then
timeout 45s "$@" 2>&1 ||
printf '[command exited %d]\n' "$?"
else
printf '[command unavailable: %s]\n' "$1"
fi
} | sanitize_stream >>"$REPORT/$destination"
}
cat >"$REPORT/README.txt" <<'EOF'
RK1 media diagnostic bundle
This is a read-only snapshot for diagnosing HDMI, GPU, RKMPP VPU, RGA,
RKNPU, ALSA, Ethernet, and storage enumeration. Commands that were missing or
permission-denied are recorded rather than treated as collector failures.
The collector attempts to redact IP and MAC addresses and omits raw EDID,
disk serials/UUIDs, environment variables, home directories, SSH material,
and unfiltered journals. Review every file before sharing the bundle.
EOF
{
printf 'collected_utc=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf 'collector_version=1\n'
printf 'active_tests=%s\n' "$ACTIVE_TESTS"
printf 'kernel='
uname -srvm 2>/dev/null || true
printf 'architecture='
uname -m 2>/dev/null || true
if [[ -r /etc/os-release ]]; then
printf '\n[os-release]\n'
grep -E '^(NAME|VERSION|VERSION_ID|ID|ID_LIKE)=' /etc/os-release || true
fi
if [[ -r "$PROC_ROOT/device-tree/model" ]]; then
printf '\nboard_model='
tr -d '\000' <"$PROC_ROOT/device-tree/model" || true
printf '\n'
fi
if [[ -r "$PROC_ROOT/cmdline" ]]; then
printf '\n[kernel-command-line-redacted]\n'
sanitize_stream <"$PROC_ROOT/cmdline"
printf '\n'
fi
} >"$REPORT/system.txt"
SELFTEST=""
if command -v rk1-media-selftest >/dev/null 2>&1; then
SELFTEST=$(command -v rk1-media-selftest)
elif [[ -x "$(dirname -- "${BASH_SOURCE[0]}")/rk1-media-selftest" ]]; then
SELFTEST="$(dirname -- "${BASH_SOURCE[0]}")/rk1-media-selftest"
fi
if [[ -n "$SELFTEST" ]]; then
selftest_args=(--json)
if ((ACTIVE_TESTS == 0)); then
selftest_args+=(--quick)
fi
if [[ -n "$MEDIA_DIR" ]]; then
selftest_args+=(--media-dir "$MEDIA_DIR")
fi
if ! timeout 600s "$SELFTEST" "${selftest_args[@]}" \
>"$REPORT/selftest.json" 2>"$REPORT/selftest.stderr"; then
printf 'self-test returned a nonzero status; inspect its JSON and stderr\n' \
>"$REPORT/selftest-status.txt"
fi
else
printf '{"error":"rk1-media-selftest is unavailable"}\n' \
>"$REPORT/selftest.json"
fi
# DRM connector details. EDID is represented by size, hash, and decoded text,
# never by the raw binary blob.
{
shopt -s nullglob
connectors=("$SYS_ROOT"/class/drm/card*-*)
shopt -u nullglob
if ((${#connectors[@]} == 0)); then
printf 'No DRM connectors found.\n'
fi
for connector in "${connectors[@]}"; do
[[ -d "$connector" ]] || continue
printf '\n[%s]\n' "${connector##*/}"
for attribute in status enabled dpms link_status; do
if [[ -r "$connector/$attribute" ]]; then
printf '%s=' "$attribute"
tr -d '\000' <"$connector/$attribute" 2>/dev/null || true
printf '\n'
fi
done
if [[ -r "$connector/modes" ]]; then
printf 'modes:\n'
sed 's/^/ /' "$connector/modes" 2>/dev/null || true
fi
if [[ -s "$connector/edid" ]]; then
printf 'edid_size=%s\n' "$(wc -c <"$connector/edid")"
printf 'edid_sha256=%s\n' \
"$(sha256sum "$connector/edid" | awk '{print $1}')"
if command -v edid-decode >/dev/null 2>&1; then
printf 'decoded_edid:\n'
timeout 15s edid-decode "$connector/edid" 2>&1 |
sed 's/^/ /' || true
fi
fi
done
} | sanitize_stream >"$REPORT/drm-connectors.txt"
run_capture drm-modetest.txt modetest -c -p
run_capture kernel-modules.txt lsmod
{
printf '[platform GPU driver bindings]\n'
for driver in panthor panfrost mali; do
directory="$SYS_ROOT/bus/platform/drivers/$driver"
[[ -d "$directory" ]] || continue
printf '%s:\n' "$driver"
find "$directory" -mindepth 1 -maxdepth 1 \( -type l -o -type d \) \
-printf ' %f\n' 2>/dev/null | sort
done
printf '\n[GPU devfreq]\n'
shopt -s nullglob
gpu_nodes=("$SYS_ROOT"/class/devfreq/*.gpu)
shopt -u nullglob
for node in "${gpu_nodes[@]}"; do
printf '%s\n' "${node##*/}"
for attribute in cur_freq min_freq max_freq governor available_frequencies; do
if [[ -r "$node/$attribute" ]]; then
printf ' %s=' "$attribute"
tr -d '\000' <"$node/$attribute" 2>/dev/null || true
printf '\n'
fi
done
done
} >"$REPORT/gpu-sysfs.txt"
run_capture vulkan.txt vulkaninfo --summary
append_command vulkan.txt eglinfo -B
{
printf '[media and accelerator platform bindings]\n'
for pattern in 'mpp*' 'rga*' 'rockchip-rga' 'RKNPU' 'rknpu'; do
shopt -s nullglob
directories=("$SYS_ROOT"/bus/platform/drivers/$pattern)
shopt -u nullglob
for directory in "${directories[@]}"; do
[[ -d "$directory" ]] || continue
printf '\n%s:\n' "${directory##*/}"
find "$directory" -mindepth 1 -maxdepth 1 \( -type l -o -type d \) \
-printf ' %f\n' 2>/dev/null | sort
done
done
printf '\n[device nodes]\n'
shopt -s nullglob
nodes=("$DEV_ROOT"/dri/* "$DEV_ROOT"/mpp_service \
"$DEV_ROOT"/rga "$DEV_ROOT"/rknpu* "$DEV_ROOT"/mali*)
shopt -u nullglob
for node in "${nodes[@]}"; do
stat -c '%A %U:%G %t:%T %n' "$node" 2>/dev/null || true
done
} >"$REPORT/accelerators.txt"
FFMPEG=""
for candidate in /opt/rkmedia/bin/ffmpeg-rk /usr/local/bin/ffmpeg-rk; do
if [[ -x "$candidate" ]]; then
FFMPEG=$candidate
break
fi
done
if [[ -z "$FFMPEG" ]] && command -v ffmpeg-rk >/dev/null 2>&1; then
FFMPEG=$(command -v ffmpeg-rk)
fi
if [[ -n "$FFMPEG" ]]; then
run_capture ffmpeg-rk.txt "$FFMPEG" -hide_banner -version
append_command ffmpeg-rk.txt "$FFMPEG" -hide_banner -hwaccels
{
printf '\n[Rockchip encoders, decoders, and filters]\n'
"$FFMPEG" -hide_banner -decoders 2>/dev/null |
grep -Ei 'h264|hevc|vp9|av1|rkmpp' || true
"$FFMPEG" -hide_banner -encoders 2>/dev/null |
grep -Ei 'h264|hevc|mjpeg|rkmpp' || true
"$FFMPEG" -hide_banner -filters 2>/dev/null |
grep -Ei 'rkrga|drm' || true
} >>"$REPORT/ffmpeg-rk.txt"
else
printf 'ffmpeg-rk is unavailable\n' >"$REPORT/ffmpeg-rk.txt"
fi
{
printf '[RKNN installation]\n'
if [[ -r "$RKNN_HOME/manifest.env" ]]; then
sed -E 's#(REPOSITORY=).*#\1<upstream-url>#' "$RKNN_HOME/manifest.env"
else
printf 'manifest unavailable at %s\n' "$RKNN_HOME/manifest.env"
fi
for asset in \
"$RKNN_HOME/lib/librknnrt.so" \
"$RKNN_HOME/share/models/rk3588/mobilenet_v1.rknn" \
"$RKNN_HOME/bin/rknn-inference-test"; do
if [[ -r "$asset" ]]; then
sha256sum "$asset"
else
printf 'missing: %s\n' "$asset"
fi
done
printf '\n[NPU devfreq]\n'
shopt -s nullglob
npu_nodes=("$SYS_ROOT"/class/devfreq/*.npu)
shopt -u nullglob
for node in "${npu_nodes[@]}"; do
printf '%s\n' "${node##*/}"
for attribute in cur_freq min_freq max_freq governor available_frequencies; do
if [[ -r "$node/$attribute" ]]; then
printf ' %s=' "$attribute"
tr -d '\000' <"$node/$attribute" 2>/dev/null || true
printf '\n'
fi
done
done
} >"$REPORT/npu.txt"
{
printf '[ALSA cards]\n'
if [[ -r "$PROC_ROOT/asound/cards" ]]; then
cat "$PROC_ROOT/asound/cards"
else
printf 'unavailable\n'
fi
} >"$REPORT/audio.txt"
append_command audio.txt aplay -l
append_command audio.txt aplay -L
{
printf 'Interface state only; addresses and MACs are intentionally omitted.\n\n'
shopt -s nullglob
interfaces=("$SYS_ROOT"/class/net/*)
shopt -u nullglob
for interface in "${interfaces[@]}"; do
name=${interface##*/}
[[ "$name" == lo ]] && continue
printf '[%s]\n' "$name"
for attribute in operstate carrier speed duplex mtu; do
if [[ -r "$interface/$attribute" ]]; then
printf '%s=' "$attribute"
tr -d '\000' <"$interface/$attribute" 2>/dev/null || true
printf '\n'
fi
done
printf '\n'
done
} >"$REPORT/network.txt"
run_capture storage.txt lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS,ROTA,TRAN
{
printf '\n[root filesystem]\n'
if command -v findmnt >/dev/null 2>&1; then
findmnt -n -o SOURCE,FSTYPE / 2>&1 | sanitize_stream
else
printf 'findmnt unavailable\n'
fi
} >>"$REPORT/storage.txt"
run_capture usb.txt lsusb -t
append_command usb.txt lspci -nnk
{
if command -v dpkg-query >/dev/null 2>&1; then
dpkg-query -W -f='${binary:Package}\t${Version}\n' 2>/dev/null |
grep -Ei 'armbian|linux-image|linux-dtb|mesa|vulkan|kodi|ffmpeg|mpp|rga|rknn|alsa|libcec' |
sort || true
elif command -v rpm >/dev/null 2>&1; then
rpm -qa 2>/dev/null |
grep -Ei 'kernel|mesa|vulkan|kodi|ffmpeg|mpp|rga|rknn|alsa|libcec' |
sort || true
else
printf 'supported package inventory tool unavailable\n'
fi
} >"$REPORT/packages.txt"
kernel_pattern='drm|hdmi|vop|edid|panthor|panfrost|mali|rknpu|npu|rkvdec|rkvenc|av1|mpp|rga|alsa|snd|cec|pcie|nvme|mmc'
{
printf '[filtered current-boot kernel messages]\n'
if command -v journalctl >/dev/null 2>&1; then
timeout 45s journalctl -b -k --no-pager 2>&1 |
grep -Ei "$kernel_pattern" | tail -2500 || true
elif command -v dmesg >/dev/null 2>&1; then
dmesg --color=never 2>&1 |
grep -Ei "$kernel_pattern" | tail -2500 || true
else
printf 'kernel log reader unavailable\n'
fi
} | sanitize_stream >"$REPORT/kernel-media.log"
if ((DIRECTORY_OUTPUT)); then
mkdir -- "$OUTPUT"
cp -a "$REPORT/." "$OUTPUT/"
chmod -R go-rwx "$OUTPUT"
else
command -v tar >/dev/null 2>&1 || die "tar is required for archive output"
tar -C "$REPORT" -czf "$OUTPUT" .
chmod 0600 "$OUTPUT"
fi
printf 'Diagnostic bundle written to %s\n' "$OUTPUT"
+574
View File
@@ -0,0 +1,574 @@
#!/usr/bin/env bash
# Read-only RK3588 media hardware inventory with optional short workloads.
set -uo pipefail
export LC_ALL=C
JSON=0
QUICK=0
STRICT=0
MEDIA_DIR=""
SYS_ROOT=${RK1_SYSFS_ROOT:-/sys}
PROC_ROOT=${RK1_PROCFS_ROOT:-/proc}
DEV_ROOT=${RK1_DEV_ROOT:-/dev}
RKNN_HOME=${RKNN_HOME:-/opt/rknn/current}
declare -a CHECK_GROUP=()
declare -a CHECK_NAME=()
declare -a CHECK_STATUS=()
declare -a CHECK_MESSAGE=()
usage() {
cat <<'EOF'
Usage: rk1-media-selftest [OPTIONS]
Options:
--json Emit one JSON document instead of readable text.
--quick Inventory only; skip GPU, codec, and NPU workloads.
--strict Return nonzero for warnings as well as failures.
--media-dir PATH Exercise RKMPP decoding with named sample files.
Recognized names contain h264, hevc, vp9, or av1.
-h, --help Show this help.
The default run is non-destructive. It executes only short inference, Vulkan,
and encoder probes when the relevant runtime and devices are present. It never
changes clocks, governors, display modes, storage, or network configuration.
EOF
}
invocation_error() {
printf 'rk1-media-selftest: %s\n' "$*" >&2
exit 2
}
while (($#)); do
case "$1" in
--json)
JSON=1
shift
;;
--quick)
QUICK=1
shift
;;
--strict)
STRICT=1
shift
;;
--media-dir)
(($# >= 2)) || invocation_error "--media-dir requires a path"
MEDIA_DIR=$2
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
invocation_error "unknown option: $1"
;;
esac
done
if [[ -n "$MEDIA_DIR" && ! -d "$MEDIA_DIR" ]]; then
invocation_error "media directory is not a directory: $MEDIA_DIR"
fi
add_check() {
CHECK_GROUP+=("$1")
CHECK_NAME+=("$2")
CHECK_STATUS+=("$3")
CHECK_MESSAGE+=("$4")
}
read_text() {
local path=$1
if [[ -r "$path" ]]; then
tr -d '\000' <"$path" 2>/dev/null || true
fi
}
short_message() {
local value=$1
value=${value//$'\r'/}
value=${value//$'\n'/'; '}
value=${value//$'\t'/ }
printf '%s' "${value:0:320}"
}
driver_binding_count() {
local pattern=$1
local directory entry base
local count=0
shopt -s nullglob
for directory in "$SYS_ROOT"/bus/platform/drivers/$pattern; do
[[ -d "$directory" ]] || continue
for entry in "$directory"/*; do
base=${entry##*/}
case "$base" in
bind|unbind|uevent|module|new_id|remove_id) continue ;;
esac
if [[ -L "$entry" || -d "$entry" ]]; then
((count++))
fi
done
done
shopt -u nullglob
printf '%d' "$count"
}
find_sample() {
local token=$1
[[ -n "$MEDIA_DIR" ]] || return 1
find "$MEDIA_DIR" -maxdepth 1 -type f \
\( -iname "*${token}*.mkv" -o -iname "*${token}*.mp4" \
-o -iname "*${token}*.webm" -o -iname "*${token}*.ts" \) \
-print -quit 2>/dev/null
}
# System identity
machine=$(uname -m 2>/dev/null || printf unknown)
if [[ "$machine" == "aarch64" ]]; then
add_check system architecture pass "aarch64 userspace"
else
add_check system architecture fail "expected aarch64, found $machine"
fi
model=$(read_text "$PROC_ROOT/device-tree/model")
if [[ "$model" == *"Turing"*"RK1"* ]]; then
add_check system board pass "$model"
elif [[ -n "$model" ]]; then
add_check system board warn "device-tree model is $model"
else
add_check system board warn "device-tree model is unavailable"
fi
# HDMI / DRM connector state
shopt -s nullglob
hdmi_connectors=("$SYS_ROOT"/class/drm/card*-HDMI-A-*)
shopt -u nullglob
if ((${#hdmi_connectors[@]} == 0)); then
add_check hdmi connector warn "no DRM HDMI connector is registered"
else
connected_count=0
for connector in "${hdmi_connectors[@]}"; do
connector_name=${connector##*/}
state=$(read_text "$connector/status")
case "$state" in
connected)
((connected_count++))
add_check hdmi "$connector_name" pass "connected"
if [[ -s "$connector/edid" ]]; then
edid_bytes=$(wc -c <"$connector/edid" 2>/dev/null || printf 0)
add_check hdmi "$connector_name-edid" pass \
"$edid_bytes bytes of EDID data"
else
add_check hdmi "$connector_name-edid" warn \
"connected but EDID is empty or unreadable"
fi
if [[ -r "$connector/modes" ]] &&
grep -qx '3840x2160' "$connector/modes" 2>/dev/null; then
add_check hdmi "$connector_name-4k" pass \
"EDID advertises a 3840x2160 mode"
else
add_check hdmi "$connector_name-4k" warn \
"3840x2160 is not present in the connector mode list"
fi
;;
disconnected)
add_check hdmi "$connector_name" warn \
"disconnected (expected when no display is attached)"
;;
*)
add_check hdmi "$connector_name" warn \
"connector state is ${state:-unknown}"
;;
esac
done
fi
hdmi_driver_count=$(driver_binding_count 'dwhdmi*')
hdmi_phy_count=$(driver_binding_count '*hdptx*hdmi*')
if ((hdmi_driver_count > 0 && hdmi_phy_count > 0)); then
add_check hdmi drivers pass \
"$hdmi_driver_count controller and $hdmi_phy_count HDMI PHY binding(s)"
else
add_check hdmi drivers warn \
"HDMI controller bindings=$hdmi_driver_count, PHY bindings=$hdmi_phy_count"
fi
# GPU / Vulkan
gpu_driver=""
for candidate in panthor panfrost mali; do
if (($(driver_binding_count "$candidate") > 0)); then
gpu_driver=$candidate
break
fi
done
case "$gpu_driver" in
panthor)
add_check gpu kernel-driver pass "Panthor is bound to the GPU"
;;
panfrost)
add_check gpu kernel-driver warn "Panfrost is bound; the image expects Panthor"
;;
mali)
add_check gpu kernel-driver warn "proprietary Mali kernel driver is bound"
;;
*)
add_check gpu kernel-driver warn "no bound Mali GPU driver was found"
;;
esac
shopt -s nullglob
gpu_devfreq=("$SYS_ROOT"/class/devfreq/*.gpu)
render_nodes=("$DEV_ROOT"/dri/renderD*)
shopt -u nullglob
if ((${#gpu_devfreq[@]} > 0)); then
add_check gpu devfreq pass "${#gpu_devfreq[@]} GPU devfreq device(s)"
else
add_check gpu devfreq warn "GPU devfreq node is missing"
fi
if ((${#render_nodes[@]} > 0)); then
add_check gpu render-node pass "${#render_nodes[@]} DRM render node(s)"
else
add_check gpu render-node warn "no accessible DRM render node"
fi
if ((QUICK)); then
add_check gpu vulkan skip "active Vulkan probe disabled by --quick"
elif command -v vulkaninfo >/dev/null 2>&1; then
vulkan_output=$(timeout 20s vulkaninfo --summary 2>&1)
vulkan_rc=$?
if ((vulkan_rc == 0)); then
gpu_name=$(printf '%s\n' "$vulkan_output" |
sed -n 's/^[[:space:]]*deviceName[[:space:]]*=[[:space:]]*//p' |
head -1)
add_check gpu vulkan pass "${gpu_name:-vulkaninfo completed}"
else
add_check gpu vulkan fail \
"vulkaninfo failed: $(short_message "$vulkan_output")"
fi
else
add_check gpu vulkan skip "vulkaninfo is not installed"
fi
# VPU bindings and FFmpeg integration
decoder_bindings=$(driver_binding_count 'mpp_rkvdec*')
encoder_bindings=$(driver_binding_count 'mpp_rkvenc*')
av1_bindings=$(driver_binding_count '*av1*')
if ((decoder_bindings > 0)); then
add_check vpu decoder-driver pass "$decoder_bindings RKMPP decoder binding(s)"
else
add_check vpu decoder-driver warn "RKMPP decoder bindings are missing"
fi
if ((encoder_bindings > 0)); then
add_check vpu encoder-driver pass "$encoder_bindings RKMPP encoder binding(s)"
else
add_check vpu encoder-driver warn "RKMPP encoder bindings are missing"
fi
if ((av1_bindings > 0)); then
add_check vpu av1-driver pass "$av1_bindings AV1 decoder binding(s)"
else
add_check vpu av1-driver warn "a separate AV1 driver binding was not found"
fi
FFMPEG=""
for candidate in /opt/rkmedia/bin/ffmpeg-rk /usr/local/bin/ffmpeg-rk; do
if [[ -x "$candidate" ]]; then
FFMPEG=$candidate
break
fi
done
if [[ -z "$FFMPEG" ]] && command -v ffmpeg-rk >/dev/null 2>&1; then
FFMPEG=$(command -v ffmpeg-rk)
fi
if [[ -z "$FFMPEG" ]]; then
add_check vpu ffmpeg-rkmpp skip "ffmpeg-rk is not installed"
add_check rga ffmpeg-filter skip "ffmpeg-rk is not installed"
else
hwaccels=$($FFMPEG -hide_banner -hwaccels 2>&1)
if grep -qE '(^|[[:space:]])rkmpp($|[[:space:]])' <<<"$hwaccels"; then
add_check vpu ffmpeg-rkmpp pass "$FFMPEG advertises the rkmpp hwaccel"
else
add_check vpu ffmpeg-rkmpp fail "$FFMPEG does not advertise rkmpp"
fi
encoders=$($FFMPEG -hide_banner -encoders 2>&1)
for codec in h264 hevc mjpeg; do
if grep -q "${codec}_rkmpp" <<<"$encoders"; then
add_check vpu "encode-$codec-capability" pass \
"${codec}_rkmpp is registered"
else
add_check vpu "encode-$codec-capability" fail \
"${codec}_rkmpp is not registered"
fi
done
filters=$($FFMPEG -hide_banner -filters 2>&1)
if grep -q 'scale_rkrga' <<<"$filters"; then
add_check rga ffmpeg-filter pass "scale_rkrga is registered"
else
add_check rga ffmpeg-filter fail "scale_rkrga is not registered"
fi
if ((QUICK)); then
add_check vpu encode-workload skip "active encoder probe disabled by --quick"
else
for codec in h264 hevc mjpeg; do
encode_output=$(timeout 30s "$FFMPEG" -nostdin -v error \
-f lavfi -i 'color=size=320x240:rate=30:duration=0.2' \
-frames:v 3 -c:v "${codec}_rkmpp" -f null - 2>&1)
encode_rc=$?
if ((encode_rc == 0)); then
add_check vpu "encode-$codec-workload" pass \
"three frames submitted successfully"
else
add_check vpu "encode-$codec-workload" fail \
"hardware encode failed: $(short_message "$encode_output")"
fi
done
fi
if [[ -n "$MEDIA_DIR" && $QUICK -eq 0 ]]; then
first_decode_sample=""
for codec in h264 hevc vp9 av1; do
sample=$(find_sample "$codec" || true)
if [[ -z "$sample" ]]; then
add_check vpu "decode-$codec-workload" skip \
"no sample containing '$codec' in $MEDIA_DIR"
continue
fi
[[ -n "$first_decode_sample" ]] || first_decode_sample=$sample
decode_output=$(timeout 180s "$FFMPEG" -nostdin -v error \
-hwaccel rkmpp -i "$sample" -map 0:v:0 -f null - 2>&1)
decode_rc=$?
if ((decode_rc == 0)); then
add_check vpu "decode-$codec-workload" pass \
"decoded ${sample##*/} through RKMPP"
else
add_check vpu "decode-$codec-workload" fail \
"decode failed: $(short_message "$decode_output")"
fi
done
if [[ -n "$first_decode_sample" ]] && grep -q 'scale_rkrga' <<<"$filters"; then
rga_output=$(timeout 60s "$FFMPEG" -nostdin -v error \
-hwaccel rkmpp -hwaccel_output_format drm_prime \
-i "$first_decode_sample" -map 0:v:0 -frames:v 30 \
-vf 'scale_rkrga=w=1280:h=720:format=nv12' -f null - 2>&1)
rga_rc=$?
if ((rga_rc == 0)); then
add_check rga workload pass "RKMPP-to-RGA scaling completed"
else
add_check rga workload fail \
"RGA workload failed: $(short_message "$rga_output")"
fi
else
add_check rga workload skip "no decode sample is available for RGA"
fi
elif [[ -z "$MEDIA_DIR" ]]; then
add_check vpu decode-workload skip "use --media-dir to test hardware decoding"
add_check rga workload skip "use --media-dir to test zero-copy RGA scaling"
fi
fi
rga2_bindings=$(driver_binding_count 'rga2')
rga3_bindings=$(driver_binding_count 'rga3')
if ((rga2_bindings + rga3_bindings > 0)); then
add_check rga kernel-driver pass \
"RGA2 bindings=$rga2_bindings, RGA3 bindings=$rga3_bindings"
else
add_check rga kernel-driver warn "no RGA2/RGA3 binding was found"
fi
# NPU runtime and per-core inference
npu_bindings=$(driver_binding_count 'rknpu')
((npu_bindings += $(driver_binding_count 'RKNPU')))
shopt -s nullglob
npu_devfreq=("$SYS_ROOT"/class/devfreq/*.npu)
shopt -u nullglob
if ((npu_bindings > 0 || ${#npu_devfreq[@]} > 0)); then
add_check npu kernel-driver pass \
"RKNPU bindings=$npu_bindings, devfreq nodes=${#npu_devfreq[@]}"
npu_present=1
else
add_check npu kernel-driver warn "RKNPU driver/device was not found"
npu_present=0
fi
runtime_so="$RKNN_HOME/lib/librknnrt.so"
model_path="$RKNN_HOME/share/models/rk3588/mobilenet_v1.rknn"
npu_test="$RKNN_HOME/bin/rknn-inference-test"
if [[ -r "$runtime_so" ]]; then
runtime_hash=$(sha256sum "$runtime_so" 2>/dev/null)
runtime_hash=${runtime_hash%% *}
if [[ "$runtime_hash" == \
"d31fc19c85b85f6091b2bd0f6af9d962d5264a4e410bfb536402ec92bac738e8" ]]; then
add_check npu runtime pass "RKNN Runtime 2.3.2 checksum matches"
else
add_check npu runtime fail "unexpected librknnrt.so checksum $runtime_hash"
fi
else
add_check npu runtime warn "RKNN runtime is not installed at $runtime_so"
fi
if ((QUICK)); then
add_check npu inference skip "active inference disabled by --quick"
elif ((npu_present == 0)); then
add_check npu inference skip "NPU driver is absent; inference was not attempted"
elif [[ ! -x "$npu_test" || ! -r "$model_path" ]]; then
add_check npu inference skip "test binary or pinned RK3588 model is unavailable"
else
for core in 0 1 2 all; do
npu_output=$(timeout 45s "$npu_test" --model "$model_path" \
--core "$core" --iterations 1 2>&1)
npu_rc=$?
if ((npu_rc == 0)) && grep -q 'RKNN_RESULT status=pass' <<<"$npu_output"; then
add_check npu "core-$core" pass "$(short_message "$npu_output")"
else
add_check npu "core-$core" fail \
"inference failed: $(short_message "$npu_output")"
fi
done
fi
# HDMI ALSA devices
asound_cards=$(read_text "$PROC_ROOT/asound/cards")
if [[ -n "$asound_cards" ]] && grep -qiE 'hdmi|rockchiphdmi' <<<"$asound_cards"; then
add_check audio hdmi-card pass "an HDMI ALSA card is registered"
elif [[ -n "$asound_cards" ]]; then
add_check audio hdmi-card warn "ALSA cards exist, but none is labeled HDMI"
else
add_check audio hdmi-card warn "ALSA card inventory is unavailable"
fi
if command -v aplay >/dev/null 2>&1; then
aplay_output=$(aplay -l 2>&1)
if grep -qi 'hdmi' <<<"$aplay_output"; then
add_check audio pcm-device pass "aplay lists an HDMI PCM device"
else
add_check audio pcm-device warn \
"aplay did not list an HDMI PCM device: $(short_message "$aplay_output")"
fi
else
add_check audio pcm-device skip "aplay is not installed"
fi
# Link state only: no pings, DNS requests, or network mutations.
shopt -s nullglob
interfaces=("$SYS_ROOT"/class/net/*)
shopt -u nullglob
interface_names=()
up_names=()
for interface in "${interfaces[@]}"; do
name=${interface##*/}
[[ "$name" == lo ]] && continue
interface_names+=("$name")
state=$(read_text "$interface/operstate")
carrier=$(read_text "$interface/carrier")
if [[ "$state" == up || "$carrier" == 1 ]]; then
up_names+=("$name")
fi
done
if ((${#interface_names[@]} == 0)); then
add_check network interfaces warn "no non-loopback interface was found"
elif ((${#up_names[@]} > 0)); then
add_check network link pass "up: ${up_names[*]}"
else
add_check network link warn \
"interfaces present but down: ${interface_names[*]}"
fi
# Root filesystem, eMMC, and NVMe presence. No serial numbers are read.
if command -v findmnt >/dev/null 2>&1; then
root_source=$(findmnt -n -o SOURCE / 2>/dev/null || true)
root_fstype=$(findmnt -n -o FSTYPE / 2>/dev/null || true)
if [[ -n "$root_source" ]]; then
add_check storage rootfs pass "$root_source ($root_fstype)"
else
add_check storage rootfs warn "root filesystem source is unavailable"
fi
else
add_check storage rootfs skip "findmnt is not installed"
fi
shopt -s nullglob
emmc_devices=("$SYS_ROOT"/block/mmcblk*)
nvme_devices=("$SYS_ROOT"/block/nvme*n1)
shopt -u nullglob
if ((${#emmc_devices[@]} > 0)); then
add_check storage emmc pass "${#emmc_devices[@]} MMC block device(s)"
else
add_check storage emmc warn "no MMC/eMMC block device was found"
fi
if ((${#nvme_devices[@]} > 0)); then
add_check storage nvme pass "${#nvme_devices[@]} NVMe namespace(s)"
else
add_check storage nvme warn "no NVMe namespace was found"
fi
pass_count=0
warn_count=0
fail_count=0
skip_count=0
for status in "${CHECK_STATUS[@]}"; do
case "$status" in
pass) ((pass_count++)) ;;
warn) ((warn_count++)) ;;
fail) ((fail_count++)) ;;
skip) ((skip_count++)) ;;
esac
done
if ((fail_count > 0)); then
overall=fail
elif ((warn_count > 0)); then
overall=warn
else
overall=pass
fi
json_escape() {
local value=$1
value=${value//\\/\\\\}
value=${value//\"/\\\"}
value=${value//$'\n'/\\n}
value=${value//$'\r'/\\r}
value=${value//$'\t'/\\t}
printf '%s' "$value"
}
if ((JSON)); then
printf '{"schema_version":1,"overall":"%s","quick":%s,' \
"$overall" "$([[ $QUICK -eq 1 ]] && printf true || printf false)"
printf '"summary":{"pass":%d,"warn":%d,"fail":%d,"skip":%d},' \
"$pass_count" "$warn_count" "$fail_count" "$skip_count"
printf '"checks":['
for ((i = 0; i < ${#CHECK_NAME[@]}; ++i)); do
((i == 0)) || printf ','
printf '{"group":"%s","name":"%s","status":"%s","message":"%s"}' \
"$(json_escape "${CHECK_GROUP[i]}")" \
"$(json_escape "${CHECK_NAME[i]}")" \
"${CHECK_STATUS[i]}" \
"$(json_escape "${CHECK_MESSAGE[i]}")"
done
printf ']}\n'
else
printf '%-9s %-24s %-6s %s\n' GROUP CHECK STATUS DETAIL
printf '%-9s %-24s %-6s %s\n' '---------' '------------------------' \
'------' '------'
for ((i = 0; i < ${#CHECK_NAME[@]}; ++i)); do
printf '%-9s %-24s %-6s %s\n' \
"${CHECK_GROUP[i]}" "${CHECK_NAME[i]}" \
"${CHECK_STATUS[i]^^}" "${CHECK_MESSAGE[i]}"
done
printf '\nOverall: %s (pass=%d warn=%d fail=%d skip=%d)\n' \
"${overall^^}" "$pass_count" "$warn_count" "$fail_count" "$skip_count"
fi
if ((fail_count > 0 || (STRICT && warn_count > 0))); then
exit 1
fi
exit 0
+315
View File
@@ -0,0 +1,315 @@
/*
* Minimal RK3588 NPU smoke test for RKNN Runtime 2.3.2.
*
* The program deliberately uses deterministic synthetic inputs. Its purpose is
* to verify model loading, core selection, command submission, and finite
* output, not the semantic accuracy of MobileNet.
*/
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include "rknn_api.h"
#define DEFAULT_MODEL "/opt/rknn/current/share/models/rk3588/mobilenet_v1.rknn"
#define MAX_TENSORS 64U
static void usage(FILE *stream, const char *program)
{
fprintf(stream,
"Usage: %s [--model PATH] [--core auto|0|1|2|all] "
"[--iterations N]\n",
program);
}
static int parse_core(const char *value, rknn_core_mask *mask)
{
if (strcmp(value, "auto") == 0) {
*mask = RKNN_NPU_CORE_AUTO;
} else if (strcmp(value, "0") == 0) {
*mask = RKNN_NPU_CORE_0;
} else if (strcmp(value, "1") == 0) {
*mask = RKNN_NPU_CORE_1;
} else if (strcmp(value, "2") == 0) {
*mask = RKNN_NPU_CORE_2;
} else if (strcmp(value, "all") == 0) {
*mask = RKNN_NPU_CORE_0_1_2;
} else {
return -1;
}
return 0;
}
static int load_file(const char *path, void **buffer, uint32_t *size)
{
struct stat info;
FILE *file = NULL;
void *data = NULL;
if (stat(path, &info) != 0) {
fprintf(stderr, "cannot stat model %s: %s\n", path, strerror(errno));
return -1;
}
if (info.st_size <= 0 || (uint64_t)info.st_size > UINT32_MAX) {
fprintf(stderr, "invalid model size: %jd\n", (intmax_t)info.st_size);
return -1;
}
file = fopen(path, "rb");
if (file == NULL) {
fprintf(stderr, "cannot open model %s: %s\n", path, strerror(errno));
return -1;
}
data = malloc((size_t)info.st_size);
if (data == NULL) {
fprintf(stderr, "cannot allocate %jd bytes for model\n",
(intmax_t)info.st_size);
fclose(file);
return -1;
}
if (fread(data, 1, (size_t)info.st_size, file) != (size_t)info.st_size) {
fprintf(stderr, "short read from model %s\n", path);
free(data);
fclose(file);
return -1;
}
fclose(file);
*buffer = data;
*size = (uint32_t)info.st_size;
return 0;
}
static double elapsed_ms(const struct timespec *start, const struct timespec *end)
{
double seconds = (double)(end->tv_sec - start->tv_sec) * 1000.0;
double nanos = (double)(end->tv_nsec - start->tv_nsec) / 1000000.0;
return seconds + nanos;
}
int main(int argc, char **argv)
{
const char *model_path = DEFAULT_MODEL;
const char *core_name = "auto";
rknn_core_mask core_mask = RKNN_NPU_CORE_AUTO;
unsigned long iterations = 1;
void *model = NULL;
uint32_t model_size = 0;
rknn_context context = 0;
rknn_sdk_version sdk_version;
rknn_input_output_num io_count;
rknn_tensor_attr *input_attrs = NULL;
rknn_input *inputs = NULL;
rknn_output *outputs = NULL;
int outputs_acquired = 0;
int context_created = 0;
int result = EXIT_FAILURE;
double total_ms = 0.0;
uint32_t last_top_index = 0;
float last_top_value = -INFINITY;
int ret;
uint32_t i;
for (i = 1; i < (uint32_t)argc; ++i) {
if (strcmp(argv[i], "--model") == 0 && i + 1U < (uint32_t)argc) {
model_path = argv[++i];
} else if (strcmp(argv[i], "--core") == 0 && i + 1U < (uint32_t)argc) {
core_name = argv[++i];
if (parse_core(core_name, &core_mask) != 0) {
fprintf(stderr, "invalid core selector: %s\n", core_name);
usage(stderr, argv[0]);
return 2;
}
} else if (strcmp(argv[i], "--iterations") == 0 &&
i + 1U < (uint32_t)argc) {
char *end = NULL;
errno = 0;
iterations = strtoul(argv[++i], &end, 10);
if (errno != 0 || end == argv[i] || *end != '\0' ||
iterations == 0 || iterations > 1000) {
fprintf(stderr, "iterations must be between 1 and 1000\n");
return 2;
}
} else if (strcmp(argv[i], "--help") == 0 ||
strcmp(argv[i], "-h") == 0) {
usage(stdout, argv[0]);
return 0;
} else {
fprintf(stderr, "unknown or incomplete option: %s\n", argv[i]);
usage(stderr, argv[0]);
return 2;
}
}
if (load_file(model_path, &model, &model_size) != 0) {
goto cleanup;
}
ret = rknn_init(&context, model, model_size, 0, NULL);
if (ret != RKNN_SUCC) {
fprintf(stderr, "rknn_init failed: %d\n", ret);
goto cleanup;
}
context_created = 1;
ret = rknn_set_core_mask(context, core_mask);
if (ret != RKNN_SUCC) {
fprintf(stderr, "rknn_set_core_mask(%s) failed: %d\n", core_name, ret);
goto cleanup;
}
memset(&sdk_version, 0, sizeof(sdk_version));
ret = rknn_query(context, RKNN_QUERY_SDK_VERSION, &sdk_version,
sizeof(sdk_version));
if (ret != RKNN_SUCC) {
fprintf(stderr, "RKNN_QUERY_SDK_VERSION failed: %d\n", ret);
goto cleanup;
}
memset(&io_count, 0, sizeof(io_count));
ret = rknn_query(context, RKNN_QUERY_IN_OUT_NUM, &io_count,
sizeof(io_count));
if (ret != RKNN_SUCC || io_count.n_input == 0 || io_count.n_output == 0 ||
io_count.n_input > MAX_TENSORS || io_count.n_output > MAX_TENSORS) {
fprintf(stderr, "invalid RKNN input/output count (%u/%u), ret=%d\n",
io_count.n_input, io_count.n_output, ret);
goto cleanup;
}
input_attrs = calloc(io_count.n_input, sizeof(*input_attrs));
inputs = calloc(io_count.n_input, sizeof(*inputs));
outputs = calloc(io_count.n_output, sizeof(*outputs));
if (input_attrs == NULL || inputs == NULL || outputs == NULL) {
fprintf(stderr, "cannot allocate tensor metadata\n");
goto cleanup;
}
for (i = 0; i < io_count.n_input; ++i) {
uint32_t byte;
input_attrs[i].index = i;
ret = rknn_query(context, RKNN_QUERY_INPUT_ATTR, &input_attrs[i],
sizeof(input_attrs[i]));
if (ret != RKNN_SUCC || input_attrs[i].n_elems == 0) {
fprintf(stderr, "query for input %u failed: %d\n", i, ret);
goto cleanup;
}
inputs[i].index = i;
inputs[i].size = input_attrs[i].n_elems;
inputs[i].type = RKNN_TENSOR_UINT8;
inputs[i].fmt = input_attrs[i].fmt == RKNN_TENSOR_UNDEFINED
? RKNN_TENSOR_NHWC
: input_attrs[i].fmt;
inputs[i].pass_through = 0;
inputs[i].buf = malloc(inputs[i].size);
if (inputs[i].buf == NULL) {
fprintf(stderr, "cannot allocate input %u (%u bytes)\n", i,
inputs[i].size);
goto cleanup;
}
for (byte = 0; byte < inputs[i].size; ++byte) {
((uint8_t *)inputs[i].buf)[byte] =
(uint8_t)((byte * 17U + i * 23U) & 0xffU);
}
}
ret = rknn_inputs_set(context, io_count.n_input, inputs);
if (ret != RKNN_SUCC) {
fprintf(stderr, "rknn_inputs_set failed: %d\n", ret);
goto cleanup;
}
for (i = 0; i < io_count.n_output; ++i) {
outputs[i].index = i;
outputs[i].want_float = 1;
outputs[i].is_prealloc = 0;
}
for (unsigned long iteration = 0; iteration < iterations; ++iteration) {
struct timespec start;
struct timespec end;
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) {
fprintf(stderr, "clock_gettime failed: %s\n", strerror(errno));
goto cleanup;
}
ret = rknn_run(context, NULL);
if (ret != RKNN_SUCC) {
fprintf(stderr, "rknn_run failed at iteration %lu: %d\n",
iteration, ret);
goto cleanup;
}
ret = rknn_outputs_get(context, io_count.n_output, outputs, NULL);
if (ret != RKNN_SUCC) {
fprintf(stderr, "rknn_outputs_get failed at iteration %lu: %d\n",
iteration, ret);
goto cleanup;
}
outputs_acquired = 1;
if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) {
fprintf(stderr, "clock_gettime failed: %s\n", strerror(errno));
goto cleanup;
}
total_ms += elapsed_ms(&start, &end);
last_top_value = -INFINITY;
last_top_index = 0;
for (i = 0; i < io_count.n_output; ++i) {
const float *values = outputs[i].buf;
uint32_t count = outputs[i].size / (uint32_t)sizeof(float);
uint32_t value_index;
if (values == NULL || count == 0) {
fprintf(stderr, "output %u is empty\n", i);
goto cleanup;
}
for (value_index = 0; value_index < count; ++value_index) {
if (!isfinite(values[value_index])) {
fprintf(stderr, "output %u contains a non-finite value\n", i);
goto cleanup;
}
if (i == 0 && values[value_index] > last_top_value) {
last_top_value = values[value_index];
last_top_index = value_index;
}
}
}
ret = rknn_outputs_release(context, io_count.n_output, outputs);
outputs_acquired = 0;
if (ret != RKNN_SUCC) {
fprintf(stderr, "rknn_outputs_release failed: %d\n", ret);
goto cleanup;
}
}
printf("RKNN_RESULT status=pass core=%s iterations=%lu avg_ms=%.3f "
"top_index=%u top_value=%.7g api=%s driver=%s\n",
core_name, iterations, total_ms / (double)iterations,
last_top_index, last_top_value, sdk_version.api_version,
sdk_version.drv_version);
result = EXIT_SUCCESS;
cleanup:
if (outputs_acquired) {
(void)rknn_outputs_release(context, io_count.n_output, outputs);
}
if (inputs != NULL) {
for (i = 0; i < io_count.n_input; ++i) {
free(inputs[i].buf);
}
}
free(outputs);
free(inputs);
free(input_attrs);
if (context_created) {
(void)rknn_destroy(context);
}
free(model);
return result;
}
+12
View File
@@ -0,0 +1,12 @@
# Pinned local-inference runtime for the RK3588 NPU.
# This file is sourced by install-rknn-runtime.sh.
RKNN_VERSION="2.3.2"
RKNN_TAG="v2.3.2"
RKNN_COMMIT="42aa1d426c0a9e0869b6374edba009f7208a1926"
RKNN_REPOSITORY="https://github.com/airockchip/rknn-toolkit2.git"
RKNN_HEADER_SHA256="c48e11a6f41b451a5fd1e4ad774ea60252d3d94f78bee9b21ea3d21b21deba9a"
RKNN_RUNTIME_AARCH64_SHA256="d31fc19c85b85f6091b2bd0f6af9d962d5264a4e410bfb536402ec92bac738e8"
RKNN_MOBILENET_RK3588_SHA256="381dae3b7038a98b10f6ec9dcdbb094a49247341856fb294e692ef518218fcfb"
RKNN_DEMO_IMAGE_SHA256="c350299c6283d5f62fecf1f845b6b3be9aafec8dff528ca09a129990f0a584b0"
RKNN_LICENSE_SHA256="d846f57d942c7dfdca7b8b54f9e8bb39e1e226790dc4f5ee205d6fd678961720"
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env bash
set -Eeuo pipefail
TEST_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
RUNTIME_DIR=$(cd -- "$TEST_DIR/.." && pwd -P)
TEST_TMP=$(mktemp -d "${TMPDIR:-/tmp}/rk1-runtime-tests.XXXXXXXX")
cleanup() {
rm -rf -- "$TEST_TMP"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$*" >&2
exit 1
}
pass() {
printf 'PASS: %s\n' "$*"
}
bash -n \
"$RUNTIME_DIR/install-rknn-runtime.sh" \
"$RUNTIME_DIR/rk1-media-selftest" \
"$RUNTIME_DIR/rk1-media-diagnostics"
pass "shell syntax"
"$RUNTIME_DIR/install-rknn-runtime.sh" --help >/dev/null
"$RUNTIME_DIR/rk1-media-selftest" --help >/dev/null
"$RUNTIME_DIR/rk1-media-diagnostics" --help >/dev/null
pass "command help"
mkdir -p \
"$TEST_TMP/empty/sys" \
"$TEST_TMP/empty/proc" \
"$TEST_TMP/empty/dev" \
"$TEST_TMP/empty/rknn"
RK1_SYSFS_ROOT="$TEST_TMP/empty/sys" \
RK1_PROCFS_ROOT="$TEST_TMP/empty/proc" \
RK1_DEV_ROOT="$TEST_TMP/empty/dev" \
RKNN_HOME="$TEST_TMP/empty/rknn" \
"$RUNTIME_DIR/rk1-media-selftest" --quick --json \
>"$TEST_TMP/empty.json"
grep -q '"overall":"warn"' "$TEST_TMP/empty.json" ||
fail "empty-hardware JSON does not report a warning"
grep -q '"checks":\[' "$TEST_TMP/empty.json" ||
fail "empty-hardware JSON has no checks"
pass "absent hardware degrades safely"
FAKE="$TEST_TMP/fake"
mkdir -p \
"$FAKE/sys/class/drm/card0-HDMI-A-1" \
"$FAKE/sys/class/devfreq/fb000000.gpu" \
"$FAKE/sys/class/devfreq/fdab0000.npu" \
"$FAKE/sys/class/net/eth0" \
"$FAKE/sys/block/mmcblk0" \
"$FAKE/sys/block/nvme0n1" \
"$FAKE/sys/bus/platform/drivers/dwhdmi-rockchip/fde80000.hdmi" \
"$FAKE/sys/bus/platform/drivers/rockchip-hdptx-phy-hdmi/fed60000.hdmiphy" \
"$FAKE/sys/bus/platform/drivers/panthor/fb000000.gpu" \
"$FAKE/sys/bus/platform/drivers/mpp_rkvdec2/fdc38100.rkvdec-core" \
"$FAKE/sys/bus/platform/drivers/mpp_rkvenc2/fdbd0000.rkvenc-core" \
"$FAKE/sys/bus/platform/drivers/mpp_av1dec/av1-decoder" \
"$FAKE/sys/bus/platform/drivers/rga2/fdb80000.rga" \
"$FAKE/sys/bus/platform/drivers/rga3/fdb60000.rga" \
"$FAKE/sys/bus/platform/drivers/RKNPU/fdab0000.npu" \
"$FAKE/proc/device-tree" \
"$FAKE/proc/asound" \
"$FAKE/dev/dri" \
"$FAKE/rknn"
printf 'connected\n' >"$FAKE/sys/class/drm/card0-HDMI-A-1/status"
printf '3840x2160\n1920x1080\n' >"$FAKE/sys/class/drm/card0-HDMI-A-1/modes"
printf 'fake-edid' >"$FAKE/sys/class/drm/card0-HDMI-A-1/edid"
printf 'Turing Machines RK1\0' >"$FAKE/proc/device-tree/model"
printf ' 0 [rockchiphdmi]: rockchip-hdmi - rockchip-hdmi\n' \
>"$FAKE/proc/asound/cards"
printf 'up\n' >"$FAKE/sys/class/net/eth0/operstate"
printf '1\n' >"$FAKE/sys/class/net/eth0/carrier"
touch "$FAKE/dev/dri/renderD128"
RK1_SYSFS_ROOT="$FAKE/sys" \
RK1_PROCFS_ROOT="$FAKE/proc" \
RK1_DEV_ROOT="$FAKE/dev" \
RKNN_HOME="$FAKE/rknn" \
"$RUNTIME_DIR/rk1-media-selftest" --quick --json \
>"$TEST_TMP/fake.json"
grep -q '"name":"card0-HDMI-A-1","status":"pass"' \
"$TEST_TMP/fake.json" || fail "fake connected HDMI was not detected"
grep -q '"name":"kernel-driver","status":"pass","message":"Panthor' \
"$TEST_TMP/fake.json" || fail "fake Panthor binding was not detected"
pass "synthetic hardware inventory"
if command -v python3 >/dev/null 2>&1; then
python3 -m json.tool "$TEST_TMP/empty.json" >/dev/null
python3 -m json.tool "$TEST_TMP/fake.json" >/dev/null
pass "JSON validity"
fi
RK1_SYSFS_ROOT="$FAKE/sys" \
RK1_PROCFS_ROOT="$FAKE/proc" \
RK1_DEV_ROOT="$FAKE/dev" \
RKNN_HOME="$FAKE/rknn" \
"$RUNTIME_DIR/rk1-media-diagnostics" --directory \
--output "$TEST_TMP/diagnostics" >/dev/null
[[ -s "$TEST_TMP/diagnostics/selftest.json" ]] ||
fail "diagnostic bundle is missing selftest.json"
[[ -s "$TEST_TMP/diagnostics/drm-connectors.txt" ]] ||
fail "diagnostic bundle is missing connector data"
[[ -s "$TEST_TMP/diagnostics/kernel-media.log" ]] ||
fail "diagnostic bundle is missing filtered kernel data"
pass "diagnostic collection"
if [[ -n ${RKNN_SOURCE_DIR:-} ]]; then
[[ -d "$RKNN_SOURCE_DIR" ]] || fail "RKNN_SOURCE_DIR is not a directory"
bundled_source="$TEST_TMP/bundled-rknn-source"
mkdir -p \
"$bundled_source/rknpu2/runtime/Linux/librknn_api/include" \
"$bundled_source/rknpu2/runtime/Linux/librknn_api/aarch64" \
"$bundled_source/rknpu2/examples/rknn_api_demo/model/RK3588" \
"$bundled_source/rknpu2/examples/rknn_api_demo/model"
cp "$RKNN_SOURCE_DIR/LICENSE" "$bundled_source/LICENSE"
cp "$RKNN_SOURCE_DIR/rknpu2/runtime/Linux/librknn_api/include/rknn_api.h" \
"$bundled_source/rknpu2/runtime/Linux/librknn_api/include/rknn_api.h"
cp "$RKNN_SOURCE_DIR/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" \
"$bundled_source/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so"
cp "$RKNN_SOURCE_DIR/rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn" \
"$bundled_source/rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn"
cp "$RKNN_SOURCE_DIR/rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg" \
"$bundled_source/rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg"
mkdir -p "$TEST_TMP/rootfs"
"$RUNTIME_DIR/install-rknn-runtime.sh" \
--rootfs "$TEST_TMP/rootfs" --source-dir "$bundled_source"
installed_runtime="$TEST_TMP/rootfs/opt/rknn/2.3.2/lib/librknnrt.so"
installed_model="$TEST_TMP/rootfs/opt/rknn/2.3.2/share/models/rk3588/mobilenet_v1.rknn"
[[ -x "$TEST_TMP/rootfs/opt/rknn/2.3.2/bin/rknn-inference-test" ]] ||
fail "compiled inference test is missing"
[[ $(sha256sum "$installed_runtime" | awk '{print $1}') == \
d31fc19c85b85f6091b2bd0f6af9d962d5264a4e410bfb536402ec92bac738e8 ]] ||
fail "installed runtime checksum differs"
[[ $(sha256sum "$installed_model" | awk '{print $1}') == \
381dae3b7038a98b10f6ec9dcdbb094a49247341856fb294e692ef518218fcfb ]] ||
fail "installed model checksum differs"
pass "pinned RKNN installation"
else
printf 'SKIP: installer integration (set RKNN_SOURCE_DIR to a v2.3.2 checkout)\n'
fi
printf 'All runtime tests passed.\n'
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# Fetch and verify every binary input required by the loopless image composer.
set -Eeuo pipefail
umask 022
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
LOCK_FILE="$PROJECT_DIR/inputs.lock.json"
DOWNLOAD_DIR="$PROJECT_DIR/downloads"
for command_name in curl git jq sha256sum stat; do
command -v "$command_name" >/dev/null || {
echo "Missing command: $command_name" >&2
exit 1
}
done
mkdir -p "$DOWNLOAD_DIR" "$PROJECT_DIR/work/sources"
fetch_asset() {
local selector=$1 output_name url expected_size expected_sha output partial
output_name=$(jq -er "$selector.output_name" "$LOCK_FILE")
url=$(jq -er "$selector.url" "$LOCK_FILE")
expected_size=$(jq -er "$selector.size" "$LOCK_FILE")
expected_sha=$(jq -er "$selector.sha256" "$LOCK_FILE")
output="$DOWNLOAD_DIR/$output_name"
if [[ ! -f "$output" ]] ||
[[ "$(stat -c '%s' "$output")" != "$expected_size" ]] ||
[[ "$(sha256sum "$output" | awk '{print $1}')" != "$expected_sha" ]]; then
partial="$output.partial"
rm -f -- "$partial"
curl --fail --location --show-error --output "$partial" "$url"
[[ "$(stat -c '%s' "$partial")" == "$expected_size" ]] || {
echo "Size mismatch for $output_name" >&2
exit 1
}
echo "$expected_sha $partial" | sha256sum --check --status || {
echo "Checksum mismatch for $output_name" >&2
exit 1
}
mv -- "$partial" "$output"
fi
echo "Verified $output_name"
}
verify_rknn_bundle() {
local bundle=$1
# shellcheck source=../runtime/rknn-version.env
source "$PROJECT_DIR/runtime/rknn-version.env"
declare -a checks=(
"rknpu2/runtime/Linux/librknn_api/include/rknn_api.h:$RKNN_HEADER_SHA256"
"rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so:$RKNN_RUNTIME_AARCH64_SHA256"
"rknpu2/examples/rknn_api_demo/model/RK3588/mobilenet_v1.rknn:$RKNN_MOBILENET_RK3588_SHA256"
"rknpu2/examples/rknn_api_demo/model/dog_224x224.jpg:$RKNN_DEMO_IMAGE_SHA256"
"LICENSE:$RKNN_LICENSE_SHA256"
)
local entry path expected
for entry in "${checks[@]}"; do
path=${entry%%:*}
expected=${entry##*:}
[[ -f "$bundle/$path" ]] || return 1
[[ "$(sha256sum "$bundle/$path" | awk '{print $1}')" == "$expected" ]] || return 1
done
}
fetch_asset '.base_image'
fetch_asset '.kodi_upstream_deb'
bundle_name=$(jq -er '.rknn_toolkit2.bundle_directory' "$LOCK_FILE")
bundle="$DOWNLOAD_DIR/$bundle_name"
if ! verify_rknn_bundle "$bundle"; then
[[ ! -e "$bundle" ]] || {
echo "Existing RKNN bundle failed verification: $bundle" >&2
exit 1
}
repository=$(jq -er '.rknn_toolkit2.repository' "$LOCK_FILE")
tag=$(jq -er '.rknn_toolkit2.tag' "$LOCK_FILE")
commit=$(jq -er '.rknn_toolkit2.commit' "$LOCK_FILE")
checkout="$PROJECT_DIR/work/sources/rknn-toolkit2-$commit"
if [[ ! -d "$checkout/.git" ]]; then
git clone --filter=blob:none --no-checkout "$repository" "$checkout"
git -C "$checkout" sparse-checkout init --cone
git -C "$checkout" sparse-checkout set \
rknpu2/runtime/Linux/librknn_api \
rknpu2/examples/rknn_api_demo/model
git -C "$checkout" checkout --detach "$tag"
fi
[[ "$(git -C "$checkout" rev-parse HEAD)" == "$commit" ]] || {
echo "RKNN checkout does not match lock" >&2
exit 1
}
"$PROJECT_DIR/runtime/make-offline-bundle.sh" "$checkout" "$bundle"
fi
verify_rknn_bundle "$bundle" || { echo "RKNN bundle verification failed" >&2; exit 1; }
echo "Verified $bundle_name"
"$PROJECT_DIR/packages/fetch-locked-debs.sh"
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# Deliberately guarded writer for the completed RK1 eMMC image.
set -Eeuo pipefail
IMAGE=""
TARGET=""
CONFIRM=""
usage() {
cat <<'EOF'
Usage: sudo ./scripts/flash-emmc.sh --image IMAGE.img.xz \
--target /dev/mmcblkN --confirm /dev/mmcblkN
The target and confirmation must match exactly. Only whole mmcblk devices are
accepted, and a device with mounted children or active swap is rejected.
EOF
}
while (($#)); do
case "$1" in
--image) IMAGE=${2:?}; shift 2 ;;
--target) TARGET=${2:?}; shift 2 ;;
--confirm) CONFIRM=${2:?}; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
esac
done
for command_name in blockdev cmp dd lsblk sha256sum xz; do
command -v "$command_name" >/dev/null || {
echo "Missing command: $command_name" >&2
exit 1
}
done
[[ ${EUID:-$(id -u)} -eq 0 ]] || { echo 'Run this command as root' >&2; exit 1; }
[[ -f "$IMAGE" && "$IMAGE" == *.img.xz ]] || { echo 'A .img.xz image is required' >&2; exit 2; }
TARGET=$(readlink -f -- "$TARGET")
[[ "$TARGET" =~ ^/dev/mmcblk[0-9]+$ ]] || {
echo 'Target must be a whole /dev/mmcblkN device (never a partition)' >&2
exit 2
}
[[ "$CONFIRM" == "$TARGET" ]] || { echo '--confirm must exactly repeat the resolved target' >&2; exit 2; }
[[ -b "$TARGET" && "$(lsblk -dnro TYPE "$TARGET")" == disk ]] || {
echo "Not a whole block device: $TARGET" >&2
exit 1
}
if lsblk -nrpo MOUNTPOINTS "$TARGET" | grep -q '[^[:space:]]'; then
echo "Target or one of its partitions is mounted: $TARGET" >&2
lsblk -o NAME,PATH,SIZE,MODEL,SERIAL,TYPE,MOUNTPOINTS "$TARGET" >&2
exit 1
fi
if command -v swapon >/dev/null && swapon --noheadings --raw --show=NAME \
| grep -Eq "^${TARGET}(p[0-9]+)?$"; then
echo "Target contains active swap: $TARGET" >&2
exit 1
fi
xz --test "$IMAGE"
if [[ -f "$IMAGE.sha256" ]]; then
(cd "$(dirname -- "$IMAGE")" && sha256sum --check --status "$(basename -- "$IMAGE.sha256")") || {
echo 'Image checksum failed' >&2
exit 1
}
fi
image_bytes=$(xz --robot --list "$IMAGE" | awk -F '\t' '$1 == "file" { print $5 }')
target_bytes=$(blockdev --getsize64 "$TARGET")
[[ "$image_bytes" =~ ^[0-9]+$ && "$target_bytes" -ge "$image_bytes" ]] || {
echo "Target is too small ($target_bytes bytes; image is $image_bytes bytes)" >&2
exit 1
}
echo 'About to overwrite this entire device:' >&2
lsblk -d -o NAME,PATH,SIZE,MODEL,SERIAL,TRAN,TYPE "$TARGET" >&2
echo "Writing verified image: $IMAGE" >&2
xz --decompress --stdout "$IMAGE" \
| dd of="$TARGET" bs=16M iflag=fullblock oflag=direct conv=fsync status=progress
sync
echo 'Reading the written region back for byte verification...' >&2
cmp --silent --bytes="$image_bytes" <(xz --decompress --stdout "$IMAGE") "$TARGET" || {
echo 'Post-write verification FAILED; do not boot this device' >&2
exit 1
}
blockdev --rereadpt "$TARGET" 2>/dev/null || true
echo "Flash and read-back verification passed: $TARGET" >&2
+335
View File
@@ -0,0 +1,335 @@
#!/usr/bin/env bash
# Move a running RK1 root filesystem from eMMC to a specifically identified
# NVMe while retaining U-Boot and /boot on eMMC.
set -Eeuo pipefail
umask 022
TARGET=""
EXPECTED_SERIAL=""
BOOT_ENV=/boot/armbianEnv.txt
BOOT_ENV_BACKUP=/boot/armbianEnv.txt.pre-nvme
INSTALL_LOG=/var/log/armbian-install.log
INSTALL_LOG_BACKUP=/var/log/armbian-install.log.pre-nvme
usage() {
cat <<'EOF'
Usage: sudo live-migrate-root-to-nvme.sh --target /dev/nvmeXnY --serial SERIAL
The target NVMe is erased. The running root must be on eMMC. U-Boot and /boot
remain on eMMC; only the root filesystem is moved to NVMe.
EOF
}
while (($#)); do
case "$1" in
--target) TARGET=${2:?}; shift 2 ;;
--serial) EXPECTED_SERIAL=${2:?}; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
esac
done
[[ $EUID -eq 0 ]] || { echo "Run this script through sudo" >&2; exit 2; }
[[ -n "$TARGET" && -n "$EXPECTED_SERIAL" ]] || { usage >&2; exit 2; }
for command_name in armbian-install blkid chmod cmp cp debugfs dpkg dpkg-query \
e2fsck findmnt flock grep lsof lsblk ps readlink sed swapon sync systemctl \
udevadm umount xargs; do
command -v "$command_name" >/dev/null || {
echo "Missing required command: $command_name" >&2
exit 1
}
done
exec 9>/run/lock/rk1-nvme-migrate.lock
flock -n 9 || { echo "Another RK1 NVMe migration is already running" >&2; exit 1; }
TARGET=$(readlink -f -- "$TARGET")
[[ "$TARGET" =~ ^/dev/nvme[0-9]+n[0-9]+$ && -b "$TARGET" ]] || {
echo "Refusing non-NVMe whole-disk target: $TARGET" >&2
exit 1
}
[[ "$(lsblk -dnro TYPE "$TARGET")" == disk ]] || {
echo "Target is not a whole disk: $TARGET" >&2
exit 1
}
[[ "$(lsblk -dnro TRAN "$TARGET")" == nvme ]] || {
echo "Target transport is not NVMe: $TARGET" >&2
exit 1
}
actual_serial=$(lsblk -dnro SERIAL "$TARGET" | xargs)
actual_model=$(lsblk -dnro MODEL "$TARGET" | xargs)
[[ "$actual_serial" == "$EXPECTED_SERIAL" ]] || {
echo "Target serial mismatch: expected '$EXPECTED_SERIAL', found '$actual_serial'" >&2
exit 1
}
root_source=$(findmnt -no SOURCE --nofsroot /)
[[ "$root_source" =~ ^/dev/mmcblk[0-9]+p[0-9]+$ ]] || {
echo "Running root is not on an eMMC partition: $root_source" >&2
exit 1
}
root_disk="/dev/$(lsblk -no PKNAME "$root_source" | sed -n '1p')"
[[ -b "$root_disk" && "$root_disk" != "$TARGET" ]] || {
echo "Could not safely distinguish root disk from target" >&2
exit 1
}
target_mounts=$(lsblk -nrpo MOUNTPOINTS "$TARGET" | sed '/^[[:space:]]*$/d')
[[ -z "$target_mounts" ]] || {
echo "Target or one of its partitions is mounted:" >&2
printf '%s\n' "$target_mounts" >&2
exit 1
}
while IFS= read -r swap_name; do
[[ -z "$swap_name" ]] && continue
if [[ "$swap_name" == "$TARGET" || "$swap_name" == "${TARGET}p"* ]]; then
echo "Target contains active swap: $swap_name" >&2
exit 1
fi
done < <(swapon --noheadings --raw --show=NAME)
[[ -e /var/lib/rk1-media/provisioned ]] || {
echo "RK1 media provisioning has not completed" >&2
exit 1
}
[[ "$(dpkg-query -W -f='${Status}' rk1-media-stack 2>/dev/null)" == \
"install ok installed" ]] || {
echo "rk1-media-stack is not fully installed" >&2
exit 1
}
dpkg_audit=$(dpkg --audit)
[[ -z "$dpkg_audit" ]] || {
echo "dpkg reports an incomplete transaction:" >&2
printf '%s\n' "$dpkg_audit" >&2
exit 1
}
emmc_uuid=$(blkid -s UUID -o value "$root_source")
[[ -n "$emmc_uuid" ]] || { echo "Could not resolve eMMC UUID" >&2; exit 1; }
grep -Fxq "rootdev=UUID=$emmc_uuid" "$BOOT_ENV" || {
echo "$BOOT_ENV does not currently point at the running eMMC root" >&2
exit 1
}
if [[ -e "$BOOT_ENV_BACKUP" ]]; then
cmp --silent "$BOOT_ENV" "$BOOT_ENV_BACKUP" || {
echo "Existing boot-environment backup differs; refusing to overwrite it" >&2
exit 1
}
else
cp -a -- "$BOOT_ENV" "$BOOT_ENV_BACKUP"
fi
for apt_unit in apt-daily.service apt-daily-upgrade.service; do
apt_state=$(systemctl is-active "$apt_unit" 2>/dev/null || true)
case "$apt_state" in
inactive|failed|unknown) ;;
*) echo "Refusing migration while $apt_unit is $apt_state" >&2; exit 1 ;;
esac
done
provision_state=$(systemctl is-active rk1-media-provision.service 2>/dev/null || true)
case "$provision_state" in
inactive|failed|unknown|active) ;;
*) echo "Provisioning is not quiescent: $provision_state" >&2; exit 1 ;;
esac
echo "Confirmed source root: $root_source (UUID=$emmc_uuid)"
echo "ERASING target: $TARGET | $actual_model | serial $actual_serial"
apt_daily_timer_was_active=0
apt_upgrade_timer_was_active=0
unattended_was_active=0
kodi_was_active=0
systemctl is-active --quiet apt-daily.timer && apt_daily_timer_was_active=1
systemctl is-active --quiet apt-daily-upgrade.timer && apt_upgrade_timer_was_active=1
systemctl is-active --quiet unattended-upgrades.service && unattended_was_active=1
systemctl is-active --quiet kodi-rk.service && kodi_was_active=1
restore_services=0
restore_boot_env=0
cleanup() {
local rc=$?
trap - EXIT
if ((restore_boot_env)); then
echo "Migration did not validate; restoring the eMMC boot environment" >&2
if ! cp -a -- "$BOOT_ENV_BACKUP" "$BOOT_ENV" || \
! cmp --silent "$BOOT_ENV_BACKUP" "$BOOT_ENV"; then
echo "CRITICAL: could not restore and verify $BOOT_ENV" >&2
rc=1
fi
if ! sync; then
echo "CRITICAL: writeback failed while restoring $BOOT_ENV" >&2
rc=1
fi
fi
if ((restore_services)); then
((apt_daily_timer_was_active)) && systemctl start apt-daily.timer 2>/dev/null || true
((apt_upgrade_timer_was_active)) && systemctl start apt-daily-upgrade.timer 2>/dev/null || true
((unattended_was_active)) && systemctl start unattended-upgrades.service 2>/dev/null || true
((kodi_was_active)) && systemctl start kodi-rk.service 2>/dev/null || true
fi
exit "$rc"
}
trap cleanup EXIT
restore_services=1
systemctl stop apt-daily.timer apt-daily-upgrade.timer 2>/dev/null || true
systemctl stop apt-daily.service apt-daily-upgrade.service 2>/dev/null || true
for apt_unit in apt-daily.service apt-daily-upgrade.service; do
apt_state=$(systemctl is-active "$apt_unit" 2>/dev/null || true)
case "$apt_state" in
inactive|failed|unknown) ;;
*) echo "Package service did not stop cleanly: $apt_unit is $apt_state" >&2; exit 1 ;;
esac
done
systemctl stop rk1-media-provision.service
! systemctl is-active --quiet rk1-media-provision.service || {
echo "Provisioning service did not stop cleanly" >&2
exit 1
}
systemctl stop unattended-upgrades.service 2>/dev/null || true
if systemctl list-unit-files kodi-rk.service --no-legend 2>/dev/null | grep -q '^kodi-rk\.service'; then
systemctl stop kodi-rk.service
! systemctl is-active --quiet kodi-rk.service || {
echo "Kodi did not stop cleanly" >&2
exit 1
}
fi
package_processes=$(ps -eo comm= | grep -E \
'^(apt|apt-get|dpkg|unattended-upgr)$' || true)
[[ -z "$package_processes" ]] || {
echo "Package-management process remains active:" >&2
printf '%s\n' "$package_processes" >&2
exit 1
}
lock_holders=$(lsof -t /var/lib/dpkg/lock /var/lib/dpkg/lock-frontend \
/var/cache/apt/archives/lock 2>/dev/null || true)
[[ -z "$lock_holders" ]] || {
echo "A package-management lock is still held by PID(s): $lock_holders" >&2
exit 1
}
dpkg_audit=$(dpkg --audit)
[[ -z "$dpkg_audit" ]] || {
echo "dpkg became inconsistent before migration:" >&2
printf '%s\n' "$dpkg_audit" >&2
exit 1
}
if [[ -s "$INSTALL_LOG" ]]; then
[[ ! -e "$INSTALL_LOG_BACKUP" ]] || {
echo "Installer log backup already exists: $INSTALL_LOG_BACKUP" >&2
exit 1
}
cp -a -- "$INSTALL_LOG" "$INSTALL_LOG_BACKUP"
fi
: >"$INSTALL_LOG"
chmod 0600 "$INSTALL_LOG"
sync
restore_boot_env=1
armbian-install --target "$TARGET" --boot sd --fs ext4 --yes
target_partition="${TARGET}p1"
udevadm settle
[[ -b "$target_partition" ]] || {
echo "Installer did not create $target_partition" >&2
exit 1
}
mapfile -t post_install_mounts < <(findmnt -rn -S "$target_partition" -o TARGET)
for mountpoint in "${post_install_mounts[@]}"; do
[[ "$mountpoint" == /mnt/armbian-install.* ]] || {
echo "Target remains mounted at unexpected path: $mountpoint" >&2
exit 1
}
umount "$mountpoint" || {
echo "Could not unmount installer path: $mountpoint" >&2
exit 1
}
done
findmnt -rn -S "$target_partition" >/dev/null && {
echo "Target partition remains mounted after installer teardown" >&2
exit 1
}
grep -Fq "scenario: sd install to $TARGET completed" "$INSTALL_LOG" || {
echo "Installer did not record successful sd-mode completion" >&2
exit 1
}
nvme_uuid=$(blkid -s UUID -o value "$target_partition")
[[ -n "$nvme_uuid" && "$nvme_uuid" != "$emmc_uuid" ]] || {
echo "NVMe UUID is missing or duplicates the eMMC UUID" >&2
exit 1
}
mapfile -t uuid_devices < <(blkid -t "UUID=$nvme_uuid" -o device)
[[ ${#uuid_devices[@]} -eq 1 && "${uuid_devices[0]}" == "$target_partition" ]] || {
echo "NVMe filesystem UUID is not unique" >&2
printf '%s\n' "${uuid_devices[@]}" >&2
exit 1
}
grep -Fxq "rootdev=UUID=$nvme_uuid" "$BOOT_ENV" || {
echo "eMMC boot environment does not point to the new NVMe root" >&2
exit 1
}
grep -Fxq 'rootfstype=ext4' "$BOOT_ENV" || {
echo "eMMC boot environment does not select ext4 for the new root" >&2
exit 1
}
e2fsck -fn "$target_partition"
target_fstab=$(debugfs -R 'cat /etc/fstab' "$target_partition" 2>/dev/null)
root_mount_count=$(grep -Ec '^[^#[:space:]][^[:space:]]*[[:space:]]+/[[:space:]]+' \
<<<"$target_fstab" || true)
[[ "$root_mount_count" -eq 1 ]] || {
echo "Target fstab does not contain exactly one root mount" >&2
exit 1
}
grep -Eq "^UUID=${nvme_uuid}[[:space:]]+/[[:space:]]+ext4" <<<"$target_fstab" || {
echo "Target fstab does not mount the NVMe UUID at /" >&2
exit 1
}
grep -Eq "^UUID=${emmc_uuid}[[:space:]]+/media/boot-media[[:space:]]+ext4" \
<<<"$target_fstab" || {
echo "Target fstab does not mount the eMMC boot medium" >&2
exit 1
}
grep -Eq '^/media/boot-media/boot[[:space:]]+/boot[[:space:]]+none[[:space:]]+bind' \
<<<"$target_fstab" || {
echo "Target fstab does not bind the eMMC /boot directory" >&2
exit 1
}
while read -r fstab_source _; do
case "$fstab_source" in
UUID=*)
fstab_uuid=${fstab_source#UUID=}
mapfile -t fstab_devices < <(blkid -t "UUID=$fstab_uuid" -o device)
[[ ${#fstab_devices[@]} -eq 1 ]] || {
echo "Target fstab UUID does not resolve uniquely: $fstab_uuid" >&2
exit 1
}
;;
PARTUUID=*)
fstab_partuuid=${fstab_source#PARTUUID=}
mapfile -t fstab_devices < <(blkid -t "PARTUUID=$fstab_partuuid" -o device)
[[ ${#fstab_devices[@]} -eq 1 ]] || {
echo "Target fstab PARTUUID does not resolve uniquely: $fstab_partuuid" >&2
exit 1
}
;;
esac
done <<<"$target_fstab"
sync
restore_boot_env=0
restore_services=0
echo "MIGRATION_VALIDATED"
echo "NVMe root: $target_partition (UUID=$nvme_uuid)"
echo "eMMC boot fallback: $BOOT_ENV_BACKUP"
echo "REBOOT_REQUIRED"
+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)"