fix: 排除规则漏了根目录的 EE 许可证引用文件;脚本补 public 方向修正

修一个会误导读者的排除漏洞:
只排除 ee/ 时,LICENSES/LicenseRef-OpenWork-EE.txt 仍会被推进镜像。
它是 REUSE 规范的引用文件,存在的唯一意义是给 ee/ 下的文件做 SPDX 引用
(ee/ 里写着 "SPDX-License-Identifier: LicenseRef-OpenWork-EE")。
ee/ 一删,它就成了孤儿。许可证文本公開本身不侵权(上游自己就公开),
但一个不含 EE 代码的仓库里躺着 EE 许可证,读者只会以为我们漏排了。

许可证块补 redistributable_after_excludes: true。
依据:根 LICENSE 原文(已读远端全文)——
  "All content that resides under the /ee directory ... OpenWork EE License"
  "Content outside ... is available under the 'MIT' license"
排除 ee/ 后为纯 MIT。

mirror_sync.sh 补 private → public 方向修正,但默认不自动翻:
原先脚本只有「收紧」方向(应为私有就 PATCH private:true),
没有放宽方向——openwork 判定为 public 但仓库停在 private,
重跑多少次都不会翻过来,档案与事实不一致却看不出原因。
现在会报告「应为 public 但当前 private」,要求显式加 --set-public。
公开不可逆,放松方向永远不该自动做。

一句话结论去掉样本计数:
maka「四个里唯一」、QwenPaw/Tutti「七个里…」——每加一批样本就过时一次。
改成不带计数的表述,「已收录样本中」本就是隐含的。

总纲 id=98 已重发。
This commit is contained in:
gitea-publish
2026-09-02 00:26:49 +08:00
parent ed1da08f42
commit 56e2c8a403
8 changed files with 201 additions and 24 deletions
+120 -1
View File
@@ -21,6 +21,9 @@ DEPTH=""
# 而这个分支只在特定体积下才走到——不初始化就成了「偶尔才炸」的雷。
SOURCE_ONLY="auto"
SOURCE_ONLY_FORCE="no"
# private → public 的显式授权开关。默认 0:公开不可逆,绝不自动放宽。
SET_PUBLIC="0"
EXCLUDE_COUNT="0"
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -29,6 +32,7 @@ while [[ $# -gt 0 ]]; do
--url) GITEA_URL="$2"; shift 2;;
--depth) DEPTH="$2"; shift 2;;
--no-source-only) SOURCE_ONLY="no"; shift;;
--set-public) SET_PUBLIC="1"; shift;;
*) echo "未知参数: $1" >&2; exit 1;;
esac
done
@@ -55,15 +59,45 @@ 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"
exc = m.get("excludes") or []
if exc:
# 目录切分许可:被排除的目录不进镜像,所以决定可见性的是**排除后**的内容。
# 这里必须显式读 redistributable_after_excludes,缺了就报错——
# 不能拿整体许可证去猜:openwork 整体是 L3(含 ee/),排除后是纯 MIT,
# 两种情况结论相反,猜错就是把受限内容公开出去,或者把能公开的白白锁起来。
ra = lic.get("redistributable_after_excludes")
if ra is None:
sys.stderr.write(
"档案声明了 mirror.excludes,但 business.license 里没有 "
"redistributable_after_excludes,无法判定排除后的可见性。\n"
"请在许可证块补上该字段(true=排除后可公开,false=仍须私有)。\n")
sys.exit(2)
vis = "public" if ra else "private"
else:
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 ''))}")
# 目录切分许可的排除项(如 OpenWork 的 ee/)。
# 必须读档案 mirror.excludes,不能读 registry.yaml 的 mirror_excludes:
# 后者只是批量抓取的提示,脚本从来没读过它,写在那里等于没写——
# openwork 的 visibility: public 曾因此建立在一条从未生效的规则上。
print("EXCLUDES=" + shlex.quote("\n".join(str(x) for x in (m.get("excludes") or []))))
PY
# shellcheck disable=SC1090
source "${CONF_FILE}"
# 换行分隔 → 数组。空输入要能正确得到零长数组,否则下面 ${#EXCLUDE_PATHS[@]}
# 在 set -u 下会因未绑定而炸(这类"偶尔才走到"的分支最容易埋雷)。
EXCLUDE_PATHS=()
if [[ -n "${EXCLUDES}" ]]; then
while IFS= read -r _line; do
[[ -n "${_line}" ]] && EXCLUDE_PATHS+=("${_line}")
done <<< "${EXCLUDES}"
fi
EXCLUDE_COUNT=${#EXCLUDE_PATHS[@]}
[[ -z "${UPSTREAM}" ]] && { echo "档案缺少 mirror.upstream" >&2; exit 1; }
echo "==> 镜像 ${ID}"
@@ -74,6 +108,10 @@ echo " 可见性 : ${VISIBILITY}"
if [[ "${VISIBILITY}" == "private" ]]; then
echo " ⚠ L3 级 source-available,已强制私有。公开镜像可能构成未经授权的分发。"
fi
if [[ ${#EXCLUDE_PATHS[@]} -gt 0 ]]; then
echo " 排除路径 : ${EXCLUDE_PATHS[*]}"
echo " ⚠ 本项目的 public 可见性建立在「上述路径不进镜像」之上,推送前会二次校验。"
fi
# ---- 凭据:优先环境变量,回退到 macOS 钥匙串 ----
if [[ -z "${GITEA_TOKEN:-}" ]]; then
@@ -102,11 +140,35 @@ else
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
# ---- 放宽方向(private → public)必须显式授权 ----
# 公开是不可逆动作:一旦有人 fork/克隆,就再也收不回来。
# 所以这里**不自动翻**——只报告,要求显式加 --set-public。
# 2026-09-02 补:原先脚本只有"收紧"方向的修正,openwork 判定为 public 但仓库
# 停在 private,重跑多少次都不会翻过来,档案与事实不一致却看不出原因。
if [[ "${VISIBILITY}" == "public" ]]; then
CUR_PRIVATE="$(curl -sS -u "${GITEA_USER}:${GITEA_TOKEN}" "${API}/repos/${ORG}/${REPO_NAME}" \
| /Users/figmarlite/.workbuddy/binaries/python/envs/default/bin/python -c 'import sys,json;print("true" if json.load(sys.stdin).get("private") else "false")' 2>/dev/null || echo unknown)"
if [[ "${CUR_PRIVATE}" == "true" ]]; then
if [[ "${SET_PUBLIC}" == "1" ]]; then
echo " ⚠ 按 --set-public 将 ${ORG}/${REPO_NAME} 从 private 翻为 public(不可逆)"
curl -sS -o /dev/null -u "${GITEA_USER}:${GITEA_TOKEN}" -X PATCH \
-H 'Content-Type: application/json' -d '{"private":false}' "${API}/repos/${ORG}/${REPO_NAME}" \
&& echo " 已翻为 public"
else
echo ""
echo " ⚠ ${ORG}/${REPO_NAME} 判定应为 public,但远端当前是 private。"
echo " 公开不可逆,脚本默认不自动翻。确认后重跑并加 --set-public。"
echo " 判定依据:许可证 ${LIC_LEVEL},排除 ${EXCLUDE_COUNT} 项后 redistributable=${VISIBILITY}"
fi
fi
fi
# ---- 同步 ----
MIRROR_DIR="${ROOT}/.mirrors/${REPO_NAME}.git"
mkdir -p "${ROOT}/.mirrors"
@@ -241,6 +303,23 @@ if [[ ( "${PACK_MB}" -gt "${MAX_PUSH_MB}" || "${SOURCE_ONLY_FORCE}" == "yes" ) \
git init -q "${SNAP}/repo"
git --git-dir="${MIRROR_DIR}" --work-tree="${SNAP}/repo" checkout HEAD -- . 2>/dev/null
# 目录切分许可的排除在这里落地:工作树 checkout 完之后整棵删掉。
# 注意必须在资产剔除**之前**做——ee/ 这类目录里也可能有图片/字体,
# 顺序反了会留下空目录和残留文件。
if [[ ${#EXCLUDE_PATHS[@]} -gt 0 ]]; then
for _exc in "${EXCLUDE_PATHS[@]}"; do
if [[ -e "${SNAP}/repo/${_exc}" ]]; then
rm -rf "${SNAP}/repo/${_exc}"
echo " 已排除 ${_exc}"
else
# 排除了个不存在的路径,多半是上游改了目录结构。
# 不静默放过——否则哪天 ee/ 挪了窝,我们还在按 public 推。
echo " ⚠ 声明要排除的 ${_exc} 在上游不存在,请核对档案的 excludes 是否过期"
fi
done
fi
find "${SNAP}/repo" -type f -not -path '*/.git/*' \( \
-iname '*.png' -o -iname '*.gif' -o -iname '*.jpg' -o -iname '*.jpeg' \
-o -iname '*.webp' -o -iname '*.ico' -o -iname '*.mp4' -o -iname '*.mov' \
@@ -258,6 +337,10 @@ if [[ ( "${PACK_MB}" -gt "${MAX_PUSH_MB}" || "${SOURCE_ONLY_FORCE}" == "yes" ) \
## 这不是完整保真镜像
本仓库只含**源码与文档**,已剔除图片、音视频、字体等非源码素材。
$(if [[ ${#EXCLUDE_PATHS[@]} -gt 0 ]]; then
printf '此外按许可证要求排除了以下目录:%s。\n' "${EXCLUDE_PATHS[*]}"
printf '本仓库的公开可见性以该排除为前提;完整上游含受限许可内容。\n'
fi)
原因:Gitea 前置 nginx 的 \`client_max_body_size\` 上限约 ${MAX_PUSH_MB} MB,
完整裸仓打包 ${PACK_MB} MB 会被 HTTP 413 拒绝。
@@ -279,6 +362,42 @@ else
PUSH_DIR="${MIRROR_DIR}"
fi
# ---- 推送前硬校验:排除项绝不能进公开镜像 ----
# 这是整套许可证分级的最后一道闸。前面所有环节(声明、checkout 后删除)都可能有
# 遗漏或顺序问题,而"public"这个动作的代价是不可逆的——东西一旦公开就收不回来。
# 所以这里不看过程只看结果:直接检查待推送目录里还有没有这些路径。
if [[ "${VISIBILITY}" == "public" && ${#EXCLUDE_PATHS[@]} -gt 0 ]]; then
if [[ "${MIRROR_MODE}" == "full" ]]; then
# full 模式直接拒:push --mirror 推的是完整裸仓,剔除目录需要重写历史
# (git filter-repo),本脚本不做。更关键的是——裸仓没有工作树,
# 文件系统层面的检查会**假通过**,而 ee/ 的内容在历史里照样推得出去。
# 这种"检查了但没真检查到"比不检查更危险,所以这里不看结果直接拦。
echo "" >&2
echo "✗ 拒绝以 full 模式推送到公开仓库:${EXCLUDE_PATHS[*]}" >&2
echo " full 模式用 push --mirror 推完整裸仓,无法在此层面剔除受限目录," >&2
echo " 且历史提交里仍含该内容。请二选一:" >&2
echo " a) 把档案 mirror.visibility 改为 private;" >&2
echo " b) 让体积超过闸门(MAX_PUSH_MB=${MAX_PUSH_MB})走 source-only 路径,排除在该路径生效。" >&2
exit 1
fi
# source-only:有真实工作树,可以做结果校验。
# 不看"我们有没有执行删除",只看"待推送内容里到底还有没有"——
# 过程可能被改坏,结果不会说谎。
LEAK=()
for _exc in "${EXCLUDE_PATHS[@]}"; do
_base="${_exc%/}"
[[ -e "${PUSH_DIR}/${_base}" ]] && LEAK+=("${_base}")
done
if [[ ${#LEAK[@]} -gt 0 ]]; then
echo "" >&2
echo "✗ 拒绝推送:以下受限目录仍在待推送内容中:${LEAK[*]}" >&2
echo " 目标仓库是 **public**,而其中含受限许可内容。" >&2
exit 1
fi
echo " 排除校验通过:${EXCLUDE_PATHS[*]} 均不存在于待推送内容"
fi
echo " 推送到 ${ORG}/${REPO_NAME} ..."
if [[ "${MIRROR_MODE}" == "full" ]]; then
git -C "${PUSH_DIR}" push --mirror "${PROTO}://${U_ENC}:${P_ENC}@${HOST}/${ORG}/${REPO_NAME}.git"