前言 手上有 r2s、N1 和 x86_64 的固件维护,r2s 的参照别人的脚本搞了在线升级固件的脚本,别人的脚本只支持 ext4 升级,而后面我也把 squashfs 格式的固件升级搞出来了。恩山上有的人的固件我也看 x86_64 也可以在线升级,后面我也会去测下 x86_64 的,理论上是通用的。
升级过程 以 r2s 为例讲解。参照目前看到的的 1988 的升级脚本 ,最初的人不知道是谁搞的在线升级,因为很久之前就看到有些人的固件能在线升级了。
升级前准备 相关命令 确保固件有下面命令:
command
package name
用途
parted
parted
修改分区和获取分区信息
losetup
losetup
loop device 命令,用于挂载固件里的文件分区
resize2fs
resize2fs
resize ext4 需要
truncate
coreutils-truncate
填充和清空文件,这里是填充扩容
curl
curl
下载,以及http 调用一些 api
wget
wget
下载命令
mksquashfs
squashfs-tools-mksquashfs
squashfs格式需要
unsquashfs
squashfs-tools-unsquashfs
squashfs格式需要
KERNEL_PARTSIZE 和 ROOTFS_PARTSIZE CONFIG_TARGET_KERNEL_PARTSIZE
和 CONFIG_TARGET_ROOTFS_PARTSIZE
是 .config
文件里的,单位是 M
,前者是类似常规大型 linux os 里的 /boot
分区,openwrt 默认就只有这两个分区。
r2s 的话 KERNEL_PARTSIZE
一般 12M
就够用了,但是很多网上互相抄的人在 r2s 的 .config
里给 32、64 之类的非常浪费。ROOTFS_PARTSIZE
是最终的根分区大小,给小了因为编译带很多插件,导致最终的打包镜像容量不够,我的固件是 635
。然后 r2s 是内存卡,一般现在内存大大小都是 4G 以上,也就是刷完固件后,根分区就是 635M ,卡的剩下空间都没使用,当然,x86_64 也是一样的问题。所以就有了这个升级顺带扩容的步骤。
提前的容量存储新固件 1988 的固件非常小,下载 300M,从一个新手初次尝试来说,很可能尴尬的情况就是卡刷后 rootfs 是 600M,然后可用就200M,固件压缩后350M,所以我固件在初次扩容升级会暂时新建一个分区,用于存储下载升级的固件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # 一般很多固件 /opt 单独挂载,或者属于 / ,所以如果已经在升级阶段扩容了,固件就存 /opt下,没扩容过,就存挂载点 /tmp/update/download if [ $(df -m /opt | awk 'NR==2{print $4}') -lt 2400 ];then NEED_GROW=1 mkdir -p /tmp/update/download warning '检测到当前未扩容,先借用初版固件扩容,后续请再执行升级脚本' df -h parted /dev/$block_device p # 该文件存 part_num ,防止机器重启后重复新建了分区表 if [ ! -f '/opt/.parted' ];then start_sec=$(parted /dev/$block_device unit s print free | awk '$1~"s"{a=$1}END{print a}') parted /dev/$block_device mkpart p ext4 ${start_sec} 4G part_num=$( parted /dev/$block_device p | awk '$5=="primary"{a=$1}END{print a}' ) sleep 3 # 此处会自动挂载造成蛋疼 if grep -E /dev/${block_device}p${part_num} /proc/mounts;then if mountpoint -q /mnt/${block_device}p${part_num};then touch /mnt/${block_device}p${part_num}/test &>/dev/null || NEED_MKFS=1 umount /mnt/${block_device}p${part_num} fi [ -n "$NEED_MKFS" ] && mkfs.ext4 -F /dev/${block_device}p${part_num} else mkfs.ext4 -F /dev/${block_device}p${part_num} fi echo ${part_num} > /opt/.parted else part_num=$(cat /opt/.parted) fi mountpoint -q /tmp/update/download || mount /dev/${block_device}p${part_num} /tmp/update/download USER_FILE=/tmp/update/download/openwrt.img.gz rm -f ${USER_FILE} # 因为初次没扩容,我的固件是存放在 docker 镜像里的,可能拉取 docker 镜像就容量满了,所以我单独有个release 存放编译好的固件,直接下载,用于初次 wget https://ghproxy.com/https://github.com/zhangguanzhang/Actions-OpenWrt/releases/download/fs/openwrt-rockchip-armv8-friendlyarm_nanopi-r2s-ext4-sysupgrade.img.gz -O ${USER_FILE} fi
对于后面的下载新版本固件,1988 的脚本我看他是 github action 每天定时编译发布存 release,感觉后面他可能会被 github 给 ban了。 我脚本里是存 docker hub 的镜像里,我的固件都自带 docker,docker 拉取镜像后提取镜像文件:
1 2 3 4 5 6 cat >${BUILD_DIR}/Dockerfile << EOF FROM alpine LABEL FILE=$file LABEL NUM=${GITHUB_RUN_NUMBER} COPY * / EOF
github action 上利用 buildx 构建存储这个镜像,用 LABEL 指定文件路径名,直接 copy 出来:
1 2 3 4 5 6 docker pull zhangguanzhang/r2s:${VER} CTR_PATH=$( docker inspect zhangguanzhang/r2s:${VER} --format '{{ .Config.Labels }}' | grep -Eo 'openwrt-.+img.gz' ) docker create --name update zhangguanzhang/r2s:${VER} docker cp update:/${CTR_PATH} ${USER_FILE} docker rm update docker rmi zhangguanzhang/r2s:${VER}
扩容和升级 固件分为两个文件系统,SquashFS 和 Ext4。
SquashFS(推荐):固件文件名带有 “squashfs”,SquashFS 为只读文件系统,支持系统还原(支持物理 Reset按钮 还原),支持后台固件升级,更能避免 SD 卡文件系统触发写保护,适合绝大部分用户使用。
Ext4:固件文件名带有 “ext4”,Ext4 文件系统具备整个分区可读写性质,更适合熟悉 Linux 系统的用户使用,但意外断电有几率造成分区写入保护。
ext4 前面两个章节是下载和存放固件 img.gz ,现在开始扩容和升级,扩容就是利用 truncate 下固件文件的大小,然后修复固件文件里的第二个分区。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 # 因为最终会把修改后的固件写入到根分区所在的块设备,所以固件需要存放在 /tmp 目录下 # 大小和内存挂钩,所以不要size太大 mount -t tmpfs -o remount,size=870m tmpfs /tmp # 后面的 true 是因为 github action 的打包会影响解压,虽然最终报错,但是解压的固件还是能用的 gzip -dc openwrt.img.gz > /tmp/update/openwrt.img || true block_device='mmcblk0' [ ! -d /sys/block/$block_device ] && block_device='mmcblk1' bs=`expr $(cat /sys/block/$block_device/size) \* 512` # 修改文件大小 truncate -s $bs /tmp/update/openwrt.img # 修改第二个分区大小,1988用的是 echo ", +" | sfdisk -N 2 /tmp/update/openwrt.img 可读性不好 parted /tmp/update/openwrt.img resizepart 2 100% # 将镜像文件虚拟成块设备,类似于 windows 的那种双击 iso 后的装载 iso ,对于块设备的操作都会时刻写入到 img 文件里 lodev=$(losetup -f) losetup -P $lodev /tmp/update/openwrt.img # 挂载 rootfs 解压备份文件 mkdir -p /mnt/img mount -t ext4 ${lodev}p2 /mnt/img # op 的备份命令 sysupgrade -b back.tar.gz tar zxf back.tar.gz -C /mnt/img if ! grep -q macaddr /etc/config/network; then warning '注意:由于已知的问题,“网络接口”配置无法继承,重启后需要重新设置WAN拨号和LAN网段信息' rm /mnt/img/etc/config/network; fi mountpoint -q /mnt/img && umount /mnt/img # openwrt 存在 auto mount,此处取消挂载 grep -q ${lodev}p1 /proc/mounts && umount ${lodev}p1 grep -q ${lodev}p2 /proc/mounts && umount ${lodev}p2 # 修复固件里扩容的分区 e2fsck -yf ${lodev}p2 || true resize2fs ${lodev}p2 # 取消 img 文件的挂载 losetup -d $lodev echo 1 > /proc/sys/kernel/sysrq echo u > /proc/sysrq-trigger && umount / || true # 这个 ddnz 命令从他那里复制的 /tmp/ddnz /tmp/update/openwrt.img /dev/$block_device printf '%b\n' "\033[1;32m[SUCCESS] 刷机完毕,正在重启...\033[0m" # 重启 echo b > /proc/sysrq-trigger
squashfs openwrt 的另一种文件系统固件,就是一个只可读写的压缩的 rootfs 解压开作为 overlay
的 lower dir 只读,提供给用户的是 overlay 的 upper dir 去写入,长按设备上的 reset 按钮恢复出厂设置就是把 overlay 的上层丢弃掉,所以 squashfs 类型的固件带快照功能。当然市面上搜了下也没找到 squashfs 类型的固件在升级的时候扩容的步骤,自己研究了下搞出来了。
1 2 3 4 5 6 7 # 挂载 rootfs 解压备份文件 mkdir -p /mnt/img # 这里会报错 wrong fs type # mount -t ext4 ${lodev} p2 /mnt/img # 被我改成这样 mount ${lodev}p2 /mnt/img IMG_FSTYPE=$(df -T /mnt/img | awk 'NR==2{print $2}')
取到了 IMG_FSTYPE
后走不同的逻辑,这里它的值是 squashfs
,而挂载后的 /mnt/img
是无法写入任何文件的。然后搜了下 squashfs
相关,自己折腾的话需要 mksquashfs
和 unsquashfs
的两个命令玩。一开始是尝试解压 ${lodev}p2
,结果经常 oom ,去找 squashfs-tools 源码作者询问如何限制内存得到下面信息。
1 2 # https://github.com/plougher/squashfs-tools/issues/139#issuecomment-991779738 unsquashfs -da 10 -fr 10 ${lodev}p2
基本一直卡着,毕竟最后肯定要重新 mksquashfs
打包的,然后直接 cp 得了:
1 2 3 4 5 6 7 8 9 10 11 if [ "$IMG_FSTYPE" = 'squashfs' ];then info "检测到使用 squashfs 固件,开始导出文件系统" # https://github.com/plougher/squashfs-tools/issues/139#issuecomment-991779738 # unsquashfs -da 10 -fr 10 /dev/loop0p2 # 这个解压太耗时了,只能拷贝整了 mkdir -p /mnt/img_sq cp -a /mnt/img/* /mnt/img_sq umount /mnt/img/ rm -rf /mnt/img mv /mnt/img_sq /mnt/img fi
然后这个目录写入备份文件,然后就打包:
1 mksquashfs /mnt/img /opt/op.squashfs
结果打包也经常 oom ,看了下命令的帮助,发现有内存限制的,加上也偶尔 oom
1 mksquashfs /mnt/img /opt/op.squashfs -mem 20M
最后逼我用 oom_score_adj
调整 oom 优先级了
1 echo -998 > /proc/$$/oom_score_adj 2>/dev/null || true
当然,实际打包很多选项的,可以先利用 unsquashfs
看下
1 2 3 4 5 6 7 8 9 10 11 unsquashfs -s ${lodev}p2 > squashfs.info comp=$(awk '$1=="Compression"{print $2}' squashfs.info) sq_block_size=$(awk '$1=="Block"{print $NF}' squashfs.info) xattrs='-xattrs' # CONFIG_SELINUX=y grep -Eq 'Xattrs.+?not' squashfs.info && xattrs='-no-xattrs' echo -998 > /proc/$$/oom_score_adj 2>/dev/null || true mksquashfs /mnt/img /opt/op.squashfs -comp ${comp} \ -b $[sq_block_size/1024]k $xattrs -mem 20M
然后写入到块设备上
1 dd if=/opt/op.squashfs of=${lodev}p2
然后卸载 ${lodev} 刷入固件发现无法开机,在 lede 的源码里 find grep 后找到了 mksquashfs 参数来源于源码下 ./include/image.mk
的 SQUASHFSOPT
和 define Image/mkfs/squashfs-common
1 2 3 4 5 6 7 8 9 10 11 12 13 CONFIG_TARGET_SQUASHFS_BLOCK_SIZE=1024k SQUASHFS_BLOCKSIZE := $(CONFIG_TARGET_SQUASHFS_BLOCK_SIZE) k SQUASHFSOPT := -b $(SQUASHFS_BLOCKSIZE) SQUASHFSOPT += -p '/dev d 755 0 0' -p '/dev/console c 600 0 0 5 1' SQUASHFSOPT += $(if $(CONFIG_SELINUX) ,-xattrs,-no-xattrs) SQUASHFSCOMP := gzip LZMA_XZ_OPTIONS := -Xpreset 9 -Xe -Xlc 0 -Xlp 2 -Xpb 2 ifeq ($(CONFIG_SQUASHFS_XZ) ,y) ifneq ($(filter arm x86 powerpc sparc,$(LINUX_KARCH) ) ,) BCJ_FILTER:=-Xbcj $(LINUX_KARCH) endif SQUASHFSCOMP := xz $(LZMA_XZ_OPTIONS) $(BCJ_FILTER) endif
本地搞个编译 openwrt 的时候 make 带上 -V=s
开详细信息看到下面信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 $ /home/guanzhang/lede/staging_dir/host/bin/mksquashfs4 /home/guanzhang/lede/build_dir/target-aarch64_generic_musl/root-rockchip /home/guanzhang/lede/build_dir/target-aarch64_generic_musl/linux-rockchip_armv8/root.squashfs -nopad -noappend -root-owned -comp xz -Xpreset 9 -Xe -Xlc 0 -Xlp 2 -Xpb 2 -b 1024k -p '/dev d 755 0 0' -p '/dev/console c 600 0 0 5 1' -no-xattrs -processors 6 Pseudo file "/dev" exists in source filesystem "/home/guanzhang/lede/build_dir/target-aarch64_generic_musl/root-rockchip/dev". Ignoring, exclude it (-e/-ef) to override. Parallel mksquashfs: Using 6 processors Creating 4.0 filesystem on /home/guanzhang/lede/build_dir/target-aarch64_generic_musl/linux-rockchip_armv8/root.squashfs, block size 1048576. [=============================================================-] 8430/8430 100% Exportable Squashfs 4.0 filesystem, xz compressed, data block size 1048576 compressed data, compressed metadata, compressed fragments, no xattrs, compressed ids duplicates are removed Filesystem size 135525.99 Kbytes (132.35 Mbytes) 25.39% of uncompressed filesystem size (533693.75 Kbytes) Inode table size 60908 bytes (59.48 Kbytes) 20.00% of uncompressed inode table size (304503 bytes) Directory table size 85796 bytes (83.79 Kbytes) 38.42% of uncompressed directory table size (223339 bytes) Number of duplicate files found 1164 Number of inodes 9212 Number of files 8077 Number of fragments 123 Number of symbolic links 647 Number of device nodes 1 Number of fifo nodes 0 Number of socket nodes 0 Number of directories 487 Number of ids (unique uids + gids) 1 Number of uids 1 root (0) Number of gids 1 root (0)
大致参数就是:
1 -nopad -noappend -root-owned -comp xz -Xpreset 9 -Xe -Xlc 0 -Xlp 2 -Xpb 2 -b 1024k -p '/dev d 755 0 0' -p '/dev/console c 600 0 0 5 1' -no-xattrs -processors 6
但是 openwrt 和 Centos 上安装的 squashfs-tools
的 mksquashfs xz 压缩时候都没有 -Xpreset 9 -Xe -Xlc 0 -Xlp 2 -Xpb 2
这些参数,后面发现了是 openwrt 编译的时候下载 squashfs-tools 后打了 patch 编译后才有的,不过后面测试这几个选项不影响。同时看到了打包的脚本:
1 PADDING=1 /home/guanzhang/lede/scripts/gen_image_generic.sh /home/guanzhang/lede/build_dir/target-aarch64_generic_musl/linux-rockchip_armv8/tmp/openwrt-rockchip-armv8-friendlyarm_nanopi-r2s-squashfs-sysupgrade.img.gz 18 /home/guanzhang/lede/build_dir/target-aarch64_generic_musl/linux-rockchip_armv8/tmp/openwrt-rockchip-armv8-friendlyarm_nanopi-r2s-squashfs-sysupgrade.img.gz.boot 635 /home/guanzhang/lede/build_dir/target-aarch64_generic_musl/linux-rockchip_armv8/root.squashfs 32768
得到了参数:
1 2 3 4 5 + dd if=/dev/zero of=/home/guanzhang/lede/build_dir/target-aarch64_generic_musl/linux-rockchip_armv8/tmp/openwrt-rockchip-armv8-friendlyarm_nanopi-r2s-squashfs-sysupgrade.img.gz bs=512 seek=131072 conv=notrunc count=1300480 1300480+0 records in 1300480+0 records out 665845760 bytes (666 MB, 635 MiB) copied, 2.12896 s, 313 MB/s + dd if=/home/guanzhang/lede/build_dir/target-aarch64_generic_musl/linux-rockchip_armv8/root.squashfs of=/home/guanzhang/lede/build_dir/target-aarch64_generic_musl/linux-rockchip_armv8/tmp/openwrt-rockchip-armv8-friendlyarm_nanopi-r2s-squashfs-sysupgrade.img.gz bs=512 seek=131072 conv=notrunc
可以看到是直接写 img 文件的,这里虽然显示的是 img.gz ,但是如果压缩后的文件的话,那 seek 大小就不对。实际上后面才压缩的,所以我的参数为
1 2 3 4 5 6 7 8 9 part2_seek=$(parted /tmp/update/openwrt.img u s p | awk '$1==2{print +$2}') mksquashfs /mnt/img /opt/op.squashfs -nopad -noappend -root-owned \ -comp ${comp} ${LZMA_XZ_OPTIONS} \ -b $[sq_block_size/1024]k \ -p '/dev d 755 0 0' -p '/dev/console c 600 0 0 5 1' \ $xattrs -mem 20M losetup -l -O NAME -n | grep -Eqw $lodev && losetup -d $lodev dd if=/opt/op.squashfs of=/tmp/update/openwrt.img bs=512 seek=${part2_seek} conv=notrunc
然后写入即可
最终参考 我的脚本当前存放在 test 分支,可能后续切到 main 分支:
1 2 3 4 5 https://github.com/zhangguanzhang/Actions-OpenWrt/blob/test/build/scripts/update.sh https://github.com/zhangguanzhang/Actions-OpenWrt/blob/main/build/scripts/update.sh
参考