fix(install): 微信下载支持断点续传 + 并发锁 + 完整性校验(修反复卡进度退出)

用户反馈安装卡在 ~28% 反复退出、多次重试无效(诊断包实证:panel.log 里短时间连点多次「触发下载安装」)。
do_install 有两处放大网络抖动的缺陷:
- 每次触发都先 rm -rf $WORK_DIR:用户重试时把上一次下到一半的包删掉;
- curl 不带 -C -:每次从 0 重下,网络稍差就永远下不完,且反复停在相近百分比。

修:
- 并发锁($STATE_DIR/.install.lock,mkdir 原子 + pid 存活判定):已有安装在跑则跳过本次触发,
  让它续着下完,不再互相踩。
- curl -C - 断点续传 + --retry-all-errors + 外层最多 6 轮,重试前【绝不删】部分文件 → 从断点继续。
- 下载后 dpkg-deb -f 完整性校验:半包/损坏才删了重下,否则不白费已下的量。
- 全程写 $STATE_DIR/install.log,便于线上定位(此前安装失败在诊断包里完全看不到原因)。

注:待 Docker 恢复后补端到端验证(真拉一次微信 deb + 并发锁)再考虑发版。
This commit is contained in:
Gloridust
2026-07-19 15:46:47 +08:00
parent 896a788a54
commit 91a220d6af

View File

@@ -52,51 +52,85 @@ print_status() {
fi
}
log() { echo "[$(date '+%F %T')] $*" >> "$STATE_DIR/install.log" 2>/dev/null; }
do_install() {
local file url tmp pid total cur pct rc=1
local file tmp pid total cur pct rc=1 attempt=0
file="$(deb_filename)"
if [ -z "$file" ]; then
write_status error 0 "不支持的架构:微信仅提供 x86_64 / arm64"
return
fi
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR"
mkdir -p "$STATE_DIR" "$WORK_DIR"
# 并发保护issue反复点安装会互相踩锁目录 mkdir 原子。已有【活着的】安装在跑 → 直接返回,
# 让它继续(下面的 curl 支持断点续传,会自己下完);否则每次触发都 rm 掉下到一半的包 → 永远装不完。
local lock="$STATE_DIR/.install.lock"
if ! mkdir "$lock" 2>/dev/null; then
local lpid; lpid="$(cat "$lock/pid" 2>/dev/null || echo)"
if [ -n "$lpid" ] && kill -0 "$lpid" 2>/dev/null; then
log "已有安装进行中(pid=$lpid),本次触发跳过"
return
fi
rm -rf "$lock"; mkdir "$lock" 2>/dev/null || { log "抢锁失败,跳过"; return; }
fi
echo "$$" > "$lock/pid"
trap 'rm -rf "$lock" 2>/dev/null' EXIT # 本次 exec 短命,退出即释放锁
log "开始安装 file=$file"
tmp="$WORK_DIR/wechat.deb"
# 取总大小用于进度HEAD 可能失败,失败则进度走不确定值 -1
# 取总大小用于进度 + 完整性判断HEAD 可能失败,失败则进度走不确定值 -1
for base in "$CDN_MAIN" "$CDN_FALLBACK"; do
total="$(curl -fsSLI -A "$UA" "$base/$file" 2>/dev/null | tr -d '\r' \
| awk 'tolower($1)=="content-length:"{v=$2} END{print v}')"
[ -n "${total:-}" ] && url="$base/$file" && break
[ -n "${total:-}" ] && break
done
: "${total:=0}" "${url:=$CDN_MAIN/$file}"
: "${total:=0}"
write_status downloading 0 "正在下载微信安装包"
# 后台下载 + 轮询已下字节算百分比(下载占 0~90
for base in "$CDN_MAIN" "$CDN_FALLBACK"; do
curl -fSL --retry 3 -A "$UA" -o "$tmp" "$base/$file" & pid=$!
while kill -0 "$pid" 2>/dev/null; do
if [ "${total:-0}" -gt 0 ] 2>/dev/null; then
cur="$(stat -c%s "$tmp" 2>/dev/null || echo 0)"
pct=$(( cur * 90 / total ))
[ "$pct" -gt 90 ] && pct=90
write_status downloading "$pct" "正在下载微信安装包"
else
write_status downloading -1 "正在下载微信安装包"
fi
sleep 1
# 断点续传下载(-C -):网络半路中断/被中间设备掐断时,下次从已下字节【继续】而非从 0 重来
#(这正是"反复卡在同一百分比退出"的解药)。--retry-all-errors 对传输中断也重试;外层再多轮兜底。
# 关键:绝不在重试前删 $tmp —— 保留部分文件才能续传。
while [ "$attempt" -lt 6 ]; do
attempt=$((attempt+1))
for base in "$CDN_MAIN" "$CDN_FALLBACK"; do
curl -fSL -C - --retry 3 --retry-all-errors --retry-delay 2 --connect-timeout 20 \
-A "$UA" -o "$tmp" "$base/$file" & pid=$!
while kill -0 "$pid" 2>/dev/null; do
if [ "${total:-0}" -gt 0 ] 2>/dev/null; then
cur="$(stat -c%s "$tmp" 2>/dev/null || echo 0)"
pct=$(( cur * 90 / total )); [ "$pct" -gt 90 ] && pct=90
write_status downloading "$pct" "正在下载微信安装包"
else
write_status downloading -1 "正在下载微信安装包"
fi
sleep 1
done
wait "$pid"; rc=$?
[ "$rc" -eq 0 ] && break 2
log "curl 退出码 $rcbase=$baseattempt=$attempt),已下 $(stat -c%s "$tmp" 2>/dev/null || echo 0) 字节"
done
wait "$pid"; rc=$?
[ "$rc" -eq 0 ] && break
write_status downloading -1 "主线路失败,尝试备用线路"
# 已下满(校验用 total也算成功——防某些实现在收尾时给非 0 退出码
cur="$(stat -c%s "$tmp" 2>/dev/null || echo 0)"
if [ "${total:-0}" -gt 0 ] && [ "$cur" -ge "$total" ]; then rc=0; break; fi
write_status downloading -1 "下载中断,正在续传重试($attempt/6"
sleep 2
done
if [ "$rc" -ne 0 ]; then
write_status error 0 "下载失败,请检查网络后重试"
rm -rf "$WORK_DIR"; return
log "下载最终失败 rc=$rc,已下 $(stat -c%s "$tmp" 2>/dev/null || echo 0)/$total"
write_status error 0 "下载失败(多次续传仍未完成,请检查网络/镜像后重试)"
return
fi
write_status extracting 92 "正在解压安装"
# 完整性校验:能被 dpkg-deb 读出版本才算完整包;半包/损坏包会解压失败或装出坏微信。
if ! dpkg-deb -f "$tmp" Version >/dev/null 2>&1; then
log "包不完整/损坏,删除重下"
rm -f "$tmp"
write_status error 0 "安装包不完整或损坏,已清理,请再次点击安装(将重新下载)"
return
fi
local newroot="$WORK_DIR/new"
rm -rf "$newroot"; mkdir -p "$newroot"
if ! dpkg-deb -x "$tmp" "$newroot" 2>/dev/null; then