升级方案概述
该方案采用以下关键设计原则:
- 双分区设计(活跃分区 / 备用分区)
- 升级包校验与签名验证
- 完整的回滚机制
- 原子操作确保系统一致性
- 详细的升级日志记录
实现方案
1. 系统分区布局
建议的分区布局:
/dev/mmcblk0p1 - 启动分区(包含U-Boot配置)
/dev/mmcblk0p2 - 系统分区A(活跃)
/dev/mmcblk0p3 - 系统分区B(备用)
/dev/mmcblk0p4 - 数据分区(持久化数据)
/dev/mmcblk0p5 - 升级包存储分区
2. 升级脚本实现
方案说明
1. 升级流程详解
该方案采用双分区策略实现安全升级:
-
准备阶段:
- 验证升级包的完整性和签名
- 挂载相关分区(活跃分区、备用分区、升级分区)
-
升级阶段:
- 将新内核、文件系统和应用程序安装到备用分区
- 保留关键配置文件
- 执行应用程序的安装后脚本(如有)
-
切换阶段:
- 更新启动配置,设置下次从备用分区启动
- 记录升级信息用于验证
-
验证阶段:
- 系统重启后,验证新系统是否正常工作
- 验证成功则标记升级完成
- 验证失败则自动回滚到之前的系统
2. 关键特性
- 双分区设计:保证升级失败时可以回滚到稳定版本
- 签名验证:防止安装未授权或被篡改的升级包
- 原子操作:确保系统不会处于半升级状态
- 自动回滚:系统异常时自动恢复到之前的工作状态
- 配置保留:升级过程中保留关键系统配置
3. 使用方法
-
创建升级包:
create_upgrade_package.sh
#!/bin/bash
# 用于创建固件升级包的脚本set -e
# 默认参数
KERNEL_IMG="kernel.img"
ROOTFS_TAR="rootfs.tar.gz"
APPS_TAR="apps.tar.gz"
VERSION_FILE="version.txt"
OUTPUT_PACKAGE="firmware_upgrade.tar.gz"
SIGN_PACKAGE=true
PRIVATE_KEY="private_key.pem"# 显示帮助信息
usage() {
echo "用法: $0 [选项]"
echo "选项:"
echo " -k <文件> 指定内核镜像文件 (默认: $KERNEL_IMG)"
echo " -r <文件> 指定根文件系统tar包 (默认: $ROOTFS_TAR)"
echo " -a <文件> 指定应用程序tar包 (默认: $APPS_TAR)"
echo " -v <文件> 指定版本信息文件 (默认: $VERSION_FILE)"
echo " -o <文件> 指定输出升级包文件名 (默认: $OUTPUT_PACKAGE)"
echo " -n 不签名升级包"
echo " -p <文件> 指定私钥文件 (默认: $PRIVATE_KEY)"
echo " -h 显示帮助信息"
exit 1
}# 解析命令行参数
parse_args() {
while getopts "k:r:a:v:o:np:h" opt; do
case $opt in
k)
KERNEL_IMG=$OPTARG
;;
r)
ROOTFS_TAR=$OPTARG
;;
a)
APPS_TAR=$OPTARG
;;
v)
VERSION_FILE=$OPTARG
;;
o)
OUTPUT_PACKAGE=$OPTARG
;;
n)
SIGN_PACKAGE=false
;;
p)
PRIVATE_KEY=$OPTARG
;;
h)
usage
;;
\?)
echo "无效的选项: -$OPTARG" >&2
usage
;;
esac
done
}# 检查必要文件
check_files() {
echo "检查必要文件..."
local REQUIRED_FILES=("$KERNEL_IMG" "$ROOTFS_TAR" "$APPS_TAR" "$VERSION_FILE")
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$file" ]; then
echo "错误: 必要文件不存在 - $file" >&2
exit 1
fi
done
# 如果需要签名,检查私钥
if [ "$SIGN_PACKAGE" = true ] && [ ! -f "$PRIVATE_KEY" ]; then
echo "错误: 私钥文件不存在 - $PRIVATE_KEY" >&2
exit 1
fi
echo "文件检查通过"
}# 创建升级包
create_package() {
echo "创建升级包 $OUTPUT_PACKAGE..."
# 创建临时目录
local TEMP_DIR=$(mktemp -d)
# 复制文件到临时目录
cp "$KERNEL_IMG" "$TEMP_DIR/"
cp "$ROOTFS_TAR" "$TEMP_DIR/"
cp "$APPS_TAR" "$TEMP_DIR/"
cp "$VERSION_FILE" "$TEMP_DIR/"
# 如果存在安装后脚本,也一并加入
if [ -f "post_install.sh" ]; then
cp "post_install.sh" "$TEMP_DIR/"
fi
# 打包文件
tar -zcf "$OUTPUT_PACKAGE" -C "$TEMP_DIR" .
# 清理临时目录
rm -rf "$TEMP_DIR"
echo "升级包创建完成"
}# 签名升级包
sign_package() {
if [ "$SIGN_PACKAGE" = false ]; then
return 0
fi
echo "签名升级包..."
# 使用私钥签名
openssl dgst -sha256 -sign "$PRIVATE_KEY" -out "${OUTPUT_PACKAGE}.sig" "$OUTPUT_PACKAGE"
echo "升级包签名完成: ${OUTPUT_PACKAGE}.sig"
}# 显示版本信息
show_version() {
echo "升级包版本信息:"
echo "-------------------------"
cat "$VERSION_FILE"
echo "-------------------------"
}# 主函数
main() {
parse_args "$@"
check_files
show_version
create_package
sign_package
echo "操作完成,升级包已准备就绪:"
echo "升级包: $OUTPUT_PACKAGE"
if [ "$SIGN_PACKAGE" = true ]; then
echo "签名文件: ${OUTPUT_PACKAGE}.sig"
fi
}main "$@"
bash
./create_upgrade_package.sh -k kernel.img -r rootfs.tar.gz -a apps.tar.gz -v version.txt -o firmware_v2.1.tar.gz
-
执行升级:
firmware_upgrade.sh
#!/bin/bash
# 主固件升级脚本
# 支持内核、文件系统和应用程序升级set -e
# 配置参数
ACTIVE_PART="/dev/mmcblk0p2"
PASSIVE_PART="/dev/mmcblk0p3"
BOOT_PART="/dev/mmcblk0p1"
DATA_PART="/dev/mmcblk0p4"
UPGRADE_DIR="/mnt/upgrade"
LOG_FILE="/var/log/firmware_upgrade.log"
MOUNT_POINT_ACTIVE="/mnt/active"
MOUNT_POINT_PASSIVE="/mnt/passive"
UPGRADE_PACKAGE=""
VERIFY_SIGNATURE=true
REBOOT_AFTER_UPGRADE=true# 初始化日志
init_log() {
echo "=== 固件升级开始: $(date) ===" >> $LOG_FILE
echo "升级脚本版本: 1.0.0" >> $LOG_FILE
}# 显示使用帮助
usage() {
echo "用法: $0 [选项] <升级包路径>"
echo "选项:"
echo " -n 升级后不自动重启"
echo " -s 不验证签名"
echo " -h 显示帮助信息"
exit 1
}# 解析命令行参数
parse_args() {
while getopts "nsh" opt; do
case $opt in
n)
REBOOT_AFTER_UPGRADE=false
;;
s)
VERIFY_SIGNATURE=false
;;
h)
usage
;;
\?)
echo "无效的选项: -$OPTARG" >&2
usage
;;
esac
done
shift $((OPTIND-1))
if [ $# -ne 1 ]; then
echo "请指定升级包路径" >&2
usage
fi
UPGRADE_PACKAGE=$1
if [ ! -f "$UPGRADE_PACKAGE" ]; then
echo "升级包不存在: $UPGRADE_PACKAGE" >&2
exit 1
fi
}# 验证升级包签名
verify_package() {
echo "验证升级包签名..." >> $LOG_FILE
if [ "$VERIFY_SIGNATURE" = false ]; then
echo "跳过签名验证" >> $LOG_FILE
return 0
fi
# 假设升级包包含签名文件
if [ ! -f "${UPGRADE_PACKAGE}.sig" ]; then
echo "签名文件不存在: ${UPGRADE_PACKAGE}.sig" >> $LOG_FILE
return 1
fi
# 使用openssl验证签名
if ! openssl dgst -sha256 -verify /etc/upgrade/public_key.pem \
-signature "${UPGRADE_PACKAGE}.sig" "$UPGRADE_PACKAGE"; then
echo "签名验证失败" >> $LOG_FILE
return 1
fi
echo "签名验证成功" >> $LOG_FILE
return 0
}# 挂载分区
mount_partitions() {
echo "挂载分区..." >> $LOG_FILE
mkdir -p $MOUNT_POINT_ACTIVE $MOUNT_POINT_PASSIVE $UPGRADE_DIR
mount $ACTIVE_PART $MOUNT_POINT_ACTIVE || {
echo "挂载活跃分区失败" >> $LOG_FILE
return 1
}
mount $PASSIVE_PART $MOUNT_POINT_PASSIVE || {
echo "挂载备用分区失败" >> $LOG_FILE
umount $MOUNT_POINT_ACTIVE
return 1
}
mount /dev/mmcblk0p5 $UPGRADE_DIR || {
echo "挂载升级分区失败" >> $LOG_FILE
umount $MOUNT_POINT_ACTIVE
umount $MOUNT_POINT_PASSIVE
return 1
}
return 0
}# 卸载分区
umount_partitions() {
echo "卸载分区..." >> $LOG_FILE
umount $UPGRADE_DIR || echo "卸载升级分区失败" >> $LOG_FILE
umount $MOUNT_POINT_PASSIVE || echo "卸载备用分区失败" >> $LOG_FILE
umount $MOUNT_POINT_ACTIVE || echo "卸载活跃分区失败" >> $LOG_FILE
rmdir $MOUNT_POINT_ACTIVE $MOUNT_POINT_PASSIVE $UPGRADE_DIR 2>/dev/null
}# 检查升级包内容
check_package() {
echo "检查升级包内容..." >> $LOG_FILE
# 解压升级包到临时目录
TEMP_DIR=$(mktemp -d)
if ! tar -zxf "$UPGRADE_PACKAGE" -C $TEMP_DIR; then
echo "解压升级包失败" >> $LOG_FILE
rm -rf $TEMP_DIR
return 1
fi
# 检查必要文件
REQUIRED_FILES=("kernel.img" "rootfs.tar.gz" "apps.tar.gz" "version.txt")
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$TEMP_DIR/$file" ]; then
echo "升级包缺少必要文件: $file" >> $LOG_FILE
rm -rf $TEMP_DIR
return 1
fi
done
# 记录版本信息
echo "升级包版本: $(cat $TEMP_DIR/version.txt)" >> $LOG_FILE
echo "当前系统版本: $(cat $MOUNT_POINT_ACTIVE/etc/version 2>/dev/null || echo '未知')" >> $LOG_FILE
# 保存临时目录路径供后续使用
export TEMP_DIR
return 0
}# 升级内核
upgrade_kernel() {
echo "升级内核..." >> $LOG_FILE
# 备份当前内核
cp $MOUNT_POINT_ACTIVE/boot/kernel.img $MOUNT_POINT_PASSIVE/boot/kernel.img.bak || {
echo "备份当前内核失败" >> $LOG_FILE
return 1
}
# 安装新内核到备用分区
cp $TEMP_DIR/kernel.img $MOUNT_POINT_PASSIVE/boot/kernel.img || {
echo "安装新内核失败" >> $LOG_FILE
return 1
}
# 更新设备树(如果有)
if [ -f "$TEMP_DIR/*.dtb" ]; then
cp $TEMP_DIR/*.dtb $MOUNT_POINT_PASSIVE/boot/ || {
echo "安装设备树失败" >> $LOG_FILE
return 1
}
fi
echo "内核升级完成" >> $LOG_FILE
return 0
}# 升级文件系统
upgrade_rootfs() {
echo "升级文件系统..." >> $LOG_FILE
# 解压新的文件系统到备用分区
if ! tar -zxf $TEMP_DIR/rootfs.tar.gz -C $MOUNT_POINT_PASSIVE; then
echo "解压文件系统失败" >> $LOG_FILE
return 1
}
# 保留必要的配置文件
if [ -d $MOUNT_POINT_ACTIVE/etc/network ]; then
cp -r $MOUNT_POINT_ACTIVE/etc/network $MOUNT_POINT_PASSIVE/etc/ || {
echo "保留网络配置失败" >> $LOG_FILE
# 非致命错误,继续执行
}
fi
# 复制版本信息
cp $TEMP_DIR/version.txt $MOUNT_POINT_PASSIVE/etc/version || {
echo "写入版本信息失败" >> $LOG_FILE
return 1
}
echo "文件系统升级完成" >> $LOG_FILE
return 0
}# 升级应用程序
upgrade_applications() {
echo "升级应用程序..." >> $LOG_FILE
# 解压应用程序包
if ! tar -zxf $TEMP_DIR/apps.tar.gz -C $MOUNT_POINT_PASSIVE/usr/local; then
echo "解压应用程序失败" >> $LOG_FILE
return 1
}
# 运行应用程序升级后脚本(如果存在)
if [ -f "$TEMP_DIR/post_install.sh" ]; then
cp $TEMP_DIR/post_install.sh $MOUNT_POINT_PASSIVE/tmp/
chmod +x $MOUNT_POINT_PASSIVE/tmp/post_install.sh
chroot $MOUNT_POINT_PASSIVE /tmp/post_install.sh >> $LOG_FILE 2>&1 || {
echo "应用程序安装后脚本执行失败" >> $LOG_FILE
# 非致命错误,继续执行
}
rm -f $MOUNT_POINT_PASSIVE/tmp/post_install.sh
fi
echo "应用程序升级完成" >> $LOG_FILE
return 0
}# 更新启动配置,切换到新分区
update_boot_config() {
echo "更新启动配置..." >> $LOG_FILE
# 备份当前启动配置
cp $BOOT_PART/uEnv.txt $BOOT_PART/uEnv.txt.bak || {
echo "备份启动配置失败" >> $LOG_FILE
return 1
}
# 修改U-Boot环境变量,下次从备用分区启动
# 假设使用uEnv.txt配置启动分区
sed -i "s/^bootpart=.*/bootpart=3/" $BOOT_PART/uEnv.txt || {
echo "修改启动配置失败" >> $LOG_FILE
# 恢复备份
mv $BOOT_PART/uEnv.txt.bak $BOOT_PART/uEnv.txt
return 1
}
# 记录分区切换信息,供首次启动验证
echo "UPGRADED=1" > $BOOT_PART/upgrade_flag.txt
echo "PREVIOUS_PART=2" >> $BOOT_PART/upgrade_flag.txt
echo "NEW_PART=3" >> $BOOT_PART/upgrade_flag.txt
echo "启动配置更新完成,下次将从分区3启动" >> $LOG_FILE
return 0
}# 清理临时文件
cleanup() {
echo "清理临时文件..." >> $LOG_FILE
if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then
rm -rf $TEMP_DIR
fi
}# 主升级流程
main() {
init_log
# 解析参数
parse_args "$@"
# 验证升级包
if ! verify_package; then
echo "升级包验证失败,终止升级" >> $LOG_FILE
exit 1
fi
# 挂载必要的分区
if ! mount_partitions; then
echo "分区挂载失败,终止升级" >> $LOG_FILE
exit 1
fi
# 检查升级包内容
if ! check_package; then
echo "升级包内容检查失败,终止升级" >> $LOG_FILE
umount_partitions
cleanup
exit 1
fi
# 执行升级步骤
if ! upgrade_kernel; then
echo "内核升级失败,终止升级" >> $LOG_FILE
umount_partitions
cleanup
exit 1
fi
if ! upgrade_rootfs; then
echo "文件系统升级失败,终止升级" >> $LOG_FILE
umount_partitions
cleanup
exit 1
fi
if ! upgrade_applications; then
echo "应用程序升级失败,终止升级" >> $LOG_FILE
umount_partitions
cleanup
exit 1
fi
# 更新启动配置
if ! update_boot_config; then
echo "启动配置更新失败,终止升级" >> $LOG_FILE
umount_partitions
cleanup
exit 1
fi
# 清理工作
umount_partitions
cleanup
echo "所有升级步骤完成" >> $LOG_FILE
echo "=== 固件升级成功: $(date) ===" >> $LOG_FILE
# 重启系统
if [ "$REBOOT_AFTER_UPGRADE" = true ]; then
echo "3秒后将重启系统..."
sleep 3
reboot
else
echo "升级完成,请手动重启系统以应用更新"
fi
}# 执行主函数
main "$@"
bash
./firmware_upgrade.sh firmware_v2.1.tar.gz
-
系统会自动重启并验证升级结果
verify_upgrade.sh
#!/bin/bash
# 系统启动后验证升级是否成功,失败则回滚LOG_FILE="/var/log/firmware_verify.log"
BOOT_PART="/dev/mmcblk0p1"
MOUNT_POINT_BOOT="/mnt/boot"
ACTIVE_PART="/dev/mmcblk0p2"
PASSIVE_PART="/dev/mmcblk0p3"# 初始化日志
init_log() {
echo "=== 升级验证开始: $(date) ===" >> $LOG_FILE
}# 挂载启动分区
mount_boot() {
mkdir -p $MOUNT_POINT_BOOT
if ! mount $BOOT_PART $MOUNT_POINT_BOOT; then
echo "挂载启动分区失败" >> $LOG_FILE
return 1
fi
return 0
}# 卸载启动分区
umount_boot() {
umount $MOUNT_POINT_BOOT || echo "卸载启动分区失败" >> $LOG_FILE
rmdir $MOUNT_POINT_BOOT 2>/dev/null
}# 验证系统状态
verify_system() {
echo "验证系统状态..." >> $LOG_FILE
# 检查关键服务是否运行
CRITICAL_SERVICES=("sshd" "ntpd" "main_app")
for service in "${CRITICAL_SERVICES[@]}"; do
if ! systemctl is-active --quiet $service; then
echo "关键服务未运行: $service" >> $LOG_FILE
return 1
fi
done
# 检查网络连接
if ! ping -c 1 8.8.8.8 >/dev/null 2>&1; then
echo "网络连接失败" >> $LOG_FILE
return 1
fi
# 检查应用程序功能
if ! /usr/local/bin/main_app --check >/dev/null 2>&1; then
echo "应用程序功能检查失败" >> $LOG_FILE
return 1
fi
echo "系统状态验证成功" >> $LOG_FILE
return 0
}# 执行回滚操作
rollback() {
echo "执行回滚操作..." >> $LOG_FILE
# 读取升级信息
if [ ! -f "$MOUNT_POINT_BOOT/upgrade_flag.txt" ]; then
echo "未找到升级标记文件,无法回滚" >> $LOG_FILE
return 1
fi
source $MOUNT_POINT_BOOT/upgrade_flag.txt
if [ -z "$PREVIOUS_PART" ] || [ -z "$NEW_PART" ]; then
echo "升级标记文件格式错误" >> $LOG_FILE
return 1
fi
# 恢复启动配置
if [ -f "$MOUNT_POINT_BOOT/uEnv.txt.bak" ]; then
mv $MOUNT_POINT_BOOT/uEnv.txt.bak $MOUNT_POINT_BOOT/uEnv.txt || {
echo "恢复启动配置失败" >> $LOG_FILE
return 1
}
else
# 直接修改启动分区配置
sed -i "s/^bootpart=.*/bootpart=$PREVIOUS_PART/" $MOUNT_POINT_BOOT/uEnv.txt || {
echo "修改启动配置失败" >> $LOG_FILE
return 1
}
fi
# 删除升级标记
rm -f $MOUNT_POINT_BOOT/upgrade_flag.txt
echo "回滚操作完成,将重启到原系统" >> $LOG_FILE
return 0
}# 标记升级成功
mark_success() {
echo "标记升级成功..." >> $LOG_FILE
# 删除升级标记
rm -f $MOUNT_POINT_BOOT/upgrade_flag.txt
# 记录成功信息
echo "SYSTEM_UPGRADED_SUCCESSFULLY=1" > $MOUNT_POINT_BOOT/upgrade_success.txt
echo "UPGRADE_DATE=$(date)" >> $MOUNT_POINT_BOOT/upgrade_success.txt
echo "升级成功标记已设置" >> $LOG_FILE
return 0
}# 主函数
main() {
init_log
# 检查是否是升级后的首次启动
if ! mount_boot; then
echo "无法挂载启动分区,无法验证升级状态" >> $LOG_FILE
exit 1
fi
if [ -f "$MOUNT_POINT_BOOT/upgrade_flag.txt" ]; then
echo "检测到刚完成升级,开始验证系统状态" >> $LOG_FILE
# 验证系统状态
if verify_system; then
# 验证成功,标记升级完成
mark_success
else
# 验证失败,执行回滚
if rollback; then
# 回滚配置已完成,重启系统
umount_boot
echo "系统验证失败,已触发回滚,将重启系统" >> $LOG_FILE
reboot
else
echo "回滚操作失败,系统可能处于不稳定状态" >> $LOG_FILE
fi
fi
else
echo "未检测到新升级,无需验证" >> $LOG_FILE
fi
umount_boot
echo "=== 升级验证完成: $(date) ===" >> $LOG_FILE
exit 0
}main "$@"
4. 注意事项
- 确保系统有足够的存储空间
- 升级过程中不要断电
- 首次使用前需生成密钥对并配置公钥
- 测试环境验证通过后再用于生产环境
- 定期备份重要数据
该方案适用于嵌入式 Linux 设备,可根据具体硬件和软件环境进行调整。
转载自CSDN-专业IT技术社区
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/shanstellar/article/details/151799857