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:
@@ -183,12 +183,12 @@ Go 守护进程),但引擎接入深度严重向 Codex 倾斜、开源版功
|
||||
| 项目 | 结论 | 适合 | 不适合 |
|
||||
| --- | --- | --- | --- |
|
||||
| **iPolloWork** | 把多个 Agent 引擎收进同一个可编辑工作台的野心之作——方向踩在点上,但三种引擎的接入深度并不齐整,且许可证决定了它暂时只适合个人或极小团队试用 | 想在同一个界面管理多引擎项目、并希望 agent 产出的 PPT / 网页 / 设计 / 视频之后还能继续手工修改的个人开发者,或少于 3 人的极小团队 | 3 人以上需要正式落地的团队(许可证要求书面授权)、期待 Codex 原生级集成的用户(目前只有 MCP 控制面)、以及追求稳定的生产环境(v0.50.x、一天多版、DSH 集成未进稳定版) |
|
||||
| **maka** | 四个里唯一把「执行记录」当地基的项目——治理与可复现性最强,产品成熟度最弱。 | 对审计、数据主权、可复现评测有硬要求的团队; 做 agent 横向对比研究的工程团队。 | 要开箱即用、要 Windows / Linux、要丰富第三方集成的人; 以为 ASF 品牌等于生产可用的决策者。 |
|
||||
| **maka** | 唯一把「执行记录」当地基的项目——治理与可复现性最强,产品成熟度最弱。 | 对审计、数据主权、可复现评测有硬要求的团队; 做 agent 横向对比研究的工程团队。 | 要开箱即用、要 Windows / Linux、要丰富第三方集成的人; 以为 ASF 品牌等于生产可用的决策者。 |
|
||||
| **OpenClaw** | 388k star 的个人 AI 助理,常驻你自己的机器、从聊天软件驱动—— 但沙箱默认关闭、版本号已经乱到读不懂发布节奏、关键提交仍系于一人之手。 | 想让 AI 真正接管日常事务且愿意自己加固部署环境的重度个人用户; 想研究"非开发者向 agent 产品长什么样"的人(本体系里唯一的样本)。 | 把 star 数当成生产就绪度的人;指望沙箱开箱即保护的人; 需要可预测升级路径的团队;希望助理"中立接入多个引擎"的人(它自己就是引擎,不接别人)。 |
|
||||
| **openwork** | 把「能力」而不是「桌面」做成产品——工程治理最好的一个,但它和自称要替代的东西不是一个物种。 | 已固定在某款 agent 上、只想让团队能力跨工具复用的开发者; 需要统一管控 agent 能力与模型配额的组织 IT。 | 想要一个开箱即用的桌面 agent 工作台的人; 以为拿到的是完整 Cowork 平替的人。 |
|
||||
| **orca** | 兼容面最广的并行 agent 编排外壳——广度做到极致,深度与治理都还是草稿。 | 已在重度使用 CLI agent、想同时跑多个方案横向比对的个人开发者与小团队。 | 需要稳定 SLA、指望社区 triage 与多人维护保障的团队; 指望它统一各引擎能力(结构化调用、统一权限、一致口径)的架构选型者。 |
|
||||
| **QwenPaw** | 七个样本里安全默认值最完整、巴士系数最健康的个人助理—— 但至今没有发过一个正式版,且 `--defaults` 安装路径会默认打开遥测。 | 把数据与隐私放第一位、希望助理完全跑在本地的个人与小团队; 需要在钉钉 / 飞书 / 微信 / QQ 渠道里用助理的国内团队; 想不买 API key 先把流程跑通的尝鲜者。 | 需要正式版与稳定升级路径的团队(它全是 beta); 指望 2B / 4B 本地模型扛复杂 agent 任务的人; 把"五层安全"当成不可绕过硬边界的人(Tool Guard 有 OFF 档)。 |
|
||||
| **Tutti** | 把「多 agent 共享上下文、接力干活」做成了产品,且是七个里发布纪律最好的—— 但接入深度严重偏袒 Codex(17 倍差距),而宣传的「多人协作」全在闭源 VM 版。 | 手里已有多个 agent 订阅、想让它们在同一工作区里接力与共享上下文的开发者; 想让 agent 产出设计稿 / 文档 / PPT 而不只是代码的人; 看重发布纪律与工程规范(NOTICE 精度、正式版节奏)的人。 | 指望开源版做团队多人协作的人(那七项功能全在闭源 VM 侧,且需邀请码); 主要用 Kimi / ComputerUse / BrowserUse 的人(这三个接入最薄、测试为零); Windows 用户且主力引擎不是 Codex 的人(只有 Codex 做了 Windows 适配)。 |
|
||||
| **QwenPaw** | 安全默认值最完整、巴士系数最健康的个人助理—— 但至今没有发过一个正式版,且 `--defaults` 安装路径会默认打开遥测。 | 把数据与隐私放第一位、希望助理完全跑在本地的个人与小团队; 需要在钉钉 / 飞书 / 微信 / QQ 渠道里用助理的国内团队; 想不买 API key 先把流程跑通的尝鲜者。 | 需要正式版与稳定升级路径的团队(它全是 beta); 指望 2B / 4B 本地模型扛复杂 agent 任务的人; 把「五层安全」当成不可绕过硬边界的人(Tool Guard 有 OFF 档)。 |
|
||||
| **Tutti** | 把「多 agent 共享上下文、接力干活」做成了产品,且是发布纪律最好的—— 但接入深度严重偏袒 Codex(17 倍差距),而宣传的「多人协作」全在闭源 VM 版。 | 手里已有多个 agent 订阅、想让它们在同一工作区里接力与共享上下文的开发者; 想让 agent 产出设计稿 / 文档 / PPT 而不只是代码的人; 看重发布纪律与工程规范(NOTICE 精度、正式版节奏)的人。 | 指望开源版做团队多人协作的人(那七项功能全在闭源 VM 侧,且需邀请码); 主要用 Kimi / ComputerUse / BrowserUse 的人(这三个接入最薄、测试为零); Windows 用户且主力引擎不是 Codex 的人(只有 Codex 做了 Windows 适配)。 |
|
||||
|
||||
<svg viewBox="0 0 720 420" width="720" height="420" xmlns="http://www.w3.org/2000/svg" style="font-family:sans-serif;max-width:100%;height:auto">
|
||||
<text x="360" y="26" text-anchor="middle" font-size="15" font-weight="600" fill="#1f2328">视图 A · 形态纵深 × 引擎立场</text>
|
||||
@@ -261,12 +261,12 @@ Go 守护进程),但引擎接入深度严重向 Codex 倾斜、开源版功
|
||||
| 项目 | 一句话结论 | 深度文 |
|
||||
| --- | --- | --- |
|
||||
| **iPolloWork** | 把多个 Agent 引擎收进同一个可编辑工作台的野心之作 | [已发布](https://blog.apescale.com/?post=97) |
|
||||
| **maka** | 四个里唯一把「执行记录」当地基的项目 | 待发布(见下方横评) |
|
||||
| **maka** | 唯一把「执行记录」当地基的项目 | 待发布(见下方横评) |
|
||||
| **OpenClaw** | 388k star 的个人 AI 助理,常驻你自己的机器、从聊天软件驱动 | 待发布(见下方横评) |
|
||||
| **openwork** | 把「能力」而不是「桌面」做成产品 | 待发布(见下方横评) |
|
||||
| **orca** | 兼容面最广的并行 agent 编排外壳 | 待发布(见下方横评) |
|
||||
| **QwenPaw** | 七个样本里安全默认值最完整、巴士系数最健康的个人助理 | 待发布(见下方横评) |
|
||||
| **Tutti** | 把「多 agent 共享上下文、接力干活」做成了产品,且是七个里发布纪律最好的 | 待发布(见下方横评) |
|
||||
| **QwenPaw** | 安全默认值最完整、巴士系数最健康的个人助理 | 待发布(见下方横评) |
|
||||
| **Tutti** | 把「多 agent 共享上下文、接力干活」做成了产品,且是发布纪律最好的 | 待发布(见下方横评) |
|
||||
|
||||
**跨项目文章**
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ chart:
|
||||
|
||||
verdict:
|
||||
|
||||
one_liner: 四个里唯一把「执行记录」当地基的项目——治理与可复现性最强,产品成熟度最弱。
|
||||
one_liner: 唯一把「执行记录」当地基的项目——治理与可复现性最强,产品成熟度最弱。
|
||||
who_should_use: |-
|
||||
对审计、数据主权、可复现评测有硬要求的团队;
|
||||
做 agent 横向对比研究的工程团队。
|
||||
|
||||
@@ -32,6 +32,15 @@ business:
|
||||
name: "目录切分许可(GitLab 式):ee/ 之外为 MIT;ee/ 为 OpenWork EE License"
|
||||
openness: L3
|
||||
redistributable: false
|
||||
# ↓ 排除 mirror.excludes 之后的状态。mirror_sync.sh 判可见性时用的是**这个**,
|
||||
# 不是上面的 redistributable——目录切分许可下两者结论相反。
|
||||
# 依据:根 LICENSE 原文(已人工阅读远端全文)
|
||||
# "All content that resides under the /ee directory ... is licensed under
|
||||
# the OpenWork Enterprise Edition (EE) License"
|
||||
# "Content outside of the above mentioned directories or restrictions
|
||||
# above is available under the 'MIT' license as defined below."
|
||||
# 排除 ee/ 后 → 纯 MIT → 可公开。
|
||||
redistributable_after_excludes: true
|
||||
key_restrictions:
|
||||
- ee/ 目录(Den 控制面、MCP 网关、推理)生产使用需有效订阅;5 人以下组织在排除 Enterprise 功能后免费
|
||||
- 每个 ee/ 版本公开发布满两年后,自动追加 MIT 授权(Grant of Future License)
|
||||
@@ -214,12 +223,27 @@ verdict:
|
||||
以为拿到的是完整 Cowork 平替的人。
|
||||
|
||||
mirror:
|
||||
gitea_repo: "gitinbox/openwork"
|
||||
visibility: public
|
||||
gitea_repo: gitinbox/openwork
|
||||
visibility: private
|
||||
upstream: "https://github.com/different-ai/openwork"
|
||||
last_sync: ""
|
||||
mode: pending
|
||||
sync_note: "暂未镜像。仓库 291 MB,按本机到 GitHub 实测 50 KB/s 计算需 1.6 小时以上,且 git clone 无断点续传。许可证为目录切分(ee/ 之外 MIT,ee/ 为 EE License)——若后续镜像,排除 ee/ 后为纯 MIT,可公开;完整镜像则必须私有。"
|
||||
last_sync: 2026-09-01
|
||||
mode: source-only
|
||||
# 目录切分许可:ee/ 之外是 MIT,ee/ 是 EE License。
|
||||
# 本档案的 visibility: public **完全建立在「这个排除真的生效」这个前提上**——
|
||||
# ee/ 一旦进了镜像,公开分发就是越权。
|
||||
# 声明必须写在档案里:mirror_sync.sh 只读档案、不读 registry.yaml。
|
||||
# registry.yaml 里的 mirror_excludes 只是批量抓取的提示,不参与镜像决策。
|
||||
#
|
||||
# 第二个排除项是 2026-09-02 补的:只排除 ee/ 时,根目录的
|
||||
# LICENSES/LicenseRef-OpenWork-EE.txt 仍会被推进去。它是 REUSE 规范的引用文件,
|
||||
# 存在的唯一意义是给 ee/ 下的文件做 SPDX 引用(ee/ 里写着
|
||||
# "SPDX-License-Identifier: LicenseRef-OpenWork-EE")。ee/ 一删,它就成了孤儿。
|
||||
# 许可证文本本身公开不侵权(上游自己就公开),但留着会让镜像名不副实——
|
||||
# 一个不含 EE 代码的仓库里躺着 EE 许可证,读者会以为漏排了。
|
||||
excludes:
|
||||
- "ee/"
|
||||
- "LICENSES/LicenseRef-OpenWork-EE.txt"
|
||||
sync_note: "已镜像(source-only 快照,2026-09-01)。上游 291 MB,本机到 GitHub 实测 50 KB/s,拉取耗时较长且 git clone 无断点续传。许可证为目录切分(根 LICENSE 原文:/ee 之外为 MIT,/ee 为 OpenWork EE License)——镜像时排除 ee/ 与 LICENSES/LicenseRef-OpenWork-EE.txt,排除后为纯 MIT 故判 public。未排除的完整镜像会被脚本在推送前拒绝(public 目标 + 有 excludes 声明 + full 模式)。"
|
||||
article:
|
||||
emlog_id: 0 # emlog 文章 ID,发布后回写
|
||||
published_at: ""
|
||||
|
||||
@@ -230,7 +230,7 @@ chart:
|
||||
chart_note: "与 OpenClaw 同赛道对照看:形态略浅、本地优先更强、安全默认值显著更高"
|
||||
verdict:
|
||||
one_liner: |-
|
||||
七个样本里安全默认值最完整、巴士系数最健康的个人助理——
|
||||
安全默认值最完整、巴士系数最健康的个人助理——
|
||||
但至今没有发过一个正式版,且 `--defaults` 安装路径会默认打开遥测。
|
||||
who_should_use: |-
|
||||
把数据与隐私放第一位、希望助理完全跑在本地的个人与小团队;
|
||||
@@ -239,7 +239,7 @@ verdict:
|
||||
who_should_not: |-
|
||||
需要正式版与稳定升级路径的团队(它全是 beta);
|
||||
指望 2B / 4B 本地模型扛复杂 agent 任务的人;
|
||||
把"五层安全"当成不可绕过硬边界的人(Tool Guard 有 OFF 档)。
|
||||
把「五层安全」当成不可绕过硬边界的人(Tool Guard 有 OFF 档)。
|
||||
mirror:
|
||||
gitea_repo: gitinbox/qwenpaw
|
||||
visibility: public
|
||||
@@ -247,8 +247,9 @@ mirror:
|
||||
last_sync: 2026-09-01
|
||||
mode: source-only
|
||||
sync_note: |-
|
||||
109 MB,理论可行(约 36 分钟拉完),但超过 Gitea 50 MB 推送上限,
|
||||
需走 mirror_sync.sh 的 source-only 快照降级。尚未执行,待带宽窗口。
|
||||
已于 2026-09-01 完成 source-only 镜像(gitinbox/qwenpaw,public)。
|
||||
109 MB 裸仓超 Gitea 50 MB 推送上限,故剔除图片/音视频/字体等素材后按快照提交:
|
||||
推送 18 MB;完整保真裸仓(281 MB)保留在 .mirrors/qwenpaw.git,增量更新重跑脚本即可。
|
||||
article:
|
||||
emlog_id: 0 # emlog 文章 ID,发布后回写
|
||||
published_at: ""
|
||||
|
||||
@@ -276,7 +276,7 @@ chart:
|
||||
chart_note: "引擎中立项给的是协议层统一;若按投入对等度重评,该项应下调至 4 左右"
|
||||
verdict:
|
||||
one_liner: |-
|
||||
把「多 agent 共享上下文、接力干活」做成了产品,且是七个里发布纪律最好的——
|
||||
把「多 agent 共享上下文、接力干活」做成了产品,且是发布纪律最好的——
|
||||
但接入深度严重偏袒 Codex(17 倍差距),而宣传的「多人协作」全在闭源 VM 版。
|
||||
who_should_use: |-
|
||||
手里已有多个 agent 订阅、想让它们在同一工作区里接力与共享上下文的开发者;
|
||||
@@ -287,15 +287,17 @@ verdict:
|
||||
主要用 Kimi / ComputerUse / BrowserUse 的人(这三个接入最薄、测试为零);
|
||||
Windows 用户且主力引擎不是 Codex 的人(只有 Codex 做了 Windows 适配)。
|
||||
mirror:
|
||||
gitea_repo: "gitinbox/tutti"
|
||||
gitea_repo: gitinbox/tutti
|
||||
visibility: public
|
||||
upstream: "https://github.com/tutti-os/tutti"
|
||||
last_sync: ""
|
||||
mode: "pending"
|
||||
last_sync: 2026-09-01
|
||||
mode: source-only
|
||||
sync_note: |-
|
||||
243 MB。本地已有浅克隆(/Users/figmarlite/.../tutti_repo,8291 文件),
|
||||
技术维度分析即基于此,可直接复用做快照,无需重新下载。
|
||||
仍需 unshallow 补全历史后走 source-only 降级(超 Gitea 50 MB 上限)。尚未执行。
|
||||
已于 2026-09-01 完成 source-only 镜像(gitinbox/tutti,public)。
|
||||
243 MB 裸仓超 Gitea 50 MB 推送上限,unshallow 补全历史后按快照提交:
|
||||
推送 14 MB;完整保真裸仓(254 MB)保留在 .mirrors/tutti.git。
|
||||
技术维度分析基于本地浅克隆 /Users/figmarlite/.../tutti_repo(8291 文件),
|
||||
与本镜像是两份独立的检出,改动不同步。
|
||||
article:
|
||||
emlog_id: 0 # emlog 文章 ID,发布后回写
|
||||
published_at: ""
|
||||
|
||||
@@ -14,6 +14,10 @@ projects:
|
||||
added_at: 2026-09-01
|
||||
license_level: L3 # 目录切分许可:ee/ 之外 MIT,ee/ 为 EE License
|
||||
mirror_visibility: public # 人工复核:镜像时排除 ee/ 后为纯 MIT,故可公开
|
||||
# ⚠️ 这个字段**不参与镜像决策**,只是批量抓取时的提示。
|
||||
# mirror_sync.sh 只读 data/projects/openwork.yaml 的 mirror.excludes。
|
||||
# 曾因只写在这里、脚本又不读它,导致 public 可见性建立在一条从未生效的规则上。
|
||||
# 2026-09-01 已修:脚本支持并强制校验档案里的 excludes。
|
||||
mirror_excludes: ["ee/"]
|
||||
- id: orca
|
||||
repo: stablyai/orca
|
||||
|
||||
@@ -361,6 +361,31 @@ def quadrant_svg(items: list[dict]) -> str:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 主流程
|
||||
# sync_note 是手写备注,mode / last_sync 是脚本回写的事实。
|
||||
# 两者会漂移:镜像跑完之后 mode 变成了 source-only,note 里还写着「尚未执行,待带宽窗口」。
|
||||
# 档案里出现和事实相反的话,比没有备注更糟——读者会照着错的那个信。
|
||||
STALE_PHRASES = ("尚未执行", "暂未镜像", "待带宽", "尚未镜像", "未镜像")
|
||||
|
||||
|
||||
def check_mirror_notes(items: list[dict]) -> None:
|
||||
"""mirror 块已跑完(mode 非 pending/空)却还写着「尚未执行」→ 提示改。"""
|
||||
bad = []
|
||||
for d in items:
|
||||
m = d.get("mirror") or {}
|
||||
mode = str(m.get("mode") or "").strip()
|
||||
if mode in ("", "pending", "link-only"):
|
||||
continue # 没跑过 / 明确不镜像,note 说「待执行」是对的
|
||||
note = str(m.get("sync_note") or "")
|
||||
hit = [p for p in STALE_PHRASES if p in note]
|
||||
if hit:
|
||||
bad.append((d.get("id") or d.get("name"), mode, "/".join(hit)))
|
||||
if not bad:
|
||||
return
|
||||
print(f" [warn] {len(bad)} 个档案的 mirror.sync_note 与实际状态矛盾:")
|
||||
for aid, mode, hit in bad:
|
||||
print(f" - {aid}:mode={mode},但 note 里仍有「{hit}」")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--stdout", action="store_true", help="只打印不写文件")
|
||||
@@ -373,6 +398,8 @@ def main() -> int:
|
||||
|
||||
print(f"==> 载入 {len(items)} 个项目:{', '.join(d.get('name','') for d in items)}")
|
||||
|
||||
check_mirror_notes(items)
|
||||
|
||||
matrix = build_tables(items)
|
||||
chart = quadrant_svg(items) if len(items) >= QUAD_THRESHOLD else stack_diagram(items)
|
||||
block = f"{BEGIN}\n{matrix}\n\n{chart}\n{END}"
|
||||
|
||||
+120
-1
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user