Files
HomeAgent/deploy/packaging/build.sh
JianFeeeee 6a3439a49c fix(initconfig): 必须带 cgo 构建,且失败不再静默
cmd/initconfig 通过 database/sql 使用 mattn/go-sqlite3,而 build.sh 一直用
CGO_ENABLED=0 构建它:该库在非 cgo 下退化成 static_mock.go 里的桩,sql.Open
是惰性的所以不报错、第一次 Exec 才失败,而 main.go 丢掉了所有返回值。合起来
是一个完全静默的空操作——打印凭据、退出码 0、config.db 里一个字节都没写。
安装脚本把这份凭据写进 credentials.txt,用户照着登录必然失败,全程无报错。

- build.sh: initconfig 改 CGO_ENABLED=1,并写明为何不能图省事去掉 cgo
- main.go: 每个 Exec 都检查;写完**回读比对**(不看返回码,看真实落盘内容),
  不一致即非零退出

反向验证:仍用 CGO_ENABLED=0 构建时,现在 stderr 报
"Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work"
且 exit 1(此前是 exit 0 并把凭据照打印出来)。
2026-09-12 08:10:26 +08:00

264 lines
11 KiB
Bash
Raw 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
set -euo pipefail
# 本脚本位于 deploy/packaging/,故仓库根在上两级。
#
# v0.7.2 的根目录清理把 package/build.sh 移到 deploy/packaging/build.sh
# (深度 1 → 2 层),但这行的 ".." 没跟着改成 "../..",于是 PROJECT_ROOT
# 变成了 <repo>/deploy产物落进 deploy/build/、GUI 去找 deploy/cmd/gui。
# 跨平台构建从那次起一直是坏的Makefile 的单平台 build 不走这里,所以没暴露)。
PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
BUILD_DIR="${PROJECT_ROOT}/build"
VERSION="${VERSION:-$(git -C "$PROJECT_ROOT" describe --tags --dirty 2>/dev/null || echo "0.8.0")}"
COMMIT="${COMMIT:-$(git -C "$PROJECT_ROOT" rev-parse --short HEAD 2>/dev/null || echo "unknown")}"
BUILD_TIME="${BUILD_TIME:-$(date -u '+%Y-%m-%dT%H:%M:%SZ')}"
GO="${GO:-$(command -v go 2>/dev/null || echo "go")}"
LDFLAGS="-X gitcode.com/JianFeeeee/HomeAgent/internal/meta.Version=${VERSION} -X gitcode.com/JianFeeeee/HomeAgent/internal/meta.Commit=${COMMIT} -X gitcode.com/JianFeeeee/HomeAgent/internal/meta.BuildTime=${BUILD_TIME}"
TARGET="${1:-native}"
COMPONENT="${2:-all}"
# ---- platform matrix ----
# homed: linux/amd64 + linux/arm64 (CGO), macOS native-only (no osxcross),
# windows/amd64 (MinGW)
# waiter: all platforms (CGO-free, raw terminal mode is a no-op on non-Linux)
# gui: electron-builder handles cross-platform natively
case "$TARGET" in
native) GOOS="" GOARCH="" ;;
linux/amd64) GOOS=linux GOARCH=amd64 CC="${CC:-}" ;;
# arm64 必须同时给 CXXgojieba 是 C++,缺 CXX 时 cgo 用宿主 g++ 编出
# x86-64 的 .o链接时报 "Relocations in generic ELF (EM: 183)"183 = aarch64
#
# 此处曾有一条注释写着「arm64 刻意不设 CXX」理由是设了会报
# "file format not recognized"。那个判断是错的:那个报错的真因是
# cmd/{homed,waiter}/*.sysox86-64 COFF Windows 资源对象)被链进了目标,
# 与 CXX 无关。四组对照:
# syso 在 + 无 CXX → Relocations in generic ELF (EM: 183)
# syso 在 + 有 CXX → 000000.o: file format not recognized
# syso 隐藏 + 无 CXX → Relocations in generic ELF (EM: 183)
# syso 隐藏 + 有 CXX → 成功ELF aarch64
# 本脚本的 hide_syso_for_target 已处理前一个条件,这里补上后一个。
linux/arm64) GOOS=linux GOARCH=arm64 CC="${CC:-aarch64-linux-gnu-gcc}" CXX="${CXX:-aarch64-linux-gnu-g++}" ;;
darwin/amd64) GOOS=darwin GOARCH=amd64 CC="${CC:-}" ;;
darwin/arm64) GOOS=darwin GOARCH=arm64 CC="${CC:-}" ;;
# Windows 必须同时给 CXXgojieba 是 C++,缺 CXX 时 cgo 回退到宿主 g++
# 而宿主 g++ 不认 mingw 的 -mthreads报 unrecognized command-line option。
windows/amd64) GOOS=windows GOARCH=amd64 CC="${CC:-x86_64-w64-mingw32-gcc}" CXX="${CXX:-x86_64-w64-mingw32-g++}" ;;
all)
"$0" linux/amd64 "$COMPONENT"
"$0" linux/arm64 "$COMPONENT"
"$0" darwin/amd64 "$COMPONENT"
"$0" darwin/arm64 "$COMPONENT"
"$0" windows/amd64 "$COMPONENT"
exit 0
;;
*)
echo "Unknown target: $TARGET"
echo "Usage: $0 [native|linux/amd64|linux/arm64|darwin/amd64|darwin/arm64|windows/amd64|all] [all|homed|waiter|initconfig|gui|payload]"
echo " [all|homed|waiter|initconfig|gui]"
exit 1
esac
if [ -n "${GOOS:-}" ]; then
SUFFIX="${GOOS}_${GOARCH}"
export GOOS GOARCH
fi
if [ -n "${CC:-}" ]; then
export CC
fi
if [ -n "${CXX:-}" ]; then
export CXX
fi
export CGO_ENABLED="${CGO_ENABLED:-1}"
mkdir -p "$BUILD_DIR"
# ---- .syso 隔离 ----
#
# cmd/{homed,waiter}/*.syso 是 Windows 资源对象COFF含图标/版本信息)。
# Go 会把同目录的 .syso 无条件链进任何目标,于是交叉编译到非 Windows 平台时:
# - linux/arm64、darwin/arm64 报 "unknown ARM64 relocation type 3"
# - 其他架构报 "file format not recognized"
# package-linux.sh 有 hide_syso(),但直接调本脚本时没有那层保护——
# 这正是 arm64 产物长期缺失的原因(曾被误判为缺 g++ 交叉编译器)。
SYSO_HIDDEN=()
hide_syso_for_target() {
[ "${GOOS:-}" = "windows" ] && return 0
local f
for f in "$PROJECT_ROOT"/cmd/homed/*.syso "$PROJECT_ROOT"/cmd/waiter/*.syso; do
[ -f "$f" ] || continue
mv "$f" "$f.hidden"
SYSO_HIDDEN+=("$f")
done
}
restore_syso_for_target() {
local f
for f in "${SYSO_HIDDEN[@]:-}"; do
[ -n "$f" ] && [ -f "$f.hidden" ] && mv "$f.hidden" "$f"
done
SYSO_HIDDEN=()
}
trap restore_syso_for_target EXIT
hide_syso_for_target
# ---- homed (CGO, sqlite3) ----
build_homed() {
local out="$BUILD_DIR/homed${SUFFIX:+_$SUFFIX}"
local plat="${GOOS:-linux}/${GOARCH:-amd64}"
if [ "$GOOS" = "darwin" ] && [ "${CC:-}" = "" ] && [ "$(uname)" != "Darwin" ]; then
echo "[SKIP] homed ${plat} — requires native macOS build (CGO + sqlite3, no osxcross)"
return
fi
if [ "$GOOS" = "windows" ]; then
out="${out}.exe"
fi
echo "[BUILD] homed ${plat}$out"
# Go 用 CC 驱动 CGO 编译与链接,用 CC 指定的交叉工具链来决定目标架构。
# 必须同时 export CC 给 Go 的 CGO 代码生成器,否则 CGO_ENABLED=1 下的
# 目标文件与 host 的 ld 不兼容(如 arm64 的 .o 给了 x86_64 的 ld
#
# HOMED_TAGS 默认带 onnxruntime发行版**默认启用**本地向量空间。
# 不带这个标签时 providers/chineseclip 与 providers/qwen3vl 仍会注册,
# 但打开时报「requires build tag」并优雅降级不静默假装成功
# 需要极简构建时可显式 HOMED_TAGS= 关掉。
#
# 运行期还需要 libonnxruntime.soprovider 按 /opt/onnxruntime、
# /usr/local/lib、/usr/lib 顺序查找);缺失时同样是「日志里的明确错误 +
# 降级」,不会假装启用。
local _cc="${CC:-cc}"
local _tags="${HOMED_TAGS-onnxruntime}"
local -a _tagargs=()
if [ -n "$_tags" ]; then _tagargs=(-tags "$_tags"); fi
CGO_ENABLED=1 CC="$_cc" "$GO" build -trimpath -installsuffix dynlink \
${_tagargs[@]+"${_tagargs[@]}"} \
-ldflags "$LDFLAGS" -o "$out" ./cmd/homed/
echo " OK ($(file "$out" | sed 's/.*: //') | $(du -h "$out" | cut -f1))"
}
# ---- waiter (cross-platform, CGO-free) ----
build_waiter() {
local plat="${GOOS:-linux}/${GOARCH:-amd64}"
local out="$BUILD_DIR/waiter${SUFFIX:+_$SUFFIX}"
if [ "$GOOS" = "windows" ]; then out="${out}.exe"; fi
echo "[BUILD] waiter ${plat}$out"
CGO_ENABLED=0 "$GO" build -trimpath -installsuffix dynlink \
-ldflags "$LDFLAGS" -o "$out" ./cmd/waiter/
echo " OK ($(du -h "$out" | cut -f1))"
}
# ---- initconfig必须 cgo写 config.db 用的是 go-sqlite3----
#
# 这里**必须** CGO_ENABLED=1。此前写的是 CGO_ENABLED=0而 cmd/initconfig 通过
# database/sql 使用 mattn/go-sqlite3CGO_ENABLED=0 时该库退化成 static_mock.go
# 里的桩sql.Open 是懒的所以不报错、第一次 Exec 才失败;而 main.go 当时忽略
# 了所有错误——于是 initconfig 打印凭据、退出码 0、一个字节都没写进 config.db。
# 安装脚本把这份凭据写进 credentials.txt用户照它登录必然失败全程无报错。
#
# NSIS 安装包installer.nsi与 package-linux.sh 的 stage_variant 都引用它,
# 但此前 build.sh 从不构建它——Windows 安装包构建会直接失败在缺文件上。
build_initconfig() {
local plat="${GOOS:-linux}/${GOARCH:-amd64}"
local out="$BUILD_DIR/initconfig${SUFFIX:+_$SUFFIX}"
if [ "$GOOS" = "windows" ]; then out="${out}.exe"; fi
echo "[BUILD] initconfig ${plat}$out"
CGO_ENABLED=1 "$GO" build -trimpath -installsuffix dynlink \
-ldflags "$LDFLAGS" -o "$out" ./cmd/initconfig/
echo " OK ($(du -h "$out" | cut -f1))"
}
# ---- linux-payload给 Windows 安装器用的 Linux 包)----
#
# Windows 不再安装 homed.exehomed 依赖 fd 继承 + 统一共享内存区的段内偏移
# 解引用Windows 句柄模型无法表达(见 cmd/homed/platform_windows.go
# Windows 安装器改为引导到 WSL2并把 **Linux 包**送进发行版里安装。
# 因此 Windows 安装包必须带上 Linux 产物——这一段就是把它暂存到
# build/linux-payload/installer.nsi 从这里 File /r 打进安装包)。
#
# 复用 package-linux.sh 的产物而不是在这里另行编译WSL 里跑的就是普通
# linux/amd64安装内容必须与 Linux 原生安装**完全一致**,否则又变成两个平台。
stage_linux_payload() {
local src="$PROJECT_ROOT/dist/linux"
local out="$BUILD_DIR/linux-payload"
rm -rf "$out"
mkdir -p "$out"
local found=0
for f in "$src"/*.deb "$src"/*.tar.gz; do
[ -f "$f" ] || continue
cp "$f" "$out/"
found=$((found + 1))
done
if [ "$found" -eq 0 ]; then
echo "[FAIL] build/linux-payload 为空:先运行 package-linux.sh 产出 dist/linux/*.deb|*.tar.gz" >&2
echo " Windows 安装器会把这里的包送进 WSL 安装;空包等于装不上)" >&2
return 1
fi
echo "[BUILD] linux-payload ← $found 个包"
ls -1 "$out" | sed 's/^/ /'
}
# ---- gui (Electron) ----
build_gui() {
if [ -n "${GOOS:-}" ] && [ "$GOOS" != "$("$GO" env GOOS)" ]; then
echo "[SKIP] gui ${GOOS}/${GOARCH} — electron-builder handles cross-platform natively; run 'all' on CI host"
return
fi
local gui_dir="$PROJECT_ROOT/cmd/gui"
echo "[BUILD] gui → $BUILD_DIR/"
if [ ! -d "$gui_dir/node_modules" ]; then
echo " npm install..."
(cd "$gui_dir" && npm install --production)
fi
# 不传 --configelectron-builder 默认从 package.json 的 "build" 键读配置。
# 传 --config package.json 会让它把**整个** package.json 当配置校验,
# 于是 devDependencies / build / scripts 全被判为 "unknown property" 而失败。
(cd "$gui_dir" && npx electron-builder \
--linux --win --mac \
--x64 --arm64 \
-p never \
-o "$BUILD_DIR")
echo " OK"
}
# ---- dispatch ----
if [ "${GOOS:-}" = "windows" ]; then
# Windows 目标:构建的**不是** homed——它已放弃 Windows 原生支持。
# 需要的是Linux 包(送进 WSL 安装)+ Windows 侧客户端waiter CLI / GUI
case "$COMPONENT" in
all) build_waiter; stage_linux_payload; build_gui ;;
waiter) build_waiter ;;
payload) stage_linux_payload ;;
gui) build_gui ;;
homed|initconfig)
echo "homed/initconfig 不再提供 Windows 原生构建:请用 WSL2或用 linux/amd64 目标)。" >&2
echo "原因见 cmd/homed/platform_windows.go。" >&2
exit 1
;;
*)
echo "Unknown component: $COMPONENT"
exit 1
;;
esac
else
case "$COMPONENT" in
all) build_homed; build_waiter; build_initconfig; build_gui ;;
homed) build_homed ;;
waiter) build_waiter ;;
initconfig) build_initconfig ;;
gui) build_gui ;;
*)
echo "Unknown component: $COMPONENT"
exit 1
;;
esac
fi