#!/bin/bash # ============================================================================ # repack_repro.sh — .upt 打包(mksquashfs + 分片 md5 链 + ISO),**时间戳可复现** # # 与 hiby_os_crack/scripts/repack.sh 的产物布局逐字节等价(ota_update.in / ota_v0.ok / # ota_config.in / rootfs.squashfs.NNNN. / ota_md5_rootfs.squashfs.), # 只是把「墙钟时间」从产物里拿掉,使同一输入**连续打包两次逐字节相同**: # # 1) squashfs:`-repro-time `(= -mkfs-time + -inode-time;mksquashfs 4.7 起)。 # 只加 -all-time 不够 —— 超级块的 mkfs_time 仍是墙钟(M60 实测)。本脚本按 # `mksquashfs -help-all` 能力探测,依次尝试: # -repro-time (4.7+) # -mkfs-time -inode-time (4.6) # -mkfs-time -all-time (老版本) # 都没有 → 告警并按旧口径(体积 + unpack 0 diff + 进包 md5)出货。 # 2) ISO:genisoimage 的卷创建时间用 `-creation-date ` 固定, # 并把暂存目录里所有文件/目录的 mtime touch 到同一 epoch(ISO 目录记录取文件 mtime)。 # # 用法: # repack_repro.sh -i -k -o # SOURCE_DATE_EPOCH=1735689600 覆盖固定时间(默认见 REPRO_EPOCH)。 # ============================================================================ set -euo pipefail REPRO_EPOCH="${SOURCE_DATE_EPOCH:-1735689600}" # 2025-01-01T00:00:00Z TEMP_DIR="${TMPDIR:-/tmp}/meow-repack-repro" # 本机(Termux)没有 /usr/bin/mksquashfs:fw 工具集放在 ~/.cache/fwtools/shims # (glibc 版 mksquashfs/unsquashfs/genisoimage 经 grun 运行)。 if ! command -v mksquashfs >/dev/null 2>&1 && [ -d "$HOME/.cache/fwtools/shims" ]; then PATH="$HOME/.cache/fwtools/shims:$PATH" fi show_help() { sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//' } input_root="" input_ximage="" output_file="" while getopts ":i:k:o:h" opt; do case "$opt" in i) input_root="$OPTARG" ;; k) input_ximage="$OPTARG" ;; o) output_file="$OPTARG" ;; h) show_help; exit 0 ;; :) echo "Error: -$OPTARG 需要参数" >&2; exit 1 ;; \?) echo "Error: 未知参数 -$OPTARG" >&2; exit 1 ;; esac done [ -n "$input_root" ] || { echo "Error: -i 必填" >&2; exit 1; } [ -n "$input_ximage" ] || { echo "Error: -k 必填" >&2; exit 1; } [ -n "$output_file" ] || { echo "Error: -o 必填" >&2; exit 1; } [ -d "$input_root" ] || { echo "Error: '$input_root' 不存在" >&2; exit 1; } [ -f "$input_ximage" ] || { echo "Error: '$input_ximage' 不存在" >&2; exit 1; } command -v mksquashfs >/dev/null 2>&1 || { echo "Error: 缺 mksquashfs" >&2; exit 1; } command -v genisoimage >/dev/null 2>&1 || echo "WARN: 无 genisoimage,将尝试 mkisofs" >&2 input_root=$(realpath "$input_root") input_ximage=$(realpath "$input_ximage") output_file=$(realpath -m "$output_file") [ -e "$output_file" ] && rm -f "$output_file" ISO_TOOL=genisoimage command -v genisoimage >/dev/null 2>&1 || ISO_TOOL=mkisofs # ---- squashfs 时间戳参数:按能力探测 ---------------------------------------- sq_help="$(mksquashfs -help-all 2>/dev/null || mksquashfs -help 2>/dev/null || true)" sq_time=() # 直接用 case 匹配字符串,**不要**写成 `printf ... | grep -q`:脚本是 set -o pipefail, # grep -q 命中后立刻退出 → printf 收 SIGPIPE(141) → 整条管道判失败 → 参数被静默丢掉 # (M56 踩过一次:ISO 卷时间因此回落成墙钟,两次打包不一致)。 case "$sq_help" in *-repro-time*) sq_time=(-repro-time "$REPRO_EPOCH") ;; *-mkfs-time*) case "$sq_help" in *-inode-time*) sq_time=(-mkfs-time "$REPRO_EPOCH" -inode-time "$REPRO_EPOCH") ;; *-all-time*) sq_time=(-mkfs-time "$REPRO_EPOCH" -all-time "$REPRO_EPOCH") ;; esac ;; esac if [ "${#sq_time[@]}" -eq 0 ]; then echo "WARN: 本机 mksquashfs 不支持 -mkfs-time/-repro-time —— squashfs 内不含可复现时间戳," >&2 echo " 只能按旧口径(体积 + unpack 0 diff + 进包 md5)验收。" >&2 fi # genisoimage 1.1.11 的 -creation-date 只吃得下「秒数」形式:传 YYYYMMDDhhmmsscc # (本工具 man 里推荐的 16 位串)会直接 *** buffer overflow detected *** 崩掉。 # 传秒数时 PVD 的 Creation/Modif. Time 都被写成同一时刻(xorriso -pvd_info 可核), # 正是可复现需要的样子。 iso_date="$REPRO_EPOCH" iso_time=() iso_help="$("$ISO_TOOL" -help 2>&1 || true)" case "$iso_help" in *-creation-date*) iso_time=(-creation-date "$iso_date") ;; esac rm -rf "$TEMP_DIR" mkdir -p "$TEMP_DIR/ota_v0" pushd "$TEMP_DIR/ota_v0" >/dev/null echo "### GENERATING NEW SQUASHFS FILES ###" echo " mksquashfs 时间参数: ${sq_time[*]:-(无)} epoch=$REPRO_EPOCH" mksquashfs "$input_root" rootfs.squashfs -comp lzo -all-root "${sq_time[@]}" rootfs_size=$(stat -c%s rootfs.squashfs) rootfs_md5=$(md5sum rootfs.squashfs | awk '{print $1}') split rootfs.squashfs -d -a 4 -b 512k rootfs.squashfs. rm rootfs.squashfs md5=$rootfs_md5 ota_md5_rootfs="ota_md5_rootfs.squashfs.${rootfs_md5}" > "$ota_md5_rootfs" for part in $(ls rootfs.squashfs.[0-9]* | sort); do md5next=$(md5sum "$part" | awk '{print $1}') echo "$md5next" >> "$ota_md5_rootfs" mv "$part" "$part.$md5" md5="$md5next" done echo "### GENERATING NEW xImage FILES ###" cp "$input_ximage" xImage ximage_size=$(stat -c%s xImage) ximage_md5=$(md5sum xImage | awk '{print $1}') split xImage -d -a 4 -b 512k xImage. rm xImage md5=$ximage_md5 ota_md5_ximage="ota_md5_xImage.${ximage_md5}" > "$ota_md5_ximage" for part in $(ls xImage.[0-9]* | sort); do md5next=$(md5sum "$part" | awk '{print $1}') echo "$md5next" >> "$ota_md5_ximage" mv "$part" "$part.$md5" md5="$md5next" done echo "### GENERATING METADATA FILES ###" cat > ota_update.in <<-EOM ota_version=0 img_type=kernel img_name=xImage img_size=${ximage_size} img_md5=${ximage_md5} img_type=rootfs img_name=rootfs.squashfs img_size=${rootfs_size} img_md5=${rootfs_md5} EOM echo > ota_v0.ok echo "current_version=0" > ../ota_config.in echo "### GENERATING FIRMWARE FILE ###" # ISO 的目录记录取文件 mtime → 全树归一化到 REPRO_EPOCH(含目录自身;-h 跟随符号链接元数据)。 find "$TEMP_DIR" -exec touch -h -d "@$REPRO_EPOCH" {} + # 先写暂存路径,成功后再 mv 到交付路径:genisoimage 中途 abort(本机 1.1.11 有已知崩溃) # 时,交付路径上不会留下「看起来像成品」的残缺 ISO(M56 踩过:28,672 B 半成品)。 iso_tmp="${TEMP_DIR}.iso" # 暂存在打包树之外(否则 ISO 会把自己读进去) "$ISO_TOOL" -o "$iso_tmp" -J -r "${iso_time[@]}" "$TEMP_DIR" [ -s "$iso_tmp" ] || { echo "!!! ISO 为空: $iso_tmp" >&2; exit 1; } mv -f "$iso_tmp" "$output_file" iso_size=$(stat -c%s "$output_file") # 反「静默半成品」:ISO 至少要装得下 rootfs 本体(+xImage + 元数据),小了就是没写完。 [ "$iso_size" -ge "$rootfs_size" ] || { echo "!!! ISO 体积 $iso_size B < rootfs $rootfs_size B —— 打包不完整" >&2 rm -f "$output_file"; exit 1; } popd >/dev/null rm -rf "$TEMP_DIR" echo echo "Repacking complete!" echo " rootfs.squashfs = ${rootfs_size} B md5=${rootfs_md5}" echo " xImage = ${ximage_size} B md5=${ximage_md5}" echo " firmware: $output_file"