把树莓派 4 做成一台 Kodi 电视盒子:下载官方 LibreELEC 镜像,用一个“带护栏”的脚本写入 SD 卡。脚本会校验 SHA256、拒绝写入系统盘或非可移动磁盘、写之前要求你手动输入设备名确认、写完再回读校验;写好以后还能检查卡上的启动分区。
先看清成品
- 镜像: 下载了官方的
LibreELEC-RPi4.aarch64-12.2.1.img.gz(LibreELEC 12.2,Kodi 21.3),sha256sum -c结果为 OK。- 写卡: box 上没有 SD 卡,我用 8 GB 的 loop 设备代替:
- 前 4 道护栏都实测过:loop 设备被识别为“不是可移动磁盘”而拒绝、系统盘
/dev/vdb被拒绝、SHA 被篡改时拒绝、设备名输错时取消。- 测试模式下真实写入 576 MB,回读校验通过。
- 检查分区:
check_card.py读出了 512 MiB 的 FAT 分区和 32 MiB 的 ext4 分区,kernel.img、SYSTEM、config.txt、cmdline.txt都在。- 没有测过: 没有在真实的树莓派上启动(box 没有这个硬件)。
注意:Kodi 官方版本本身不带任何内容。只播放你有权使用的媒体(自己拍的、自己购买的、公共领域或开放授权的),不要安装盗版插件或第三方“全能”仓库。Team Kodi 明确表示不认可盗版内容,官方论坛也禁止讨论盗版插件。
⚠ 变砖和保修风险:刷系统可能导致设备无法启动,并且可能让厂商保修失效;运营商定制的盒子通常属于运营商资产,刷机前先确认你有权这样处理。本文只演示从 SD 卡或 U 盘启动,不改动盒子内置的 eMMC。写卡之前反复确认目标设备名,写错了会清空你电脑上的硬盘。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
flash_sd.sh |
校验 → 设备检查 → 手动确认 → 写入 → 回读校验 |
check_card.py |
读取分区表,检查启动文件,打印 config.txt 里的有效行 |
| 一台 Kodi 盒子 | 树莓派 4 + LibreELEC 12.2 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| 硬件 | 树莓派 4(官方下载页上 RPi 4/400/CM4 用同一个镜像)、官方电源、HDMI 线 | 树莓派 5 用 RPi5 镜像 |
| SD 卡 | 16 GB 以上的品牌卡 | 扩容卡在回读校验时会暴露出来 |
| 电脑 | Linux(Windows 和 macOS 可以用官方的 USB-SD Creator) | 需要 sudo,mtools 用来检查分区 |
文件结构
37-kodi-A-src/
├── flash_sd.sh
└── check_card.py
第 1 步:下载并校验
wget https://releases.libreelec.tv/LibreELEC-RPi4.aarch64-12.2.1.img.gz
wget https://releases.libreelec.tv/LibreELEC-RPi4.aarch64-12.2.1.img.gz.sha256
sha256sum -c LibreELEC-RPi4.aarch64-12.2.1.img.gz.sha256 # 实测:OK
LibreELEC Wiki 推荐用官方的 “LibreELEC USB-SD Creator”(支持 Windows 10/11 和 macOS 14 以上)。替代工具有 Raspberry Pi Imager 和 balenaEtcher。在 Linux 上可以用下面的脚本。
第 2 步:带护栏的写卡脚本
flash_sd.sh:
#!/usr/bin/env bash
# 把 LibreELEC 镜像(.img.gz)安全地写入 SD 卡或 U 盘(Linux)。
# 用法:sudo ./flash_sd.sh LibreELEC-xxx.img.gz /dev/sdX [--dry-run]
# 安全检查:校验官方 .sha256 → 目标必须是整块可移动磁盘 → 不能有分区处于挂载状态 → 容量 4–256 GB → 手动输入设备名确认
# 测试时可以设 ALLOW_LOOP=1,用 loop 设备代替 SD 卡(loop 设备不算“可移动磁盘”)
set -euo pipefail
IMG=${1:?镜像文件}; DEV=${2:?目标设备,例如 /dev/sdb}; DRY=${3:-}
die() { echo "✗ $*" >&2; exit 1; }
[ -f "$IMG.sha256" ] || die "缺少 $IMG.sha256(从同一个下载页获取)"
want=$(awk '{print $1}' "$IMG.sha256"); got=$(sha256sum "$IMG" | awk '{print $1}')
[ "$want" = "$got" ] || die "SHA256 不一致:文件损坏或被篡改(期望 $want,实际 $got)"
echo "✓ SHA256 校验通过"
[ -b "$DEV" ] || die "$DEV 不是块设备"
[ "$(lsblk -dno TYPE "$DEV")" = disk ] || [ "$(lsblk -dno TYPE "$DEV")" = loop ] || die "$DEV 不是整块磁盘(不要写到分区上,例如 /dev/sdb1)"
rm=$(lsblk -dno RM "$DEV" | tr -d ' '); tran=$(lsblk -dno TRAN "$DEV" | tr -d ' ')
if [ "${ALLOW_LOOP:-0}" = 1 ] && [[ "$DEV" == /dev/loop* ]]; then echo "! 测试模式:使用 loop 设备"
else [ "$rm" = 1 ] || [ "$tran" = usb ] || [ "$tran" = mmc ] || die "$DEV 不是可移动磁盘(RM=$rm TRAN=$tran),为防止写坏系统盘,已停止"; fi
root_src=$(findmnt -no SOURCE / || true)
[[ "$root_src" == "$DEV"* ]] && die "$DEV 是当前系统盘"
mnt=$(lsblk -nro MOUNTPOINT "$DEV" | grep -v '^$' || true)
[ -z "$mnt" ] || die "$DEV 上有分区正在挂载:$mnt(先 umount)"
size=$(lsblk -bdno SIZE "$DEV"); gb=$((size / 1000000000))
[ "$gb" -ge 4 ] && [ "$gb" -le 256 ] || die "容量 ${gb} GB 不在 4–256 GB 范围内,不像 SD 卡或 U 盘"
echo "目标:$DEV ${gb} GB $(lsblk -dno MODEL "$DEV" 2>/dev/null)"
[ "$DRY" = --dry-run ] && { echo "(--dry-run:检查全部通过,不写入)"; exit 0; }
read -rp "会清空 $DEV 上的全部数据。确认请输入设备名($DEV):" ans
[ "$ans" = "$DEV" ] || die "输入不一致,已取消"
gzip -dc "$IMG" | dd of="$DEV" bs=4M iflag=fullblock conv=fsync status=progress
sync
# 回读校验:从设备读出与镜像同样长度的数据,比较 SHA256
n=$(gzip -dc "$IMG" | wc -c)
a=$(gzip -dc "$IMG" | sha256sum | awk '{print $1}')
b=$(head -c "$n" "$DEV" | sha256sum | awk '{print $1}')
[ "$a" = "$b" ] || die "回读校验失败:卡可能有坏块或者是扩容卡,换一张卡再试"
echo "✓ 写入并回读校验通过($n 字节),可以拔出后插到设备上启动"
lsblk -o NAME,SIZE,RM,TRAN,MODEL # 先找到读卡器,比如 sdb:RM=1 或者 TRAN=usb
sudo ./flash_sd.sh LibreELEC-RPi4.aarch64-12.2.1.img.gz /dev/sdb --dry-run
sudo ./flash_sd.sh LibreELEC-RPi4.aarch64-12.2.1.img.gz /dev/sdb
护栏的实测结果:
✗ /dev/vdb 不是可移动磁盘(RM=0 TRAN=virtio),为防止写坏系统盘,已停止
✗ SHA256 不一致:文件损坏或被篡改(期望 789e8e9b…,实际 689e8e9b…)
✗ 输入不一致,已取消
137+1 records out
575668224 bytes (576 MB, 549 MiB) copied, 3.37692 s, 170 MB/s
✓ 写入并回读校验通过(575668224 字节),可以拔出后插到设备上启动
第 3 步:检查卡上的内容
check_card.py:
"""写卡后检查:读取 MBR 分区表,列出启动分区(FAT)里的关键文件,并打印 config.txt 的有效行。
需要 mtools(Debian/Ubuntu:sudo apt install mtools)。设备需要 sudo,镜像文件不需要。
用法:python check_card.py /dev/sdX 或 python check_card.py 镜像.img"""
import struct, subprocess, sys
def partitions(path):
b = open(path, "rb").read(512)
if b[510:512] != b"\x55\xaa": raise SystemExit("没有 MBR 签名:写入失败,或者写到了别的设备上")
out = []
for i in range(4):
e = b[446 + 16 * i: 462 + 16 * i]
start, count = struct.unpack("<II", e[8:16])
if e[4]: out.append((i + 1, e[4], start, count * 512 // 2**20))
return out
if __name__ == "__main__":
dev = sys.argv[1]; parts = partitions(dev)
for n, t, s, mib in parts: print(f"分区 {n}: 类型 0x{t:02x} 起始扇区 {s} {mib} MiB")
at = f"{dev}@@{parts[0][2] * 512}"
files = subprocess.run(["mdir", "-i", at, "-b", "::/"], capture_output=True, text=True, check=True).stdout.split()
need = ["::/kernel.img", "::/SYSTEM", "::/config.txt", "::/cmdline.txt"]
for f in need: print(("✓ " if f in files else "✗ 缺少 ") + f[3:])
cfg = subprocess.run(["mtype", "-i", at, "::/config.txt"], capture_output=True, text=True).stdout
print("config.txt 有效行:", [l for l in cfg.splitlines() if l.strip() and not l.startswith("#")])
$ sudo python check_card.py /dev/sdb
分区 1: 类型 0x0c 起始扇区 8192 512 MiB
分区 2: 类型 0x83 起始扇区 1056768 32 MiB
✓ kernel.img
✓ SYSTEM
✓ config.txt
✓ cmdline.txt
config.txt 有效行: ['gpu_mem=76', 'hdmi_ignore_cec_init=1', 'hdmi_enable_4kp60=1', '[all]', 'include distroconfig.txt']
检查点:插卡、接上 HDMI 和电源,第一次启动会进入 LibreELEC 的设置向导(语言、网络、SSH、Samba)。如果要用 C 篇的遥控脚本,在 设置 → 服务 → 控制 里打开 HTTP 远程控制,并设置密码。这一步没有在真机上测过。
调试示例:dd 输出里的 “0+17516”
第一次写入时,dd 输出了:
0+17516 records in
0+17516 records out
0+N 表示每一块都是“不完整块”:从管道里读数据时,dd 每次只拿到不足 4M 的一小段。加上 iflag=fullblock 以后变成了 137+1 records out。为了确认写入的数据确实完整,我又加了回读校验:从设备读出和镜像同样长度的数据,比较 SHA256。这一步也能发现坏卡和扩容卡。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 屏幕上只有彩虹画面 | 镜像版本和硬件不匹配,或者电源不足 | 换用对应型号的镜像,使用官方电源 |
| 回读校验失败 | 卡有问题,或者是扩容卡 | 换一张品牌卡 |
| 脚本拒绝写入 USB 读卡器 | 某些读卡器显示 RM=0 | 用 lsblk -o NAME,TRAN 确认是 usb;确认无误再处理 |
| 4K 输出有问题 | HDMI 线或者显示设置 | 换一根认证的 HDMI 线;在 LibreELEC 的显示设置里检查分辨率 |
模板规则
- 镜像只从官方下载页获取,并且一定要校验 SHA256。
- 写入前用
--dry-run跑一遍;写入后做回读校验。 - 只播放合法内容,不装盗版插件。
项目资源
| 用途 | 地址 |
|---|---|
| LibreELEC 树莓派下载 | https://libreelec.tv/downloads/raspberry/ |
| LibreELEC 安装(Create Media) | https://wiki.libreelec.tv/installation |
| mtools | https://www.gnu.org/software/mtools/ |
| Is Kodi legal?(官方) | https://kodi.tv/article/is-kodi-legal/ |
| Kodi 论坛规则与盗版政策 | https://kodi.wiki/view/Official:Forum_rules |
写卡这一步出错的代价,可能是清空你电脑上的硬盘,所以护栏比速度更重要:先校验、确认两次再写,写完再回读一遍。