Files
opsxshell/scripts/build.sh
T

240 lines
7.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# OpsXShell v2.1.x native release builder.
# Builds only for the current host OS; macOS and Windows installers must be
# produced and verified on their respective systems.
set -Eeuo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
VERSION_ARG=""
TARGET="auto"
CHECK_MODE="full"
usage() {
cat <<'EOF'
OpsXShell 原生安装包构建脚本
用法:
./scripts/build.sh [v2.1.x] [目标] [选项]
目标:
auto 根据当前系统自动选择(默认)
mac-arm64 macOS Apple Silicon,生成 .app 和 .dmg
mac-x64 macOS Intel,生成 .app 和 .dmg
win-x64 Windows x86_64,生成 NSIS/MSI 安装包
linux-x64 Linux x86_64,生成 deb/rpm/AppImage
linux-arm64 Linux aarch64,生成 deb/rpm/AppImage
选项:
--quick 构建前只运行快速门禁
--skip-checks 跳过门禁,仅用于本地重复打包
-h, --help 显示帮助
示例:
./scripts/build.sh
./scripts/build.sh v2.1.0 mac-arm64
./scripts/build.sh v2.1.1 mac-arm64 --quick
说明:
- 显式传入新版本时,会同步 package.json、package-lock.json、
Cargo.toml、Cargo.lock 和 tauri.conf.json,但不会创建或推送 Git tag。
- Windows 包必须在 Windows 上构建;macOS 包必须在 macOS 上构建;Linux 包必须在 Linux 上构建。
- 安装包留在 Tauri 标准目录 src-tauri/target/<triple>/release/bundle/,不拷到仓库根 release/。
- 正式签名构建请先设置 REQUIRE_SIGNING=true 及对应签名环境变量。
EOF
}
fail() {
printf '\n[build] ERROR: %s\n' "$*" >&2
exit 1
}
log() {
printf '\n[build] %s\n' "$*"
}
while [ "$#" -gt 0 ]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--quick)
CHECK_MODE="quick"
;;
--skip-checks)
CHECK_MODE="none"
;;
auto|mac-arm64|mac-x64|win-x64|linux-x64|linux-arm64)
TARGET="$1"
;;
v[0-9]*|[0-9]*)
[ -z "$VERSION_ARG" ] || fail "只能指定一个版本号"
VERSION_ARG="${1#v}"
;;
*)
fail "未知参数:$1(使用 ./scripts/build.sh --help 查看用法)"
;;
esac
shift
done
for command in node npm npx cargo rustc; do
command -v "$command" >/dev/null 2>&1 || fail "缺少构建依赖:$command"
done
CURRENT_VERSION="$(node -p "require('./package.json').version")"
VERSION="${VERSION_ARG:-$CURRENT_VERSION}"
if ! printf '%s' "$VERSION" | grep -Eq '^2\.1\.[0-9]+([.-][0-9A-Za-z.-]+)?$'; then
fail "版本必须是 v2.1.x 语义版本,当前输入:$VERSION"
fi
HOST_OS="$(uname -s)"
HOST_ARCH="$(uname -m)"
if [ "$TARGET" = "auto" ]; then
case "$HOST_OS/$HOST_ARCH" in
Darwin/arm64|Darwin/aarch64) TARGET="mac-arm64" ;;
Darwin/x86_64) TARGET="mac-x64" ;;
MINGW*/*|MSYS*/*|CYGWIN*/*) TARGET="win-x64" ;;
Linux/x86_64) TARGET="linux-x64" ;;
Linux/aarch64|Linux/arm64) TARGET="linux-arm64" ;;
*) fail "当前平台 $HOST_OS/$HOST_ARCH 不支持自动打包,请显式指定目标" ;;
esac
fi
case "$TARGET" in
mac-arm64)
[ "$HOST_OS" = "Darwin" ] || fail "mac-arm64 必须在 macOS 上构建"
RUST_TARGET="aarch64-apple-darwin"
RELEASE_PLATFORM="macos"
OUTPUT_PLATFORM="macos-arm64"
;;
mac-x64)
[ "$HOST_OS" = "Darwin" ] || fail "mac-x64 必须在 macOS 上构建"
RUST_TARGET="x86_64-apple-darwin"
RELEASE_PLATFORM="macos"
OUTPUT_PLATFORM="macos-x64"
;;
win-x64)
case "$HOST_OS" in
MINGW*|MSYS*|CYGWIN*) ;;
*) fail "win-x64 必须在 Windows 上构建,macOS 交叉编译不能替代 Windows 实机打包" ;;
esac
RUST_TARGET="x86_64-pc-windows-msvc"
RELEASE_PLATFORM="windows"
OUTPUT_PLATFORM="windows-x64"
;;
linux-x64)
[ "$HOST_OS" = "Linux" ] || fail "linux-x64 必须在 Linux 上构建"
RUST_TARGET="x86_64-unknown-linux-gnu"
RELEASE_PLATFORM="linux"
OUTPUT_PLATFORM="linux-x64"
;;
linux-arm64)
[ "$HOST_OS" = "Linux" ] || fail "linux-arm64 必须在 Linux 上构建"
RUST_TARGET="aarch64-unknown-linux-gnu"
RELEASE_PLATFORM="linux"
OUTPUT_PLATFORM="linux-arm64"
;;
esac
if command -v rustup >/dev/null 2>&1 && ! rustup target list --installed | grep -Fxq "$RUST_TARGET"; then
fail "Rust target 未安装:$RUST_TARGET;请先运行 rustup target add $RUST_TARGET"
fi
if [ -n "$VERSION_ARG" ] && [ "$VERSION" != "$CURRENT_VERSION" ]; then
log "同步版本号到 v$VERSION"
node scripts/set-version.mjs "$VERSION"
fi
node scripts/set-version.mjs --check "$VERSION"
if [ ! -d node_modules ]; then
log "安装锁定依赖"
npm ci
fi
log "OpsXShell v$VERSION · $TARGET · $(git rev-parse --short HEAD 2>/dev/null || printf unknown)"
case "$CHECK_MODE" in
full)
log "运行完整发布门禁"
npm run acceptance:full
;;
quick)
log "运行快速发布门禁"
npm run acceptance:quick
;;
none)
log "已按参数跳过构建前门禁"
;;
esac
node scripts/release-preflight.mjs "$RELEASE_PLATFORM"
log "构建原生安装包"
TAURI_ARGS=(--ci --target "$RUST_TARGET")
CODESIGN_WRAP_DIR=""
if [ "$RELEASE_PLATFORM" = "macos" ]; then
TAURI_ARGS+=(--bundles app,dmg)
CODESIGN_WRAP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/opsx-codesign.XXXXXX")"
cp "$ROOT_DIR/scripts/macos-codesign-wrapper.sh" "$CODESIGN_WRAP_DIR/codesign"
chmod +x "$CODESIGN_WRAP_DIR/codesign"
export PATH="$CODESIGN_WRAP_DIR:$PATH"
log "macOS 签名使用 HTTP 时间戳(可用 CODESIGN_USE_SYSTEM_TIMESTAMP=1 关闭)"
fi
if [ -f "$ROOT_DIR/src-tauri/tauri.signing.conf.json" ]; then
TAURI_ARGS+=(--config src-tauri/tauri.signing.conf.json)
log "使用 src-tauri/tauri.signing.conf.json 签名"
fi
npx tauri build "${TAURI_ARGS[@]}"
if [ -n "$CODESIGN_WRAP_DIR" ]; then
rm -rf "$CODESIGN_WRAP_DIR"
fi
BUNDLE_ROOT="${CARGO_TARGET_DIR:-$ROOT_DIR/src-tauri/target}/$RUST_TARGET/release/bundle"
node scripts/verify-release.mjs "$RELEASE_PLATFORM" "$BUNDLE_ROOT"
# 产物留在 Tauri 标准目录,不再拷到仓库根 release/
shopt -s nullglob
if [ "$RELEASE_PLATFORM" = "macos" ]; then
SOURCE_ARTIFACTS=("$BUNDLE_ROOT"/dmg/*.dmg)
elif [ "$RELEASE_PLATFORM" = "linux" ]; then
SOURCE_ARTIFACTS=("$BUNDLE_ROOT"/deb/*.deb "$BUNDLE_ROOT"/rpm/*.rpm "$BUNDLE_ROOT"/appimage/*.AppImage)
else
SOURCE_ARTIFACTS=("$BUNDLE_ROOT"/nsis/*.exe "$BUNDLE_ROOT"/msi/*.msi "$BUNDLE_ROOT"/wix/*.msi)
fi
[ "${#SOURCE_ARTIFACTS[@]}" -gt 0 ] || fail "构建完成但未找到安装包:$BUNDLE_ROOT"
checksum_line() {
local artifact="$1" digest
if command -v shasum >/dev/null 2>&1; then
digest="$(shasum -a 256 "$artifact" | awk '{print $1}')"
elif command -v sha256sum >/dev/null 2>&1; then
digest="$(sha256sum "$artifact" | awk '{print $1}')"
else
digest="$(node --input-type=module -e '
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
console.log(createHash("sha256").update(readFileSync(process.argv[1])).digest("hex"));
' "$artifact")"
fi
printf '%s %s\n' "$digest" "$(basename "$artifact")"
}
CHECKSUM_FILE="$BUNDLE_ROOT/SHA256SUMS.txt"
: >"$CHECKSUM_FILE"
for artifact in "${SOURCE_ARTIFACTS[@]}"; do
checksum_line "$artifact" >>"$CHECKSUM_FILE"
done
log "构建完成(Tauri bundle):$BUNDLE_ROOT ($OUTPUT_PLATFORM)"
ls -lh "${SOURCE_ARTIFACTS[@]}"
printf '\n[build] SHA-256\n'
cat "$CHECKSUM_FILE"