Files
drouter/packaging/build-offline-bundle.sh
liuzhuohua 6df3a94b90 适配 arm64 Debian(Armbian 26.8.1 trixie):去掉架构假设
有用户要在 arm64 的 Debian 上跑 drouter。这一轮只对一个发行版做了适配
—— Armbian 26.8.1 trixie(Debian 13.6)/ aarch64 / 内核 6.18.35-ophub
(Amlogic S912 板),别的 arm64 发行版未验证,验证范围与实测数据全部
写在 docs/07-arm64-适配说明.md。

改掉的四处 x86 假设:

1. helper._read_bios():ARM 板既无 /sys/firmware/efi 也无 /sys/class/dmi,
   原代码把它们一律落到 else 分支,系统概览显示「传统 BIOS」(错的,
   这类机器根本没有 BIOS)。新增设备树分支,判据顺序 EFI → 设备树 → BIOS
   (树莓派 4 这类两可的机器要优先认 EFI);读 /proc/device-tree 与
   /sys/firmware/devicetree 两个候选路径,compatible 按 NUL 切开取第一条;
   没有 DMI 厂商时拿设备树板名顶上,厂商格不再空白。返回结构加 fw_type
   / is_dt / compat,旧字段一个没动。

2. update.py:离线包资源名原先写死 -offline-amd64,arm64 机器点一键更新
   会下载 x86 离线包,传几十 MB 才在装包阶段炸依赖。改为按
   dpkg --print-architecture 拼名(_deb_arch / build_assets),并为带架构
   后缀的附件在下载前先拿官方 SHA256SUMS 核对存在性,没有就明确报错。

3. packaging:离线包说明里两处写死的 amd64 换成 $ARCH(包名早就是变量拼的,
   但包里那份 README 还在教用户核对 amd64);build-docker.sh 新增可选的
   第 2 个参数 [arch],交叉构建时加 --platform 并给标签加 -<arch> 后缀,
   不传第二参数时行为与改动前逐字节一致。

4. web/i18n.js:给新增的「设备树 (Device Tree)」展示串登记英文,
   否则英文界面漏中文。

配套:
- _dev/t-arch.py(31 条判据)+ _dev/t-arch-inject.py(20 例反向验证)。
  反向验证抓出一条假绿:i18n 词条的 n/d 判据用了 300 字符松弛窗口,
  会跨到下一条词条,删掉本词条的 d 之后照样绿 —— 已收紧为本词条花括号内。
- _dev/run-static.py 的 EXCL 补上 t-update-inject —— 它名字也是 t- 前缀,
  之前一直被全量回归自动执行,会在回归过程中反复改写 backend/ 与 web/。
- devtools/verify-arm64.py:只读验收脚本,方便他人在自己的板子上复验。
- README 支持矩阵里 arm64 从「理论可用」改为「已实测 1 个发行版」。

本机全量静态回归 71 个检查器 / 0 失败;目标机上只读验证 12 条全通
(构建 753 396 字节的 deb / depcheck 73 项 / 8 个渲染模块 / 架构感知清单)。
目标机未安装 drouter,未启用任何服务,未改动网络。
2026-10-16 06:16:31 +08:00

189 lines
8.0 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# =============================================================================
# 构建「自包含离线安装包」——一个文件装完,不需要任何外网。
#
# bash packaging/build-offline-bundle.sh [版本号] [输出目录]
#
# 产物:dist/drouter-<版本>-offline-<架构>.tar.gz
# 架构取自 `dpkg --print-architecture`(amd64 / arm64 / armhf …)。
# ⚠️ 离线包是**分架构**的:里头几百个 .deb 全是某一 CPU 的二进制,
# 所以必须在与目标机同架构的机器上构建(或用 chroot/qemu 交叉构建),
# 不能拿 amd64 打出来的包往 arm64 上装。
#
# 为什么要有这个(而不是让用户自己拼 deb + 依赖):
# .deb 的 Depends 只声明依赖,真正把依赖装上去是 apt 的活。目标机没外网时
# apt 无源可用,用户得自己想办法把 200+ 个 .deb 凑齐 —— 这几乎不可能做对
# (传递依赖、Essential 包、版本匹配,任何一环错了装不上)。
# 所以这里把「本体 + 全部依赖 + 安装脚本」打成一个 tar.gz,
# 拷过去解包、跑一条命令就完事。
#
# 包内结构:
# drouter-<版本>-offline-<架构>/
# ├── install.sh 一条命令装完
# ├── README-离线安装.md 给不熟悉的人看的步骤说明
# ├── SHA256SUMS 每个 deb 的校验和
# ├── drouter_<版本>_all.deb 本体(含 systemd 单元)
# └── debs/ 全部依赖(含 Essential 与传递依赖)
# ├── *.deb
# └── Packages + Packages.gz 本地仓库索引(**必须在 debs/ 里**,
# 否则 apt 找不到会静默退化成 dpkg -i)
# =============================================================================
set -euo pipefail
HERE="$(cd "$(dirname "$0")/.." && pwd)"
# 版本号唯一真源是 packaging/VERSION(见 build-deb.sh 里的说明)
VF="$HERE/packaging/VERSION"
[ -f "$VF" ] || { echo "缺少 $VF(版本号唯一真源)"; exit 1; }
VERSION="${1:-$(tr -d ' \t\r\n' < "$VF")}"
OUTDIR="${2:-$HERE/dist}"
ARCH="$(dpkg --print-architecture 2>/dev/null || echo amd64)"
NAME="drouter-${VERSION}-offline-${ARCH}"
STAGE="$OUTDIR/$NAME"
BUNDLE="$OUTDIR/${NAME}.tar.gz"
command -v dpkg-deb >/dev/null 2>&1 || { echo "需要 dpkg-deb(请在 Debian/Ubuntu 上执行)"; exit 1; }
echo "======================================================================"
echo "构建自包含离线安装包:$NAME"
echo "======================================================================"
echo ""
echo "== 1/5 构建 drouter 本体 .deb =="
bash "$HERE/packaging/build-deb.sh" "$VERSION" >/dev/null
DEB="$HERE/dist/drouter_${VERSION}_all.deb"
[ -f "$DEB" ] || { echo "✘ 没生成 $DEB"; exit 1; }
echo " $(basename "$DEB") $(du -h "$DEB" | cut -f1)"
echo ""
echo "== 2/5 生成离线依赖库 =="
# 注意:make-offline-deps.sh 直接往 $STAGE 里写(debs/ + install.sh),
# 不需要中间目录。早期版本这里写过 `mv $OUTDIR/offline-deps/*` 是多余的,
# 那段在目录不存在时会静默跳过,容易让人以为「搬过了」。
rm -rf "$STAGE"
bash "$HERE/scripts/make-offline-deps.sh" "$STAGE" 2>&1 | sed 's/^/ /' | tail -8
NDEB=$(ls -1 "$STAGE"/debs/*.deb 2>/dev/null | wc -l)
[ "$NDEB" -gt 50 ] || { echo "✘ 依赖包只有 $NDEB 个,明显不对"; exit 1; }
# 索引必须在 debs/ 里,否则 apt 找不到会静默退化成 dpkg -i 乱装
if [ ! -f "$STAGE/debs/Packages" ] && [ ! -f "$STAGE/debs/Packages.gz" ]; then
echo "✘ debs/ 里没有 Packages 索引 —— 离线安装会退化成 dpkg -i"; exit 1
fi
# 硬性依赖抽查:这几个缺任何一个,干净目标机一定装不上
for must in libssl3t64 hostname tar dash diffutils libc-bin liblz4-1 libapt-pkg7.0; do
ls "$STAGE/debs/${must}"_*.deb >/dev/null 2>&1 || \
ls "$STAGE/debs/${must}t64"_*.deb >/dev/null 2>&1 || \
{ echo "✘ 依赖里缺少关键包:$must"; exit 1; }
done
echo " 依赖包 $NDEB 个,索引与关键包校验通过"
echo ""
echo "== 3/5 放入本体 + 说明 =="
cp -f "$DEB" "$STAGE/"
# 把 make-offline-deps.sh 生成的 install.sh 换成 bundle 版(更完整、带自检)
cp -f "$HERE/packaging/offline-install.sh" "$STAGE/install.sh"
chmod +x "$STAGE/install.sh"
cat > "$STAGE/README-离线安装.md" <<EOF
# Drouter ${VERSION} 离线安装包
**适用场景**:目标机(Debian 13 / ${ARCH})**没有外网**,想一次装完。
> ⚠️ **架构必须匹配**:这个包里的依赖是按 **${ARCH}** 下载的,
> 只能装在同架构的机器上(如 arm64 主机要打 arm64 的包)。
> 想知道本机架构:\`dpkg --print-architecture\`。
## 三步装完
\`\`\`bash
# 1) 把这个目录整个拷到目标机(U 盘 / scp / 内网共享都行)
# 比如拷到 /tmp/drouter-offline
# 2) 进目录
cd /tmp/drouter-offline
# 3) 一条命令装完(会自动 sudo)
bash install.sh
\`\`\`
装完访问 \`https://<目标机IP>:8443/\`,默认账号 \`admin\` / \`admin123\`。
## 这个包里有什么
| 文件 | 说明 |
|---|---|
| \`drouter_${VERSION}_all.deb\` | Drouter 本体 |
| \`debs/\` | 全部依赖的 .deb,**含传递依赖与 Essential 包** |
| \`debs/Packages.gz\` | 本地仓库索引,让 apt 自己算依赖顺序 |
| \`install.sh\` | 安装脚本 |
依赖是完整的:从直接依赖(nftables / dnsmasq / radvd / ppp …)
一路到最底层(libc6 / zlib1g / dash / diffutils),全部在里面。
不需要目标机有任何网络。
## 检查完整性
\`\`\`bash
cd /tmp/drouter-offline
sha256sum -c SHA256SUMS # 校验每个 deb 没损坏
ls debs/*.deb | wc -l # 应该有两百多个
\`\`\`
## 如果装不上
1. **先看硬件够不够**:\`free -m\` / \`df -h /\`。内存 < 1G 或根分区 < 3G 会失败。
2. **确认系统版本与架构**:\`cat /etc/os-release\` 要是 Debian 13 (trixie),
\`dpkg --print-architecture\` 要是 **${ARCH}**。别的版本/架构依赖对不上。
3. **看具体报错**:\`bash install.sh 2>&1 | tee /tmp/install.log\`,
然后按 log 里的第一条 \`E:\` 定位。
## 卸载
\`\`\`bash
sudo apt remove drouter # 删程序,保留配置
sudo apt purge drouter # 连配置一起删
\`\`\`
EOF
echo ""
echo "== 4/5 生成校验和 =="
# 先把构建期中间产物清掉再算校验和,免得它们被算进去又被打包
rm -rf "$STAGE/.solveroot" "$STAGE/.closure" "$STAGE/.allpkgs" \
"$STAGE/.sim.txt" "$STAGE/.sim.err" 2>/dev/null || true
( cd "$STAGE" && find . -name '*.deb' -type f | sed 's|^\./||' \
| sort | xargs -r sha256sum > SHA256SUMS )
echo " SHA256SUMS: $(wc -l < "$STAGE/SHA256SUMS") 个文件"
# 打包前的最终结构校验:只允许出现下面这些顶层条目。
# 出现别的东西(隐藏目录、临时文件)说明前面的清理有漏,宁可在这里报错,
# 也不要让用户解包后看到一堆莫名其妙的东西。
BAD=$(cd "$STAGE" && ls -A | grep -vE '^(debs|drouter_.*_all\.deb|install\.sh|README-离线安装\.md|SHA256SUMS)$' || true)
if [ -n "$BAD" ]; then
echo "✘ 离线包里有不该出现的顶层条目:"
echo "$BAD" | sed 's/^/ /'
exit 1
fi
echo " 顶层结构校验通过"
echo ""
echo "== 5/5 打包 =="
# 用 tar.gz 而不是 zip:Linux 上天然可用,且保留可执行位
( cd "$OUTDIR" && tar czf "${NAME}.tar.gz" "$NAME" )
rm -rf "$STAGE"
echo ""
echo "======================================================================"
echo "✅ 自包含离线安装包已生成"
echo ""
echo " 文件:$BUNDLE"
echo " 大小:$(du -h "$BUNDLE" | cut -f1)"
echo ""
echo " 上传到 GitHub Release 时,只传这一个文件即可 ——"
echo " 目标机解包后跑 install.sh 就能在完全无外网的环境装好。"
echo ""
echo " ⚠️ 这是 $ARCH 架构的包。其他架构请在对应架构的机器上重跑本脚本,"
echo " 包名里的架构后缀会自动变(别的 CPU 装了这一份会依赖不满足)。"
echo "======================================================================"