You may build your using using
curl https://gist.githubusercontent.com/npenin/f3c9f59460f3fb6424be9aef5c6ce47b/raw/alpineos.sh | bash | #!/bin/sh | |
| set -e | |
| # ---------------------------------- | |
| # Config | |
| # ---------------------------------- | |
| ALPINE_VERSION="latest-stable" | |
| ARCH="aarch64" | |
| MIRROR="https://dl-cdn.alpinelinux.org/alpine" | |
| FIRMWARE_BASE_URL="https://github.com/raspberrypi/firmware/raw/master/boot" | |
| WORKDIR="$(pwd)/alpine-pi-sdcard" | |
| OUTPUT_DIR="$WORKDIR/boot" | |
| ROOTFS_DIR="$WORKDIR/rootfs" | |
| ROOTFS_TAR="$WORKDIR/rootfs.tar.gz" | |
| ZIP_OUT="$(pwd)/alpineos.zip" | |
| ALPINE_URL="$MIRROR/$ALPINE_VERSION/releases/$ARCH/alpine-minirootfs-latest-$ARCH.tar.gz" | |
| mkdir -p "$OUTPUT_DIR" "$ROOTFS_DIR" | |
| # ---------------------------------- | |
| # Helper: cached curl | |
| # ---------------------------------- | |
| cached_curl() { | |
| url="$1" | |
| out="$2" | |
| if [ -f "$out" ]; then | |
| echo "Already cached: $out" | |
| return 0 | |
| fi | |
| echo "Downloading: $url" | |
| curl -L --fail --silent --show-error -o "$out" "$url" | |
| } | |
| # ---------------------------------- | |
| # 1️⃣ Download Alpine minirootfs | |
| # ---------------------------------- | |
| echo "[1/7] Downloading Alpine minirootfs ($ARCH)..." | |
| # Resolve latest stable full version | |
| ALPINE_DIR=$(curl -s https://dl-cdn.alpinelinux.org/alpine/ | grep -Eo 'v[0-9]+\.[0-9]+/' | sort -V | tail -n1 | tr -d '/') | |
| echo "[info] Latest stable directory: $ALPINE_DIR" | |
| # Fetch latest version number inside that directory | |
| FULL_VERSION=$(curl -s https://dl-cdn.alpinelinux.org/alpine/$ALPINE_DIR/releases/$ARCH/ | \ | |
| grep -Eo 'alpine-minirootfs-[0-9]+\.[0-9]+\.[0-9]+-'$ARCH'.tar.gz' | head -n1 | \ | |
| sed -E 's/alpine-minirootfs-(.*)-'$ARCH'.tar.gz/\1/') | |
| echo "[info] Full version detected: $FULL_VERSION" | |
| # Download URL | |
| ALPINE_URL="https://dl-cdn.alpinelinux.org/alpine/$ALPINE_DIR/releases/$ARCH/alpine-minirootfs-$FULL_VERSION-$ARCH.tar.gz" | |
| cached_curl "$ALPINE_URL" "$ROOTFS_TAR" | |
| echo "[2/7] Alpine rootfs download complete." | |
| # ---------------------------------- | |
| # 2️⃣ Raspberry Pi firmware (optimized, POSIX) | |
| # ---------------------------------- | |
| echo "[3/7] Downloading Raspberry Pi firmware..." | |
| download_once() { | |
| url="$1" | |
| out="$2" | |
| if [ -f "$out" ]; then | |
| echo "Already downloaded: $out" | |
| return | |
| fi | |
| cached_curl "$url" "$out" | |
| } | |
| # Shared firmware for modern Pis | |
| FILES="start4.elf start4cd.elf start4x.elf fixup4.dat fixup4cd.dat fixup4x.dat kernel7.img kernel7l.img kernel8.img" | |
| for f in $FILES; do | |
| URL="https://github.com/raspberrypi/firmware/raw/master/boot/$f" | |
| download_once "$URL" "$OUTPUT_DIR/$f" | |
| done | |
| DTB_FILES=" | |
| bcm2708-rpi-b-plus.dtb | |
| bcm2708-rpi-b-rev1.dtb | |
| bcm2708-rpi-b.dtb | |
| bcm2708-rpi-cm.dtb | |
| bcm2708-rpi-zero-w.dtb | |
| bcm2708-rpi-zero.dtb | |
| bcm2709-rpi-2-b.dtb | |
| bcm2709-rpi-cm2.dtb | |
| bcm2710-rpi-2-b.dtb | |
| bcm2710-rpi-3-b-plus.dtb | |
| bcm2710-rpi-3-b.dtb | |
| bcm2710-rpi-cm0.dtb | |
| bcm2710-rpi-cm3.dtb | |
| bcm2710-rpi-zero-2-w.dtb | |
| bcm2710-rpi-zero-2.dtb | |
| bcm2711-rpi-4-b.dtb | |
| bcm2711-rpi-400.dtb | |
| bcm2711-rpi-cm4-io.dtb | |
| bcm2711-rpi-cm4.dtb | |
| bcm2711-rpi-cm4s.dtb | |
| bcm2712-d-rpi-5-b.dtb | |
| bcm2712-rpi-5-b.dtb | |
| bcm2712-rpi-500.dtb | |
| bcm2712-rpi-cm5-cm4io.dtb | |
| bcm2712-rpi-cm5-cm5io.dtb | |
| bcm2712-rpi-cm5l-cm4io.dtb | |
| bcm2712-rpi-cm5l-cm5io.dtb | |
| bcm2712d0-rpi-5-b.dtb | |
| " | |
| echo "[4/7] Downloading device trees..." | |
| for dtb in $DTB_FILES; do | |
| URL="https://github.com/raspberrypi/firmware/raw/master/boot/$dtb" | |
| download_once "$URL" "$OUTPUT_DIR/$dtb" | |
| done | |
| # ---------------------------------- | |
| # 3️⃣ Unpack rootfs | |
| # ---------------------------------- | |
| echo "[5/7] Extracting Alpine rootfs..." | |
| tar -xzf "$ROOTFS_TAR" -C "$ROOTFS_DIR" | |
| # ---------------------------------- | |
| # 4️⃣ Add system management scripts | |
| # ---------------------------------- | |
| echo "[6/7] Adding update-system and rollback-system..." | |
| mkdir -p "$ROOTFS_DIR/usr/local/sbin" | |
| # --- update-system --- | |
| cat > "$ROOTFS_DIR/usr/local/sbin/update-system" <<"EOF" | |
| #!/bin/sh | |
| set -e | |
| MNT_ROOT="/mnt" | |
| BTRFS_DEV="/dev/mmcblk0p2" # adjust as needed | |
| MIRROR="https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/aarch64" | |
| TMPDIR="/tmp/update" | |
| NEW_SUBVOL="@system-new" | |
| OLD_SUBVOL="@system-prev" | |
| CURRENT_SUBVOL="@system" | |
| echo "[update-system] Starting atomic update..." | |
| mkdir -p "$MNT_ROOT" "$TMPDIR" | |
| mount -o subvol=/ "$BTRFS_DEV" "$MNT_ROOT" | |
| btrfs subvolume delete "$MNT_ROOT/$NEW_SUBVOL" 2>/dev/null || true | |
| echo "[1/4] Downloading new rootfs..." | |
| wget -qO "$TMPDIR/rootfs.tar.gz" "$MIRROR/alpine-minirootfs-$(uname -m).tar.gz" | |
| echo "[2/4] Creating new snapshot..." | |
| btrfs subvolume create "$MNT_ROOT/$NEW_SUBVOL" | |
| tar -xzf "$TMPDIR/rootfs.tar.gz" -C "$MNT_ROOT/$NEW_SUBVOL" | |
| echo "[3/4] Rotating subvolumes..." | |
| if [ -d "$MNT_ROOT/$OLD_SUBVOL" ]; then | |
| btrfs subvolume delete "$MNT_ROOT/$OLD_SUBVOL" | |
| fi | |
| mv "$MNT_ROOT/$CURRENT_SUBVOL" "$MNT_ROOT/$OLD_SUBVOL" | |
| mv "$MNT_ROOT/$NEW_SUBVOL" "$MNT_ROOT/$CURRENT_SUBVOL" | |
| umount "$MNT_ROOT" | |
| rm -rf "$TMPDIR" | |
| echo "[4/4] Update complete. Reboot to use new system." | |
| echo "Use rollback-system to revert if needed." | |
| EOF | |
| chmod +x "$ROOTFS_DIR/usr/local/sbin/update-system" | |
| # --- rollback-system --- | |
| cat > "$ROOTFS_DIR/usr/local/sbin/rollback-system" <<"EOF" | |
| #!/bin/sh | |
| set -e | |
| MNT_ROOT="/mnt" | |
| BTRFS_DEV="/dev/mmcblk0p2" # adjust as needed | |
| CURRENT_SUBVOL="@system" | |
| PREV_SUBVOL="@system-prev" | |
| BROKEN_SUBVOL="@system-broken" | |
| echo "[rollback-system] Starting rollback..." | |
| mkdir -p "$MNT_ROOT" | |
| mount -o subvol=/ "$BTRFS_DEV" "$MNT_ROOT" | |
| if [ ! -d "$MNT_ROOT/$PREV_SUBVOL" ]; then | |
| echo "Error: No previous snapshot found." | |
| umount "$MNT_ROOT" | |
| exit 1 | |
| fi | |
| if [ -d "$MNT_ROOT/$BROKEN_SUBVOL" ]; then | |
| btrfs subvolume delete "$MNT_ROOT/$BROKEN_SUBVOL" | |
| fi | |
| mv "$MNT_ROOT/$CURRENT_SUBVOL" "$MNT_ROOT/$BROKEN_SUBVOL" | |
| mv "$MNT_ROOT/$PREV_SUBVOL" "$MNT_ROOT/$CURRENT_SUBVOL" | |
| sync | |
| umount "$MNT_ROOT" | |
| echo "Rollback complete. Reboot to use previous system." | |
| EOF | |
| chmod +x "$ROOTFS_DIR/usr/local/sbin/rollback-system" | |
| # ---------------------------------- | |
| # 5️⃣ Add boot configuration | |
| # ---------------------------------- | |
| echo "[7/7] Creating boot configuration..." | |
| cat > "$OUTPUT_DIR/cmdline.txt" <<'EOF' | |
| console=tty1 root=/dev/mmcblk0p2 rootfstype=btrfs rootflags=subvol=@system rw rootwait | |
| EOF | |
| cat > "$OUTPUT_DIR/cmdline-prev.txt" <<'EOF' | |
| console=tty1 root=/dev/mmcblk0p2 rootfstype=btrfs rootflags=subvol=@system-prev rw rootwait | |
| EOF | |
| cat > "$OUTPUT_DIR/config.txt" <<'EOF' | |
| enable_uart=1 | |
| arm_64bit=1 | |
| kernel=kernel8.img | |
| initramfs initramfs.img followkernel | |
| EOF | |
| # ---------------------------------- | |
| # 6️⃣ Finalize image folder | |
| # ---------------------------------- | |
| echo "[✔] System ready in $WORKDIR" | |
| echo "Creating distributable ZIP..." | |
| cd "$WORKDIR" | |
| zip -qr "$ZIP_OUT" . -x "rootfs.tar.gz" | |
| cd - | |
| echo "[✅] Image build complete: $ZIP_OUT" | |
| echo "→ To use: format SD card as FAT32, unzip contents directly onto it." | |
| echo "→ On first boot, system will self-install onto BTRFS and create subvolumes." | |