install.sh: stop dying on macOS at the compile step

"$RUST_HOST;" is one variable name to macOS /bin/sh (bash 3.2): it
swallows the first byte of the full-width punctuation, so with set -u a
plain \`sh install.sh app\` aborted at [3/4] with "RUST_HOST\*: unbound
variable". Four such lines in install.sh, one in the server setup.
Brace them, add make lint-sh (sh -n for every shipped script plus a
check for this pattern) so it stays fixed.
This commit is contained in:
王性驊 2026-11-10 14:32:56 +08:00
parent 818e551e6b
commit 983a2c2f02
4 changed files with 54 additions and 6 deletions

View File

@ -20,7 +20,7 @@ APPDIR ?= $(HOME)/Applications
BIN := $(TARGET)/release/anybot BIN := $(TARGET)/release/anybot
RELAY := $(if $(CARGO_TARGET_DIR),$(CARGO_TARGET_DIR),relay/target)/release/anybot-relay RELAY := $(if $(CARGO_TARGET_DIR),$(CARGO_TARGET_DIR),relay/target)/release/anybot-relay
.PHONY: all build app relay run serve setup relay-run test deps doctor install uninstall hands server dist clean help check-rust .PHONY: all build app relay run serve setup relay-run test deps doctor install uninstall hands server dist clean help check-rust lint-sh
all: build all: build
@ -34,6 +34,7 @@ help:
@echo " make deps 裝好建置需要的東西:系統套件和 Rust(Linux 會問 sudo 密碼)" @echo " make deps 裝好建置需要的東西:系統套件和 Rust(Linux 會問 sudo 密碼)"
@echo " make doctor 檢查這台缺什麼" @echo " make doctor 檢查這台缺什麼"
@echo " make test 跑測試" @echo " make test 跑測試"
@echo " make lint-sh 檢查 shell 腳本(語法、中文標點黏變數)"
@echo "" @echo ""
@echo " make serve 不開視窗執行(伺服器、一直開著的電腦)" @echo " make serve 不開視窗執行(伺服器、一直開著的電腦)"
@echo " make setup 把這台接到你的 AnyBot(終端機裡的引導畫面)" @echo " make setup 把這台接到你的 AnyBot(終端機裡的引導畫面)"
@ -72,6 +73,9 @@ test: check-rust
$(CARGO) test --bin anybot $(CARGO) test --bin anybot
cd relay && $(CARGO) test cd relay && $(CARGO) test
lint-sh:
@sh deploy/ci/lint-sh.sh
deps: deps:
sh ./install.sh deps sh ./install.sh deps

44
deploy/ci/lint-sh.sh Executable file
View File

@ -0,0 +1,44 @@
#!/bin/sh
# Shell lint for the install scripts. Two checks:
# 1. POSIX syntax (sh -n) for every .sh we ship.
# 2. $VAR or ${VAR} glued to a non-ASCII character, which macOS's /bin/sh
# (bash 3.2) reads as part of the variable name: "($X;…" is an unbound
# variable there even when X is set, and install.sh runs with set -u.
# Write ${X}與… — braces fix it.
# Run: make lint-sh
set -u
cd "$(dirname "$0")/../.." || exit 1
fail=0
for f in install.sh $(find deploy -name '*.sh' | sort); do
if sh -n "$f" 2>/tmp/anybot-sh-lint.log; then
printf ' ✓ %s\n' "$f"
else
printf ' ✗ %s\n' "$f"; sed 's/^/ /' /tmp/anybot-sh-lint.log; fail=1
fi
done
printf '\n'
python3 - <<'PY' || fail=1
import glob, re, os, sys
pat = re.compile(r'(?<![A-Za-z0-9_$\\])\$([A-Za-z_][A-Za-z0-9_]*)([^\x00-\x7f])')
root = os.getcwd()
files = []
for d, dirs, fs in os.walk('.'):
dirs[:] = [x for x in dirs if x not in ('target', '.git', 'node_modules')]
files += [os.path.join(d, x) for x in fs if x.endswith('.sh')]
bad = 0
for f in sorted(files):
for i, line in enumerate(open(f, encoding='utf-8'), 1):
if line.strip().startswith('#'):
continue
for m in pat.finditer(line):
print(f' ✗ {f}:{i}: ${m.group(1)}{m.group(2)} → 在 macOS 的 /bin/sh 是未設定變數(改用 ${{{m.group(1)}}}{m.group(2)})')
print(f' {line.strip()[:100]}')
bad += 1
print(' ✓ no $VAR glued to Chinese punctuation' if not bad else f' {bad} places to fix')
sys.exit(1 if bad else 0)
PY
[ "$fail" = 0 ] && printf '\n 都過了。\n' || printf '\n 有東西要修。\n'
exit $fail

View File

@ -204,7 +204,7 @@ if [ "${SKIP:-0}" != 1 ]; then
fi fi
while [ "$LAN" = 1 ] && [ "$AUTO" = 0 ]; do while [ "$LAN" = 1 ] && [ "$AUTO" = 0 ]; do
header; card_open "這台在區網的 IP" header; card_open "這台在區網的 IP"
note "手機和電腦在同一個區網打開 https://這個 IP。$LANDESC。" note "手機和電腦在同一個區網打開 https://這個 IP。${LANDESC}。"
line "" line ""
[ -n "$MSG" ] && { printf '%s\n' "$MSG"; line ""; } [ -n "$MSG" ] && { printf '%s\n' "$MSG"; line ""; }
ask LANIP "IP" "${LANIP:-$(lan_ip)}" ask LANIP "IP" "${LANIP:-$(lan_ip)}"

View File

@ -180,7 +180,7 @@ $1"
fi fi
} }
[ -z "$RUST_HOST" ] && note "這個 CPU 架構($ARCH)沒有對應的 Rust 目標,一鍵安裝不會編譯。" [ -z "$RUST_HOST" ] && note "這個 CPU 架構(${ARCH})沒有對應的 Rust 目標,一鍵安裝不會編譯。"
[ "$MUSL" = 1 ] && note "這是 musl(多半是 Alpine)。可以編譯,桌面視窗比 glibc 發行版容易缺函式庫;當手腳比較穩。" [ "$MUSL" = 1 ] && note "這是 musl(多半是 Alpine)。可以編譯,桌面視窗比 glibc 發行版容易缺函式庫;當手腳比較穩。"
[ "$WSL" = 1 ] && [ "$DESKTOP" = 0 ] && note "這是沒有 WSLg 的 WSL。這裡裝的是 Linux 手腳(虛擬螢幕)。要操控 Windows 桌面,請在 Windows 的 PowerShell 執行 install.ps1,不要在 WSL 裡裝。" [ "$WSL" = 1 ] && [ "$DESKTOP" = 0 ] && note "這是沒有 WSLg 的 WSL。這裡裝的是 Linux 手腳(虛擬螢幕)。要操控 Windows 桌面,請在 Windows 的 PowerShell 執行 install.ps1,不要在 WSL 裡裝。"
[ "$WSL" = 1 ] && [ "$DESKTOP" = 1 ] && note "偵測到 WSLg,會用 Linux 桌面。要操控 Windows 自己的視窗,另外在 Windows 裝 install.ps1。" [ "$WSL" = 1 ] && [ "$DESKTOP" = 1 ] && note "偵測到 WSLg,會用 Linux 桌面。要操控 Windows 自己的視窗,另外在 Windows 裝 install.ps1。"
@ -478,14 +478,14 @@ ensure_rust() {
[ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env" [ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env"
else else
if have rustc; then warn "系統的 Rust $(rustc --version | cut -d' ' -f2) 太舊(要 1.$RUST_MIN 以上),另外用 rustup 裝新版"; fi if have rustc; then warn "系統的 Rust $(rustc --version | cut -d' ' -f2) 太舊(要 1.$RUST_MIN 以上),另外用 rustup 裝新版"; fi
info "安裝 Rust(rustup,目標 $RUST_HOST,裝在 ~/.cargo,不用 root)…" info "安裝 Rust(rustup,目標 ${RUST_HOST},裝在 ~/.cargo,不用 root)…"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -q --profile minimal --default-host "$RUST_HOST" --no-modify-path >/dev/null 2>&1 \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -q --profile minimal --default-host "$RUST_HOST" --no-modify-path >/dev/null 2>&1 \
|| die "安裝 Rust 失敗。" "手動:curl --proto '=https' -sSf https://sh.rustup.rs | sh" || die "安裝 Rust 失敗。" "手動:curl --proto '=https' -sSf https://sh.rustup.rs | sh"
. "$HOME/.cargo/env" . "$HOME/.cargo/env"
fi fi
# 有人用 Rosetta 或跨架構:確保這個目標有標準庫。 # 有人用 Rosetta 或跨架構:確保這個目標有標準庫。
if ! rustup target list --installed 2>/dev/null | grep -q "^${RUST_HOST}$"; then if ! rustup target list --installed 2>/dev/null | grep -q "^${RUST_HOST}$"; then
rustup target add "$RUST_HOST" >/dev/null 2>&1 || warn "沒能加上 Rust 目標 $RUST_HOST,仍用 rustup 預設的主機目標試試" rustup target add "$RUST_HOST" >/dev/null 2>&1 || warn "沒能加上 Rust 目標 ${RUST_HOST},仍用 rustup 預設的主機目標試試"
fi fi
rust_ok || die "Rust 還是太舊:$(rustc --version 2>/dev/null)" "執行 rustup update stable,或移除系統的 rustc/cargo 套件。" rust_ok || die "Rust 還是太舊:$(rustc --version 2>/dev/null)" "執行 rustup update stable,或移除系統的 rustc/cargo 套件。"
ok "Rust $(rustc --version | cut -d' ' -f2)" ok "Rust $(rustc --version | cut -d' ' -f2)"
@ -535,7 +535,7 @@ build_jobs() {
build() { build() {
jobs=$(build_jobs) jobs=$(build_jobs)
info "編譯中,第一次約 5–15 分鐘($RUST_HOST;之後更新只重編改過的部分)…" info "編譯中,第一次約 5–15 分鐘(${RUST_HOST};之後更新只重編改過的部分)…"
log="$SRC/target/install-build.log"; mkdir -p "$SRC/target" log="$SRC/target/install-build.log"; mkdir -p "$SRC/target"
if (cd "$SRC" && cargo build --release --bin anybot ${jobs:+-j "$jobs"}) >"$log" 2>&1; then if (cd "$SRC" && cargo build --release --bin anybot ${jobs:+-j "$jobs"}) >"$log" 2>&1; then
ok "編譯完成" ok "編譯完成"