Initial RK1 media-center image project
This commit is contained in:
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"
|
||||
Reference in New Issue
Block a user