#!/usr/bin/env bash
# 小白复盘服务器本地目录安全更新/构建入口（HEL-235B 固化）
# 作用：把 /opt/1panel/docker/compose/xiaobaifupan 的 Git 工作目录安全快进到 Gitea main，
#       校验“本地 HEAD = origin/main = 镜像 revision”后，从本地目录构建带提交号的镜像。
# 禁止：不从 main 构建；不使用不带提交短号的 tag；本地有改动/落后/分叉时一律停止。
# 说明：目录顶层归 root，本脚本用“截断写入”绕开 git 对顶层文件 unlink+重建的权限要求；
#       但 main 新增/删除顶层文件时无法自动处理，会列出需管理员执行的精确清单。
set -euo pipefail

GIT_DIR_PATH="$HOME/xiaobai-build/repos/xiaobai-review.git"
WORK_TREE="/opt/1panel/docker/compose/xiaobaifupan"
IMAGE_REPO="xiaobai-review"
LOG_FILE="$HOME/xiaobai-build/BUILD_LOG.tsv"
MODE="${1:-build}"

g() { git --git-dir="$GIT_DIR_PATH" --work-tree="$WORK_TREE" "$@"; }

refuse() { printf '拒绝：%s\n' "$*" >&2; exit 1; }

[ "$MODE" = "build" ] || [ "$MODE" = "verify-tag" ] || refuse "未知子命令「${MODE}」（可用：build / verify-tag <tag>）"
[ -d "$GIT_DIR_PATH" ] || refuse "Git 目录不存在：$GIT_DIR_PATH"

echo "==> 拉取 Gitea origin/main"
g fetch --quiet origin main || refuse "无法连接 Gitea 拉取 origin/main"

echo "==> 检查分支与工作区"
BRANCH="$(g symbolic-ref --short HEAD 2>/dev/null || true)"
[ "$BRANCH" = "main" ] || refuse "当前不在 main 分支（${BRANCH:-detached}），停止"
DIRTY="$(g status --porcelain)"
[ -z "$DIRTY" ] || refuse "本地目录有未提交改动或多余文件，先处理再构建：
$DIRTY"

LOCAL_HEAD="$(g rev-parse HEAD)"
REMOTE_HEAD="$(g rev-parse origin/main)"
if [ "$LOCAL_HEAD" != "$REMOTE_HEAD" ]; then
  g merge-base --is-ancestor "$LOCAL_HEAD" "$REMOTE_HEAD" \
    || refuse "本地 main 与 origin/main 历史分叉，停止（未改写工作目录）"
  CHANGES="$(g diff --no-renames --name-status HEAD origin/main)"
  TOP_AD="$(printf '%s\n' "$CHANGES" | grep -E "^[AD][[:space:]]+[^/]+$" || true)"
  [ -z "$TOP_AD" ] || refuse "main 相比本地新增/删除了顶层文件，目录顶层归 root，需管理员执行：
$TOP_AD"
  echo "==> 同步 origin/main 文件（顶层文件保留原 inode，避免目录权限限制）"
  while IFS=$'\t' read -r status path; do
    [ -n "$path" ] || continue
    case "$status" in
      D)
        case "$path" in
          */*) rm -f -- "$WORK_TREE/$path" ;;
          *) refuse "main 删除了顶层文件 $path，需管理员处理" ;;
        esac
        ;;
      A|M)
        MODE_BITS="$(g ls-tree origin/main -- "$path" | awk '{print $1}')"
        case "$MODE_BITS" in
          100644|100755) ;;
          *) refuse "文件 ${path} 的 Git 类型 ${MODE_BITS} 不支持自动同步，需管理员处理" ;;
        esac
        mkdir -p -- "$(dirname "$WORK_TREE/$path")"
        g show "origin/main:$path" > "$WORK_TREE/$path"
        [ "$MODE_BITS" = "100755" ] && chmod 755 "$WORK_TREE/$path" || chmod 644 "$WORK_TREE/$path"
        ;;
      *) refuse "遇到未支持的 Git 变更类型 ${status}：${path}" ;;
    esac
  done <<< "$CHANGES"
  g read-tree origin/main
  g update-ref refs/heads/main "$REMOTE_HEAD" "$LOCAL_HEAD"
  DIRTY="$(g status --porcelain)"
  [ -z "$DIRTY" ] || refuse "快进后工作区仍不一致，停止：
$DIRTY"
  LOCAL_HEAD="$(g rev-parse HEAD)"
fi
[ "$LOCAL_HEAD" = "$REMOTE_HEAD" ] || refuse "本地 HEAD 与 origin/main 不一致，停止"
SHORT="${LOCAL_HEAD:0:7}"
echo "==> 校验通过：本地 HEAD = origin/main = ${LOCAL_HEAD}（${SHORT}）"

if [ "$MODE" = "verify-tag" ]; then
  TAG="${2:?用法: update-from-main.sh verify-tag <tag>}"
  [ "$TAG" = "main-${SHORT}" ] \
    || refuse "镜像标签必须是当前 main 对应的 main-${SHORT}，收到：${TAG}"
  LABEL="$(docker image inspect "${IMAGE_REPO}:${TAG}" \
    --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' 2>/dev/null)" \
    || refuse "镜像 ${IMAGE_REPO}:${TAG} 不存在"
  [ "$LABEL" = "$LOCAL_HEAD" ] || refuse "镜像 revision（${LABEL}）与当前 main（${LOCAL_HEAD}）不一致，禁止部署"
  echo "==> 通过：${IMAGE_REPO}:${TAG} 的 revision 与 main 一致，可以部署"
  exit 0
fi

TAG="main-${SHORT}"
echo "==> 从本地目录构建 ${IMAGE_REPO}:${TAG}"
docker build --rm -t "${IMAGE_REPO}:${TAG}" \
  --label "org.opencontainers.image.revision=${LOCAL_HEAD}" \
  --label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  "$WORK_TREE" 2>&1 | tail -5

echo "==> 回读校验镜像 revision"
GOT="$(docker image inspect "${IMAGE_REPO}:${TAG}" \
  --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')"
if [ "$GOT" != "$LOCAL_HEAD" ]; then
  docker rmi "${IMAGE_REPO}:${TAG}" >/dev/null 2>&1 || true
  refuse "镜像 revision（${GOT}）与 main（${LOCAL_HEAD}）不一致，已删除镜像"
fi
IMAGE_ID="$(docker image inspect "${IMAGE_REPO}:${TAG}" --format '{{.Id}}' | cut -c8-19)"
mkdir -p "$(dirname "$LOG_FILE")"
printf '%s\t%s\t%s\t%s\tlocal-worktree\n' \
  "$(date '+%F %T')" "${IMAGE_REPO}:${TAG}" "${LOCAL_HEAD}" "${IMAGE_ID}" >> "$LOG_FILE"

cat <<EOF
==> 完成：${IMAGE_REPO}:${TAG}（revision=${LOCAL_HEAD}）
    部署需人工确认，参考 ~/xiaobai-build/README.md 的换版与回滚步骤。
EOF
