最初搭这套 Windows / Mac Git 系统时,我最在意的不是备份速度,也不是省一笔 GitHub 费用,而是一个更早就定下的前提:长期投入、具有商业价值的核心源代码,不以明文形式交给任何第三方云平台保管。 即使仓库标着 Private,这条原则也不变。

我不是在断言某家平台会私自拿走代码。风险恰恰在于,我无法把未来很多年的商业资产押在“平台永远没有内部越权、漏洞、供应链攻击、账户泄露或服务侧安全事故”这个假设上。Private 控制的是谁被授权访问,不等于服务商在技术上拿不到原始 Git 对象。我希望由自己掌握解密密钥,云端只接触密文;出了问题,至少不会因为一个云账号被攻破就直接交出完整源码。

这才解释了为什么我让 Windows 的 bare repo 做本地 Central,Mac 通过 SMB 同步开发,而不是从一开始就把核心项目推到 GitHub 私有仓。后来加的 E 盘 mirror、移动硬盘 7z 归档和 Cryptomator + OneDrive / Google Drive,首先是为了在不松动明文边界的前提下实现异地容灾。不过,能把密文备份上云,并不等于已经拥有随时可用的“加密 Git 远程仓库”;现在手机端想借助 ChatGPT/Codex 协助开发时,这道区别尤其明显。

下面保留 2024–2026 年的操作记录及 Git 2.47.3 小仓库实测;加密 Git remote 和手机端方案仍属于设计讨论,不混作运行结果。

为什么不直接用 GitHub Private 托管核心代码

这里的“不信任”是架构上的威胁模型,不是对 GitHub、OneDrive 或 Google Drive 的指控。GitHub 对 Private 仓库提供访问控制和保密承诺,但其服务条款也规定了若干服务侧访问例外。对一般项目,这可能足够;对希望云端运营方也无法解密源码的商业项目,就不是同一种保护。

我需要同时满足两个要求:日常仍然像 Git 一样提交、回滚、切分支和在 Win/Mac 间同步;即使整套本地设备丢失,也能从异地找回一份加密后的历史。于是信任边界落在开发设备和自己管理的密钥上,云盘与远端存储只负责保存字节。

做法服务侧可能接触到什么适用边界
直接推到 GitHub Private普通 Git 对象、源码与提交历史,由平台访问控制保护不满足核心源码不明文上云的前提
本地 Git Central + Cryptomator 密文网盘云端看到加密文件及部分大小、时间等元数据;解密在受信任客户端适合异地备份,但不是原生在线 Git 协作
客户端加密整个 Git 远端再推送目标是远端只存密文;具体保护范围取决于实现候选方向,必须验证性能、Git 语义和恢复
断开连接的 7z 归档离线介质保存加密历史快照防灾层之一,不提供跨地域实时协作

这条边界也有代价:本机被恶意软件控制、密钥泄露、AI 工具获得解密后的工作区时,客户端加密帮不上忙。Cryptomator 的安全说明明确列出了本机恶意软件以及文件大小、时间等元数据限制。“不上云明文”是可以审计的设计约束,不是宣称代码绝对不会泄漏。

核心源代码不明文上云的 Git 备份架构示意图:Windows 和 Mac 本地开发仓库通过 Windows Central 裸仓库同步,E 盘镜像分别进入 Cryptomator 密文云备份与 7z 离线归档。

架构示意:日常 Git 协作保留在可信本地,云端只存客户端加密后的备份,离线归档独立保存历史。加密 Git remote 和手机端开发属于后续设计方向。

Win/Mac 局域网 Git 同步:为什么用裸仓库做 Central

先看运行时的关系:

Windows 开发仓库 ─── push / pull ──┐
                                  ├── Windows Central(bare repo)
Mac 开发仓库 ─ SMB ─ push / pull ──┘           │
                                             │ mirror 更新
                                             ▼
                                      E 盘镜像裸仓库
                                             │
                                 加密归档 / 离线复制
                                             ▼
                                    移动硬盘、异地网盘

四层里其实只有后三层才承担备份职责。把它们摊开看,哪一层会跟着出问题就清楚了:

层级与位置关键动作主要应对不能解决的事
协作 Central:Windows D 盘裸仓库Win/Mac push、pull已推送代码在单台开发机损坏时仍可取回Windows 关机、SMB 断连、Central 本身损坏;它不算独立冷备
同机镜像:Windows E 盘 mirrorclone --mirror、remote updateCentral 目录级损坏后从最近镜像恢复D/E 同机失效;--prune 可能同步删分支
离线归档:断开的移动硬盘7z 加密包、7z t主机故障、勒索软件影响在线副本更新靠人工;最新版本取决于归档时间
异地密文:Cryptomator + OneDrive / Google Driverobocopy、密文清单校验、下载验证本地设备全部丢失、异地取回云同步也能传播错误;不含未提交代码及外部数据

RPO(最多损失到哪个版本)由各层最后一次成功提交、同步或归档时间决定;RTO(多久能恢复)还取决于介质速度、解密和演练情况。以前没有专门记录实际恢复耗时,所以这里只比较恢复路径,不用假定的分钟数冒充 RTO/RPO。D/E 同属一台 Windows,两个盘符也消除不了整机故障这个共同风险。

Central 的关键不是“有个共享文件夹”,而是里面存放的是一个 bare repository(裸仓库)。它有 Git 的对象、引用和仓库配置,却没有普通工作区。两台开发机都持有自己的工作树,修改在本机完成,提交以后才推到 Central。

按照 Git 官方的 git init 文档,新建裸仓库的基本形式是:

# 示例路径,不是我原机器的目录
git init --bare "D:/git-central/project.git"

# 已有 Windows 工作仓库中配置 Central
git remote add central "D:/git-central/project.git"
git push -u central master

Mac 侧先把 SMB 共享挂载到本地,再把挂载路径设为 central 的 remote。下面的 master 是当年仓库使用的分支名;如果你的默认分支是 main,不要照抄 master。

git remote add central "/Volumes/git-central/project.git"
git fetch central
git pull --ff-only central master

旧操作手册里还有几行非常具体的 Mac 端排查命令,省去了“究竟是 Git 没更新,还是 SMB 根本没挂上”的猜测:

# 在 Mac 上挂载 Windows 的 SMB 共享(示例地址)
open "smb://<windows-host>/<share-name>"

# 先确定共享真正挂载到了哪里
ls /Volumes
ls "/Volumes/<share-name>"

# 再从项目工作区确认 Git remote 指向的位置和当前分支
git remote -v
git branch --show-current
git status --short

原笔记记录的是先检查共享目录确实能看到裸仓库,再操作 central remote;不是看到 git pull 报错就急着 reset --hard。Windows 侧也遵循先看 git status、提交,再 git push central master 的顺序。这些排查命令保留自当年的操作笔记。

在原来的日常习惯里,开发前先 git pull central master,开发结束后看 git status,再 git add .、git commit、git push central master。另一台电脑从同一个入口拉取。示例中的 --ff-only 是我现在更愿意保留的保护:遇到分叉就停下来处理,避免普通 pull 默默产生合并提交。

这种安排有个很实在的好处:Central 坏掉时,两台开发机的 Git 历史还在;其中一台开发机坏掉,也不等于历史随它消失。我一直把“完整仓库可以作为重建的种子”当成设计原则。不过这句话有条件:那份仓库必须真的保有你需要的分支、标签和对象。只 clone 过单分支、做了浅克隆,或者把未提交文件放在工作树里的情况,不能自动算作完整备份。

还有一个容易忽略的边界:通过 SMB 直接访问裸仓库,实际依赖共享文件系统的权限、锁、网络稳定性。旧方案能满足自己的双机习惯,不代表适合跨地域、多用户或者不稳定网络。新建长期 Git 服务时,我会更愿意使用 Git over SSH 这样的远程传输,而不是把共享盘当成通用 Git 服务器。

Git mirror 冷备:同步删除和历史快照是两回事

真正拉开安全距离的是 Central 后面那几层。原来的 E 盘放一份 --mirror 方式克隆的裸仓库,不参与开发,只做更新;移动硬盘留加密归档,平时离线保存;网盘再留一份经过加密处理的异地副本。它们不是四个相同的 git clone,而是应对不同故障范围。

Git 官方对 git clone --mirror 的定义值得仔细看:mirror 不仅包含分支,还会按镜像 refspec 同步其他引用。它与普通开发克隆不同,后续 git remote update 可能覆盖对应 refs。初次创建和日常更新的示例可以写成:

# 只在初始化冷备仓库时执行一次
git clone --mirror "D:/git-central/project.git" "E:/git-cold/project.git"

# 后续更新镜像。--prune 可能删除源端已经不存在的引用
git --git-dir="E:/git-cold/project.git" remote update --prune

# 只检查 Git 对象,并不等于完成一次恢复演练
git --git-dir="E:/git-cold/project.git" fsck --full

当年的 E 盘冷备操作手册除了 git remote update,还明确写了一个确认当前提交的办法:

# 在 mirror 裸仓库内执行;本例沿用历史 master 分支
git remote update
git log origin/master -1 --oneline

# 不只比最近一条 log;正式恢复还需要核对 refs 和对象
git for-each-ref --format='%(refname) %(objectname)' refs/heads refs/tags

这里 origin/master 是历史笔记中的命令写法;裸镜像能否以该名字访问引用,要以实际配置和 git show-ref 结果为准。更可靠的通用做法是直接查看 refs/heads/master 以及其他需要保存的 refs。

我用 Git 2.47.3 建了一个临时仓库,走了一遍 --prune 的删除链:推送 test-prune 分支、同步 mirror,然后从 Central 删除这个分支,再更新镜像。最关键的输出只有几行:

git_version=git version 2.47.3
feature_after_sync=present 733c4dc75735672f2db8b4ed8185058ceddaf8c7
feature_after_prune=absent
git_fsck_exit=0
git_fsck_output=dangling commit 733c4dc75735672f2db8b4ed8185058ceddaf8c7
restored_master_sha=c7b763f667cbbba6c1bb39a6719236a554257a8c

注意看:git fsck 返回 0,还能找到 dangling commit,但镜像已经没有 test-prune 的分支引用。悬空对象还可能在后续垃圾回收时消失。镜像不是“时光机”——它会同步最新状态,也会同步删除。这个实验验证的是 Git 引用行为;7z 和网盘的可靠性仍需要用真实归档做恢复演练。

按照 git remote 文档,remote update 做的是 fetch;--prune 会按配置清除已经失效的远端引用。每次更新通常只传输需要的新对象,不必把整个仓库重新拷一遍。但 mirror 是同步副本,不是不可变快照。上游误删了分支,下一次带 prune 的同步也可能把这种删除传播到镜像。它很适合快速恢复当前状态,却不能独自承担“防误删、防勒索”的所有责任。

所以当年还保留了两条不同的备份支线:一条把冷备仓库打成带日期的 7z 加密归档,放到平时断开的移动硬盘;另一条把 Git 镜像保存在 Cryptomator 的加密 vault,再将 vault 的密文目录复制到 OneDrive / Google Drive 同步目录。它们可能保护同一份 Git 历史,但不能简单画成“所有网盘副本都先经过 7z,再经过 Cryptomator”。真正有价值的是某一份历史归档在 Central 下一次更新时不会被覆盖。加密口令不能与归档放在一起,复制成功也不等于恢复成功。

同一台 Windows 上的 D 盘 Central 与 E 盘镜像,能缓解仓库误操作或单卷损坏,却挡不住整机故障、被盗或者同机勒索。移动硬盘只有在备份完成后真正断开,才有离线隔离的意义;云端存储如果同步了删除或损坏,也未必保留可用旧版。这样的差异,是我后来不再满足于“已经有好几份备份”的原因。

还要补一处范围:Git 保存已提交的 Git 对象和引用,并不会自动替你备份 .gitignore 排除的大数据、未提交的工作区变更、外部数据库。使用 Git LFS 或子模块的项目,也需要分别确认相应的对象和依赖是否已备份。仓库镜像的完整,不等于整个开发环境的完整。类似 Docker volume 这样的运行数据,要用另一套备份方案处理;我之前也单独写过 Docker 数据卷备份到 GitHub 的容灾实践。

Cryptomator + 网盘:SHA-256 如何校验加密备份

网盘这一层最麻烦的不是上传,而是上传之后如何确定没漏文件。当时我专门为 Cryptomator + OneDrive、Cryptomator + Google Drive 留了两份操作笔记,里面有密文基准清单、VaultIntegrityCheck.ps1 的调用顺序,以及复制完成后如何定位 MISSING、CHANGED、EXTRA。

Cryptomator 负责对文件内容、名称做客户端加密,网盘客户端负责传输,它们不是同一个软件的两项功能。 Cryptomator 官方安全说明也强调加密不能代替备份。实际旧流程更接近:

E: 冷备镜像 / Cryptomator 加密 vault(基准)
     │
     ├─ 对稳定的密文目录生成指纹清单(Update)
     │
     ├─ robocopy 到 OneDrive 或 Google Drive 本地同步目录
     │
     ├─ 等网盘客户端显示同步完成
     │
     └─ 对目标密文目录只读比对(Verify)
          ├─ MISSING:源有、目标没有
          ├─ CHANGED:路径相同,但文件指纹变化
          ├─ EXTRA:目标多了文件,需判断冲突/临时文件
          └─ 匹配:本地同步目录与清单对应

原来的自用 VaultIntegrityCheck.ps1 固定了 Update 与 Verify 两种模式:只在确认 E 盘密文源副本正确时更新基准,验证目标目录时不刷新基准。下面是历史调用形式(路径为示例);没有这个脚本,也可以用后面的原生 PowerShell 比较当前两个本地密文目录,但它不具备持久基准功能:

# 只在确认 E: 源 vault 停止写入并且内容正确时更新基准
powershell -NoProfile -File "D:\BackupTools\VaultIntegrityCheck.ps1" `
  -VaultPath "E:\BackupVault" `
  -ManifestPath "D:\BackupTools\vault-manifest.json" -Mode Update

# 等 OneDrive 上传完成,再对目标密文目录做只读校验
powershell -NoProfile -File "D:\BackupTools\VaultIntegrityCheck.ps1" `
  -VaultPath "C:\CloudSync\OneDrive\BackupVault" `
  -ManifestPath "D:\BackupTools\vault-manifest.json" -Mode Verify

Google Drive 版本在两步中间还保留了一条 robocopy 复制命令,参数为 /E /R:2 /W:2 /MT:8 /FFT /Z /NP。换成中性目录后的可复制骨架是:

robocopy "E:\BackupVault" "G:\MyDrive\BackupVault" /E /R:2 /W:2 /MT:8 /FFT /Z /NP
if ($LASTEXITCODE -ge 8) { throw "Robocopy failed with code $LASTEXITCODE" }

微软的 Robocopy 退出码说明确认:返回码 8 及以上才表示至少有一次复制失败。但 0–7 只是复制阶段没有报告这种失败,绝不能把它写成“所有文件 SHA-256 均通过”。/E 会包含子目录,但不负责删除目标已有的多余文件;我也不会在唯一云端副本上随手换成会删目标文件的 /MIR。

历史笔记留下的校验反馈有 [OK] Vault matches the manifest exactly.、MISSING、CHANGED、EXTRA 等类型。看到 MISSING 或 CHANGED,应先查上传是否完成、文件是否仍在写入,不要对网盘目录运行 Update 来把异常覆盖成新基准。Google Drive 笔记还记过 .tmp.driveupload 临时文件;它可能是同步中间状态,不能无条件忽略,等完成同步后重新验证更稳妥。

如果读者没有那份私人脚本,至少能先用 Windows 自带的 Get-FileHash 检查单个文件的 SHA-256。它是 PowerShell 的标准 cmdlet,算法和 -LiteralPath 参数见 微软官方文档:

Get-FileHash -Algorithm SHA256 -LiteralPath "E:\BackupVault\vault.cryptomator"
Get-FileHash -Algorithm SHA256 -LiteralPath "C:\CloudSync\OneDrive\BackupVault\vault.cryptomator"

但只查一个配置文件远远不够。整个 vault 的清单至少应覆盖相对路径、文件大小和每个密文文件的 SHA-256,并检测目标端多文件、少文件、同名变更;还要有明确的源基准日期,不能在两端一边写入一边比。没有现成校验工具,也可以用一段通用的只读 PowerShell 脚本处理:递归列出源、目标文件的相对路径、字节数和 SHA-256,再用 Compare-Object 找出差异:

function Get-Inventory([string]$root) {
  $base = (Resolve-Path -LiteralPath $root -ErrorAction Stop).ProviderPath.TrimEnd([char]'\')
  Get-ChildItem -LiteralPath $base -File -Recurse -Force -ErrorAction Stop |
    ForEach-Object {
      [pscustomobject]@{
        Path   = $_.FullName.Substring($base.Length).TrimStart([char]'\')
        Bytes  = $_.Length
        SHA256 = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256 -ErrorAction Stop).Hash
      }
    }
}
$original = @(Get-Inventory "E:\BackupVault")
$replica  = @(Get-Inventory "C:\CloudSync\OneDrive\BackupVault")
if (!$original.Count -or !$replica.Count) { throw "Empty or unavailable vault" }
$diff = Compare-Object $original $replica -Property Path,Bytes,SHA256
if ($diff) {
  $diff | Format-Table -AutoSize
  throw "Local encrypted vaults differ"
}
Write-Host "[OK] Local encrypted vault file hashes match"

这份新示例只覆盖文件,不覆盖空目录或云服务端状态,也没有历史 Update/Verify 的持久基准能力。真正的云端校验还差一步:把云端某一版加密归档重新下载到与同步目录无关的位置,再拿下载文件的哈希去比上传前留下的可信记录。只有“网盘同步图标已完成 + 本地目录一致”,并不能单独证明远端文件可取回。

对离线移动硬盘,我当年采用 7z 全量加密包而不是始终依赖热镜像。7z 的 -mhe=on 会加密 7z 格式内的文件名,-p 表示使用口令;不要把口令明文拼进共享的脚本、命令历史或日志。7-Zip 官方 7z 格式说明确认支持 AES-256。创建归档时,我更建议在 GUI 里确认口令和“加密文件名”;下面保留 CLI 参数示例,操作前先用本机 7z -h 核对版本和密码输入方式,不要把密码直接写进脚本或命令历史:

# 只在冷备仓库没有写入时,对其目录创建带口令的 7z 归档
7z a -t7z -p -mhe=on "git-snapshot-YYYYMMDD.7z" "E:\GitCold\project.git"

# 测试归档是否能读完:输入口令后检查退出码
7z t "git-snapshot-YYYYMMDD.7z"

例如把这个归档从云端重新下载到 D:\Downloads,可以这样验证上传前后字节一致:

$before = (Get-FileHash "D:\GitArchive\git-snapshot-YYYYMMDD.7z" -Algorithm SHA256).Hash
$after  = (Get-FileHash "D:\Downloads\git-snapshot-YYYYMMDD.7z" -Algorithm SHA256).Hash
if ($before -ne $after) { throw "Downloaded archive SHA-256 mismatch" }

哈希相同只能说明这两份归档字节一致,不证明基准本身未被篡改,也不能替代 7z t、解压、git fsck、refs 对比和实际克隆。基准清单最好独立保留并防止被同一故障一起篡改;移动硬盘备份完成后要真的断开。这样把“密文传输正确”和“代码历史可恢复”分成不同的检查,才不会在出事时把一个 [OK] 当成万能保险。

能否像 Git push 一样,把密文实时推到云端?

历史方案把两件容易混淆的事分开了:Windows / Mac 对本地明文 bare Central 做 Git push、Git pull;另一侧是把 Git mirror 的副本放进 Cryptomator vault,再将加密后的文件目录交给网盘同步。后者上传得再频繁,也只说明密文文件在同步;它不自动变成一个支持 Git 分支、fetch 和 push 的云端远程仓库,更不能据此说现有架构已经实现实时密文 Git push。

我还希望补上一条链路:本地每次 commit 后,尽可能及时地把整份 Git 历史的密文版本推到异地,服务侧读不到正常的源码与提交历史。真正需要解决的不是加一句“自动同步”,而是出站前加密、密钥和恢复凭证独立保管、push 失败与并发覆盖的处理,以及远程灾备的实际恢复验证。

可研究的工具之一是 git-remote-gcrypt。它用 GnuPG 加密 Git remote 的表示,并可桥接 Git transport。但项目文档警告:使用普通 Git URL 后端时,push 会重复上传整个仓库历史,仓库增长后可能不再实用;还记录了与非快进覆盖相关的危险默认行为。它的 rsync 后端能更好地进行增量传输,但这意味着使用自己控制的 rsync/SSH 存储端,而不是给普通 GitHub Private 配一个 remote 就能实现同等效果。这里的“及时推送”也只覆盖已提交的 Git 历史,不会自动同步未提交的工作区。在用于商业仓库前,还需要在隔离环境验证远端对象、增量成本、非快进保护、断线冲突、密钥轮换与恢复。

仅用 git-crypt 加密少数文件也不能满足同一目标:它不隐藏文件名、commit message 等 Git 元数据。更现实的备选方向,是继续本地正常 Git 协作 + 自动生成密文异地增量备份。这能缩短备份的滞后,却不能让 GitHub 网页直接显示 diff、搜索源文件或替云端 AI Agent 运行代码。我宁愿承认这是两种能力,也不为了手机访问方便,把原来的安全前提悄悄撤销。

Git 灾难恢复:从哪份副本恢复代码和历史

原先给自己留了两套恢复路线。 遇到事故时,我会先判断手里哪一份仓库还保有目标 refs,而不是立刻重建一套空项目:

需要恢复 Git 项目
  ├─ 开发机还有完整的提交 / 分支 / 标签
  │    └─ 新建空 bare Central → 检查 refs → 推送本地分支与 tags
  ├─ 开发机不完整,但 E 盘 mirror 仍能读取
  │    └─ fsck → 查看 refs → clone 出工作区,再比对历史
  └─ 在线副本都不可用
       └─ 找离线或异地历史归档 → 校验 SHA-256 → 7z 测试与解压
                                     → git fsck → refs 对照 → clone

这个判断也提醒了我:git push --all 只推送本地分支。不能因为当前工作区能启动,就默认过去所有开发机的全部分支都在这一份里。

一种是 Central 丢了,但还有完整的开发仓库。此时可以重新创建一个裸仓库,再从开发机推回需要的分支和标签:

# 在新 Central 的可写位置创建空裸仓库
git init --bare "/path/to/recovered/project.git"

# 在幸存的开发仓库内执行
git remote add recovered "/path/to/recovered/project.git"
git push recovered --all
git push recovered --tags

这里必须说明:--all 推送的是这台开发机的本地分支,不是所有 refs/remotes/*。如果另一台机器曾有独立分支,而这台开发机没有保存为本地分支,不能声称 git push --all 会替你凭空找回来。正式切换 Central 前,需要对照现存仓库的 refs 清单,必要时从其他幸存副本补齐。

另一种更极端:本地开发机和 Central 都不可用了。恢复的入口就变成移动硬盘或网盘里的归档。顺序应该是拿到一个明确日期的备份、完成解密和解压、检查裸仓库对象、核对预期 refs,最后 clone 出工作区。先不要把刚解出来的备份覆盖掉,更不要第一步就执行带 --force 或 --prune 的镜像推送。

# 在解压得到的裸仓库上执行
git --git-dir="/restore/project.git" fsck --full
git --git-dir="/restore/project.git" for-each-ref \
  --format='%(refname) %(objectname)' refs/heads refs/tags

# 确认仓库可读后,再建工作区
git clone "/restore/project.git" recovered-worktree

git fsck 检查 Git 对象的连通性与有效性;git for-each-ref 能列出分支和标签对应的对象 ID。两者一起,至少能回答“包是不是损坏了”“预期的分支和标签在不在”。但要证明真的可恢复,还得检查文件内容、依赖、外部数据,以及是否能在新的机器上继续构建和工作。

后来整理 verify-coldbackup.ps1 时,我把检查顺序固定成一条链:7z 完整性测试 → 解密解包 → clone 出工作区 → git fsck 检查对象 → 对照全部分支和标签 SHA。只看“压缩包能打开”远远不够;要确认代码真的备住了,就得在独立目录完整走一次恢复。

顺带说一句,另一条“把上游公开项目镜像到自己 GitHub 私有仓”的路径,与这个局域网 Central 是两件事。那条路线要处理 GitHub 的受限 refs、分支覆盖和 --force --prune 的目标删除风险,不应该把个人开发分支混在镜像目标里。具体镜像问题我已经单独放在GitHub 公开仓库自动同步到私有镜像的实践里,不在这里重复展开。官方 git push 文档对 --mirror、--prune 和 refspec 的差异也说得很清楚。

手机端 AI 开发遇到的瓶颈,恰好是这套架构的代价

过去在 Windows 和 Mac 前面工作,这个选择并不难忍受:两台机器都有完整代码,Windows Central 负责中转。现在有时只有手机,想临时让 ChatGPT/Codex 查看仓库、修个小问题或跑一次检查,限制就集中暴露出来了。

首先,Windows 是单点在线入口,关机、网络变化或 SMB 无法访问时,跨设备协作就停了。Mac 的完整仓库也占空间,但 partial clone、sparse checkout 只能缓解容量压力,解决不了谁有权解密的问题。更关键的是,云端 AI 编程工作流通常需要直接读取、检索和执行源码。如果云端仓库只有密文,普通 GitHub PR、网页 diff、代码搜索和云端 Agent 无法理解它;如果为了让 Agent 工作而在不受信任的服务器上解密,又回到我最初拒绝的明文上云。

所以下一步更符合这条边界的选择,不是把 GitHub Private 直接升级成唯一 Central,而是让手机成为控制台,让代码继续留在我信任的开发机器上:例如通过受控的私有网络和 SSH 连接一台始终开机的自有 Mac/Linux 主机,在那里运行 Git、测试和获得授权的开发工具。手机不持有完整仓库,也无需把密钥交给云 Git 服务。这条路仍依赖主机在线、强身份认证和最小权限。若云端模型需要接收完整源码上下文,仍须另行判断数据出站是否违反同一边界,不能因为执行主机在家中就默认安全。

即使未来建成独立的加密 Git remote,也只能解决远程传输和历史保存,不能自动解决云端 Agent 读取明文的需求。需要分别回答:谁能解密?明文在哪台设备运行?手机断线后任务能否安全继续?这几个问题没闭环之前,我不会把移动协作包装成已经完成的升级。

冷备与恢复演练仍然要留着。一个推送更方便的通道,不会自动变成防误删和可恢复的历史快照。

Git 裸仓库、SMB 和备份恢复的几个常见问题

为什么不用 SMB 直接共享普通 Git 工作区?

普通工作仓库有工作树,Git 默认拒绝向当前检出的非裸仓库分支直接推送(receive.denyCurrentBranch)。裸仓库无工作树,适合做 Central;开发仍留在 Win/Mac 各自的工作区。

Mac 挂载 SMB 后 git pull 失败,该先检查什么?

先核对 ls /Volumes、git remote -v 和共享权限,再确认是否仍有 Git 进程。锁文件可能出现在工作仓库或裸仓库的 refs 等位置;未确认进程结束前,不要直接删除 .lock。跨网络频繁操作可考虑 Git over SSH。

git clone --mirror 比普通 git clone 更安全吗?

--mirror 会同步覆盖 refs,源端删掉的分支可能在下一次更新时从镜像消失。防误删靠保留不随源端更新的历史归档,不是再复制一份同样的镜像。

OneDrive / Google Drive 显示“已同步”,就算通过哈希校验了吗?

不算。客户端“已同步”、本地密文目录与清单一致、云端重新下载后 SHA-256 一致,以及最终解密、git fsck、核对 refs、实际 clone,是不同检查。历史脚本的 [OK] 只覆盖它比对的本地目标与基准。

为什么不直接用 GitHub Private?恢复演练多久做一次?

GitHub Private 的权限保护不等于云端没有明文源码,不满足我的核心商业代码边界。首次配置或修改加密备份链后至少独立恢复一次,检查密钥、Git 对象与 refs,再实际 clone;丢失密钥就无法解密备份。

参考资料:Git init · Git clone · Git remote · Git push · Git fsck · Robocopy · Get-FileHash · Cryptomator 安全边界 · 7-Zip 官方 7z 格式 · GitHub Private 仓库条款 · git-remote-gcrypt · git-crypt 限制