Files
AskaEth 7a64e83ecf fix(build): build-linux.sh 自己重新生成随包说明书,不再把旧文档打进包
踩过一次:Linux 包内「升级说明.txt」停在 dev_0.5.2,而源码已经走到 0.5.11。
根因是脚本只 **拷** console/extra/*.txt(那些是 deploy/prepare-extra.mjs 的产物),
自己从不重新生成 —— 于是把「上一次生成时的旧说明书」原样打进了包。
(Windows 侧不受影响:build.bat 里有这一步,所以那边一直是新的。)

现在脚本在摆随包文件之前先跑一次 node deploy/prepare-extra.mjs;
生成失败**不阻断**构建(Linux 包不该因为说明书而失败),但打印一行警告,
避免以后又静默发一份旧文档出去。

已验证:重跑脚本后,包内「升级说明.txt」的最新节是 ## dev_0.5.11。
2026-09-17 00:12:53 +08:00

141 lines
8.4 KiB
Bash
Executable File
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.
#!/usr/bin/env bash
# Scoreboard Console · Linux/arm64 发布包(tar.gz)构建脚本
#
# 为什么手工拼包,而不是让 electron-builder 出 Linux 包:
# 1) electron-builder 在 Linux 上有一条必崩的路径(打 win 目标时读不到的
# 'ReadWrite' 属性),本项目今晚实测复现过,跑不通就没法作为发布手段;
# 2) 它还要现下 Linux 版 Electron 运行时,本机网络下这个下载极不稳定;
# 3) Linux 的 tar.gz 本质就是「Electron 运行时 + resources/app/ 里的应用文件」,
# 布局是可数的几层目录,手工拼比驱动一个有崩溃路径的工具更可靠,也不需要联网。
# 因此:运行时直接复用本机已有的 electron/dist(arm64),应用文件按 console/package.json
# 的 build.files / build.extraFiles 声明的位置原样摆放 —— 摆错了 Console 就找不到服务端
# 二进制(见下面「服务端二进制放哪」那一段)。
#
# 服务端二进制放哪(依据 console/main/ 的实际代码,不是猜的):
# console/main/index.js 的 appDir() 在打包态返回 path.dirname(app.getPath('exe')),
# 即 Console 可执行文件所在目录 = 部署包根目录;index.js 再把它作为 baseDir 交给
# config.resolveExecutable(),候选表第一项就是 <部署包根>/scoreboard(文件名由
# platform.serverExeName() 按平台给出,Linux 无 .exe 后缀)。
# 所以服务端二进制与 Console 可执行文件**同级**放在包根,不能塞进 resources/。
# 同一段代码还决定 config.yaml / rules / config.json 也按包根解析(userpaths.js 首次启动
# 从包根复制到用户目录当模板),所以这几样同样摆在包根。
#
# 用法:deploy/build-linux.sh
# 环境变量:
# ELECTRON_DIST=<dir> 指定 Electron 运行时目录(默认取本机 ClockLab 里那份 arm64)
# SERVER_BIN=<file> 跳过交叉编译,直接用现成的 arm64 服务端二进制
# SERVER_OUT=<file> 交叉编译出的裸服务端另存位置(默认 debug/服务端-arm64/scoreboard;
# 设成空串则不另存,只放进 Console 包)
# OUT_DIR=<dir> 输出目录(默认 debug/console-linux-arm64)
# 产物:<OUT_DIR>/ScoreboardConsole-<VERSION>-linux-arm64.tar.gz
# 解压后顶层目录是 ScoreboardConsole/,进去直接跑 ./ScoreboardConsole 即可。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OUT_DIR="${OUT_DIR:-$ROOT/debug/console-linux-arm64}"
ELECTRON_DIST="${ELECTRON_DIST:-$HOME/Project/ClockLab/node_modules/electron/dist}"
SERVER_BIN="${SERVER_BIN:-}"
# 默认要另存一份裸服务端;显式设 SERVER_OUT= 空串表示只放进 Console 包、不另存
SERVER_OUT="${SERVER_OUT-$ROOT/debug/服务端-arm64/scoreboard}"
die() { echo "[build-linux] 错误:$*" >&2; exit 1; }
# ---- 0. 版本号:只认仓库根的 VERSION(唯一真相),与 deploy/build.bat 同一口径 ----
[ -f "$ROOT/VERSION" ] || die "找不到 $ROOT/VERSION"
VERSION="$(sed -e 's/^\xEF\xBB\xBF//' "$ROOT/VERSION" | grep -m1 -v '^[[:space:]]*$' | tr -d '\r\n' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
[ -n "$VERSION" ] || die "VERSION 文件是空的"
# ---- 1. 清干净再拼:重复执行不会把上一版的文件叠进包里 ----
STAGE="$OUT_DIR/ScoreboardConsole"
TARBALL="$OUT_DIR/ScoreboardConsole-${VERSION}-linux-arm64.tar.gz"
mkdir -p "$OUT_DIR"
rm -rf "$STAGE"
rm -f "$TARBALL"
# ---- 2. Electron 运行时:只用本机已有的,绝不下载 ----
[ -x "$ELECTRON_DIST/electron" ] || die "本机没有可用的 Electron 运行时:$ELECTRON_DIST(本脚本不联网下载)"
ELECTRON_VERSION="$(cat "$ELECTRON_DIST/version" 2>/dev/null || echo '?')"
echo "[build-linux] 版本 $VERSION,Electron 运行时 $ELECTRON_VERSION($ELECTRON_DIST)"
cp -a "$ELECTRON_DIST" "$STAGE"
# 可执行文件改名成产品名:窗口标题、任务栏分组、托盘都按它走;
# 同时这一步定下了 app.getPath('exe'),也就是后面所有路径的基准
mv "$STAGE/electron" "$STAGE/ScoreboardConsole"
# 运行时自带的是「没给 app 目录时打开的那张欢迎页」,我们的应用不放这儿,留着只是多余的 100KB
rm -rf "$STAGE/resources/default_app.asar"
# ---- 3. 应用文件:对应 console/package.json 的 build.files ----
APP_DIR="$STAGE/resources/app"
mkdir -p "$APP_DIR"
for item in main renderer scripts config.json package.json; do
[ -e "$ROOT/console/$item" ] || die "console/$item 不存在,应用文件不全"
cp -a "$ROOT/console/$item" "$APP_DIR/"
done
# 单元测试不进发布包:*.test.js 是开发期的东西,带上只会让包内代码与「跑的是什么」对不上
find "$APP_DIR" -name '*.test.js' -delete
# ---- 4. 服务端二进制(linux/arm64):与 Console 可执行文件同级,见文件头说明 ----
if [ -n "$SERVER_BIN" ]; then
[ -f "$SERVER_BIN" ] || die "SERVER_BIN 指向的文件不存在:$SERVER_BIN"
echo "[build-linux] 使用现成服务端二进制:$SERVER_BIN"
cp -f "$SERVER_BIN" "$STAGE/scoreboard"
else
command -v go >/dev/null 2>&1 || die "没装 Go,交叉编译不了 linux/arm64 服务端(可用 SERVER_BIN=... 指定现成二进制)"
echo "[build-linux] 交叉编译服务端 linux/arm64(CGO 关掉 = 静态链接,不依赖目标机的 libc)"
BUILD_TIME="$(date '+%Y-%m-%d %H:%M:%S')"
# 版本号与构建时间在编译这一刻写死进二进制;运行期不再读任何文件
( cd "$ROOT/server" && CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \
go build -trimpath \
-ldflags "-s -w -X 'main.version=$VERSION' -X 'git.yeij.top/AskaEth/Scoreboard/server/internal/version.BuildTime=$BUILD_TIME'" \
-o "$STAGE/scoreboard" . )
# 裸服务端也单独留一份:排障时要能脱离 Console 直接起它看日志,
# 与 Windows 侧 build.bat 把服务端落到 dist\ 是同一个用途。SERVER_OUT= 可关掉。
if [ -n "$SERVER_OUT" ]; then
mkdir -p "$(dirname "$SERVER_OUT")"
cp -f "$STAGE/scoreboard" "$SERVER_OUT"
echo "[build-linux] 裸服务端另存:$SERVER_OUT"
fi
fi
chmod +x "$STAGE/scoreboard"
# ---- 5. 包根随包文件:对应 console/package.json 的 build.extraFiles ----
cp -a "$ROOT/config.yaml" "$STAGE/config.yaml"
cp -a "$ROOT/console/config.json" "$STAGE/config.json"
cp -a "$ROOT/console/scoreboard.png" "$STAGE/scoreboard.png"
cp -a "$ROOT/rules" "$STAGE/rules"
# 说明书之类平铺在包根(extraFiles 的 from: extra, to: .)。
# ⚠️ 这些 .txt 由 deploy/prepare-extra.mjs 从 docs/ 生成,**必须在这里现场重新生成一次**:
# 只拷现成的,包里就会带上"上一次生成时的旧说明书"——已经踩过一次:
# 包内「升级说明.txt」停在 dev_0.5.2,而源码已经走到 0.5.11。
# 生成失败**不阻断**构建(Linux 包不该因为说明书而失败),但打印一行警告,别静默发旧文档。
if [ -f "$ROOT/deploy/prepare-extra.mjs" ] && command -v node >/dev/null 2>&1; then
if ! (cd "$ROOT" && node deploy/prepare-extra.mjs >/dev/null 2>&1); then
echo " ⚠️ 随包说明书重新生成失败(node deploy/prepare-extra.mjs),包里可能仍是旧版" >&2
fi
fi
if [ -d "$ROOT/console/extra" ]; then
find "$ROOT/console/extra" -maxdepth 1 -type f ! -name '.gitkeep' -exec cp -a {} "$STAGE/" \;
fi
# ---- 6. 打 tar.gz ----
chmod +x "$STAGE/ScoreboardConsole" "$STAGE/chrome-sandbox" 2>/dev/null || true
# --owner/--group 归零:包里的属主信息没有意义(非 root 解压时本来就会被忽略),
# 归一以后谁解出来的东西都一样。
# 注意 setuid 位没法靠 tar 交付:Electron 的 chrome-sandbox 在禁用 user namespace 的机器上
# 需要 root:root + 4755,那是解压后由现场自己补的一步(见末尾提示),包里带不了。
tar --create --gzip --file "$TARBALL" --owner=0 --group=0 --numeric-owner -C "$OUT_DIR" ScoreboardConsole
echo
echo "[build-linux] 完成"
echo " 包内布局(前两层):"
tar -tzf "$TARBALL" | awk -F/ 'NF<=2' | sed 's/^/ /'
echo " 产物:$TARBALL"
echo " 大小:$(du -h "$TARBALL" | cut -f1)($(stat -c%s "$TARBALL") 字节)"
echo " SHA256:$(sha256sum "$TARBALL" | cut -d' ' -f1)"
echo
echo " 无头环境试跑(有桌面环境时不需要 --no-sandbox):"
echo " ./ScoreboardConsole --no-sandbox"
echo " 内核禁用了 user namespace 时,沙箱要现场补一次(tar 交付不了 setuid):"
echo " sudo chown root:root chrome-sandbox && sudo chmod 4755 chrome-sandbox"