diff --git a/projects/ArchR/packages/archr/package.mk b/projects/ArchR/packages/archr/package.mk index 2fb4faae88..48dc6d763b 100644 --- a/projects/ArchR/packages/archr/package.mk +++ b/projects/ArchR/packages/archr/package.mk @@ -146,6 +146,12 @@ EOF cp ${PKG_DIR}/sources/scripts/* ${INSTALL}/usr/bin chmod 0755 ${INSTALL}/usr/bin/* 2>/dev/null ||: + # alpm hooks (pacman.conf points HookDir at /etc/pacman.d/hooks): the + # flash-sync hook keeps the FAT boot partition in step with kernel or + # DTB updates delivered through pacman. + mkdir -p ${INSTALL}/etc/pacman.d/hooks + cp ${PKG_DIR}/sources/pacman-hooks/*.hook ${INSTALL}/etc/pacman.d/hooks + ### Install man pages if [ -d "${PKG_DIR}/sources/man" ]; then for page in ${PKG_DIR}/sources/man/*.[1-9]; do diff --git a/projects/ArchR/packages/archr/sources/pacman-hooks/90-archr-flash-sync.hook b/projects/ArchR/packages/archr/sources/pacman-hooks/90-archr-flash-sync.hook new file mode 100644 index 0000000000..f9bfdc331c --- /dev/null +++ b/projects/ArchR/packages/archr/sources/pacman-hooks/90-archr-flash-sync.hook @@ -0,0 +1,15 @@ +# Sync the kernel and device trees delivered by pacman into the FAT +# boot partition (/flash), so kernel-side fixes reach users through the +# normal update flow. See /usr/bin/archr-flash-sync for the safety +# model; u-boot/TPL stays image-flash only. +[Trigger] +Operation = Install +Operation = Upgrade +Type = Path +Target = usr/share/bootloader/Image +Target = usr/share/bootloader/device_trees/* + +[Action] +Description = Syncing kernel and device trees to the boot partition... +When = PostTransaction +Exec = /usr/bin/archr-flash-sync diff --git a/projects/ArchR/packages/archr/sources/scripts/archr-flash-sync b/projects/ArchR/packages/archr/sources/scripts/archr-flash-sync new file mode 100644 index 0000000000..3b9f1c8684 --- /dev/null +++ b/projects/ArchR/packages/archr/sources/scripts/archr-flash-sync @@ -0,0 +1,111 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2026-present ArchR (https://github.com/archr-linux/Arch-R) +# +# archr-flash-sync: bring the boot partition (FAT, /flash) in line with +# the kernel and device trees shipped in the rootfs. This is what lets +# kernel and DTB fixes flow through `pacman -Syu` / the ES update menu +# instead of demanding a reflash: the linux package delivers the new +# files under /usr/share/bootloader and an alpm PostTransaction hook +# runs this script to copy the changed ones over. +# +# Safety model: each target is written as .new in the same FAT +# directory, synced, read back and hash-verified, then renamed over the +# old file. The window where a power cut leaves a torn file is the +# rename itself, the smallest FAT allows. Anything user-owned on the +# FAT is never touched: /flash/overlays (the user's mipi-panel.dtbo), +# extlinux/, boot.scr, boot.ini. +# +# u-boot/TPL (idbloader) stays OUT of scope on purpose: it lives in raw +# sectors before the first partition and a torn write there bricks the +# card with no recovery besides a reflash. Bootloader updates remain +# image-flash only. + +SRC="/usr/share/bootloader" +BOOT="/flash" + +log() { echo "flash-sync: $*"; } + +if [ ! -d "${BOOT}" ] || ! grep -q " ${BOOT} " /proc/mounts; then + log "no boot partition mounted at ${BOOT}, nothing to do" + exit 0 +fi + +if ! mount -o remount,rw "${BOOT}" 2>/dev/null; then + log "WARN: could not remount ${BOOT} read-write, skipping" + exit 0 +fi + +finish() { + sync + mount -o remount,ro "${BOOT}" 2>/dev/null +} +trap finish EXIT + +CHANGED=0 +FAILED=0 + +# sync_file : copy only when the content differs, via a +# verified temp file in the destination directory. +sync_file() { + s="$1" + d="$2" + [ -f "${s}" ] || return 0 + + src_sum=$(sha256sum "${s}" | cut -d' ' -f1) + if [ -f "${d}" ]; then + dst_sum=$(sha256sum "${d}" | cut -d' ' -f1) + [ "${src_sum}" = "${dst_sum}" ] && return 0 + fi + + tmp="${d}.new" + if ! cp "${s}" "${tmp}" 2>/dev/null; then + log "ERROR: writing ${tmp} failed (FAT full?)" + rm -f "${tmp}" 2>/dev/null + FAILED=1 + return 1 + fi + sync + new_sum=$(sha256sum "${tmp}" | cut -d' ' -f1) + if [ "${new_sum}" != "${src_sum}" ]; then + log "ERROR: verify of ${tmp} failed, keeping the old file" + rm -f "${tmp}" 2>/dev/null + FAILED=1 + return 1 + fi + mv -f "${tmp}" "${d}" + sync + log "updated ${d}" + CHANGED=1 + return 0 +} + +# Kernel: /usr/share/bootloader/Image -> /flash/KERNEL. Refresh the +# sidecar md5 too (legacy .update flow reads it with the target/KERNEL +# name baked in). +if [ -f "${SRC}/Image" ]; then + if sync_file "${SRC}/Image" "${BOOT}/KERNEL" && [ "${CHANGED}" = "1" ]; then + md5=$(md5sum "${BOOT}/KERNEL" | cut -d' ' -f1) + printf '%s target/KERNEL\n' "${md5}" > "${BOOT}/KERNEL.md5" 2>/dev/null + fi +else + log "no ${SRC}/Image in this rootfs (older build), kernel left alone" +fi + +# Device trees: only into the dtbs/ dir the image layout uses. The +# user's panel overlay lives in /flash/overlays and is never touched. +if [ -d "${SRC}/device_trees" ] && [ -d "${BOOT}/dtbs" ]; then + for dtb in "${SRC}/device_trees/"*.dtb; do + [ -f "${dtb}" ] || continue + sync_file "${dtb}" "${BOOT}/dtbs/$(basename "${dtb}")" + done +fi + +if [ "${FAILED}" = "1" ]; then + log "finished WITH ERRORS, the untouched files remain bootable" +elif [ "${CHANGED}" = "1" ]; then + log "boot partition updated, changes take effect on the next boot" +else + log "boot partition already up to date" +fi +exit 0 diff --git a/projects/ArchR/packages/linux/package.mk b/projects/ArchR/packages/linux/package.mk index c95cb1bded..122614ef43 100644 --- a/projects/ArchR/packages/linux/package.mk +++ b/projects/ArchR/packages/linux/package.mk @@ -378,6 +378,11 @@ makeinstall_target() { if [ "${BOOTLOADER}" = "u-boot" ]; then mkdir -p ${INSTALL}/usr/share/bootloader + # Ship the kernel image in the rootfs as well: pacman carries it to + # updated systems and archr-flash-sync copies it over /flash/KERNEL + # (the FAT copy u-boot actually boots). Fresh images stay in sync by + # construction; this closes the update-without-reflash path. + cp -p arch/${TARGET_KERNEL_ARCH}/boot/${KERNEL_TARGET} ${INSTALL}/usr/share/bootloader/ for dtb in arch/${TARGET_KERNEL_ARCH}/boot/dts/**/*.dtb; do if [ -f ${dtb} ]; then if [ "${DEVICE}" = "H700" -o "${DEVICE}" = "RK3326" -o "${DEVICE}" = "RK3399" -o "${DEVICE}" = "RK3566" -o "${DEVICE}" = "RK3588" ]; then diff --git a/scripts/repo/gen-pacman-repo b/scripts/repo/gen-pacman-repo index d2f6410887..ce7a84ea96 100755 --- a/scripts/repo/gen-pacman-repo +++ b/scripts/repo/gen-pacman-repo @@ -139,7 +139,10 @@ EOF filelist="$(mktemp)" { echo ".PKGINFO" - find . -mindepth 1 -maxdepth 1 -not -name '.PKGINFO' -printf '%P\n' + # .image is build-system plumbing (raw kernel + symbols the + # image script consumes); shipping it would dump ~25MB at + # /.image on every kernel update for nothing. + find . -mindepth 1 -maxdepth 1 -not -name '.PKGINFO' -not -name '.image' -printf '%P\n' } > "${filelist}" tar --zstd -cf "${outfile}" --owner=0 --group=0 -T "${filelist}" rm -f "${filelist}"