# コアコードを平文でGitHubに預けない理由：Win/Macの私設Git、暗号化クラウドバックアップとモバイル開発の壁

> Canonical: https://vxi.me/ja/posts/git-backup-win-mac-bare-repo-disaster-recovery/
> Locale: ja-jp
> Published: 2026-10-15
> Updated: 2026-10-16
> Markdown: https://vxi.me/ja/posts/git-backup-win-mac-bare-repo-disaster-recovery.md

Windows と Mac の間に Git の仕組みを作ったとき、最優先だったのは利便性でも費用でもなかった。長い年月をかけて育てる商用プロジェクトの**コアソースコードを、第三者のクラウドに平文では置かない**。たとえ GitHub の Private リポジトリであっても、この条件は変えないと最初から決めていた。

GitHub が勝手にコードを利用すると断定したいわけではない。問題は、プラットフォームの内部アクセス、アカウント侵害、サプライチェーン攻撃や将来の事故が絶対に起きないと信じなければ成り立たない設計にしたくないことだ。Private はアクセス制御であり、**サービス提供者が Git オブジェクトを技術的に読めないことを意味しない**。復号鍵を自分で管理し、クラウドには暗号文だけを預ける設計にしたかった。

そこで Windows の bare repo をローカル Central にして、Mac は SMB 経由で push/pull するようにした。その後、E ドライブへの mirror、オフラインの 7z 暗号化アーカイブ、Cryptomator を使った OneDrive / Google Drive の暗号化コピーを追加した。ただし、**暗号化したバックアップをクラウドへ同期することと、Git の remote 自体を暗号化して push することは別物**だ。スマホから ChatGPT/Codex を使って開発を進めたい今、その違いが大きな制約になっている。

以下では 2024～2026 年の実際の運用記録と Git 2.47.3 の合成リポジトリでの再現結果はそのまま残す。Git の refs や mirror の検証結果を、暗号化 remote やモバイル開発の実地検証として扱うことはしない。

## GitHub Private では満たせなかった前提

これは GitHub、OneDrive、Google Drive への告発ではなく、私が採用した脅威モデルの話だ。[GitHub のプライベートリポジトリ条項](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service#e-private-repositories)は機密性とサービス側からのアクセス条件を説明するが、利用者だけが鍵を持つクライアント側のリポジトリ全体暗号化とは違う。通常の開発では Private で十分な場合も多いが、商用コアコードにどこまでの境界を求めるかは別の判断になる。

| 方式 | クラウド側から見えるもの | 私の用途での位置づけ |
|---|---|---|
| 通常の GitHub Private | 平文の Git オブジェクト、コード、コミット履歴を権限で保護 | 「平文をクラウドに置かない」条件は満たせない |
| ローカル Git + Cryptomator | 暗号化ファイルと一部のサイズ・時刻メタデータ | オフサイトバックアップ向き、Git のクラウド共同作業ではない |
| Git remote 全体を送信前に暗号化 | 実装により暗号文だけを保管することが目標 | 別途試験が必要な push/pull 候補 |
| 切り離した 7z 暗号化アーカイブ | 独立した暗号化スナップショット | 誤削除や障害への備えであり、リアルタイム同期ではない |

クライアント側のマルウェア、鍵の漏洩、復号済みのワークツリーを取得した AI サービスにはこの暗号化では対処できない。[Cryptomator のセキュリティ上の限界](https://docs.cryptomator.org/security/security-target/)にも注意が必要だ。

![コアソースコードをクラウドに平文で預けない Git バックアップ構成。Windows と Mac がローカルのベア Central に同期し、ミラーを Cryptomator の暗号文クラウドバックアップと 7z オフライン履歴に分ける。](/images/posts/git-backup-win-mac-bare-repo-disaster-recovery/zero-trust-git-backup.ja-jp.svg)

*構成図：Git 共同作業は信頼するローカル環境に残し、クラウドにはクライアント側で暗号化したバックアップのみ保存。暗号化 Git remote とスマホ開発は今後の設計課題。*

## Win/Mac LAN 内 Git 同期：なぜベアリポジトリを Central にしたのか

まずは実行時の関係を見てみよう：

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


この4層のうち、実際にバックアップの役割を担うのは後ろの3層だけだ。並べてみると、どの層がどこで一緒に倒れるかがはっきりする：

| 層と場所 | 主要アクション | 主な対策対象 | 解決できないこと |
|---|---|---|---|
| 連携 Central：Windows D ドライブのベアリポジトリ | Win/Mac の `push`、`pull` | push 済みコードが開発マシン1台の故障時にも取り戻せる | Windows のシャットダウン、SMB 切断、Central 自体の破損。独立したコールドバックアップではない |
| 同一マシンのミラー：Windows E ドライブの mirror | `clone --mirror`、`remote update` | Central のディレクトリ単位の破損後に最新のミラーから復旧 | D/E の同一マシン障害。`--prune` がブランチ削除を同期する可能性 |
| オフラインアーカイブ：切断された外付け HDD | 7z 暗号化アーカイブ、`7z t` | ホスト故障、ランサムウェアがオンラインコピーに及ぶ場合 | 更新は手動。最新版はアーカイブ時点に依存 |
| オフサイト暗号文：Cryptomator + OneDrive / Google Drive | `robocopy`、暗号文マニフェスト検証、ダウンロード検証 | ローカル機器の全損、遠隔地からの取り戻し | クラウド同期もエラーを伝播しうる。未コミットのコードや外部データは含まない |

RPO（どこまで遡って失うか）は各層の**最後の成功したコミット・同期・アーカイブの時点**で決まる。RTO（復旧にかかる時間）はさらにメディア速度・復号・訓練の有無に左右される。実際の復旧時間は記録していなかったので、ここでは復旧パスだけを比較し、仮の分数で RTO/RPO を語ることはしない。D と E は同じ Windows マシンであり、ドライブ文字が違ってもマシン全体の故障という共通リスクは消えない。

Central の要点は「共有フォルダがあること」ではなく、中身が **bare repository（ベアリポジトリ）** であることだ。Git のオブジェクト・参照・リポジトリ設定を持ちながら、通常のワークツリーを持たない。2台の開発マシンはそれぞれワークツリーを持ち、変更はローカルで完結させ、コミットしてから Central に push する。

[Git 公式の `git init` ドキュメント](https://git-scm.com/docs/git-init) によれば、ベアリポジトリの基本形はこうだ：

```bash
# 示例路径，不是我原机器的目录
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` をそのままコピーしないこと。

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


旧操作マニュアルには、Mac 側の切り分けコマンドが具体的に数行残っていた。「Git が更新されていないのか、SMB がそもそもマウントされていないのか」という推測を省いてくれる：

```bash
# 在 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`。もう1台のマシンも同じ入口から pull する。サンプルの `--ff-only` は、今の私が残しておきたい保護だ。分岐があれば止めて対処し、通常の `pull` が黙ってマージコミットを作るのを避ける。

この構成の実利的な良さはこうだ。Central が壊れても2台の開発マシンの Git 履歴は残る。1台が壊れても、履歴が一緒に消えるわけではない。「完全なリポジトリは再構築の種になる」をずっと設計原則にしてきた。ただし条件付きだ。そのリポジトリが本当に必要なブランチ・タグ・オブジェクトを持っていること。単一ブランチだけ clone したもの、シャロークローン、未コミットのファイルをワークツリーに置いたままのものは、自動的には完全なバックアップとはみなせない。

見落としがちな境界がもう1つある。SMB 経由でベアリポジトリに直接アクセスするのは、共有ファイルシステムの権限・ロック・ネットワーク安定性に依存するということだ。旧方式は自分の2台体制には合っていたが、遠隔地・マルチユーザー・不安定なネットワークに適するとは限らない。長期運用の Git サービスを新設するなら、共有ディスクを汎用 Git サーバー代わりにするより、Git over SSH のようなリモート転送を選ぶ。

## Git mirror の冷備：削除の同期と履歴スナップショットは別物だ

本当に安全マージンを稼いだのは Central の後ろの層だ。E ドライブには `--mirror` でクローンしたベアリポジトリを置き、開発には使わず更新専用にした。外付け HDD には暗号化アーカイブを置いて普段はオフライン保管。クラウドにも暗号化処理したオフサイトコピーを置いた。これらは4つの同じ `git clone` ではなく、異なる障害範囲への対策だ。

Git 公式の [`git clone --mirror`](https://git-scm.com/docs/git-clone) の定義はよく読む価値がある。mirror はブランチだけでなく、ミラー refspec に従って他の参照も同期する。通常の開発用クローンとは異なり、後の `git remote update` が対応する refs を上書きしうる。初回作成と日常更新のサンプルはこう書ける：

```bash
# 只在初始化冷备仓库时执行一次
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` のほか、現在のコミットを確認する方法も明記されていた：

```bash
# 在 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` ブランチを push して mirror に同期し、Central からそのブランチを削除してからミラーを更新する。肝心の出力は数行だけだ：

```text
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` ドキュメント](https://git-scm.com/docs/git-remote) によれば `remote update` は fetch であり、`--prune` は設定に従って無効になったリモート参照を掃除する。更新のたびに通常は必要な新規オブジェクトだけが転送され、リポジトリ全体をコピーし直す必要はない。ただし **mirror は同期コピーであり、不変のスナップショットではない**。上流でブランチを誤削除すれば、次回の prune 付き同期でその削除がミラーに伝播しうる。現状への高速な復旧には向くが、「誤削除・ランサムウェア対策」の全責任を単独で負わせることはできない。

だから当時は2本の異なるバックアップ系統を残していた。1本は冷備リポジトリを日付付きの 7z 暗号化アーカイブにして、普段は切断された外付け HDD に置く。もう1本は Git ミラーを Cryptomator の暗号化 vault に保存し、vault の**暗号文ディレクトリ**を OneDrive / Google Drive の同期ディレクトリにコピーする。同じ Git 履歴を守るものだが、「すべてのクラウドコピーが 7z を経て Cryptomator を経る」と単純化はできない。本当に価値があるのは、Central の次回更新で上書きされない、ある時点の履歴アーカイブだ。暗号化のパスフレーズはアーカイブと一緒に置かず、コピー成功は復旧成功とイコールではない。

同じ Windows マシン上の D ドライブ Central と E ドライブのミラーは、リポジトリの誤操作や単一ボリュームの破損には効くが、マシン全体の故障・盗難・同一マシン上のランサムウェアは防げない。外付け HDD はバックアップ完了後に本当に切断してこそオフライン隔離の意味がある。クラウドストレージも削除や破損を同期してしまえば、使える旧版が残るとは限らない。こうした違いに気づいて、「バックアップが何份もある」ことへの満足はなくなった。

範囲の補足がもう1つ要る。Git が保存するのはコミット済みの Git オブジェクトと参照であり、`.gitignore` で除外した大容量データ・未コミットのワークツリー変更・外部データベースまで自動でバックアップしてくれるわけではない。Git LFS やサブモジュールを使うプロジェクトは、対応するオブジェクトや依存関係がバックアップされているか別途確認が必要だ。リポジトリミラーの完全性は、開発環境全体の完全性とイコールではない。Docker volume のような実行時データは別のバックアップ方式で扱う。私も以前 [Docker データボリュームを GitHub にバックアップする災害対策の実践](/ja/posts/docker-volume-backup-github-sync/) を別記事に書いている。

## Cryptomator + クラウド：SHA-256 で暗号化バックアップを検証する

クラウド層で一番面倒なのはアップロードではなく、アップロード後にファイル漏れがないことをどう確定させるかだ。当時は Cryptomator + OneDrive、Cryptomator + Google Drive 用に操作メモを2部残し、暗号文ベースラインマニフェスト、`VaultIntegrityCheck.ps1` の呼び出し順序、コピー完了後に `MISSING`・`CHANGED`・`EXTRA` をどう特定するかを書いていた。

Cryptomator はファイル内容・ファイル名のクライアント側暗号化を担い、クラウドクライアントは転送を担う。同じソフトの2機能ではない。[Cryptomator 公式のセキュリティ説明](https://docs.cryptomator.org/security/security-target/) も、暗号化はバックアップの代替にならないと強調している。実際の旧フローはこういう形に近い：

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

当時の自作 `VaultIntegrityCheck.ps1` は `Update` と `Verify` を分離していた。**正しい E ドライブの暗号文マスターを確認した場合だけ基準を更新**し、対象の照合では基準を変更しない。以下は当時の呼び出し例だ。後述の標準 PowerShell だけでもローカルの暗号文フォルダーを比較できるが、永続的な基準マニフェストは残せない：

```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 版は2ステップの間に `robocopy` のコピーコマンドが残っており、パラメータは `/E /R:2 /W:2 /MT:8 /FFT /Z /NP` だった。中立的なディレクトリに置き換えたコピー可能な骨格はこうだ：

```powershell
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" }
```

Microsoft の [Robocopy 終了コード説明](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy/) によれば、**8 以上**の戻り値でのみ少なくとも1回の複製失敗を表す。ただし `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` パラメータは [Microsoft 公式ドキュメント](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash) を参照：

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

ただし設定ファイル1つを確認するだけでは到底足りない。vault 全体のマニフェストは最低でも**相対パス・ファイルサイズ・各暗号文ファイルの SHA-256** をカバーし、対象側のファイル過不足・同名変更を検出すること。さらにソースベースラインの日付を明確にし、両端で書き込みながら比較しないこと。既製の検証ツールがなければ、汎用の読み取り専用 PowerShell スクリプトでも処理できる。ソースと対象のファイルの相対パス・バイト数・SHA-256 を再帰列挙し、`Compare-Object` で差分を出す：

```powershell
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` のような永続ベースライン能力もない。本当のクラウド検証にはあと1ステップ要る。**クラウド上のある版の暗号化アーカイブを、同期ディレクトリとは無関係の場所にダウンロードし直し**、ダウンロードしたファイルのハッシュをアップロード前に残した信頼できる記録と突き合わせることだ。「クラウドの同期アイコンが完了＋ローカルディレクトリ一致」だけでは、遠隔地のファイルが取り戻せるとは証明できない。

オフラインの外付け HDD には、当時 7z の全量暗号化アーカイブを使い、ホットミラーに頼り切りにはしなかった。7z の `-mhe=on` は 7z 形式内のファイル名を暗号化し、`-p` はパスフレーズの使用を表す。パスフレーズの平文を共有スクリプト・コマンド履歴・ログに埋め込んではいけない。[7-Zip 公式の 7z 形式説明](https://www.7-zip.org/7z.html) によれば AES-256 に対応している。アーカイブ作成時は GUI でパスフレーズと「ファイル名の暗号化」を確認するのを勧める。以下は CLI パラメータのサンプルを残したもので、実行前に手元の `7z -h` でバージョンとパスワード入力方式を確認し、パスワードを直接スクリプトやコマンド履歴に書かないこと：

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

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

たとえばこのアーカイブをクラウドから `D:\Downloads` にダウンロードし直したら、アップロード前後でバイト一致をこう検証できる：

```powershell
$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" }
```

ハッシュ一致が示すのは**この2つのアーカイブのバイトが一致すること**だけで、ベースライン自体が改ざんされていないことの証明にはならないし、`7z t`・展開・`git fsck`・refs 照合・実際のクローンの代替にもならない。ベースラインマニフェストは独立に保管し、同じ障害で一緒に改ざんされないようにする。外付け HDD はバックアップ完了後に本当に切断する。こうして「暗号文の転送正当性」と「コード履歴の復旧可能性」を別のチェックに分けることで、事故のときに1つの `[OK]` を万能の保険と勘違いせずに済む。


## まだ足りないのは「暗号文を Git push できること」

現在記録に残っている `git push` と `git pull` は、Windows/Mac からローカルの平文 bare Central に対して行った操作だ。別ルートで mirror の内容を Cryptomator vault に保存し、**vault の暗号化ファイル**をクラウドストレージで同期した。後者が速くても、GitHub に暗号化 Git remote の push/pull が成立した証拠にはならない。

本当に欲しいのは commit のあと速やかに、リポジトリの履歴を送信元で暗号化してオフサイトに送れる仕組みだ。そのためには鍵の独立保管、競合更新、再試行、強制上書き、別環境での復旧テストが必要になる。未コミットの作業ファイルまでは自動で保護されない。

候補のひとつは GnuPG で Git remote を暗号化する [git-remote-gcrypt](https://github.com/spwhitton/git-remote-gcrypt)。ただし[公式 README](https://github.com/spwhitton/git-remote-gcrypt/blob/master/README.rst)によれば、一般的な Git URL のバックエンドでは push のたびに履歴全体を再送し、大きなリポジトリでは現実的でなくなる。また強制 push の既知の危険もある。rsync 系バックエンドなら増分転送できるが、GitHub Private の通常の使い方とは異なる。これは研究対象であり、商用コードで実証済みの仕組みではない。

部分的なファイル暗号化ツール [git-crypt](https://github.com/AGWA/git-crypt/blob/master/README.md) だけでも代替できない。ファイル名やコミットメッセージなどのメタデータは保護されない。ローカル Git を維持したまま暗号化バックアップを頻繁に自動化する道もあるが、GitHub 上の diff、PR、クラウド Agent で暗号文を直接開発できるわけではない。

## Git 災害復旧：どのコピーからコードと履歴を戻すか

自分用に2系統の復旧ルートを残していた。事故時には、空のプロジェクトをすぐ作り直すのではなく、まず手元の**どのリポジトリが目標の refs をまだ持っているか**を判断する：

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

この判断が教えてくれたこと：`git push --all` が push するのは**ローカル**ブランチだけだ。今のワークツリーが起動するからといって、過去の全開発マシンの全ブランチがこの1份に入っているとデフォルトで考えてはいけない。

1つは Central を失ったが完全な開発リポジトリが残っている場合だ。空のベアリポジトリを作り直し、開発マシンから必要なブランチとタグを push し戻す：

```bash
# 在新 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` が push するのはこの開発マシンの**ローカルブランチ**であり、すべての `refs/remotes/*` ではない。別のマシンに独立ブランチがあり、この開発マシンにローカルブランチとして保存されていなければ、`git push --all` が勝手に探し出してくれるとは言えない。Central を正式に切り替える前に、現存リポジトリの refs 一覧と突き合わせ、必要なら他の生き残りコピーから補完すること。

もっと極端なのは、ローカル開発マシンも Central も使えない場合だ。復旧の入口は外付け HDD やクラウドのアーカイブになる。手順は、日付のはっきりしたバックアップを手に入れ、復号と展開を済ませ、ベアリポジトリのオブジェクトを検査し、期待する refs と照合し、最後にワークツリーを clone する。解凍したばかりのバックアップを上書きしないこと。まして最初の1手で `--force` や `--prune` 付きのミラー push を実行しないこと。

```bash
# 在解压得到的裸仓库上执行
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`](https://git-scm.com/docs/git-fsck) は Git オブジェクトの連結性と有効性を検査する。[`git for-each-ref`](https://git-scm.com/docs/git-for-each-ref) はブランチとタグに対応するオブジェクト ID を列挙できる。両方合わせれば、少なくとも「パッケージは壊れていないか」「期待するブランチとタグはあるか」に答えられる。ただ本当に復旧できると証明するには、ファイル内容・依存関係・外部データの確認、そして新しいマシンでビルドして作業を続けられるかまで見る必要がある。

後に `verify-coldbackup.ps1` を整理したとき、チェック順序を1本のチェーンに固定した。**7z 完全性テスト → 復号・展開 → ワークツリーの clone → `git fsck` でオブジェクト検査 → 全ブランチ・タグの SHA 照合** だ。「圧縮ファイルが開ける」だけでは到底足りない。コードが本当に備わっていると確認するには、独立したディレクトリで復旧を一通り回す必要がある。

ついでに言えば、「上流の公開プロジェクトを自分の GitHub プライベートリポジトリにミラーする」ルートは、この LAN 内 Central とは別の話だ。あちらは GitHub の制限された refs・ブランチ上書き・`--force --prune` の対象削除リスクを扱い、個人の開発ブランチをミラー対象に混ぜてはいけない。具体的なミラー問題は [公開 GitHub リポジトリを Private ミラーへ同期するバックアップ実践](/ja/posts/github-public-repo-private-mirror-backup/) に別途書いたので、ここでは繰り返さない。公式の [`git push` ドキュメント](https://git-scm.com/docs/git-push) も `--mirror`・`--prune` と refspec の違いを明確に説明している。

## スマホから開発したいときに表面化した代償

PC の前で作業していた頃は、Windows と Mac がそれぞれコードを持っていれば十分だった。しかしスマホだけで ChatGPT/Codex に修正を頼みたいとき、Windows Central がシャットダウンしていると始められない。Mac のディスク容量を節約する partial clone や sparse checkout は、このオンライン依存や復号境界を解決しない。

クラウド上の AI コーディング環境は通常、実際のコードを読んで検索・ビルド・テストする必要がある。クラウドに暗号文だけを置けば GitHub の diff や PR、コード検索、Agent はそれを理解できない。一方、クラウド環境で復号してしまえば「クラウドに平文を置かない」という初期条件を破る。

次の方向として考えているのは、**スマホを操作端末にし、コードと実行環境は信頼する常時起動の自宅 Mac/Linux に残す**構成だ。プライベートネットワークと SSH を使い、そこで Git と開発ツールを動かす。ただし、まだ生産環境で動作・安全性を確認したわけではない。可用性、認証、鍵、権限の最小化に加え、リモートの AI モデルに復号済みのコード断片を送る場合は、それ自体が別の平文流出経路になり得る。

暗号化 Git remote は遠隔地への転送と保存を改善できても、クラウド Agent に平文を読ませる手段にはならない。**誰が復号するのか、どこでコードを実行するのか、スマホが切れたあと処理はどうなるのか**。この三点を検証するまでは、GitHub Private への単純移行を完成形とは呼べない。

## Git ベアリポジトリ・SMB・バックアップ復旧のよくある質問

### なぜ SMB で通常の Git ワークツリーを直接共有しないのか

通常の作業リポジトリでは、Git はチェックアウト中のブランチへの push を既定で拒否する（[`receive.denyCurrentBranch`](https://git-scm.com/docs/git-config)）。ベアリポジトリにはワークツリーがなく、Windows/Mac の開発用コピーとは別の Central に向く。

### Mac で SMB マウント後に `git pull` が失敗したら何を先に確認する？

最初に `ls /Volumes` と `git remote -v`、共有権限を確認する。ロックを調べる場合、**Git プロセスが動いていないと確認するまで `.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](https://git-scm.com/docs/git-init) · [Git clone](https://git-scm.com/docs/git-clone) · [Git remote](https://git-scm.com/docs/git-remote) · [Git push](https://git-scm.com/docs/git-push) · [Git fsck](https://git-scm.com/docs/git-fsck) · [Robocopy](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy/) · [Get-FileHash](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash) · [Cryptomator セキュリティ境界](https://docs.cryptomator.org/security/security-target/) · [7-Zip 公式 7z 形式](https://www.7-zip.org/7z.html)
