update-client.sh 18 KB

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