Initial RK1 media-center image project
This commit is contained in:
+15
@@ -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
@@ -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 rkadmin@rk1-media.local
|
||||
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 NVIDIA’s 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 Turing’s exact
|
||||
hardware guidance before modifying it:
|
||||
|
||||
https://docs.turingpi.com/docs/turing-rk1-flashing-os
|
||||
@@ -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; root’s 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. Armbian’s 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
@@ -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.
|
||||
@@ -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 rkadmin@rk1-media.local
|
||||
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 carrier’s physical HPD/DDC/TMDS path, the TV’s 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.
|
||||
@@ -0,0 +1,2 @@
|
||||
.work/
|
||||
|
||||
@@ -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.
|
||||
Executable
+164
@@ -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)|sk-ssh-ed25519@openssh.com|sk-ecdsa-sha2-nistp256@openssh.com)$/ { 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"
|
||||
}
|
||||
]
|
||||
Executable
+54
@@ -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."
|
||||
@@ -0,0 +1,2 @@
|
||||
0.1.0
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Executable
+136
@@ -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
@@ -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
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
Executable
+325
@@ -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)|sk-ssh-ed25519@openssh.com|sk-ecdsa-sha2-nistp256@openssh.com)$/ { 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"
|
||||
Executable
+61
@@ -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";
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG9jdtdu7zOd98m+tTreOwXk5b5QB5wA4zZV3f/mIb/v rk1-media
|
||||
@@ -0,0 +1,2 @@
|
||||
/out/
|
||||
|
||||
+108
@@ -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.
|
||||
@@ -0,0 +1,2 @@
|
||||
20260816.1
|
||||
|
||||
@@ -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.
|
||||
Executable
+34
@@ -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
|
||||
Executable
+16
@@ -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
|
||||
Executable
+8
@@ -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
@@ -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
@@ -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
@@ -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 getty@tty1.service
|
||||
|
||||
[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>
|
||||
Executable
+226
@@ -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"
|
||||
Executable
+85
@@ -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}"
|
||||
|
||||
Executable
+63
@@ -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[@]}"
|
||||
Executable
+62
@@ -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
|
||||
}
|
||||
|
||||
Executable
+134
@@ -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}"
|
||||
Executable
+144
@@ -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}"
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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
|
||||
|
Executable
+40
@@ -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"
|
||||
Executable
+65
@@ -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"
|
||||
@@ -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.
|
||||
Executable
+209
@@ -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"
|
||||
Executable
+57
@@ -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"
|
||||
Executable
+437
@@ -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"
|
||||
Executable
+574
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
Executable
+149
@@ -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'
|
||||
Executable
+97
@@ -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"
|
||||
Executable
+87
@@ -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
|
||||
Executable
+335
@@ -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"
|
||||
@@ -0,0 +1,19 @@
|
||||
# Validation
|
||||
|
||||
Run all host-side checks, the offline package-transaction simulation, and the
|
||||
real acquisition-only APT regression (no packages are installed):
|
||||
|
||||
```bash
|
||||
./tests/run-static.sh
|
||||
```
|
||||
|
||||
Validate a finished image without mounting it or using loop devices:
|
||||
|
||||
```bash
|
||||
./tests/validate-image.sh dist/rk1-media-20260817-r4-trixie-vendor-6.1.115-turing-rk1-emmc.img
|
||||
```
|
||||
|
||||
Passing these checks establishes image structure, input integrity, package
|
||||
closure, boot payload preservation, and security invariants. HDMI link,
|
||||
hardware codecs, RGA, GPU, audio/CEC, and RKNN execution still require the
|
||||
physical acceptance test after flashing.
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
|
||||
while IFS= read -r -d '' script; do
|
||||
bash -n "$script"
|
||||
done < <(find "$PROJECT_DIR" -path '*/sources/*' -prune -o -type f -name '*.sh' -print0)
|
||||
"$PROJECT_DIR/armbian/tests/lint.sh"
|
||||
"$SCRIPT_DIR/test-firstboot-ssh.sh"
|
||||
RKNN_SOURCE_DIR="$PROJECT_DIR/downloads/rknn-toolkit2-v2.3.2-minimal" \
|
||||
"$PROJECT_DIR/runtime/tests/run-tests.sh"
|
||||
"$SCRIPT_DIR/test-media-package.sh"
|
||||
bash "$SCRIPT_DIR/test-provisioning-recipe.sh"
|
||||
"$SCRIPT_DIR/test-offline-apt.sh"
|
||||
echo 'PASS: all static/integration tests'
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
IDENTITY="$PROJECT_DIR/image/overlay/usr/local/sbin/rk1-media-identity"
|
||||
UNIT="$PROJECT_DIR/image/overlay/etc/systemd/system/rk1-media-identity.service"
|
||||
DROPIN="$PROJECT_DIR/image/overlay/etc/systemd/system/ssh.service.d/10-rk1-media-identity.conf"
|
||||
|
||||
runtime_line=$(grep -nF 'install -d -o root -g root -m 0755 /run/sshd' "$IDENTITY" | cut -d: -f1)
|
||||
validation_line=$(grep -nF '/usr/sbin/sshd -t' "$IDENTITY" | cut -d: -f1)
|
||||
[[ -n "$runtime_line" && -n "$validation_line" && "$runtime_line" -lt "$validation_line" ]] || {
|
||||
echo 'SSH runtime directory is not created before configuration validation' >&2
|
||||
exit 1
|
||||
}
|
||||
grep -qx 'RuntimeDirectory=sshd' "$UNIT"
|
||||
grep -qx 'RuntimeDirectoryMode=0755' "$UNIT"
|
||||
grep -qx 'Requires=rk1-media-identity.service' "$DROPIN"
|
||||
grep -qx 'After=rk1-media-identity.service' "$DROPIN"
|
||||
|
||||
echo 'PASS: first-boot SSH runtime ordering regression'
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
DEB=${1:-"$PROJECT_DIR/media/out/rk1-media-stack_20260721.1741+rk1.1_arm64.deb"}
|
||||
[[ -f "$DEB" ]] || { echo "Missing media package: $DEB" >&2; exit 1; }
|
||||
|
||||
TEST_TMP=$(mktemp -d /tmp/rk1-media-package-test.XXXXXXXX)
|
||||
trap 'rm -rf -- "$TEST_TMP"' EXIT
|
||||
dpkg-deb --info "$DEB" >/dev/null
|
||||
dpkg-deb --extract "$DEB" "$TEST_TMP/root"
|
||||
dpkg-deb --control "$DEB" "$TEST_TMP/control"
|
||||
|
||||
[[ "$(dpkg-deb --field "$DEB" Package)" == rk1-media-stack ]]
|
||||
[[ "$(dpkg-deb --field "$DEB" Architecture)" == arm64 ]]
|
||||
if find "$TEST_TMP/root" \( -type d -o -type f \) -perm /0022 -print -quit | grep -q .; then
|
||||
echo 'Package contains a group/world-writable path' >&2
|
||||
exit 1
|
||||
fi
|
||||
! find "$TEST_TMP/root" -type f -name '*.a' -print -quit | grep -q .
|
||||
! find "$TEST_TMP/root" -type d -name pkgconfig -print -quit | grep -q .
|
||||
[[ -x "$TEST_TMP/root/opt/rkmedia/bin/ffmpeg-rk" ]]
|
||||
[[ -x "$TEST_TMP/root/opt/rkmedia/bin/kodi-rk" ]]
|
||||
[[ -f "$TEST_TMP/root/usr/lib/systemd/system/kodi-rk.service" ]]
|
||||
[[ ! -e "$TEST_TMP/root/usr/lib/systemd/system/kodi.service" ]]
|
||||
grep -q 'KERNEL=="rknpu\*"' "$TEST_TMP/root/usr/lib/udev/rules.d/70-rkmedia.rules"
|
||||
grep -q 'udevadm trigger.*subsystem-match=drm' "$TEST_TMP/control/postinst"
|
||||
! grep -q '^ConditionPathExists=/dev/dri/card0' \
|
||||
"$TEST_TMP/root/usr/lib/systemd/system/kodi-rk.service"
|
||||
|
||||
echo "PASS: media package structure ($DEB)"
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
DEPENDENCY_ARCHIVES="$PROJECT_DIR/work/apt/archives"
|
||||
MEDIA_DEB="$PROJECT_DIR/media/out/rk1-media-stack_20260721.1741+rk1.1_arm64.deb"
|
||||
[[ -d "$DEPENDENCY_ARCHIVES" ]] || { echo "Missing APT archives: $DEPENDENCY_ARCHIVES" >&2; exit 1; }
|
||||
[[ -f "$MEDIA_DEB" ]] || { echo "Missing media package: $MEDIA_DEB" >&2; exit 1; }
|
||||
|
||||
TEST_TMP=$(mktemp -d "$PROJECT_DIR/work/rk1-offline-apt.XXXXXXXX")
|
||||
trap 'rm -rf -- "$TEST_TMP"' EXIT
|
||||
SEED_ARCHIVES="$TEST_TMP/opt/rk1-seed/debs"
|
||||
mkdir -p "$SEED_ARCHIVES/partial" "$TEST_TMP/state"
|
||||
|
||||
# Hard links keep this acquisition regression test fast while presenting APT
|
||||
# with the exact single-directory cache layout used in the finished image.
|
||||
while IFS= read -r -d '' source_deb; do
|
||||
ln -- "$source_deb" "$SEED_ARCHIVES/$(basename -- "$source_deb")"
|
||||
done < <(find "$DEPENDENCY_ARCHIVES" -maxdepth 1 -type f -name '*.deb' -print0 | sort -z)
|
||||
ln -- "$MEDIA_DEB" "$SEED_ARCHIVES/$(basename -- "$MEDIA_DEB")"
|
||||
mapfile -d '' debs < <(find "$SEED_ARCHIVES" -maxdepth 1 -type f -name '*.deb' -print0 | sort -z)
|
||||
[[ "${#debs[@]}" == 141 ]]
|
||||
|
||||
apt_options=(
|
||||
-o APT::Architecture=arm64
|
||||
-o Dir::State::status="$PROJECT_DIR/work/base-meta/status"
|
||||
-o Dir::State::lists="$PROJECT_DIR/work/apt/lists"
|
||||
-o Dir::State::extended_states="$TEST_TMP/state/extended_states"
|
||||
-o Dir::Cache::archives="$SEED_ARCHIVES"
|
||||
-o Dir::Cache::pkgcache="$TEST_TMP/pkgcache.bin"
|
||||
-o Dir::Cache::srcpkgcache="$TEST_TMP/srcpkgcache.bin"
|
||||
-o Dir::Etc::sourcelist="$PROJECT_DIR/work/base-meta/debian-build.sources"
|
||||
-o Dir::Etc::sourceparts=-
|
||||
-o Debug::NoLocking=true
|
||||
-o APT::Sandbox::User=
|
||||
-o APT::Keep-Downloaded-Packages=true
|
||||
)
|
||||
|
||||
simulation_log="$TEST_TMP/simulation.log"
|
||||
apt-get "${apt_options[@]}" --simulate --no-download --no-install-recommends \
|
||||
install "${debs[@]}" >"$simulation_log"
|
||||
grep -q '^0 upgraded, 141 newly installed, 0 to remove' "$simulation_log"
|
||||
[[ "$(grep -c '^Inst ' "$simulation_log")" == 141 ]]
|
||||
[[ "$(grep -c '^Conf ' "$simulation_log")" == 141 ]]
|
||||
|
||||
(
|
||||
cd "$SEED_ARCHIVES"
|
||||
sha256sum -- *.deb >"$TEST_TMP/debs.before.sha256"
|
||||
)
|
||||
acquisition_log="$TEST_TMP/acquisition.log"
|
||||
apt-get "${apt_options[@]}" --download-only --no-download \
|
||||
--no-install-recommends --yes install "${debs[@]}" >"$acquisition_log"
|
||||
grep -q '^0 upgraded, 141 newly installed, 0 to remove' "$acquisition_log"
|
||||
grep -Eq '^Download complete( and in download only mode)?$' "$acquisition_log"
|
||||
! grep -q '^Need to get ' "$acquisition_log"
|
||||
(
|
||||
cd "$SEED_ARCHIVES"
|
||||
sha256sum -- *.deb >"$TEST_TMP/debs.after.sha256"
|
||||
)
|
||||
cmp --silent "$TEST_TMP/debs.before.sha256" "$TEST_TMP/debs.after.sha256"
|
||||
|
||||
echo 'PASS: offline APT closure and real acquisition (141 packages, zero downloads)'
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
PROVISION="$PROJECT_DIR/image/overlay/usr/local/sbin/rk1-media-provision"
|
||||
UNIT="$PROJECT_DIR/image/overlay/etc/systemd/system/rk1-media-provision.service"
|
||||
|
||||
grep -Fq 'seed_archives=/opt/rk1-seed/debs' "$PROVISION"
|
||||
grep -Fq 'install -d -o _apt -g root -m 0700 "$seed_archives/partial"' "$PROVISION"
|
||||
grep -Fq -- '-o Dir::Cache::archives="$seed_archives"' "$PROVISION"
|
||||
grep -Fq -- '--no-download --no-install-recommends --yes install' "$PROVISION"
|
||||
|
||||
grep -Eq '^Before=.*kodi-rk\.service$' "$UNIT"
|
||||
grep -Eq '^systemctl --no-block start .*kodi-rk\.service$' "$PROVISION"
|
||||
! grep -Eq '^systemctl start .*kodi-rk\.service' "$PROVISION"
|
||||
|
||||
for subsystem in drm dma_heap misc; do
|
||||
grep -Fq "udevadm trigger --action=add --subsystem-match=$subsystem" "$PROVISION"
|
||||
done
|
||||
! grep -Eq '^udevadm trigger --action=change' "$PROVISION"
|
||||
|
||||
echo 'PASS: provisioning cache, unit ordering, and device-event recipe'
|
||||
Executable
+150
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
# Read-only, loopless validation of a composed Turing RK1 image.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
IMAGE=${1:-}
|
||||
BASE_RAW=${2:-"$PROJECT_DIR/downloads/Armbian_community_26.11.0-trunk.1_Turing-rk1_trixie_vendor_6.1.115_minimal.img"}
|
||||
[[ -n "$IMAGE" && -f "$IMAGE" ]] || {
|
||||
echo "Usage: $0 IMAGE.img[.xz] [BASE.img]" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
for tool in cmp debugfs e2fsck jq sfdisk sgdisk sha256sum xz; do
|
||||
command -v "$tool" >/dev/null || { echo "Missing command: $tool" >&2; exit 1; }
|
||||
done
|
||||
|
||||
TEST_TMP=$(mktemp -d /tmp/rk1-image-validation.XXXXXXXX)
|
||||
trap 'rm -rf -- "$TEST_TMP"' EXIT
|
||||
RAW_IMAGE=$IMAGE
|
||||
if [[ "$IMAGE" == *.xz ]]; then
|
||||
xz --test "$IMAGE"
|
||||
RAW_IMAGE="$TEST_TMP/image.img"
|
||||
xz --decompress --stdout "$IMAGE" >"$RAW_IMAGE"
|
||||
fi
|
||||
|
||||
sgdisk --verify "$RAW_IMAGE" >/dev/null
|
||||
partition_json=$(sfdisk --json "$RAW_IMAGE")
|
||||
[[ "$(jq '.partitiontable.partitions | length' <<<"$partition_json")" == 1 ]] || {
|
||||
echo 'Image must contain exactly one root partition' >&2
|
||||
exit 1
|
||||
}
|
||||
partition_start=$(jq -r '.partitiontable.partitions[0].start' <<<"$partition_json")
|
||||
partition_size=$(jq -r '.partitiontable.partitions[0].size' <<<"$partition_json")
|
||||
partition_type=$(jq -r '.partitiontable.partitions[0].type' <<<"$partition_json")
|
||||
[[ "$partition_start" == 32768 ]] || { echo "Unexpected partition start: $partition_start" >&2; exit 1; }
|
||||
[[ "$partition_type" == B921B045-1DF0-41C3-AF44-4C6F280D3FAE ]] || {
|
||||
echo "Unexpected ARM64 root partition type: $partition_type" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ -f "$BASE_RAW" ]]; then
|
||||
cmp --silent --bytes=440 "$BASE_RAW" "$RAW_IMAGE" || {
|
||||
echo 'MBR bootstrap differs from the locked base image' >&2
|
||||
exit 1
|
||||
}
|
||||
loader_offset=$((64 * 512))
|
||||
loader_length=$(((32768 - 64) * 512))
|
||||
cmp --silent --ignore-initial="$loader_offset:$loader_offset" \
|
||||
--bytes="$loader_length" "$BASE_RAW" "$RAW_IMAGE" || {
|
||||
echo 'Rockchip loader region differs from the locked base image' >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
ROOTFS="$TEST_TMP/rootfs.ext4"
|
||||
dd if="$RAW_IMAGE" of="$ROOTFS" bs=512 skip="$partition_start" \
|
||||
count="$partition_size" status=none
|
||||
e2fsck -fn "$ROOTFS" >"$TEST_TMP/e2fsck.log" 2>&1 || {
|
||||
cat "$TEST_TMP/e2fsck.log" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
ext4_stat() { debugfs -R "stat $1" "$ROOTFS" 2>/dev/null; }
|
||||
ext4_exists() { ext4_stat "$1" | grep -q '^Inode:'; }
|
||||
assert_contains() {
|
||||
local path=$1 pattern=$2
|
||||
debugfs -R "cat $path" "$ROOTFS" 2>/dev/null | grep -Eq "$pattern" || {
|
||||
echo "Missing $pattern in $path" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
assert_mode() {
|
||||
local path=$1 mode=$2
|
||||
ext4_stat "$path" | grep -Eq "Mode:[[:space:]]+$mode" || {
|
||||
echo "Unexpected mode for $path (wanted $mode)" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
assert_contains /etc/os-release '^VERSION_ID="?13"?$'
|
||||
assert_contains /etc/armbian-release '^BOARD=turing-rk1$'
|
||||
assert_contains /boot/armbianEnv.txt '^fdtfile=rockchip/rk3588-turing-rk1\.dtb$'
|
||||
assert_contains /boot/armbianEnv.txt '^overlays=.*panthor-gpu'
|
||||
assert_contains /etc/shadow '^root:!:'
|
||||
assert_contains /etc/default/armbian-firstrun '^OPENSSHD_REGENERATE_HOST_KEYS=false$'
|
||||
assert_contains /etc/ssh/sshd_config.d/90-rk1-media.conf '^PermitRootLogin no$'
|
||||
assert_contains /etc/ssh/sshd_config.d/90-rk1-media.conf '^PasswordAuthentication no$'
|
||||
assert_contains /etc/ssh/sshd_config.d/90-rk1-media.conf '^AuthenticationMethods publickey$'
|
||||
assert_contains /etc/systemd/system/rk1-media-provision.service '^Before=.*kodi-rk\.service$'
|
||||
assert_contains /usr/local/sbin/rk1-media-provision \
|
||||
'^seed_archives=/opt/rk1-seed/debs$'
|
||||
assert_contains /usr/local/sbin/rk1-media-provision \
|
||||
'Dir::Cache::archives=.*seed_archives'
|
||||
assert_contains /usr/local/sbin/rk1-media-provision \
|
||||
'^systemctl --no-block start .*kodi-rk\.service$'
|
||||
assert_contains /usr/local/sbin/rk1-media-provision \
|
||||
'^udevadm trigger --action=add --subsystem-match=misc'
|
||||
assert_contains /etc/systemd/system/rk1-media-identity.service '^RuntimeDirectory=sshd$'
|
||||
assert_contains /usr/local/sbin/rk1-media-identity \
|
||||
'^install -d -o root -g root -m 0755 /run/sshd$'
|
||||
assert_contains /usr/local/sbin/rk1-media-identity '^/usr/sbin/sshd -t$'
|
||||
assert_contains /opt/rk1-seed/debs.sha256 \
|
||||
'^dd0ca68696aa35b1998f9c9e131fdf8f0d1ef4ba6f58a36d0d99f0625d47f430 rk1-media-stack_'
|
||||
|
||||
for required_path in \
|
||||
/boot/dtb/rockchip/rk3588-turing-rk1.dtb \
|
||||
/boot/dtb/rockchip/overlay/rockchip-rk3588-panthor-gpu.dtbo \
|
||||
/lib/modules/6.1.115-vendor-rk35xx/kernel/drivers/gpu/drm/panthor/panthor.ko \
|
||||
/opt/rknn/2.3.2/lib/librknnrt.so \
|
||||
/opt/rknn/2.3.2/share/models/rk3588/mobilenet_v1.rknn \
|
||||
/usr/local/bin/rknn-inference-test \
|
||||
/usr/local/bin/rk1-media-selftest \
|
||||
/usr/local/sbin/rk1-media-migrate-root-to-nvme \
|
||||
/usr/local/sbin/rk1-media-diagnostics; do
|
||||
ext4_exists "$required_path" || { echo "Missing image payload: $required_path" >&2; exit 1; }
|
||||
done
|
||||
|
||||
for critical_dir in / /etc /etc/ssh /etc/systemd /usr /opt; do
|
||||
assert_mode "$critical_dir" 0755
|
||||
done
|
||||
assert_mode /etc/shadow 0640
|
||||
ext4_stat /etc/shadow | grep -Eq 'Group:[[:space:]]+42' || {
|
||||
echo '/etc/shadow is not owned by the shadow group' >&2
|
||||
exit 1
|
||||
}
|
||||
assert_mode /usr/local/sbin/rk1-media-identity 0755
|
||||
assert_mode /usr/local/sbin/rk1-media-provision 0755
|
||||
assert_mode /usr/local/sbin/rk1-media-migrate-root-to-nvme 0755
|
||||
|
||||
ext4_stat /etc/systemd/system/sysinit.target.wants/rk1-media-identity.service \
|
||||
| grep -Fq 'Fast link dest: "../rk1-media-identity.service"' || exit 1
|
||||
ext4_stat /etc/systemd/system/multi-user.target.wants/rk1-media-provision.service \
|
||||
| grep -Fq 'Fast link dest: "../rk1-media-provision.service"' || exit 1
|
||||
|
||||
for forbidden_path in /root/.not_logged_in_yet /root/.ssh/authorized_keys \
|
||||
/etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_ecdsa_key \
|
||||
/etc/ssh/ssh_host_ed25519_key; do
|
||||
if ext4_exists "$forbidden_path"; then
|
||||
echo "Image contains forbidden credential: $forbidden_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
deb_count=$(debugfs -R 'ls -p /opt/rk1-seed/debs' "$ROOTFS" 2>/dev/null \
|
||||
| grep -c '/.*\.deb/' || true)
|
||||
[[ "$deb_count" == 141 ]] || { echo "Expected 141 offline packages, found $deb_count" >&2; exit 1; }
|
||||
|
||||
echo "PASS: loopless RK1 image validation ($IMAGE)"
|
||||
Reference in New Issue
Block a user