| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- #!/usr/bin/env bash
- ################################################################################
- # H5 应用更新客户端脚本
- #
- # 功能:从版本服务器下载并更新 H5 应用
- # 版本:1.0.0
- # 作者:自动生成
- ################################################################################
- set -euo pipefail # 严格模式:遇到错误立即退出
- # ============================================================================
- # 颜色定义
- # ============================================================================
- readonly RED='\033[0;31m'
- readonly GREEN='\033[0;32m'
- readonly YELLOW='\033[1;33m'
- readonly BLUE='\033[0;34m'
- readonly NC='\033[0m' # No Color
- # ============================================================================
- # 全局变量
- # ============================================================================
- readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- readonly LOCK_FILE="/tmp/update-client-${USER}.lock"
- readonly SERVER="dros.overs.games"
- readonly DEFAULT_USER="deploy"
- readonly DEFAULT_DIR="./h5"
- readonly LOG_DIR="./logs"
- # 用户输入变量
- USERNAME=""
- PASSWORD=""
- DOWNLOAD_DIR=""
- VERSION=""
- SILENT_MODE=false
- SKIP_CONFIRM=false
- INCLUDE_STATIC=false # 是否包含 static 文件夹
- # 日志文件
- LOG_FILE=""
- # ============================================================================
- # 工具函数
- # ============================================================================
- # 打印信息
- log_info() {
- echo -e "${GREEN}[INFO]${NC} $*" | tee -a "$LOG_FILE"
- }
- # 打印警告
- log_warn() {
- echo -e "${YELLOW}[WARN]${NC} $*" | tee -a "$LOG_FILE"
- }
- # 打印错误
- log_error() {
- echo -e "${RED}[ERROR]${NC} $*" | tee -a "$LOG_FILE" >&2
- }
- # 打印成功
- log_success() {
- echo -e "${GREEN}[✓]${NC} $*" | tee -a "$LOG_FILE"
- }
- # 清理函数
- cleanup() {
- if [ -f "$LOCK_FILE" ]; then
- rm -f "$LOCK_FILE"
- fi
- }
- # 设置清理陷阱
- trap cleanup EXIT INT TERM
- # ============================================================================
- # 帮助信息
- # ============================================================================
- show_help() {
- cat << EOF
- H5 应用更新客户端
- 用法: $0 [选项]
- 选项:
- -u, --user USERNAME 指定用户名(默认: $DEFAULT_USER)
- -p, --password PASS 指定密码
- -d, --dir DIRECTORY 指定下载目录(默认: $DEFAULT_DIR)
- -v, --version VERSION 指定版本号(默认: latest)
- -i, --include-static 包含 static 文件夹更新(默认: 不包含)
- -y, --yes 跳过确认提示
- -s, --silent 静默模式(最小输出)
- -h, --help 显示此帮助信息
- 示例:
- # 交互模式
- $0
- # 非交互模式(不包含 static)
- $0 -u deploy -p mypassword -d ./h5 -v latest -y
- # 包含 static 文件夹
- $0 -u deploy -p mypassword -d ./h5 -v latest -y --include-static
- # 静默模式
- $0 -u deploy -p mypassword -v v1.1.5-20251129-062104 -y -s
- EOF
- }
- # ============================================================================
- # 检查依赖工具
- # ============================================================================
- check_dependencies() {
- log_info "检查依赖工具..."
-
- local missing_tools=()
-
- for tool in curl wget tar; do
- if ! command -v "$tool" &> /dev/null; then
- missing_tools+=("$tool")
- fi
- done
-
- if [ ${#missing_tools[@]} -gt 0 ]; then
- log_error "缺少必需工具: ${missing_tools[*]}"
- log_error "请安装缺少的工具后重试"
- exit 1
- fi
-
- log_success "依赖工具检查通过"
- }
- # ============================================================================
- # 检查并发执行
- # ============================================================================
- check_lock() {
- if [ -f "$LOCK_FILE" ]; then
- log_error "更新脚本已在运行中(锁文件: $LOCK_FILE)"
- log_error "如确认没有其他实例运行,请删除锁文件后重试"
- exit 1
- fi
-
- touch "$LOCK_FILE"
- log_info "已创建锁文件"
- }
- # ============================================================================
- # 初始化日志
- # ============================================================================
- init_log() {
- mkdir -p "$LOG_DIR"
- LOG_FILE="$LOG_DIR/update_$(date +%Y%m%d_%H%M%S).log"
- log_info "日志文件: $LOG_FILE"
- }
- # ============================================================================
- # 验证用户名格式
- # ============================================================================
- validate_username() {
- local username=$1
-
- if [[ ! $username =~ ^[a-zA-Z0-9_]+$ ]]; then
- log_error "用户名格式错误(仅允许字母、数字和下划线)"
- return 1
- fi
-
- return 0
- }
- # ============================================================================
- # 验证密码
- # ============================================================================
- validate_password() {
- local password=$1
-
- if [ -z "$password" ]; then
- log_error "密码不能为空"
- return 1
- fi
-
- if [ ${#password} -lt 6 ]; then
- log_error "密码长度至少6位"
- return 1
- fi
-
- return 0
- }
- # ============================================================================
- # 获取用户输入
- # ============================================================================
- get_user_input() {
- # 用户名
- if [ -z "$USERNAME" ]; then
- read -p "请输入用户名 [默认: $DEFAULT_USER]: " USERNAME
- USERNAME=${USERNAME:-$DEFAULT_USER}
- fi
-
- if ! validate_username "$USERNAME"; then
- exit 1
- fi
-
- log_info "用户名: $USERNAME"
-
- # 密码
- if [ -z "$PASSWORD" ]; then
- while true; do
- read -s -p "请输入密码: " PASSWORD
- echo
-
- if validate_password "$PASSWORD"; then
- break
- fi
- done
- fi
-
- log_info "密码已设置(已脱敏)"
-
- # 下载目录
- if [ -z "$DOWNLOAD_DIR" ]; then
- read -p "请输入下载目录 [默认: $DEFAULT_DIR]: " DOWNLOAD_DIR
- DOWNLOAD_DIR=${DOWNLOAD_DIR:-$DEFAULT_DIR}
- fi
-
- log_info "下载目录: $DOWNLOAD_DIR"
-
- # 询问是否更新 static 文件夹(仅在交互模式且未通过参数指定时询问)
- if [ "$INCLUDE_STATIC" = "false" ]; then
- read -p "是否需要更新 static 文件夹? [y/N]: " include_static
- if [[ $include_static =~ ^[Yy]$ ]]; then
- INCLUDE_STATIC=true
- log_info "static 文件夹: 将包含在更新中"
- else
- INCLUDE_STATIC=false
- log_info "static 文件夹: 将跳过更新"
- fi
- fi
- }
- # ============================================================================
- # 验证路径权限
- # ============================================================================
- validate_path() {
- local dir=$1
-
- # 检查路径是否存在
- if [ ! -d "$dir" ]; then
- log_warn "目录不存在: $dir"
-
- if [ "$SKIP_CONFIRM" = false ]; then
- read -p "是否创建该目录? [y/N]: " confirm
- if [[ ! $confirm =~ ^[Yy]$ ]]; then
- log_error "用户取消操作"
- exit 1
- fi
- fi
-
- mkdir -p "$dir"
- log_success "已创建目录: $dir"
- fi
-
- # 检查写入权限
- if [ ! -w "$dir" ]; then
- log_error "无写入权限: $dir"
- exit 1
- fi
-
- log_success "路径验证通过"
- }
- # ============================================================================
- # 测试服务器连接
- # ============================================================================
- test_server_connection() {
- log_info "测试服务器连接: https://$SERVER"
-
- if ! curl -k -s --max-time 10 --user "$USERNAME:$PASSWORD" \
- "https://$SERVER/releases/" > /dev/null 2>&1; then
- log_error "无法连接到服务器"
- log_error "请检查网络连接、用户名和密码"
- exit 1
- fi
-
- log_success "服务器连接成功"
- }
- # ============================================================================
- # 获取版本列表
- # ============================================================================
- get_version_list() {
- log_info "正在获取版本列表..."
-
- local versions
- versions=$(curl -k -s --user "$USERNAME:$PASSWORD" \
- "https://$SERVER/releases/" | \
- grep -oP 'v\d+\.\d+\.\d+-\d{8}-\d{6}' | \
- sort -Vr | \
- head -10)
-
- if [ -z "$versions" ]; then
- log_error "无法获取版本列表"
- exit 1
- fi
-
- echo "$versions"
- }
- # ============================================================================
- # 显示版本列表并选择
- # ============================================================================
- select_version() {
- if [ -n "$VERSION" ]; then
- log_info "使用指定版本: $VERSION"
- return
- fi
-
- log_info "获取可用版本..."
- local versions
- versions=$(get_version_list)
-
- echo
- echo "可用版本列表(最新10个):"
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- echo " 0. latest (最新版本)"
-
- local i=1
- while IFS= read -r version; do
- echo " $i. $version"
- ((i++))
- done <<< "$versions"
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- echo
-
- # 用户选择
- while true; do
- read -p "请选择版本 [0-$((i-1)),默认: 0]: " choice
- choice=${choice:-0}
-
- if [[ $choice =~ ^[0-9]+$ ]]; then
- if [ "$choice" -eq 0 ]; then
- VERSION="latest"
- break
- elif [ "$choice" -lt "$i" ]; then
- VERSION=$(echo "$versions" | sed -n "${choice}p")
- break
- fi
- fi
-
- log_error "无效选择,请重新输入"
- done
-
- log_info "已选择版本: $VERSION"
- }
- # ============================================================================
- # 验证版本是否存在
- # ============================================================================
- verify_version_exists() {
- local version=$1
-
- log_info "验证版本: $version"
-
- if ! curl -k -s --fail --head --user "$USERNAME:$PASSWORD" \
- "https://$SERVER/releases/$version/" > /dev/null 2>&1; then
- log_error "版本不存在: $version"
- exit 1
- fi
-
- log_success "版本验证通过"
- }
- # ============================================================================
- # 备份当前版本
- # ============================================================================
- backup_current_version() {
- local dir=$1
-
- if [ ! -d "$dir" ] || [ -z "$(ls -A "$dir" 2>/dev/null)" ]; then
- log_info "无需备份(目录为空或不存在)"
- return
- fi
-
- local backup_name="${dir}_backup_$(date +%Y%m%d_%H%M%S)"
-
- log_info "备份当前版本到: $backup_name"
-
- if ! mv "$dir" "$backup_name"; then
- log_error "备份失败"
- exit 1
- fi
-
- log_success "备份完成: $backup_name"
- }
- # ============================================================================
- # 下载并更新
- # ============================================================================
- download_and_update() {
- local version=$1
- local target_dir=$2
-
- log_info "开始下载版本: $version"
- log_info "目标目录: $target_dir"
-
- mkdir -p "$target_dir"
-
- # 使用 wget 下载(显示进度)
- local wget_opts=(
- "--user=$USERNAME"
- "--password=$PASSWORD"
- "--no-check-certificate"
- "--recursive"
- "--no-parent"
- "--no-host-directories"
- "--cut-dirs=2"
- "--level=inf"
- "-N"
- "-P"
- "$target_dir"
- )
-
- # 如果不包含 static,添加排除参数
- if [ "$INCLUDE_STATIC" = "false" ]; then
- wget_opts+=("-X" "static")
- wget_opts+=("--reject-regex" ".*/static/.*")
- log_info "static 文件夹: 跳过下载(使用排除参数)"
- else
- log_info "static 文件夹: 包含在更新中"
- fi
-
- if [ "$SILENT_MODE" = true ]; then
- wget_opts+=("-q")
- fi
-
- if ! wget "${wget_opts[@]}" "https://$SERVER/releases/$version/"; then
- log_error "下载失败"
- exit 1
- fi
-
- log_success "下载完成"
- }
- # ============================================================================
- # 验证文件完整性(如果存在校验文件)
- # ============================================================================
- verify_integrity() {
- local dir=$1
-
- # 检查是否存在 MD5 校验文件
- if [ -f "$dir/checksum.md5" ]; then
- log_info "验证文件完整性(MD5)..."
-
- cd "$dir" || exit 1
- if md5sum -c checksum.md5 > /dev/null 2>&1; then
- log_success "文件完整性验证通过"
- else
- log_error "文件完整性验证失败"
- exit 1
- fi
- cd - > /dev/null || exit 1
- elif [ -f "$dir/checksum.sha256" ]; then
- log_info "验证文件完整性(SHA256)..."
-
- cd "$dir" || exit 1
- if sha256sum -c checksum.sha256 > /dev/null 2>&1; then
- log_success "文件完整性验证通过"
- else
- log_error "文件完整性验证失败"
- exit 1
- fi
- cd - > /dev/null || exit 1
- else
- log_warn "未找到校验文件,跳过完整性验证"
- fi
- }
- # ============================================================================
- # 显示更新摘要
- # ============================================================================
- show_update_summary() {
- local static_status
- if [ "$INCLUDE_STATIC" = "true" ]; then
- static_status="包含"
- else
- static_status="跳过"
- fi
-
- echo
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- echo " 更新摘要"
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- echo " 服务器: $SERVER"
- echo " 用户名: $USERNAME"
- echo " 目标目录: $DOWNLOAD_DIR"
- echo " 版本: $VERSION"
- echo " static 文件夹: $static_status"
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- echo
- }
- # ============================================================================
- # 确认更新
- # ============================================================================
- confirm_update() {
- if [ "$SKIP_CONFIRM" = true ]; then
- return
- fi
-
- read -p "确认执行更新? [y/N]: " confirm
- if [[ ! $confirm =~ ^[Yy]$ ]]; then
- log_info "用户取消更新"
- exit 0
- fi
- }
- # ============================================================================
- # 解析命令行参数
- # ============================================================================
- parse_args() {
- while [[ $# -gt 0 ]]; do
- case $1 in
- -u|--user)
- USERNAME="$2"
- shift 2
- ;;
- -p|--password)
- PASSWORD="$2"
- shift 2
- ;;
- -d|--dir)
- DOWNLOAD_DIR="$2"
- shift 2
- ;;
- -v|--version)
- VERSION="$2"
- shift 2
- ;;
- -i|--include-static)
- INCLUDE_STATIC=true
- shift
- ;;
- -y|--yes)
- SKIP_CONFIRM=true
- shift
- ;;
- -s|--silent)
- SILENT_MODE=true
- shift
- ;;
- -h|--help)
- show_help
- exit 0
- ;;
- *)
- log_error "未知选项: $1"
- show_help
- exit 1
- ;;
- esac
- done
- }
- # ============================================================================
- # 主函数
- # ============================================================================
- main() {
- # 解析命令行参数
- parse_args "$@"
-
- # 初始化日志
- init_log
-
- # 检查依赖
- check_dependencies
-
- # 检查并发
- check_lock
-
- # 获取用户输入
- get_user_input
-
- # 验证路径
- validate_path "$DOWNLOAD_DIR"
-
- # 测试服务器连接
- test_server_connection
-
- # 选择版本
- select_version
-
- # 验证版本存在
- verify_version_exists "$VERSION"
-
- # 显示摘要
- show_update_summary
-
- # 确认更新
- confirm_update
-
- log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- log_info "开始更新流程"
- log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
-
- # 备份当前版本
- backup_current_version "$DOWNLOAD_DIR"
-
- # 下载并更新
- download_and_update "$VERSION" "$DOWNLOAD_DIR"
-
- # 验证完整性
- verify_integrity "$DOWNLOAD_DIR"
-
- log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- log_success "更新完成!"
- log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- log_info "目标目录: $DOWNLOAD_DIR"
- log_info "版本: $VERSION"
- log_info "日志文件: $LOG_FILE"
-
- exit 0
- }
- # 运行主函数
- main "$@"
|