#!/bin/bash # ============================================================ # release-server.sh # 为 server 子项目生成增量发布 zip(基于两个 git ref 之间的差异) # # 用法(在 Windows 项目根下用 WSL 调用): # wsl bash scripts/release-server.sh # wsl bash scripts/release-server.sh # # 两个 hash 不区分顺序,脚本会自动识别哪个较新作为发布目标 # # 输出: # release-.zip 项目根目录(月日时分 8 位) # zip 内含: # - 增量保留文件(保持 server/ 相对路径) # - __REMOVE_LIST.txt 需要在服务器删除的文件清单 # - apply.sh 服务器端执行的应用脚本 # - README.txt 使用说明 # ============================================================ set -e # 两个参数不区分顺序,脚本会根据 commit time 自动判断: # 较早 → BASE(只拿变更起点) # 较晚 → HEAD(要发布的目标版本) REF_A="${1:-origin/develop}" REF_B="${2:-HEAD}" # 项目根(脚本所在目录的上一级) SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" SERVER_DIR="$PROJECT_ROOT/server" if [ ! -d "$SERVER_DIR/.git" ]; then echo "✗ server 不是 git 仓库: $SERVER_DIR" >&2 exit 1 fi cd "$SERVER_DIR" # 关闭 git 对非 ASCII 路径的八进制转义,避免中文文件名拷贝丢失 export GIT_CONFIG_PARAMETERS="'core.quotepath=false'" # 校验 ref 可达 if ! git rev-parse --verify "$REF_A" >/dev/null 2>&1; then echo "✗ 参数 1 不存在: $REF_A" >&2 exit 1 fi if ! git rev-parse --verify "$REF_B" >/dev/null 2>&1; then echo "✗ 参数 2 不存在: $REF_B" >&2 exit 1 fi # 拿到实际 commit hash + commit time A_FULL="$(git rev-parse "$REF_A")" B_FULL="$(git rev-parse "$REF_B")" if [ "$A_FULL" = "$B_FULL" ]; then echo "✗ 两个 ref 指向同一 commit ($A_FULL),无增量可打包" >&2 exit 1 fi A_TIME="$(git show -s --format=%ct "$REF_A")" B_TIME="$(git show -s --format=%ct "$REF_B")" # 较早 -> BASE,较晚 -> HEAD if [ "$A_TIME" -le "$B_TIME" ]; then BASE_REF="$REF_A" HEAD_REF="$REF_B" else BASE_REF="$REF_B" HEAD_REF="$REF_A" echo " (检测到顺序反了,已自动交换: BASE=$REF_B HEAD=$REF_A)" fi COMMIT_HASH="$(git rev-parse --short "$HEAD_REF")" TIMESTAMP="$(date '+%m%d%H%M')" ZIP_NAME="release-${TIMESTAMP}.zip" ZIP_PATH="$PROJECT_ROOT/$ZIP_NAME" WORK_DIR="$PROJECT_ROOT/.release-tmp" echo "================================" echo "Release 打包" echo "================================" echo "BASE: $BASE_REF" echo "HEAD: $HEAD_REF ($COMMIT_HASH)" echo "输出: $ZIP_PATH" echo "" # 清理 rm -rf "$WORK_DIR" rm -f "$ZIP_PATH" mkdir -p "$WORK_DIR" # ----------- 收集变更清单(按状态分类) ----------- DIFF_RAW="$(git diff --name-status "$BASE_REF..$HEAD_REF")" ADDED_FILES="$(echo "$DIFF_RAW" | awk -F'\t' '$1 == "A" { print $2 }')" MODIFIED_FILES="$(echo "$DIFF_RAW" | awk -F'\t' '$1 == "M" { print $2 }')" DELETED_FILES="$(echo "$DIFF_RAW" | awk -F'\t' '$1 == "D" { print $2 }')" # 重命名保持 "旧\t新" 形式 RENAMED_PAIRS="$(echo "$DIFF_RAW" | awk -F'\t' '$1 ~ /^R[0-9]*$/ { print $2 "\t" $3 }')" # 拷需要的保留文件:A + M + R的新名 KEEP_LIST="$(echo "$DIFF_RAW" | awk -F'\t' ' $1 == "A" || $1 == "M" { print $2 } $1 ~ /^R[0-9]*$/ { print $3 } ')" # 删除清单:D + R的旧名 echo "$DIFF_RAW" | awk -F'\t' ' $1 == "D" { print $2 } $1 ~ /^R[0-9]*$/ { print $2 } ' > "$WORK_DIR/__REMOVE_LIST.txt" ADDED_COUNT=$( [ -n "$ADDED_FILES" ] && echo "$ADDED_FILES" | wc -l || echo 0) MODIFIED_COUNT=$([ -n "$MODIFIED_FILES" ] && echo "$MODIFIED_FILES" | wc -l || echo 0) DELETED_COUNT=$( [ -n "$DELETED_FILES" ] && echo "$DELETED_FILES" | wc -l || echo 0) RENAMED_COUNT=$( [ -n "$RENAMED_PAIRS" ] && echo "$RENAMED_PAIRS" | wc -l || echo 0) # ----------- 打印变更详情 ----------- print_section() { local title="$1"; local symbol="$2"; local content="$3"; local count="$4" echo "" echo "=== $title ($count) ===" if [ "$count" -gt 0 ] && [ -n "$content" ]; then echo "$content" | while IFS= read -r line; do [ -z "$line" ] && continue echo " $symbol $line" done fi } print_section "新增 (Added)" "+" "$ADDED_FILES" "$ADDED_COUNT" print_section "更新 (Modified)" "~" "$MODIFIED_FILES" "$MODIFIED_COUNT" echo "" echo "=== 重命名 (Renamed) ($RENAMED_COUNT) ===" if [ "$RENAMED_COUNT" -gt 0 ] && [ -n "$RENAMED_PAIRS" ]; then echo "$RENAMED_PAIRS" | while IFS=$'\t' read -r oldp newp; do [ -z "$oldp" ] && continue echo " → $oldp ⇒ $newp" done fi print_section "删除 (Deleted)" "-" "$DELETED_FILES" "$DELETED_COUNT" echo "" echo "================================" echo "开始拷贝文件到工作区…" echo "================================" KEEP_COUNT=0 MISSING_COUNT=0 # ----------- 拷贝保留文件到工作区 ----------- while IFS= read -r f; do [ -z "$f" ] && continue if [ -f "$SERVER_DIR/$f" ]; then mkdir -p "$WORK_DIR/$(dirname "$f")" cp "$SERVER_DIR/$f" "$WORK_DIR/$f" KEEP_COUNT=$((KEEP_COUNT + 1)) else MISSING_COUNT=$((MISSING_COUNT + 1)) echo " ! 工作树缺失: $f" >&2 fi done <<< "$KEEP_LIST" REMOVE_COUNT=$(wc -l < "$WORK_DIR/__REMOVE_LIST.txt" | tr -d ' ') # ----------- 生成服务器端 apply.sh ----------- cat > "$WORK_DIR/apply.sh" <<'APPLY' #!/bin/bash # 在服务器 server 项目根目录执行: bash apply.sh set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REMOVE_LIST="$SCRIPT_DIR/__REMOVE_LIST.txt" if [ ! -f "$REMOVE_LIST" ]; then echo "✗ 未找到 $REMOVE_LIST" >&2 exit 1 fi echo "=== [1/3] 删除废弃文件 ===" DEL_OK=0 DEL_MISS=0 while IFS= read -r f; do [ -z "$f" ] && continue if [ -e "$SCRIPT_DIR/$f" ]; then rm -f "$SCRIPT_DIR/$f" DEL_OK=$((DEL_OK + 1)) else DEL_MISS=$((DEL_MISS + 1)) fi done < "$REMOVE_LIST" echo " 已删除: $DEL_OK 不存在: $DEL_MISS" echo "" echo "=== [2/3] 清理空目录 ===" for d in public/truth-social public/mobile/assets public/mobile; do if [ -d "$SCRIPT_DIR/$d" ]; then find "$SCRIPT_DIR/$d" -type d -empty -delete 2>/dev/null || true fi done echo " 完成" echo "" echo "=== [3/3] 自我清理 ===" rm -f "$SCRIPT_DIR/__REMOVE_LIST.txt" rm -f "$SCRIPT_DIR/README.txt" rm -f "$SCRIPT_DIR/apply.sh" echo " 完成" echo "" echo "✓ 增量更新已应用" APPLY chmod +x "$WORK_DIR/apply.sh" # ----------- 生成 README.txt ----------- cat > "$WORK_DIR/README.txt" </dev/null 2>&1; then return 0 fi echo "" echo " ! 未找到 zip 命令,尝试自动安装..." # 选择 sudo 前缀(root 用户不需要 sudo) local SUDO="" if [ "$(id -u)" -ne 0 ]; then if command -v sudo >/dev/null 2>&1; then SUDO="sudo" else echo "✗ 当前非 root 用户且无 sudo 命令,无法自动安装" >&2 echo " 请手动执行(任选其一匹配你的发行版):" >&2 echo " apt-get install -y zip" >&2 echo " yum install -y zip" >&2 echo " apk add zip" >&2 return 1 fi fi if command -v apt-get >/dev/null 2>&1; then echo " -> 使用 apt-get 安装" $SUDO apt-get update -qq && $SUDO apt-get install -y -qq zip elif command -v yum >/dev/null 2>&1; then echo " -> 使用 yum 安装" $SUDO yum install -y zip elif command -v apk >/dev/null 2>&1; then echo " -> 使用 apk 安装" $SUDO apk add --no-cache zip elif command -v dnf >/dev/null 2>&1; then echo " -> 使用 dnf 安装" $SUDO dnf install -y zip else echo "✗ 未识别的包管理器(apt-get/yum/apk/dnf 都不可用)" >&2 return 1 fi if ! command -v zip >/dev/null 2>&1; then echo "✗ 安装后仍无 zip 命令,请手动检查" >&2 return 1 fi echo " ✓ zip 安装成功" return 0 } ensure_zip || exit 1 # ----------- 打包 zip ----------- cd "$WORK_DIR" zip -qr "$ZIP_PATH" . # ----------- 清理工作区 ----------- rm -rf "$WORK_DIR" ZIP_SIZE_HUMAN=$(du -h "$ZIP_PATH" | cut -f1) echo "" echo "================================" echo "✓ 打包完成" echo "================================" echo "文件: $ZIP_PATH" echo "大小: $ZIP_SIZE_HUMAN" echo "--------------------------------" echo "变更总览:" echo " 新增 (A): $ADDED_COUNT" echo " 更新 (M): $MODIFIED_COUNT" echo " 重命名(R): $RENAMED_COUNT" echo " 删除 (D): $DELETED_COUNT" echo "--------------------------------" echo "打包结果:" echo " zip 内保留文件数: $KEEP_COUNT" echo " 工作树缺失文件数: $MISSING_COUNT" echo " 服务器待删除文件: $REMOVE_COUNT" echo "================================"