文件
websafe-kb/scripts/sync-gitea.sh
2026-03-17 23:14:38 -07:00

361 行
9.7 KiB
Bash
可执行文件

#!/bin/bash
# sync-gitea.sh - 自动同步到 Gitea 仓库
#
# 用法:
# ./sync-gitea.sh # 正常提交和推送
# ./sync-gitea.sh --init # 初始化仓库
# ./sync-gitea.sh --commit # 仅提交
# ./sync-gitea.sh --push # 仅推送
set -euo pipefail
# 配置
REPO_DIR="/Users/x/websafe"
GITEA_URL="https://git.hk.hao.work"
GITEA_API="${GITEA_URL}/api/v1"
REPO_NAME="${REPO_NAME:-websafe-kb}"
REPO_DESC="${REPO_DESC:-授权攻防实验与研究知识库}"
GITEA_TOKEN="${GITEA_TOKEN:-}"
GIT_USER="${GIT_USER:-hao}"
GIT_EMAIL="${GIT_EMAIL:-hao@users.noreply.git.hk.hao.work}"
AUTO_PUSH_MAIN="${AUTO_PUSH_MAIN:-1}"
LOCK_DIR="${REPO_DIR}/.git/.sync-gitea.lock"
RETRY_COUNT="${RETRY_COUNT:-3}"
RETRY_DELAY_SECONDS="${RETRY_DELAY_SECONDS:-3}"
cd "$REPO_DIR"
# 颜色定义
RED='\033[91m'
GREEN='\033[92m'
YELLOW='\033[93m'
BLUE='\033[94m'
END='\033[0m'
log_info() {
echo -e "${BLUE}[INFO]${END} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${END} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${END} $1"
}
log_error() {
echo -e "${RED}[ERROR]${END} $1"
}
run_with_retries() {
local attempt=1
while true; do
if "$@"; then
return 0
fi
if [ "$attempt" -ge "$RETRY_COUNT" ]; then
return 1
fi
log_warning "命令失败,${RETRY_DELAY_SECONDS}s 后重试 (${attempt}/${RETRY_COUNT})"
sleep "$RETRY_DELAY_SECONDS"
attempt=$((attempt + 1))
done
}
repo_api_url() {
echo "${GITEA_API}/repos/${GIT_USER}/${REPO_NAME}"
}
repo_git_url() {
echo "${GITEA_URL}/${GIT_USER}/${REPO_NAME}.git"
}
remote_repo_reachable() {
run_with_retries git ls-remote --exit-code origin HEAD >/dev/null 2>&1
}
current_branch_name() {
local branch
branch=$(git branch --show-current 2>/dev/null || true)
if [ -z "$branch" ]; then
branch="main"
fi
echo "$branch"
}
current_branch_needs_push() {
if ! git rev-parse --verify "@{upstream}" >/dev/null 2>&1; then
return 0
fi
[ "$(git rev-list --count @{upstream}..HEAD)" -gt 0 ]
}
main_ref_needs_push() {
if [ "$AUTO_PUSH_MAIN" != "1" ]; then
return 1
fi
if ! git rev-parse --verify "refs/remotes/origin/main" >/dev/null 2>&1; then
return 0
fi
! git merge-base --is-ancestor "refs/remotes/origin/main" HEAD
}
needs_push() {
current_branch_needs_push || main_ref_needs_push
}
ensure_remote_repo() {
if remote_repo_reachable; then
log_info "远程仓库可访问: ${GIT_USER}/${REPO_NAME}"
return 0
fi
if run_with_retries curl --connect-timeout 5 --max-time 15 -fsS ${GITEA_TOKEN:+-H} ${GITEA_TOKEN:+"Authorization: token ${GITEA_TOKEN}"} "$(repo_api_url)" >/dev/null 2>&1; then
log_info "远程仓库已存在: ${GIT_USER}/${REPO_NAME}"
return 0
fi
if [ -z "$GITEA_TOKEN" ]; then
log_error "远程仓库不可达或不存在,且未提供 GITEA_TOKEN,无法自动创建"
return 1
fi
log_info "创建远程仓库: ${GIT_USER}/${REPO_NAME}"
local payload
payload=$(cat <<EOF
{"name":"${REPO_NAME}","description":"${REPO_DESC}","private":false,"auto_init":false}
EOF
)
curl -fsS -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "$payload" \
"${GITEA_API}/user/repos" >/dev/null
log_success "远程仓库创建完成"
}
run_validations() {
log_info "运行校验: lab validate"
python3 "$REPO_DIR/scripts/lab/main.py" validate
log_info "运行校验: intel validate"
python3 "$REPO_DIR/scripts/intel/main.py" validate
log_success "校验通过"
}
acquire_lock() {
if mkdir "$LOCK_DIR" 2>/dev/null; then
trap 'rm -rf "$LOCK_DIR"' EXIT
return 0
fi
log_warning "检测到另一个同步任务正在运行,跳过本次执行"
return 1
}
# 初始化仓库
init_repo() {
log_info "初始化 Git 仓库..."
if [ -d ".git" ]; then
log_warning "Git 仓库已存在"
else
git init
git config user.name "$GIT_USER"
git config user.email "$GIT_EMAIL"
log_success "Git 仓库初始化完成"
fi
# 添加远程仓库
if git remote | grep -q "origin"; then
git remote set-url origin "$(repo_git_url)"
log_info "远程仓库 URL 已更新"
else
git remote add origin "$(repo_git_url)"
log_success "远程仓库已添加"
fi
# 凭证处理:
# 默认不在仓库脚本中写入真实凭证。
# 如需使用 token,请在运行时通过环境变量 GITEA_TOKEN 注入,
# 推送时通过临时 HTTP Header 使用,不写入仓库或全局凭证文件。
if [ -n "$GITEA_TOKEN" ]; then
log_info "检测到 GITEA_TOKEN 环境变量,将在推送时临时注入 HTTP Header"
else
log_warning "未提供 GITEA_TOKEN;推送时将使用本机已有认证方式"
fi
log_success "初始化完成"
}
# 提交更改
commit_changes() {
log_info "检查更改..."
if git status --porcelain | grep -q .; then
if [ "${SKIP_VALIDATE:-0}" != "1" ]; then
run_validations
else
log_warning "已跳过 validate"
fi
else
log_info "没有需要提交的更改"
return 0
fi
# 添加所有文件
git add -A
# 检查是否有更改
if git diff --staged --quiet; then
log_info "没有需要提交的更改"
return 0
fi
# 生成提交信息
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local changed_files=$(git diff --staged --name-only | wc -l | tr -d ' ')
local commit_msg="更新: ${changed_files} 个文件 - ${timestamp}"
# 如果提供了自定义提交信息
local custom_msg="${1:-}"
if [ -n "$custom_msg" ]; then
commit_msg="$custom_msg"
fi
git commit -m "$commit_msg"
log_success "提交完成: $commit_msg"
}
# 推送到远程
push_changes() {
log_info "推送到远程仓库..."
# 获取当前分支
local branch
branch=$(current_branch_name)
ensure_remote_repo
# 推送
if [ -n "$GITEA_TOKEN" ]; then
run_with_retries git -c http.extraHeader="Authorization: token ${GITEA_TOKEN}" push -u origin "$branch"
else
run_with_retries git push -u origin "$branch"
fi
if [ $? -eq 0 ]; then
log_success "推送完成: $branch"
else
log_error "推送失败"
return 1
fi
if [ "$AUTO_PUSH_MAIN" = "1" ]; then
if run_with_retries git ls-remote --exit-code --heads origin main >/dev/null 2>&1; then
if run_with_retries git fetch origin main:refs/remotes/origin/main >/dev/null 2>&1; then
if git merge-base --is-ancestor refs/remotes/origin/main HEAD; then
if [ -n "$GITEA_TOKEN" ]; then
run_with_retries git -c http.extraHeader="Authorization: token ${GITEA_TOKEN}" push origin HEAD:main
else
run_with_retries git push origin HEAD:main
fi
git branch -f main HEAD >/dev/null 2>&1 || true
log_success "main 已快进到当前提交"
else
log_warning "origin/main 不是当前 HEAD 的祖先,跳过 main 快进推送"
fi
else
log_warning "刷新 origin/main 失败,跳过 main 快进推送"
fi
else
if [ -n "$GITEA_TOKEN" ]; then
run_with_retries git -c http.extraHeader="Authorization: token ${GITEA_TOKEN}" push origin HEAD:main
else
run_with_retries git push origin HEAD:main
fi
git branch -f main HEAD >/dev/null 2>&1 || true
log_success "main 已创建并指向当前提交"
fi
fi
}
# 完整同步
full_sync() {
init_repo
commit_changes
if needs_push; then
push_changes
else
log_info "没有需要推送的提交"
fi
}
auto_sync() {
acquire_lock || return 0
full_sync
}
# 显示帮助
show_help() {
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " --init 初始化 Git 仓库"
echo " --commit 仅提交更改"
echo " --push 仅推送到远程"
echo " --autosync 定时任务模式: 无并发锁 + 校验 + 提交 + 推送"
echo " --ensure 检查远程仓库;不存在则创建"
echo " --status 显示仓库状态"
echo " --help 显示此帮助"
echo ""
echo "环境变量:"
echo " GITEA_TOKEN 可选;脚本不会自动写入 ~/.git-credentials"
echo " GIT_USER 可选;默认 hao"
echo " GIT_EMAIL 可选;默认 hao@users.noreply.git.hk.hao.work"
echo " REPO_NAME 可选;默认 websafe-kb"
echo " REPO_DESC 可选;默认 授权攻防实验与研究知识库"
echo ""
echo "无参数运行时执行完整同步 (提交 + 推送)"
}
# 显示状态
show_status() {
log_info "仓库状态:"
echo ""
git status -s
echo ""
local ahead=$(git rev-list --count @{upstream}..HEAD 2>/dev/null || echo "0")
local behind=$(git rev-list --count HEAD..@{upstream} 2>/dev/null || echo "0")
log_info "领先远程 $ahead 个提交, 落后 $behind 个提交"
}
# 主程序
case "${1:-}" in
--init)
init_repo
;;
--commit)
commit_changes "$2"
;;
--push)
push_changes
;;
--autosync)
auto_sync
;;
--ensure)
init_repo
ensure_remote_repo
;;
--status)
show_status
;;
--help|-h)
show_help
;;
*)
full_sync
;;
esac