penguin/monok8s

k8s image for Mono Gateway Dev Kit

commit f67c338e60900074b3c02ea207b35e159ed5fb682fd04595b76f821b3bfca41a

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-03-30T20:26:03Z
subjectDraft for making OTA images
commit f67c338e60900074b3c02ea207b35e159ed5fb682fd04595b76f821b3bfca41a
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-03-30T20:26:03Z

    Draft for making OTA images
---
 alpine/build-rootfs.sh       | 162 ++++++++++++++++++++++++++++++++++++-------
 alpine/sync-loop.sh          |  22 ++++--
 docker/alpine.Dockerfile     |   4 +-
 docker/build-base.Dockerfile |   2 +
 makefile                     |   6 ++
 5 files changed, 161 insertions(+), 35 deletions(-)

diff --git a/alpine/build-rootfs.sh b/alpine/build-rootfs.sh
index a50ad6b..a57e359 100755
--- a/alpine/build-rootfs.sh
+++ b/alpine/build-rootfs.sh
@@ -50,12 +50,122 @@ rm -r "$ROOTFS/build"
 rm "$ROOTFS/etc/resolv.conf"
 
 ### Begin making full disk image for the device
-echo "##################################################### Packaging RootFS "$( du -sh "$ROOTFS/" )
+#!/bin/bash
+set -euo pipefail
+
+ROOTFS="${ROOTFS:?ROOTFS is required}"
+BOARD_ITB="${BOARD_ITB:-/build/board.itb}"
+
+IMG="${IMG:-output.img}"
+DISK_SIZE="${DISK_SIZE:-8G}"
+
+ROOTFS_IMG="${ROOTFS_IMG:-rootfs.ext4}"
+ROOTFS_IMG_ZST="${ROOTFS_IMG_ZST:-rootfs.ext4.zst}"
+ROOTFS_PART_SIZE_MIB="${ROOTFS_PART_SIZE_MIB:-2048}"
+
+FAKE_DEV="/tmp/dev"
+MNT_ROOTFS_IMG="/mnt/rootfs-img"
+MNT_DATA="/mnt/data"
+
+LOOP=""
+ROOTFS_LOOP=""
+TMP_LOOP=""
+ROOTFS_TMP_LOOP=""
+
+cleanup_fake_nodes() {
+    local prefix="$1"
+    [ -n "$prefix" ] || return 0
+    find "$FAKE_DEV" -maxdepth 1 -type b -name "${prefix}*" -exec rm -f {} \; 2>/dev/null || true
+}
+
+cleanup() {
+    set +e
+
+    mountpoint -q "$MNT_ROOTFS_IMG" && umount "$MNT_ROOTFS_IMG"
+    mountpoint -q "$MNT_DATA" && umount "$MNT_DATA"
+
+    if [ -n "$ROOTFS_LOOP" ]; then
+        losetup -d "$ROOTFS_LOOP" 2>/dev/null || true
+    fi
+    if [ -n "$LOOP" ]; then
+        losetup -d "$LOOP" 2>/dev/null || true
+    fi
+
+    if [ -n "$ROOTFS_LOOP" ]; then
+        cleanup_fake_nodes "$(basename "$ROOTFS_LOOP")"
+    fi
+    if [ -n "$LOOP" ]; then
+        cleanup_fake_nodes "$(basename "$LOOP")"
+    fi
+}
+trap cleanup EXIT
+
+mkdir -p "$FAKE_DEV" "$MNT_ROOTFS_IMG" "$MNT_DATA"
+
+echo "##################################################### Packaging RootFS $(du -sh "$ROOTFS" | awk '{print $1}')"
+
+###############################################################################
+# 1. Build reusable rootfs ext4 image once
+###############################################################################
+
+ROOTFS_BYTES=$(du -s -B1 "$ROOTFS" | awk '{print $1}')
+EXTRA_BYTES=$((256 * 1024 * 1024))
+IMG_BYTES=$(( ROOTFS_BYTES + ROOTFS_BYTES / 4 + EXTRA_BYTES ))
+
+ALIGN=$((4 * 1024 * 1024))
+IMG_BYTES=$(( (IMG_BYTES + ALIGN - 1) / ALIGN * ALIGN ))
 
-IMG=output.img
-SIZE=8GB
+MAX_BYTES=$(( ROOTFS_PART_SIZE_MIB * 1024 * 1024 ))
+if [ "$IMG_BYTES" -ge "$MAX_BYTES" ]; then
+    echo "ERROR: estimated rootfs image size $IMG_BYTES exceeds slot size $MAX_BYTES" >&2
+    exit 1
+fi
 
-dd if=/dev/zero of="$IMG" bs=1 count=0 seek=$SIZE
+rm -f "$ROOTFS_IMG" "$ROOTFS_IMG_ZST"
+
+truncate -s "$IMG_BYTES" "$ROOTFS_IMG"
+mkfs.ext4 -F -L rootfs "$ROOTFS_IMG"
+
+ROOTFS_LOOP=$(losetup --find --show -P "$ROOTFS_IMG")
+/sync-loop.sh "$ROOTFS_LOOP"
+
+# For a raw ext4 image there is usually no partition, so mount the loop device directly.
+mount "$ROOTFS_LOOP" "$MNT_ROOTFS_IMG"
+
+(
+    cd "$ROOTFS"
+    tar cpf - --exclude='./var' .
+) | (
+    cd "$MNT_ROOTFS_IMG"
+    tar xpf -
+)
+
+mkdir -p "$MNT_ROOTFS_IMG/var"
+mkdir -p "$MNT_ROOTFS_IMG/boot"
+cp "$BOARD_ITB" "$MNT_ROOTFS_IMG/boot/kernel.itb"
+
+sync
+umount "$MNT_ROOTFS_IMG"
+
+losetup -d "$ROOTFS_LOOP"
+cleanup_fake_nodes "$(basename "$ROOTFS_LOOP")"
+ROOTFS_LOOP=""
+
+e2fsck -fy "$ROOTFS_IMG"
+resize2fs -M "$ROOTFS_IMG"
+e2fsck -fy "$ROOTFS_IMG"
+
+echo "##################################################### Compressing OTA Image"
+zstd -19 -T0 -f "$ROOTFS_IMG" -o "$ROOTFS_IMG_ZST"
+sha256sum "$ROOTFS_IMG" > "$ROOTFS_IMG.sha256"
+sha256sum "$ROOTFS_IMG_ZST" > "$ROOTFS_IMG_ZST.sha256"
+
+###############################################################################
+# 2. Build full disk image
+###############################################################################
+
+rm -f "$IMG"
+truncate -s "$DISK_SIZE" "$IMG"
 
 sgdisk -o "$IMG" \
   -n 1:2048:+64M  -t 1:0700 -c 1:config \
@@ -65,38 +175,38 @@ sgdisk -o "$IMG" \
 
 losetup -D
 LOOP=$(losetup --find --show -P "$IMG")
-
 /sync-loop.sh "$LOOP"
 
-TMP_LOOP="/tmp$LOOP"
-mkfs.vfat -F 32 -n MONOK8S_CFG "${TMP_LOOP}p1"
-mkfs.ext4 -F "${TMP_LOOP}p2"
-mkfs.ext4 -F "${TMP_LOOP}p3"
-mkfs.ext4 -F "${TMP_LOOP}p4"
+TMP_LOOP="$FAKE_DEV/$(basename "$LOOP")"
 
-mkdir -p /mnt/img-root /mnt/data
+mkfs.vfat -F 32 -n MONOK8S_CFG "${TMP_LOOP}p1"
+mkfs.ext4 -F -L rootfsB "${TMP_LOOP}p3"
+mkfs.ext4 -F -L data "${TMP_LOOP}p4"
 
-mount "${TMP_LOOP}p2" /mnt/img-root
-mount "${TMP_LOOP}p4" /mnt/data
+dd if="$ROOTFS_IMG" of="${TMP_LOOP}p2" bs=4M conv=fsync
 
-# Put the real /var onto the data partition
-cp -a "$ROOTFS/var" /mnt/data/
-mkdir -p /mnt/data/etc-overlay/work
-mkdir -p /mnt/data/etc-overlay/upper
+# Grow each filesystem to fill its partition
+e2fsck -fy "${TMP_LOOP}p2"
+resize2fs "${TMP_LOOP}p2"
 
-# Copy rootfs to root partition, but exclude /var
-cp -a "$ROOTFS"/. /mnt/img-root/
-rm -rf /mnt/img-root/var
-mkdir -p /mnt/img-root/var
+# Populate data partition
+mount "${TMP_LOOP}p4" "$MNT_DATA"
 
-mkdir -p /mnt/img-root/boot
-cp /build/board.itb /mnt/img-root/boot/kernel.itb
+cp -a "$ROOTFS/var" "$MNT_DATA/"
+mkdir -p "$MNT_DATA/etc-overlay/work"
+mkdir -p "$MNT_DATA/etc-overlay/upper"
 
 sync
-umount /mnt/img-root
-umount /mnt/data
+umount "$MNT_DATA"
 
 losetup -d "$LOOP"
+cleanup_fake_nodes "$(basename "$LOOP")"
+LOOP=""
+
+echo "Built artifacts:"
+echo "  Full disk image: $IMG"
+echo "  Rootfs OTA image: $ROOTFS_IMG"
+echo "  Rootfs OTA compressed: $ROOTFS_IMG_ZST"
 
-echo "##################################################### Compressing Image"
+echo "##################################################### Compressing Full Disk Image"
 gzip "/build/$IMG"
diff --git a/alpine/sync-loop.sh b/alpine/sync-loop.sh
index c7baeaa..0e8daa6 100755
--- a/alpine/sync-loop.sh
+++ b/alpine/sync-loop.sh
@@ -1,21 +1,29 @@
 #!/bin/bash
+set -euo pipefail
+
 DEVICE="$1"
 FAKE_DEV="/tmp/dev"
 
 mkdir -p "$FAKE_DEV"
+PARENT_NAME=$(basename "$DEVICE")
+
+echo "Refreshing partition table for $DEVICE..."
+partx -u "$DEVICE" 2>/dev/null || partx -a "$DEVICE" || true
 
-echo "Refreshing partition table..."
-partx -u "$DEVICE" 2>/dev/null || partx -a "$DEVICE"
+# Remove old fake nodes for this loop device first
+find "$FAKE_DEV" -maxdepth 1 -type b -name "${PARENT_NAME}*" -exec rm -f {} \;
 
-# Find partitions and their Major:Minor numbers
 lsblk -rn -o NAME,MAJ:MIN "$DEVICE" | while read -r NAME MAJMIN; do
-    # Skip the parent loop0
-    if [[ "$NAME" == "loop0" ]]; then continue; fi
+    # Skip the parent loop device itself
+    if [[ "$NAME" == "$PARENT_NAME" ]]; then
+        continue
+    fi
 
     PART_PATH="$FAKE_DEV/$NAME"
-    MAJOR=$(echo $MAJMIN | cut -d: -f1)
-    MINOR=$(echo $MAJMIN | cut -d: -f2)
+    MAJOR="${MAJMIN%%:*}"
+    MINOR="${MAJMIN##*:}"
 
     echo "Creating node: $PART_PATH (b $MAJOR $MINOR)"
+    rm -f "$PART_PATH"
     mknod "$PART_PATH" b "$MAJOR" "$MINOR"
 done
diff --git a/docker/alpine.Dockerfile b/docker/alpine.Dockerfile
index 2cdb6a5..3ca42a2 100644
--- a/docker/alpine.Dockerfile
+++ b/docker/alpine.Dockerfile
@@ -1,7 +1,7 @@
-ARG TAG=dev
+ARG BUILD_BASE_TAG=dev
 ARG DOCKER_IMAGE_ROOT=monok8s
 
-FROM --platform=$BUILDPLATFORM ${DOCKER_IMAGE_ROOT}/build-base:${TAG} AS build-base
+FROM --platform=$BUILDPLATFORM ${DOCKER_IMAGE_ROOT}/build-base:${BUILD_BASE_TAG} AS build-base
 
 ARG TAG
 ARG ALPINE_ARCH
diff --git a/docker/build-base.Dockerfile b/docker/build-base.Dockerfile
index 7d4e361..264e3e3 100644
--- a/docker/build-base.Dockerfile
+++ b/docker/build-base.Dockerfile
@@ -28,6 +28,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
     pahole \
 	parted \
     perl \
+	pv \
     python3 \
 	qemu-user-static \
 	podman \
@@ -36,6 +37,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
     tar \
 	udev \
     xz-utils \
+	zstd \
     dwarves \
     gcc-aarch64-linux-gnu \
     binutils-aarch64-linux-gnu \
diff --git a/makefile b/makefile
index 04f8179..a797be8 100644
--- a/makefile
+++ b/makefile
@@ -39,6 +39,7 @@ BUILD_VERSION   ?= $(KUBE_VERSION)
 BUILD_DATE      := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
 BUILD_GIT       := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
 BUILD_INFO_FILE := $(OUT_DIR)/build-info
+BUILD_BASE_TAG := $(shell docker image inspect monok8s/build-base:dev | jq -r '.[].Id' | cut -d':' -f2 | cut -c -8 || echo dev)
 
 # ---- File groups -------------------------------------------------------------
 
@@ -148,6 +149,8 @@ $(BUILD_BASE_STAMP): $(BUILD_BASE_DEPS) | $(OUT_DIR)
 		-f docker/build-base.Dockerfile \
 		--build-arg TAG=$(TAG) \
 		-t $(DOCKER_IMAGE_ROOT)/build-base:$(TAG) .
+	@iid=$$(docker image inspect monok8s/build-base:$(TAG) | jq -r '.[].Id' | cut -d':' -f2 | cut -c -8); \
+	docker tag monok8s/build-base:$(TAG) monok8s/build-base:$$iid; \
 	touch $@
 
 $(KERNEL_IMAGE): $(KERNEL_DEPS) | $(OUT_DIR)
@@ -191,6 +194,7 @@ $(BOARD_ITB): $(ITB_DEPS) | $(OUT_DIR)
 $(RELEASE_IMAGE): $(RELEASE_DEPS) | $(OUT_DIR)
 	docker build \
 		-f docker/alpine.Dockerfile \
+		--no-cache \
 		--build-arg DOCKER_IMAGE_ROOT=$(DOCKER_IMAGE_ROOT) \
 		--build-arg TAG=$(TAG) \
 		--build-arg ALPINE_ARCH=$(ALPINE_ARCH) \
@@ -198,6 +202,7 @@ $(RELEASE_IMAGE): $(RELEASE_DEPS) | $(OUT_DIR)
 		--build-arg KUBE_VERSION=$(KUBE_VERSION) \
 		--build-arg CRIO_VERSION=$(CRIO_VERSION) \
 		--build-arg DEVICE_TREE_TARGET=$(DEVICE_TREE_TARGET) \
+		--build-arg BUILD_BASE_TAG=$(BUILD_BASE_TAG) \
 		-t $(DOCKER_IMAGE_ROOT)/buildenv-alpine:$(TAG) .
 
 	@cid=$$(docker create \
@@ -219,6 +224,7 @@ $(RELEASE_IMAGE): $(RELEASE_DEPS) | $(OUT_DIR)
 		bash -lc '/build-rootfs.sh'); \
 	docker start -a $$cid; \
 	docker cp $$cid:/build/output.img.gz $@; \
+	docker cp $$cid:/build/rootfs.ext4.zst $(OUT_DIR)/rootfs.ext4.zst; \
 	docker rm $$cid
 
 	test -f $@