update-client.sh 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. #!/usr/bin/env bash
  2. ################################################################################
  3. # H5 应用更新客户端脚本
  4. #
  5. # 功能:从版本服务器下载并更新 H5 应用
  6. # 版本:1.0.0
  7. # 作者:自动生成
  8. ################################################################################
  9. set -euo pipefail # 严格模式:遇到错误立即退出
  10. # ============================================================================
  11. # 操作系统检测
  12. # ============================================================================
  13. detect_os() {
  14. case "$(uname -s)" in
  15. MINGW*|MSYS*|CYGWIN*)
  16. echo "windows"
  17. ;;
  18. Linux*)
  19. echo "linux"
  20. ;;
  21. Darwin*)
  22. echo "macos"
  23. ;;
  24. *)
  25. echo "unknown"
  26. ;;
  27. esac
  28. }
  29. readonly OS_TYPE=$(detect_os)
  30. # ============================================================================
  31. # 颜色定义
  32. # ============================================================================
  33. readonly RED='\033[0;31m'
  34. readonly GREEN='\033[0;32m'
  35. readonly YELLOW='\033[1;33m'
  36. readonly BLUE='\033[0;34m'
  37. readonly NC='\033[0m' # No Color
  38. # ============================================================================
  39. # 全局变量
  40. # ============================================================================
  41. readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  42. # 跨平台临时目录和用户名处理
  43. if [ "$OS_TYPE" = "windows" ]; then
  44. # Windows 环境 (Git Bash)
  45. TEMP_DIR="${TEMP:-${TMP:-/tmp}}"
  46. CURRENT_USER="${USERNAME:-${USER:-unknown}}"
  47. else
  48. # Linux/macOS 环境
  49. TEMP_DIR="/tmp"
  50. CURRENT_USER="${USER:-unknown}"
  51. fi
  52. readonly LOCK_FILE="${TEMP_DIR}/update-client-${CURRENT_USER}.lock"
  53. readonly SERVER="dros.overs.games"
  54. readonly DEFAULT_USER="deploy"
  55. readonly DEFAULT_DIR="./h5"
  56. readonly LOG_DIR="./logs"
  57. # 用户输入变量
  58. USERNAME=""
  59. PASSWORD=""
  60. DOWNLOAD_DIR=""
  61. VERSION=""
  62. SILENT_MODE=false
  63. SKIP_CONFIRM=false
  64. INCLUDE_STATIC=false # 是否包含 static 文件夹
  65. # 日志文件
  66. LOG_FILE=""
  67. # ============================================================================
  68. # 工具函数
  69. # ============================================================================
  70. # 打印信息
  71. log_info() {
  72. echo -e "${GREEN}[INFO]${NC} $*" | tee -a "$LOG_FILE"
  73. }
  74. # 打印警告
  75. log_warn() {
  76. echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "$LOG_FILE"
  77. }
  78. # 打印错误
  79. log_error() {
  80. echo -e "${RED}[ERROR]${NC} $*" | tee -a "$LOG_FILE" >&2
  81. }
  82. # 打印成功
  83. log_success() {
  84. echo -e "${GREEN}[✓]${NC} $*" | tee -a "$LOG_FILE"
  85. }
  86. # 清理函数
  87. cleanup() {
  88. if [ -f "$LOCK_FILE" ]; then
  89. rm -f "$LOCK_FILE"
  90. fi
  91. }
  92. # 设置清理陷阱
  93. trap cleanup EXIT INT TERM
  94. # ============================================================================
  95. # 显示操作系统信息
  96. # ============================================================================
  97. show_os_info() {
  98. log_info "检测到操作系统: $OS_TYPE"
  99. log_info "临时目录: $TEMP_DIR"
  100. log_info "当前用户: $CURRENT_USER"
  101. }
  102. # ============================================================================
  103. # 帮助信息
  104. # ============================================================================
  105. show_help() {
  106. cat << EOF
  107. H5 应用更新客户端
  108. 用法: $0 [选项]
  109. 选项:
  110. -u, --user USERNAME 指定用户名(默认: $DEFAULT_USER)
  111. -p, --password PASS 指定密码
  112. -d, --dir DIRECTORY 指定下载目录(默认: $DEFAULT_DIR)
  113. -v, --version VERSION 指定版本号(默认: latest)
  114. -i, --include-static 包含 static 文件夹更新(默认: 不包含)
  115. -y, --yes 跳过确认提示
  116. -s, --silent 静默模式(最小输出)
  117. -h, --help 显示此帮助信息
  118. 示例:
  119. # 交互模式
  120. $0
  121. # 非交互模式(不包含 static)
  122. $0 -u deploy -p mypassword -d ./h5 -v latest -y
  123. # 包含 static 文件夹
  124. $0 -u deploy -p mypassword -d ./h5 -v latest -y --include-static
  125. # 静默模式
  126. $0 -u deploy -p mypassword -v v1.1.5-20251129-062104 -y -s
  127. EOF
  128. }
  129. # ============================================================================
  130. # 检查依赖工具
  131. # ============================================================================
  132. check_dependencies() {
  133. log_info "检查依赖工具..."
  134. local missing_tools=()
  135. for tool in curl wget tar; do
  136. if ! command -v "$tool" &> /dev/null; then
  137. missing_tools+=("$tool")
  138. fi
  139. done
  140. if [ ${#missing_tools[@]} -gt 0 ]; then
  141. log_error "缺少必需工具: ${missing_tools[*]}"
  142. log_error "请安装缺少的工具后重试"
  143. exit 1
  144. fi
  145. log_success "依赖工具检查通过"
  146. }
  147. # ============================================================================
  148. # 检查并发执行
  149. # ============================================================================
  150. check_lock() {
  151. if [ -f "$LOCK_FILE" ]; then
  152. log_error "更新脚本已在运行中(锁文件: $LOCK_FILE)"
  153. log_error "如确认没有其他实例运行,请删除锁文件后重试"
  154. exit 1
  155. fi
  156. touch "$LOCK_FILE"
  157. log_info "已创建锁文件"
  158. }
  159. # ============================================================================
  160. # 初始化日志
  161. # ============================================================================
  162. init_log() {
  163. mkdir -p "$LOG_DIR"
  164. LOG_FILE="$LOG_DIR/update_$(date +%Y%m%d_%H%M%S).log"
  165. log_info "日志文件: $LOG_FILE"
  166. }
  167. # ============================================================================
  168. # 验证用户名格式
  169. # ============================================================================
  170. validate_username() {
  171. local username=$1
  172. if [[ ! $username =~ ^[a-zA-Z0-9_]+$ ]]; then
  173. log_error "用户名格式错误(仅允许字母、数字和下划线)"
  174. return 1
  175. fi
  176. return 0
  177. }
  178. # ============================================================================
  179. # 验证密码
  180. # ============================================================================
  181. validate_password() {
  182. local password=$1
  183. if [ -z "$password" ]; then
  184. log_error "密码不能为空"
  185. return 1
  186. fi
  187. if [ ${#password} -lt 6 ]; then
  188. log_error "密码长度至少6位"
  189. return 1
  190. fi
  191. return 0
  192. }
  193. # ============================================================================
  194. # 获取用户输入
  195. # ============================================================================
  196. get_user_input() {
  197. # 用户名
  198. if [ -z "$USERNAME" ]; then
  199. read -p "请输入用户名 [默认: $DEFAULT_USER]: " USERNAME
  200. USERNAME=${USERNAME:-$DEFAULT_USER}
  201. fi
  202. if ! validate_username "$USERNAME"; then
  203. exit 1
  204. fi
  205. log_info "用户名: $USERNAME"
  206. # 密码
  207. if [ -z "$PASSWORD" ]; then
  208. while true; do
  209. read -s -p "请输入密码: " PASSWORD
  210. echo
  211. if validate_password "$PASSWORD"; then
  212. break
  213. fi
  214. done
  215. fi
  216. log_info "密码已设置(已脱敏)"
  217. # 下载目录
  218. if [ -z "$DOWNLOAD_DIR" ]; then
  219. read -p "请输入下载目录 [默认: $DEFAULT_DIR]: " DOWNLOAD_DIR
  220. DOWNLOAD_DIR=${DOWNLOAD_DIR:-$DEFAULT_DIR}
  221. fi
  222. log_info "下载目录: $DOWNLOAD_DIR"
  223. # 询问是否更新 static 文件夹(仅在交互模式且未通过参数指定时询问)
  224. if [ "$INCLUDE_STATIC" = "false" ]; then
  225. read -p "是否需要更新 static 文件夹? [y/N]: " include_static
  226. if [[ $include_static =~ ^[Yy]$ ]]; then
  227. INCLUDE_STATIC=true
  228. log_info "static 文件夹: 将包含在更新中"
  229. else
  230. INCLUDE_STATIC=false
  231. log_info "static 文件夹: 将跳过更新"
  232. fi
  233. fi
  234. }
  235. # ============================================================================
  236. # 验证路径权限
  237. # ============================================================================
  238. validate_path() {
  239. local dir=$1
  240. # 检查路径是否存在
  241. if [ ! -d "$dir" ]; then
  242. log_warn "目录不存在: $dir"
  243. if [ "$SKIP_CONFIRM" = false ]; then
  244. read -p "是否创建该目录? [y/N]: " confirm
  245. if [[ ! $confirm =~ ^[Yy]$ ]]; then
  246. log_error "用户取消操作"
  247. exit 1
  248. fi
  249. fi
  250. mkdir -p "$dir"
  251. log_success "已创建目录: $dir"
  252. fi
  253. # 检查写入权限
  254. if [ ! -w "$dir" ]; then
  255. log_error "无写入权限: $dir"
  256. exit 1
  257. fi
  258. log_success "路径验证通过"
  259. }
  260. # ============================================================================
  261. # 测试服务器连接
  262. # ============================================================================
  263. test_server_connection() {
  264. log_info "测试服务器连接: https://$SERVER"
  265. if ! curl -k -s --max-time 10 --user "$USERNAME:$PASSWORD" \
  266. "https://$SERVER/releases/" > /dev/null 2>&1; then
  267. log_error "无法连接到服务器"
  268. log_error "请检查网络连接、用户名和密码"
  269. exit 1
  270. fi
  271. log_success "服务器连接成功"
  272. }
  273. # ============================================================================
  274. # 获取版本列表
  275. # ============================================================================
  276. get_version_list() {
  277. log_info "正在获取版本列表..."
  278. local versions
  279. versions=$(curl -k -s --user "$USERNAME:$PASSWORD" \
  280. "https://$SERVER/releases/" | \
  281. grep -oP 'v\d+\.\d+\.\d+-\d{8}-\d{6}' | \
  282. sort -Vr | \
  283. head -10)
  284. if [ -z "$versions" ]; then
  285. log_error "无法获取版本列表"
  286. exit 1
  287. fi
  288. echo "$versions"
  289. }
  290. # ============================================================================
  291. # 显示版本列表并选择
  292. # ============================================================================
  293. select_version() {
  294. if [ -n "$VERSION" ]; then
  295. log_info "使用指定版本: $VERSION"
  296. return
  297. fi
  298. log_info "获取可用版本..."
  299. local versions
  300. versions=$(get_version_list)
  301. echo
  302. echo "可用版本列表(最新10个):"
  303. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  304. echo " 0. latest (最新版本)"
  305. local i=1
  306. while IFS= read -r version; do
  307. echo " $i. $version"
  308. ((i++))
  309. done <<< "$versions"
  310. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  311. echo
  312. # 用户选择
  313. while true; do
  314. read -p "请选择版本 [0-$((i-1)),默认: 0]: " choice
  315. choice=${choice:-0}
  316. if [[ $choice =~ ^[0-9]+$ ]]; then
  317. if [ "$choice" -eq 0 ]; then
  318. VERSION="latest"
  319. break
  320. elif [ "$choice" -lt "$i" ]; then
  321. VERSION=$(echo "$versions" | sed -n "${choice}p")
  322. break
  323. fi
  324. fi
  325. log_error "无效选择,请重新输入"
  326. done
  327. log_info "已选择版本: $VERSION"
  328. }
  329. # ============================================================================
  330. # 验证版本是否存在
  331. # ============================================================================
  332. verify_version_exists() {
  333. local version=$1
  334. log_info "验证版本: $version"
  335. if ! curl -k -s --fail --head --user "$USERNAME:$PASSWORD" \
  336. "https://$SERVER/releases/$version/" > /dev/null 2>&1; then
  337. log_error "版本不存在: $version"
  338. exit 1
  339. fi
  340. log_success "版本验证通过"
  341. }
  342. # ============================================================================
  343. # 备份当前版本
  344. # ============================================================================
  345. backup_current_version() {
  346. local dir=$1
  347. if [ ! -d "$dir" ] || [ -z "$(ls -A "$dir" 2>/dev/null)" ]; then
  348. log_info "无需备份(目录为空或不存在)"
  349. return
  350. fi
  351. local backup_name="${dir}_backup_$(date +%Y%m%d_%H%M%S)"
  352. log_info "备份当前版本到: $backup_name"
  353. if ! mv "$dir" "$backup_name"; then
  354. log_error "备份失败"
  355. exit 1
  356. fi
  357. log_success "备份完成: $backup_name"
  358. }
  359. # ============================================================================
  360. # 下载并更新
  361. # ============================================================================
  362. download_and_update() {
  363. local version=$1
  364. local target_dir=$2
  365. log_info "开始下载版本: $version"
  366. log_info "目标目录: $target_dir"
  367. mkdir -p "$target_dir"
  368. # 使用 wget 下载(显示进度)
  369. local wget_opts=(
  370. "--user=$USERNAME"
  371. "--password=$PASSWORD"
  372. "--no-check-certificate"
  373. "--recursive"
  374. "--no-parent"
  375. "--no-host-directories"
  376. "--cut-dirs=2"
  377. "--level=inf"
  378. "-N"
  379. "-P"
  380. "$target_dir"
  381. )
  382. # 如果不包含 static,添加排除参数
  383. if [ "$INCLUDE_STATIC" = "false" ]; then
  384. wget_opts+=("-X" "static")
  385. wget_opts+=("--reject-regex" ".*/static/.*")
  386. log_info "static 文件夹: 跳过下载(使用排除参数)"
  387. else
  388. log_info "static 文件夹: 包含在更新中"
  389. fi
  390. if [ "$SILENT_MODE" = true ]; then
  391. wget_opts+=("-q")
  392. fi
  393. if ! wget "${wget_opts[@]}" "https://$SERVER/releases/$version/"; then
  394. log_error "下载失败"
  395. exit 1
  396. fi
  397. log_success "下载完成"
  398. }
  399. # ============================================================================
  400. # 验证文件完整性(如果存在校验文件)
  401. # ============================================================================
  402. verify_integrity() {
  403. local dir=$1
  404. # 检查是否存在 MD5 校验文件
  405. if [ -f "$dir/checksum.md5" ]; then
  406. log_info "验证文件完整性(MD5)..."
  407. cd "$dir" || exit 1
  408. if md5sum -c checksum.md5 > /dev/null 2>&1; then
  409. log_success "文件完整性验证通过"
  410. else
  411. log_error "文件完整性验证失败"
  412. exit 1
  413. fi
  414. cd - > /dev/null || exit 1
  415. elif [ -f "$dir/checksum.sha256" ]; then
  416. log_info "验证文件完整性(SHA256)..."
  417. cd "$dir" || exit 1
  418. if sha256sum -c checksum.sha256 > /dev/null 2>&1; then
  419. log_success "文件完整性验证通过"
  420. else
  421. log_error "文件完整性验证失败"
  422. exit 1
  423. fi
  424. cd - > /dev/null || exit 1
  425. else
  426. log_warn "未找到校验文件,跳过完整性验证"
  427. fi
  428. }
  429. # ============================================================================
  430. # 显示更新摘要
  431. # ============================================================================
  432. show_update_summary() {
  433. local static_status
  434. if [ "$INCLUDE_STATIC" = "true" ]; then
  435. static_status="包含"
  436. else
  437. static_status="跳过"
  438. fi
  439. echo
  440. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  441. echo " 更新摘要"
  442. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  443. echo " 服务器: $SERVER"
  444. echo " 用户名: $USERNAME"
  445. echo " 目标目录: $DOWNLOAD_DIR"
  446. echo " 版本: $VERSION"
  447. echo " static 文件夹: $static_status"
  448. echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  449. echo
  450. }
  451. # ============================================================================
  452. # 确认更新
  453. # ============================================================================
  454. confirm_update() {
  455. if [ "$SKIP_CONFIRM" = true ]; then
  456. return
  457. fi
  458. read -p "确认执行更新? [y/N]: " confirm
  459. if [[ ! $confirm =~ ^[Yy]$ ]]; then
  460. log_info "用户取消更新"
  461. exit 0
  462. fi
  463. }
  464. # ============================================================================
  465. # 解析命令行参数
  466. # ============================================================================
  467. parse_args() {
  468. while [[ $# -gt 0 ]]; do
  469. case $1 in
  470. -u|--user)
  471. USERNAME="$2"
  472. shift 2
  473. ;;
  474. -p|--password)
  475. PASSWORD="$2"
  476. shift 2
  477. ;;
  478. -d|--dir)
  479. DOWNLOAD_DIR="$2"
  480. shift 2
  481. ;;
  482. -v|--version)
  483. VERSION="$2"
  484. shift 2
  485. ;;
  486. -i|--include-static)
  487. INCLUDE_STATIC=true
  488. shift
  489. ;;
  490. -y|--yes)
  491. SKIP_CONFIRM=true
  492. shift
  493. ;;
  494. -s|--silent)
  495. SILENT_MODE=true
  496. shift
  497. ;;
  498. -h|--help)
  499. show_help
  500. exit 0
  501. ;;
  502. *)
  503. log_error "未知选项: $1"
  504. show_help
  505. exit 1
  506. ;;
  507. esac
  508. done
  509. }
  510. # ============================================================================
  511. # 主函数
  512. # ============================================================================
  513. main() {
  514. # 解析命令行参数
  515. parse_args "$@"
  516. # 初始化日志
  517. init_log
  518. # 显示操作系统信息
  519. show_os_info
  520. # 检查依赖
  521. check_dependencies
  522. # 检查并发
  523. check_lock
  524. # 获取用户输入
  525. get_user_input
  526. # 验证路径
  527. validate_path "$DOWNLOAD_DIR"
  528. # 测试服务器连接
  529. test_server_connection
  530. # 选择版本
  531. select_version
  532. # 验证版本存在
  533. verify_version_exists "$VERSION"
  534. # 显示摘要
  535. show_update_summary
  536. # 确认更新
  537. confirm_update
  538. log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  539. log_info "开始更新流程"
  540. log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  541. # 备份当前版本
  542. backup_current_version "$DOWNLOAD_DIR"
  543. # 下载并更新
  544. download_and_update "$VERSION" "$DOWNLOAD_DIR"
  545. # 验证完整性
  546. verify_integrity "$DOWNLOAD_DIR"
  547. log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  548. log_success "更新完成!"
  549. log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
  550. log_info "目标目录: $DOWNLOAD_DIR"
  551. log_info "版本: $VERSION"
  552. log_info "日志文件: $LOG_FILE"
  553. exit 0
  554. }
  555. # 运行主函数
  556. main "$@"