Initial RK1 media-center image project

This commit is contained in:
2026-08-17 18:42:29 +00:00
commit 5fe41e79e9
81 changed files with 5725 additions and 0 deletions
+226
View File
@@ -0,0 +1,226 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
LOCK_FILE="${RKMEDIA_ROOT}/sources.lock.json"
WORK_DIR="${RKMEDIA_ROOT}/out/work"
SOURCE_DIR="${RKMEDIA_ROOT}/out/sources"
OUTPUT_DIR="${RKMEDIA_ROOT}/out"
JOBS="$(nproc)"
FETCH=1
PACKAGE_VERSION="$(tr -d '[:space:]' < "${RKMEDIA_ROOT}/VERSION")"
RUNTIME_PREFIX=/opt/rkmedia
usage() {
cat <<'EOF'
Usage: build-media.sh [options]
--source-dir DIR Locked source checkouts (default: out/sources)
--work-dir DIR Build work directory (default: out/work)
--output-dir DIR Package output directory (default: out)
--jobs N Parallel jobs (default: nproc)
--no-fetch Require sources to exist; never access the network
--version VERSION Debian package version (default: VERSION file)
EOF
}
while (($#)); do
case "$1" in
--source-dir) [[ $# -ge 2 ]] || die "--source-dir requires a directory"; SOURCE_DIR="$2"; shift 2 ;;
--work-dir) [[ $# -ge 2 ]] || die "--work-dir requires a directory"; WORK_DIR="$2"; shift 2 ;;
--output-dir) [[ $# -ge 2 ]] || die "--output-dir requires a directory"; OUTPUT_DIR="$2"; shift 2 ;;
--jobs) [[ $# -ge 2 ]] || die "--jobs requires a number"; JOBS="$2"; shift 2 ;;
--no-fetch) FETCH=0; shift ;;
--version) [[ $# -ge 2 ]] || die "--version requires a value"; PACKAGE_VERSION="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
[[ "${JOBS}" =~ ^[1-9][0-9]*$ ]] || die "--jobs must be a positive integer"
[[ "${PACKAGE_VERSION}" =~ ^[0-9A-Za-z.+:~-]+$ ]] || die "invalid Debian package version: ${PACKAGE_VERSION}"
require_arm64_trixie
for command_name in cmake git jq meson ninja pkg-config make patch readelf strip; do
need_command "${command_name}"
done
SOURCE_DIR="$(absolute_path "${SOURCE_DIR}")"
WORK_DIR="$(absolute_path "${WORK_DIR}")"
OUTPUT_DIR="$(absolute_path "${OUTPUT_DIR}")"
mkdir -p -- "${SOURCE_DIR}" "${WORK_DIR}" "${OUTPUT_DIR}"
if ((FETCH)); then
"${SCRIPT_DIR}/fetch-sources.sh" --lock "${LOCK_FILE}" --dest "${SOURCE_DIR}"
fi
while IFS=$'\t' read -r name commit; do
dir="${SOURCE_DIR}/${name}"
[[ -d "${dir}/.git" ]] || die "missing locked source: ${dir}"
[[ "$(git -C "${dir}" rev-parse HEAD)" == "${commit}" ]] || die "source commit mismatch: ${name}"
done < <(jq -r '.sources[] | select(.build == true) | [.name, .commit] | @tsv' "${LOCK_FILE}")
BUILD_ROOT="${WORK_DIR}/build"
BUILD_PREFIX="${WORK_DIR}/prefix"
STAGE_DIR="${WORK_DIR}/stage"
reset_child_dir "${WORK_DIR}" "${BUILD_ROOT}"
reset_child_dir "${WORK_DIR}" "${BUILD_PREFIX}"
reset_child_dir "${WORK_DIR}" "${STAGE_DIR}"
export SOURCE_DATE_EPOCH
SOURCE_DATE_EPOCH="$(jq -er '.source_date_epoch' "${LOCK_FILE}")"
export CFLAGS="${CFLAGS:--O2 -pipe -ffile-prefix-map=${WORK_DIR}=. -fdebug-prefix-map=${WORK_DIR}=.}"
export CXXFLAGS="${CXXFLAGS:--O2 -pipe -ffile-prefix-map=${WORK_DIR}=. -fdebug-prefix-map=${WORK_DIR}=.}"
export LDFLAGS="${LDFLAGS:-}"
log "building MPP"
cmake -S "${SOURCE_DIR}/mpp" -B "${BUILD_ROOT}/mpp" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${BUILD_PREFIX}" \
-DCMAKE_INSTALL_LIBDIR=lib \
-DBUILD_SHARED_LIBS=ON \
-DBUILD_TEST=OFF
cmake --build "${BUILD_ROOT}/mpp" --parallel "${JOBS}"
cmake --install "${BUILD_ROOT}/mpp"
log "building RGA"
meson setup "${BUILD_ROOT}/rga" "${SOURCE_DIR}/rga" \
--prefix="${BUILD_PREFIX}" \
--libdir=lib \
--buildtype=release \
--default-library=shared \
-Dcpp_args=-fpermissive \
-Dlibdrm=false \
-Dlibrga_demo=false
meson compile -C "${BUILD_ROOT}/rga" -j "${JOBS}"
meson install -C "${BUILD_ROOT}/rga"
log "building dav1d"
meson setup "${BUILD_ROOT}/dav1d" "${SOURCE_DIR}/dav1d" \
--prefix="${BUILD_PREFIX}" \
--libdir=lib \
--buildtype=release \
--default-library=shared \
-Denable_tools=false \
-Denable_tests=false \
-Denable_examples=false \
-Denable_docs=false
meson compile -C "${BUILD_ROOT}/dav1d" -j "${JOBS}"
meson install -C "${BUILD_ROOT}/dav1d"
export PKG_CONFIG_PATH="${BUILD_PREFIX}/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
export LD_LIBRARY_PATH="${BUILD_PREFIX}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
log "building FFmpeg-Rockchip"
mkdir -p -- "${BUILD_ROOT}/ffmpeg"
pushd "${BUILD_ROOT}/ffmpeg" >/dev/null
"${SOURCE_DIR}/ffmpeg-rockchip/configure" \
--prefix="${BUILD_PREFIX}" \
--libdir="${BUILD_PREFIX}/lib" \
--enable-shared \
--disable-static \
--disable-debug \
--disable-doc \
--enable-pic \
--enable-gpl \
--enable-version3 \
--enable-libdrm \
--enable-rkmpp \
--enable-rkrga \
--enable-libdav1d \
--extra-cflags="-I${BUILD_PREFIX}/include" \
--extra-ldflags="-L${BUILD_PREFIX}/lib -Wl,-rpath,${RUNTIME_PREFIX}/lib"
make -j "${JOBS}"
make install
popd >/dev/null
FFMPEG_BUILD="${BUILD_PREFIX}/bin/ffmpeg"
for decoder in h264_rkmpp hevc_rkmpp vp9_rkmpp av1_rkmpp mjpeg_rkmpp; do
"${FFMPEG_BUILD}" -hide_banner -decoders 2>/dev/null | awk '{print $2}' | grep -Fxq "${decoder}" \
|| die "FFmpeg is missing decoder ${decoder}"
done
for encoder in h264_rkmpp hevc_rkmpp mjpeg_rkmpp; do
"${FFMPEG_BUILD}" -hide_banner -encoders 2>/dev/null | awk '{print $2}' | grep -Fxq "${encoder}" \
|| die "FFmpeg is missing encoder ${encoder}"
done
for filter in scale_rkrga vpp_rkrga overlay_rkrga; do
"${FFMPEG_BUILD}" -hide_banner -filters 2>/dev/null | awk '{print $2}' | grep -Fxq "${filter}" \
|| die "FFmpeg is missing filter ${filter}"
done
log "building libdisplay-info"
meson setup "${BUILD_ROOT}/libdisplay-info" "${SOURCE_DIR}/libdisplay-info" \
--prefix="${BUILD_PREFIX}" \
--libdir=lib \
--buildtype=release \
--default-library=shared
meson compile -C "${BUILD_ROOT}/libdisplay-info" -j "${JOBS}"
meson install -C "${BUILD_ROOT}/libdisplay-info"
log "building Kodi for GBM/GLES with external FFmpeg-Rockchip"
cmake -S "${SOURCE_DIR}/kodi" -B "${BUILD_ROOT}/kodi" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="${RUNTIME_PREFIX}" \
-DCMAKE_INSTALL_RPATH="${RUNTIME_PREFIX}/lib" \
-DCMAKE_BUILD_RPATH="${BUILD_PREFIX}/lib" \
-DCMAKE_PREFIX_PATH="${BUILD_PREFIX}" \
-DCORE_PLATFORM_NAME=gbm \
-DAPP_RENDER_SYSTEM=gles \
-DENABLE_INTERNAL_FFMPEG=OFF \
-DFFMPEG_PATH="${BUILD_PREFIX}" \
-DENABLE_INTERNAL_FMT=ON \
-DENABLE_INTERNAL_FLATBUFFERS=ON
cmake --build "${BUILD_ROOT}/kodi" --parallel "${JOBS}"
mkdir -p -- "${STAGE_DIR}${RUNTIME_PREFIX}"
cp -a -- "${BUILD_PREFIX}/." "${STAGE_DIR}${RUNTIME_PREFIX}/"
DESTDIR="${STAGE_DIR}" cmake --install "${BUILD_ROOT}/kodi"
log "adding runtime integration and source provenance"
cp -a -- "${RKMEDIA_ROOT}/packaging/rootfs/." "${STAGE_DIR}/"
mkdir -p -- "${STAGE_DIR}/usr/bin" "${STAGE_DIR}/usr/share/doc/rk1-media-stack/licenses"
ln -sfn /opt/rkmedia/bin/ffmpeg-rk "${STAGE_DIR}/usr/bin/ffmpeg-rk"
ln -sfn /opt/rkmedia/bin/ffprobe-rk "${STAGE_DIR}/usr/bin/ffprobe-rk"
ln -sfn /opt/rkmedia/bin/kodi-rk "${STAGE_DIR}/usr/bin/kodi-rk"
install -m 0644 "${LOCK_FILE}" "${STAGE_DIR}/usr/share/doc/rk1-media-stack/sources.lock.json"
while IFS=$'\t' read -r name license_path; do
source_license="${SOURCE_DIR}/${name}/${license_path}"
destination="${STAGE_DIR}/usr/share/doc/rk1-media-stack/licenses/${name}"
[[ -e "${source_license}" ]] || die "locked license path is missing: ${name}/${license_path}"
mkdir -p -- "${destination}"
cp -a -- "${source_license}" "${destination}/"
done < <(jq -r '.sources[] | select(.build == true) as $source | $source.license_files[] | [$source.name, .] | @tsv' "${LOCK_FILE}")
rm -rf -- \
"${STAGE_DIR}${RUNTIME_PREFIX}/include" \
"${STAGE_DIR}${RUNTIME_PREFIX}/lib/pkgconfig" \
"${STAGE_DIR}${RUNTIME_PREFIX}/share/pkgconfig"
find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f \( -name '*.a' -o -name '*.la' \) -delete
while IFS= read -r -d '' candidate; do
if file --brief "${candidate}" | grep -q '^ELF '; then
strip --strip-unneeded "${candidate}" || die "failed to strip ${candidate}"
fi
done < <(find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f -print0)
for required in \
"${STAGE_DIR}${RUNTIME_PREFIX}/bin/ffmpeg" \
"${STAGE_DIR}${RUNTIME_PREFIX}/bin/ffprobe" \
"${STAGE_DIR}${RUNTIME_PREFIX}/bin/kodi" \
"${STAGE_DIR}/usr/lib/systemd/system/kodi-rk.service"; do
[[ -e "${required}" ]] || die "staged runtime file is missing: ${required}"
done
if find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f -exec grep -IlF "${WORK_DIR}" {} + | grep -q .; then
die "build path leaked into a staged text file"
fi
"${SCRIPT_DIR}/package-media.sh" \
--stage "${STAGE_DIR}" \
--output-dir "${OUTPUT_DIR}" \
--version "${PACKAGE_VERSION}"
log "media build complete"
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
LOCK_FILE="${RKMEDIA_ROOT}/sources.lock.json"
DEST_DIR="${RKMEDIA_ROOT}/out/sources"
usage() {
cat <<'EOF'
Usage: fetch-sources.sh [--lock FILE] [--dest DIRECTORY]
Fetch every build input at the exact commit recorded in the source lock.
Existing repositories are accepted only when their origin and HEAD match.
EOF
}
while (($#)); do
case "$1" in
--lock)
[[ $# -ge 2 ]] || die "--lock requires a file"
LOCK_FILE="$2"
shift 2
;;
--dest)
[[ $# -ge 2 ]] || die "--dest requires a directory"
DEST_DIR="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*) die "unknown argument: $1" ;;
esac
done
need_command git
need_command jq
LOCK_FILE="$(absolute_path "${LOCK_FILE}")"
DEST_DIR="$(absolute_path "${DEST_DIR}")"
[[ -f "${LOCK_FILE}" ]] || die "source lock not found: ${LOCK_FILE}"
jq -e '.schema_version == 1 and (.sources | type == "array")' "${LOCK_FILE}" >/dev/null \
|| die "invalid source lock: ${LOCK_FILE}"
mkdir -p -- "${DEST_DIR}"
verify_checkout() {
local dir="$1" expected_repo="$2" expected_commit="$3"
local actual_repo actual_commit
[[ -d "${dir}/.git" ]] || return 1
actual_repo="$(git -C "${dir}" remote get-url origin 2>/dev/null || true)"
actual_commit="$(git -C "${dir}" rev-parse HEAD 2>/dev/null || true)"
[[ "${actual_repo}" == "${expected_repo}" && "${actual_commit}" == "${expected_commit}" ]]
}
while IFS=$'\t' read -r name repository ref commit; do
[[ "${name}" =~ ^[a-z0-9][a-z0-9._-]*$ ]] || die "unsafe source name in lock: ${name}"
[[ "${commit}" =~ ^[0-9a-f]{40}$ ]] || die "invalid commit for ${name}: ${commit}"
source_dir="${DEST_DIR}/${name}"
if verify_checkout "${source_dir}" "${repository}" "${commit}"; then
log "source already verified: ${name} ${commit}"
continue
fi
if [[ -e "${source_dir}" ]]; then
die "${source_dir} exists but does not match the lock; move it aside and retry"
fi
log "fetching ${name} at ${commit}"
mkdir -p -- "${source_dir}"
git -C "${source_dir}" init --quiet
git -C "${source_dir}" remote add origin "${repository}"
if ! git -C "${source_dir}" fetch --quiet --depth=1 origin "${commit}"; then
log "direct commit fetch was unavailable; fetching history from ${ref}"
git -C "${source_dir}" fetch --quiet --filter=blob:none origin "${ref}"
fi
git -C "${source_dir}" checkout --quiet --detach "${commit}"
verify_checkout "${source_dir}" "${repository}" "${commit}" \
|| die "checkout verification failed for ${name}"
done < <(jq -r '.sources[] | select(.build == true) | [.name, .repository, .ref, .commit] | @tsv' "${LOCK_FILE}")
log "all locked sources are present in ${DEST_DIR}"
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
MODE=check
case "${1:---check}" in
--check) MODE=check ;;
--install) MODE=install ;;
-h|--help)
printf 'Usage: install-build-deps.sh [--check|--install]\n'
exit 0
;;
*) die "unknown argument: $1" ;;
esac
require_arm64_trixie
need_command dpkg-query
packages=(
adduser autoconf automake autopoint autotools-dev build-essential ca-certificates
cmake default-jre devscripts dpkg-dev file gawk gcc g++ gdc gettext git gperf
hwdata jq libasound2-dev libass-dev libavahi-client-dev libavahi-common-dev
libbluetooth-dev libbluray-dev libbz2-dev libcdio++-dev libcdio-dev libcec-dev
libcrossguid-dev libcwiid-dev libcurl4-openssl-dev libdbus-1-dev libdrm-dev
libegl1-mesa-dev libenca-dev libexiv2-dev libflac-dev libfmt-dev
libfontconfig-dev libfreetype6-dev libfribidi-dev libfstrcmp-dev libgbm-dev
libgcrypt20-dev libgif-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev
libgnutls28-dev libgpg-error-dev libgtest-dev libinput-dev libiso9660-dev
libjpeg-dev liblcms2-dev liblirc-dev libltdl-dev liblzo2-dev libmariadb-dev
libmicrohttpd-dev libnfs-dev libogg-dev libp8-platform-dev libpcre2-dev
libplist-dev libpng-dev libpulse-dev libshairplay-dev libsmbclient-dev
libspdlog-dev libsqlite3-dev libssl-dev libtag1-dev libtiff-dev
libtinyxml2-dev libtinyxml-dev libtool libudev-dev libunistring-dev
libvorbis-dev libxkbcommon-dev libxslt1-dev libxt-dev lsb-release meson nasm
ninja-build nlohmann-json3-dev patch pkg-config python3-dev python3-pil
python3-pip rapidjson-dev swig unzip uuid-dev zip zlib1g-dev
)
missing=()
for package in "${packages[@]}"; do
if ! dpkg-query -W -f='${db:Status-Abbrev}' "${package}" 2>/dev/null | grep -q '^ii '; then
missing+=("${package}")
fi
done
if ((${#missing[@]} == 0)); then
log "all build dependencies are installed"
exit 0
fi
if [[ "${MODE}" == "check" ]]; then
printf 'Missing build packages:\n'
printf ' %s\n' "${missing[@]}"
exit 1
fi
[[ "${EUID}" -eq 0 ]] || die "--install must run as root (use sudo)"
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends "${missing[@]}"
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
set -Eeuo pipefail
RKMEDIA_SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
RKMEDIA_ROOT="$(cd -- "${RKMEDIA_SCRIPT_DIR}/.." && pwd -P)"
log() {
printf '[rkmedia] %s\n' "$*" >&2
}
die() {
printf '[rkmedia] ERROR: %s\n' "$*" >&2
exit 1
}
need_command() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}
absolute_path() {
realpath -m -- "$1"
}
assert_child_path() {
local parent child
parent="$(absolute_path "$1")"
child="$(absolute_path "$2")"
case "${child}" in
"${parent}"/*) ;;
*) die "refusing operation outside ${parent}: ${child}" ;;
esac
[[ "${child}" != "${parent}" ]] || die "refusing operation on parent directory itself: ${parent}"
}
reset_child_dir() {
local parent="$1"
local child="$2"
assert_child_path "${parent}" "${child}"
rm -rf -- "${child}"
mkdir -p -- "${child}"
}
require_arm64_trixie() {
local arch codename
arch="$(dpkg --print-architecture 2>/dev/null || true)"
codename=""
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
codename="${VERSION_CODENAME:-}"
fi
if [[ "${arch}" != "arm64" || "$(uname -m)" != "aarch64" || "${codename}" != "trixie" ]]; then
if [[ "${RKMEDIA_ALLOW_UNSUPPORTED_HOST:-0}" == "1" ]]; then
log "warning: unsupported build host (dpkg=${arch}, uname=$(uname -m), suite=${codename:-unknown})"
return
fi
die "native Debian Trixie arm64 is required (set RKMEDIA_ALLOW_UNSUPPORTED_HOST=1 only for development)"
fi
}
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
set -Eeuo pipefail
umask 022
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
STAGE_DIR=""
OUTPUT_DIR="${RKMEDIA_ROOT}/out"
PACKAGE_VERSION="$(tr -d '[:space:]' < "${RKMEDIA_ROOT}/VERSION")"
DEPENDS_FROM_DEB=""
usage() {
printf 'Usage: package-media.sh --stage DIRECTORY [--output-dir DIRECTORY] [--version VERSION] [--depends-from-deb FILE]\n'
}
while (($#)); do
case "$1" in
--stage) [[ $# -ge 2 ]] || die "--stage requires a directory"; STAGE_DIR="$2"; shift 2 ;;
--output-dir) [[ $# -ge 2 ]] || die "--output-dir requires a directory"; OUTPUT_DIR="$2"; shift 2 ;;
--version) [[ $# -ge 2 ]] || die "--version requires a value"; PACKAGE_VERSION="$2"; shift 2 ;;
--depends-from-deb) [[ $# -ge 2 ]] || die "--depends-from-deb requires a file"; DEPENDS_FROM_DEB="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
[[ -n "${STAGE_DIR}" ]] || die "--stage is required"
[[ "${PACKAGE_VERSION}" =~ ^[0-9A-Za-z.+:~-]+$ ]] || die "invalid Debian package version: ${PACKAGE_VERSION}"
for command_name in dpkg-deb dpkg-shlibdeps file jq md5sum sed sha256sum; do
need_command "${command_name}"
done
STAGE_DIR="$(absolute_path "${STAGE_DIR}")"
OUTPUT_DIR="$(absolute_path "${OUTPUT_DIR}")"
[[ -x "${STAGE_DIR}/opt/rkmedia/bin/ffmpeg" ]] || die "stage does not contain the RKMedia runtime"
mkdir -p -- "${OUTPUT_DIR}"
PACKAGE_ROOT="${OUTPUT_DIR}/package-root"
reset_child_dir "${OUTPUT_DIR}" "${PACKAGE_ROOT}"
cp -a -- "${STAGE_DIR}/." "${PACKAGE_ROOT}/"
mkdir -p -- "${PACKAGE_ROOT}/DEBIAN" "${PACKAGE_ROOT}/usr/share/doc/rk1-media-stack"
cp -- "${RKMEDIA_ROOT}/packaging/postinst" "${PACKAGE_ROOT}/DEBIAN/postinst"
cp -- "${RKMEDIA_ROOT}/packaging/prerm" "${PACKAGE_ROOT}/DEBIAN/prerm"
cp -- "${RKMEDIA_ROOT}/packaging/postrm" "${PACKAGE_ROOT}/DEBIAN/postrm"
chmod 0755 "${PACKAGE_ROOT}/DEBIAN/postinst" "${PACKAGE_ROOT}/DEBIAN/prerm" "${PACKAGE_ROOT}/DEBIAN/postrm"
printf '/etc/rkmedia/kodi.env\n' > "${PACKAGE_ROOT}/DEBIAN/conffiles"
# A collaborative workspace or permissive host umask must never make core
# target directories such as /etc, /usr, or /opt group-writable via dpkg.
find "${PACKAGE_ROOT}" -type d -exec chmod 0755 {} +
find "${PACKAGE_ROOT}" -type f ! -perm /0111 -exec chmod 0644 {} +
find "${PACKAGE_ROOT}" -type f -perm /0111 -exec chmod 0755 {} +
if find "${PACKAGE_ROOT}" \( -type d -o -type f \) -perm /0022 -print -quit \
| grep -q .; then
die "package contains a group/world-writable path"
fi
if [[ -n "${DEPENDS_FROM_DEB}" ]]; then
DEPENDS_FROM_DEB="$(absolute_path "${DEPENDS_FROM_DEB}")"
[[ -f "${DEPENDS_FROM_DEB}" ]] || die "dependency source package not found: ${DEPENDS_FROM_DEB}"
shlib_depends="$(dpkg-deb --field "${DEPENDS_FROM_DEB}" Depends)"
[[ -n "${shlib_depends}" ]] || die "upstream package has an empty Depends field"
else
analysis_root="${OUTPUT_DIR}/shlibdeps"
reset_child_dir "${OUTPUT_DIR}" "${analysis_root}"
mkdir -p -- "${analysis_root}/debian"
cat > "${analysis_root}/debian/control" <<'EOF'
Source: rk1-media-stack
Section: video
Priority: optional
Package: rk1-media-stack
Architecture: arm64
Description: temporary metadata for dpkg-shlibdeps
EOF
elf_files=()
while IFS= read -r -d '' candidate; do
if file --brief "${candidate}" | grep -q '^ELF '; then
elf_files+=("${candidate}")
fi
done < <(find "${PACKAGE_ROOT}/opt/rkmedia" -type f -print0)
((${#elf_files[@]} > 0)) || die "no ELF files found in media stage"
pushd "${analysis_root}" >/dev/null
shlib_output="$(dpkg-shlibdeps --ignore-missing-info -O \
-l"${PACKAGE_ROOT}/opt/rkmedia/lib" "${elf_files[@]}")"
popd >/dev/null
shlib_depends="${shlib_output#shlibs:Depends=}"
[[ -n "${shlib_depends}" && "${shlib_depends}" != "${shlib_output}" ]] \
|| die "dpkg-shlibdeps did not produce runtime dependencies"
fi
depends="adduser, systemd, udev, ${shlib_depends}"
installed_size="$(du -sk "${PACKAGE_ROOT}" | awk '{print $1}')"
if [[ -f "${PACKAGE_ROOT}/usr/share/doc/rk1-media-stack/upstream-deb.lock.json" ]]; then
provenance_file="${PACKAGE_ROOT}/usr/share/doc/rk1-media-stack/upstream-deb.lock.json"
else
provenance_file="${PACKAGE_ROOT}/usr/share/doc/rk1-media-stack/sources.lock.json"
fi
lock_sha256="$(sha256sum "${provenance_file}" | awk '{print $1}')"
sed \
-e "s/@VERSION@/${PACKAGE_VERSION}/g" \
-e "s/@INSTALLED_SIZE@/${installed_size}/g" \
-e "s/@LOCK_SHA256@/${lock_sha256}/g" \
-e "s/@DEPENDS@/${depends}/g" \
"${RKMEDIA_ROOT}/packaging/control.in" > "${PACKAGE_ROOT}/DEBIAN/control"
pushd "${PACKAGE_ROOT}" >/dev/null
find etc opt usr -type f -print0 \
| sort -z \
| xargs -0 md5sum > DEBIAN/md5sums
popd >/dev/null
if find "${PACKAGE_ROOT}" \( -type d -o -type f \) -perm /0022 -print -quit \
| grep -q .; then
die "package contains a group/world-writable path after metadata generation"
fi
output_deb="${OUTPUT_DIR}/rk1-media-stack_${PACKAGE_VERSION}_arm64.deb"
rm -f -- "${output_deb}"
SOURCE_DATE_EPOCH="$(jq -er '.source_date_epoch' "${RKMEDIA_ROOT}/sources.lock.json")" \
dpkg-deb --root-owner-group --build "${PACKAGE_ROOT}" "${output_deb}"
(
cd "${OUTPUT_DIR}"
sha256sum "$(basename -- "${output_deb}")" \
>"$(basename -- "${output_deb}").sha256"
)
dpkg-deb --info "${output_deb}" >/dev/null
log "created ${output_deb}"
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
# shellcheck source=lib.sh
. "${SCRIPT_DIR}/lib.sh"
LOCK_FILE="${RKMEDIA_ROOT}/upstream-deb.lock.json"
WORK_DIR="${RKMEDIA_ROOT}/out/repack-work"
OUTPUT_DIR="${RKMEDIA_ROOT}/out"
CACHE_DIR="${RKMEDIA_ROOT}/out/downloads"
INPUT_DEB=""
PACKAGE_VERSION="20260721.1741+rk1.1"
usage() {
cat <<'EOF'
Usage: repack-upstream-deb.sh [options]
--deb FILE Use an existing upstream .deb instead of downloading
--work-dir DIR Temporary extraction directory
--output-dir DIR Repacked package output directory
--cache-dir DIR Download cache directory
--version VERSION Output Debian package version
EOF
}
while (($#)); do
case "$1" in
--deb) [[ $# -ge 2 ]] || die "--deb requires a file"; INPUT_DEB="$2"; shift 2 ;;
--work-dir) [[ $# -ge 2 ]] || die "--work-dir requires a directory"; WORK_DIR="$2"; shift 2 ;;
--output-dir) [[ $# -ge 2 ]] || die "--output-dir requires a directory"; OUTPUT_DIR="$2"; shift 2 ;;
--cache-dir) [[ $# -ge 2 ]] || die "--cache-dir requires a directory"; CACHE_DIR="$2"; shift 2 ;;
--version) [[ $# -ge 2 ]] || die "--version requires a value"; PACKAGE_VERSION="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
for command_name in curl dpkg-deb file jq readelf sha256sum stat; do
need_command "${command_name}"
done
[[ -f "${LOCK_FILE}" ]] || die "upstream asset lock is missing: ${LOCK_FILE}"
jq -e '.schema_version == 1 and (.asset_sha256 | test("^[0-9a-f]{64}$"))' "${LOCK_FILE}" >/dev/null \
|| die "invalid upstream asset lock"
WORK_DIR="$(absolute_path "${WORK_DIR}")"
OUTPUT_DIR="$(absolute_path "${OUTPUT_DIR}")"
CACHE_DIR="$(absolute_path "${CACHE_DIR}")"
mkdir -p -- "${WORK_DIR}" "${OUTPUT_DIR}" "${CACHE_DIR}"
asset_name="$(jq -er '.asset_name' "${LOCK_FILE}")"
asset_url="$(jq -er '.asset_url' "${LOCK_FILE}")"
expected_size="$(jq -er '.asset_size' "${LOCK_FILE}")"
expected_sha256="$(jq -er '.asset_sha256' "${LOCK_FILE}")"
if [[ -z "${INPUT_DEB}" ]]; then
INPUT_DEB="${CACHE_DIR}/${asset_name}"
if [[ ! -f "${INPUT_DEB}" ]]; then
partial="${INPUT_DEB}.partial"
rm -f -- "${partial}"
log "downloading pinned upstream Kodi package"
curl -L --fail --show-error --output "${partial}" "${asset_url}"
mv -- "${partial}" "${INPUT_DEB}"
fi
fi
INPUT_DEB="$(absolute_path "${INPUT_DEB}")"
[[ -f "${INPUT_DEB}" ]] || die "upstream package not found: ${INPUT_DEB}"
actual_size="$(stat -c '%s' "${INPUT_DEB}")"
actual_sha256="$(sha256sum "${INPUT_DEB}" | awk '{print $1}')"
[[ "${actual_size}" == "${expected_size}" ]] || die "upstream package size mismatch"
[[ "${actual_sha256}" == "${expected_sha256}" ]] || die "upstream package SHA-256 mismatch"
[[ "$(dpkg-deb --field "${INPUT_DEB}" Package)" == "$(jq -er '.package' "${LOCK_FILE}")" ]] \
|| die "upstream package name mismatch"
[[ "$(dpkg-deb --field "${INPUT_DEB}" Version)" == "$(jq -er '.package_version' "${LOCK_FILE}")" ]] \
|| die "upstream package version mismatch"
[[ "$(dpkg-deb --field "${INPUT_DEB}" Architecture)" == "arm64" ]] \
|| die "upstream package is not arm64"
STAGE_DIR="${WORK_DIR}/stage"
reset_child_dir "${WORK_DIR}" "${STAGE_DIR}"
dpkg-deb --extract "${INPUT_DEB}" "${STAGE_DIR}"
local_bin="${STAGE_DIR}/usr/local/bin"
local_lib="${STAGE_DIR}/usr/local/lib"
runtime_bin="${STAGE_DIR}/opt/rkmedia/bin"
runtime_lib="${STAGE_DIR}/opt/rkmedia/lib"
mkdir -p -- "${runtime_bin}" "${runtime_lib}"
for program in ffmpeg ffprobe; do
[[ -x "${local_bin}/${program}" ]] || die "upstream package is missing ${program}"
mv -- "${local_bin}/${program}" "${runtime_bin}/${program}"
done
if [[ -x "${local_bin}/di-edid-decode" ]]; then
mv -- "${local_bin}/di-edid-decode" "${runtime_bin}/di-edid-decode"
fi
shopt -s nullglob
runtime_libraries=(
"${local_lib}"/libdav1d.so*
"${local_lib}"/librga.so*
"${local_lib}"/librockchip_mpp.so*
"${local_lib}"/librockchip_vpu.so*
"${local_lib}"/aarch64-linux-gnu/libdisplay-info.so*
)
((${#runtime_libraries[@]} > 0)) || die "no Rockchip runtime libraries found in upstream package"
mv -- "${runtime_libraries[@]}" "${runtime_lib}/"
shopt -u nullglob
find "${local_lib}" -type f \( -name '*.a' -o -name '*.la' \) -delete
rm -rf -- "${local_lib}/pkgconfig" "${local_lib}/aarch64-linux-gnu/pkgconfig"
rm -f -- \
"${STAGE_DIR}/usr/lib/systemd/system/kodi.service" \
"${STAGE_DIR}/usr/lib/systemd/system/kodi-pulse.service" \
"${STAGE_DIR}/usr/lib/systemd/system/pulseaudio.service"
cp -a -- "${RKMEDIA_ROOT}/packaging/rootfs/." "${STAGE_DIR}/"
mkdir -p -- "${STAGE_DIR}/usr/bin" "${STAGE_DIR}/usr/share/doc/rk1-media-stack"
ln -sfn /opt/rkmedia/bin/ffmpeg-rk "${STAGE_DIR}/usr/bin/ffmpeg-rk"
ln -sfn /opt/rkmedia/bin/ffprobe-rk "${STAGE_DIR}/usr/bin/ffprobe-rk"
ln -sfn /opt/rkmedia/bin/kodi-rk "${STAGE_DIR}/usr/bin/kodi-rk"
install -m 0644 "${LOCK_FILE}" "${STAGE_DIR}/usr/share/doc/rk1-media-stack/upstream-deb.lock.json"
[[ -x "${STAGE_DIR}/usr/local/bin/kodi" ]] || die "Kodi launcher was not retained at its compiled prefix"
[[ -x "${STAGE_DIR}/usr/local/lib/kodi/kodi-gbm" ]] || die "Kodi GBM binary is missing"
for soname in librga.so.2 libdav1d.so.7 librockchip_mpp.so.1 libdisplay-info.so.4; do
[[ -e "${runtime_lib}/${soname}" ]] || die "relocated runtime is missing ${soname}"
readelf -d "${STAGE_DIR}/usr/local/lib/kodi/kodi-gbm" | grep -Fq "Shared library: [${soname}]" \
|| die "Kodi binary does not declare expected dependency ${soname}"
done
if find "${STAGE_DIR}" -type f -name '*.a' -print -quit | grep -q .; then
die "static library remained after repack"
fi
if find "${STAGE_DIR}/usr/local/lib" -type d -name pkgconfig -print -quit | grep -q .; then
die "pkg-config development directory remained after repack"
fi
"${SCRIPT_DIR}/package-media.sh" \
--stage "${STAGE_DIR}" \
--output-dir "${OUTPUT_DIR}" \
--version "${PACKAGE_VERSION}" \
--depends-from-deb "${INPUT_DEB}"
log "repack complete; authoritative input SHA-256: ${actual_sha256}"