149 lines
6.2 KiB
Bash
Executable File
149 lines
6.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 把被追踪项目的源码镜像到 gitinbox 组织,供代码级追踪。
|
||
#
|
||
# 用法:
|
||
# bash scripts/mirror_sync.sh --id ipollowork
|
||
# bash scripts/mirror_sync.sh --id ipollowork --depth 200 # 超大仓只取近期历史
|
||
#
|
||
# 可见性由档案的 business.license.redistributable 决定(L1/L2 → public,L3 → private)。
|
||
# 这条规则不能手工绕过:把 source-available 项目公开镜像,等于替对方做了一次
|
||
# 未经授权的公开分发。
|
||
set -euo pipefail
|
||
|
||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
GITEA_URL="${GITEA_URL:-https://gitea.apescale.com}"
|
||
DISPLAY_URL="${GITEA_DISPLAY_URL:-https://gitea.apescale.com}"
|
||
ORG="${GITEA_ORG:-gitinbox}"
|
||
ID=""
|
||
DEPTH=""
|
||
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--id) ID="$2"; shift 2;;
|
||
--org) ORG="$2"; shift 2;;
|
||
--url) GITEA_URL="$2"; shift 2;;
|
||
--depth) DEPTH="$2"; shift 2;;
|
||
*) echo "未知参数: $1" >&2; exit 1;;
|
||
esac
|
||
done
|
||
|
||
[[ -z "$ID" ]] && { echo "缺少 --id" >&2; exit 1; }
|
||
YAML="$ROOT/data/projects/$ID.yaml"
|
||
[[ -f "$YAML" ]] || { echo "档案不存在: $YAML" >&2; exit 1; }
|
||
|
||
# ---- 从档案读取镜像配置(bash 不适合解析 YAML,借用 python 输出 shell 赋值) ----
|
||
# 用"写临时文件再 source"而不是 eval "$(...)":
|
||
# eval 会二次解析内容,YAML 里的括号/引号一旦漏转义就会变成难定位的
|
||
# syntax error,而且报错行号会偏移到完全无关的位置。
|
||
CONF_FILE="$(mktemp)"
|
||
trap 'rm -f "$CONF_FILE"' EXIT
|
||
|
||
/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python - "$YAML" > "$CONF_FILE" <<'PY'
|
||
import sys, shlex
|
||
from pathlib import Path
|
||
sys.path.insert(0, str(Path(sys.argv[1]).resolve().parent.parent.parent / "scripts"))
|
||
from _common import load_yaml
|
||
d = load_yaml(Path(sys.argv[1]))
|
||
m = d.get("mirror") or {}
|
||
lic = (d.get("business") or {}).get("license") or {}
|
||
print(f"UPSTREAM={shlex.quote(str(m.get('upstream') or ''))}")
|
||
print(f"REPO_NAME={shlex.quote(str(m.get('gitea_repo') or '').split('/')[-1] or d.get('id',''))}")
|
||
# 可见性以许可证判定为准,不信任档案里手写的 visibility
|
||
vis = "public" if lic.get("redistributable") else "private"
|
||
print(f"VISIBILITY={shlex.quote(vis)}")
|
||
print(f"LIC_LEVEL={shlex.quote(str(lic.get('openness') or '?'))}")
|
||
print(f"LIC_NAME={shlex.quote(str(lic.get('name') or ''))}")
|
||
PY
|
||
|
||
# shellcheck disable=SC1090
|
||
source "$CONF_FILE"
|
||
|
||
[[ -z "$UPSTREAM" ]] && { echo "档案缺少 mirror.upstream" >&2; exit 1; }
|
||
|
||
echo "==> 镜像 $ID"
|
||
echo " 上游 : $UPSTREAM"
|
||
echo " 目标 : $ORG/$REPO_NAME"
|
||
echo " 许可证 : $LIC_LEVEL ($LIC_NAME)"
|
||
echo " 可见性 : $VISIBILITY"
|
||
if [[ "$VISIBILITY" == "private" ]]; then
|
||
echo " ⚠ L3 级 source-available,已强制私有。公开镜像可能构成未经授权的分发。"
|
||
fi
|
||
|
||
# ---- 凭据:优先环境变量,回退到 macOS 钥匙串 ----
|
||
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||
CRED="$(printf "host=%s\nprotocol=https\n" "${DISPLAY_URL#*://}" | git credential-osxkeychain get 2>/dev/null || true)"
|
||
GITEA_USER="${GITEA_USER:-$(printf '%s' "$CRED" | sed -n 's/^username=//p')}"
|
||
GITEA_TOKEN="$(printf '%s' "$CRED" | sed -n 's/^password=//p')"
|
||
fi
|
||
[[ -z "${GITEA_TOKEN:-}" || -z "${GITEA_USER:-}" ]] && {
|
||
echo "未拿到 Gitea 凭据(设 GITEA_TOKEN/GITEA_USER,或存入钥匙串)" >&2; exit 1; }
|
||
|
||
# ---- 确保目标仓库存在(组织端点,不是 /user/repos) ----
|
||
API="$GITEA_URL/api/v1"
|
||
HTTP="$(curl -sS -o /dev/null -w '%{http_code}' -u "$GITEA_USER:$GITEA_TOKEN" "$API/repos/$ORG/$REPO_NAME" || echo 000)"
|
||
if [[ "$HTTP" == "200" ]]; then
|
||
echo " 仓库已存在"
|
||
elif [[ "$HTTP" == "404" ]]; then
|
||
echo " 创建仓库 $ORG/$REPO_NAME (private=$([[ $VISIBILITY == private ]] && echo true || echo false)) ..."
|
||
CREATE="$(curl -sS -o /tmp/gitea_mirror_create.json -w '%{http_code}' -u "$GITEA_USER:$GITEA_TOKEN" \
|
||
-H 'Content-Type: application/json' -X POST "$API/org/$ORG/repos" \
|
||
-d "{\"name\":\"$REPO_NAME\",\"private\":$([[ $VISIBILITY == private ]] && echo true || echo false),\"auto_init\":false,\"description\":\"Mirror of $UPSTREAM (license: $LIC_NAME)\"}")"
|
||
[[ "$CREATE" =~ ^(200|201)$ ]] || {
|
||
echo "创建失败 (HTTP $CREATE):" >&2; cat /tmp/gitea_mirror_create.json >&2; exit 1; }
|
||
echo " 已创建"
|
||
else
|
||
echo "查询仓库失败 (HTTP $HTTP)" >&2; exit 1
|
||
fi
|
||
|
||
# 已存在的仓库若应为私有,显式修正(创建时的 private 对已存在仓库不生效)
|
||
if [[ "$VISIBILITY" == "private" ]]; then
|
||
curl -sS -o /dev/null -u "$GITEA_USER:$GITEA_TOKEN" -X PATCH \
|
||
-H 'Content-Type: application/json' -d '{"private":true}' "$API/repos/$ORG/$REPO_NAME" || true
|
||
fi
|
||
|
||
# ---- 同步 ----
|
||
MIRROR_DIR="$ROOT/.mirrors/$REPO_NAME.git"
|
||
mkdir -p "$ROOT/.mirrors"
|
||
|
||
if [[ -d "$MIRROR_DIR" ]]; then
|
||
echo " 更新已有裸仓 ..."
|
||
git -C "$MIRROR_DIR" remote update --prune
|
||
else
|
||
echo " 首次全量镜像(大仓请耐心)..."
|
||
if [[ -n "$DEPTH" ]]; then
|
||
git clone --mirror --depth "$DEPTH" "$UPSTREAM" "$MIRROR_DIR"
|
||
else
|
||
git clone --mirror "$UPSTREAM" "$MIRROR_DIR"
|
||
fi
|
||
fi
|
||
|
||
PROTO="${GITEA_URL%%://*}"
|
||
HOST="${GITEA_URL#*//}"
|
||
U_ENC="$(/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python -c \
|
||
'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "$GITEA_USER")"
|
||
P_ENC="$(/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python -c \
|
||
'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1],safe=""))' "$GITEA_TOKEN")"
|
||
|
||
echo " 推送到 $ORG/$REPO_NAME ..."
|
||
git -C "$MIRROR_DIR" push --mirror "$PROTO://$U_ENC:$P_ENC@$HOST/$ORG/$REPO_NAME.git"
|
||
|
||
# ---- 回写档案 ----
|
||
/Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python - "$YAML" "$VISIBILITY" "$REPO_NAME" <<'PY'
|
||
import sys
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
sys.path.insert(0, str(Path(sys.argv[1]).resolve().parent.parent.parent / "scripts"))
|
||
from _common import today, update_block
|
||
p = Path(sys.argv[1])
|
||
text = p.read_text(encoding="utf-8")
|
||
text = update_block(text, "mirror", {
|
||
"visibility": sys.argv[2],
|
||
"gitea_repo": f"{'gitinbox'}/{sys.argv[3]}",
|
||
"last_sync": today(),
|
||
})
|
||
p.write_text(text, encoding="utf-8")
|
||
PY
|
||
|
||
SIZE="$(du -sh "$MIRROR_DIR" 2>/dev/null | cut -f1 || echo '?')"
|
||
echo "==> 完成。$DISPLAY_URL/$ORG/$REPO_NAME (本地裸仓 $SIZE)"
|