320 lines
9.7 KiB
Bash
320 lines
9.7 KiB
Bash
#!/bin/bash
|
|
# ============================================================
|
|
# release-server.sh
|
|
# 为 server 子项目生成增量发布 zip(基于两个 git ref 之间的差异)
|
|
#
|
|
# 用法(在 Windows 项目根下用 WSL 调用):
|
|
# wsl bash scripts/release-server.sh
|
|
# wsl bash scripts/release-server.sh <hashA> <hashB>
|
|
#
|
|
# 两个 hash 不区分顺序,脚本会自动识别哪个较新作为发布目标
|
|
#
|
|
# 输出:
|
|
# release-<MMDDHHmm>.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" <<EOF
|
|
${ZIP_NAME}
|
|
================================================================
|
|
基于 ${BASE_REF} ($(git rev-parse --short "$BASE_REF"))
|
|
.. ${HEAD_REF} ($(git rev-parse --short "$HEAD_REF"))
|
|
的增量发布包
|
|
|
|
使用方法:
|
|
1. 把 zip 上传到服务器 server 项目根
|
|
例: /www/wwwroot/test-server.sbnews.net/
|
|
2. 解压: unzip -o ${ZIP_NAME}
|
|
3. 应用: bash apply.sh
|
|
4. apply.sh 会自动删除废弃文件、清理空目录,并自我清理
|
|
|
|
统计:
|
|
保留文件数: ${KEEP_COUNT}
|
|
缺失文件数: ${MISSING_COUNT}
|
|
待删除文件数: ${REMOVE_COUNT}
|
|
生成时间: $(date '+%Y-%m-%d %H:%M:%S')
|
|
================================================================
|
|
EOF
|
|
|
|
# ----------- 确保 zip 命令可用(缺失时自动安装) -----------
|
|
ensure_zip() {
|
|
if command -v zip >/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 "================================"
|