English | 日本語
cf-edgeNixは独立したプロジェクトであり、Cloudflare社とは提携、推奨、または出資を受けているものではありません。
Cloudflare、Cloudflare Workers、およびR2は、Cloudflare, Inc.の商標です。
cf-edgeNix は Cloudflare Workers に署名付き Nix binary cache 基盤をdeployするプロジェクトです。
R2 が NAR / narinfo の正本、Workers Cache が配信キャッシュ、D1 が build 履歴・latest・rollback root・GC live-set を持つ control plane。narinfo / NAR は Workers Cache → R2 で配信し、D1 を経由しない。KV は quota 状態と任意の nix-cache-info コピーを保持する。
Self-hosted、かつ Cloudflare 内で完結。 自分の Cloudflare アカウントに自分の Worker を deploy する形なので、cache も署名鍵も完全に自分の手元にあり、サードパーティを経由しない。
さらに実体は Cloudflare のエッジ(Workers + R2 + KV + D1 + Workers Cache + Workers Builds)に閉じており、VPS も origin server もコンテナも、GitHub Actions の deploy pipeline も不要。
Cloudflare の外で動くのは、自分の NixOS flake repo に置いた GitHub Actions job が cf-edgeNix を checkout してその scripts/publish.sh を回し、署名済み NAR を build & upload する部分だけ。
flake repo 側に publish ロジックは持たず、
.github/templates/publish-cache.ymlの workflow テンプレートをそのまま配置するだけで済む。
目標は Cloudflare 無料枠で global な署名付き binary cache を持つ こと。
Deploy Button
| 名前 | 設定場所 | 用途 |
|---|---|---|
ADMIN_TOKEN |
Worker Secret | 管理 API の認証。未設定だと publish などの書き込み操作と定期 GC が動かない。 |
CF_ANALYTICS_TOKEN |
Worker Secret | R2 使用量を調べる quota Cron 用。未設定だと quota チェックをスキップする。 |
CF_ACCOUNT_ID |
wrangler.toml の [vars] |
quota チェック対象の Cloudflare アカウント ID。 |
QUOTA_R2_BUCKET_NAME |
wrangler.toml の [vars] |
quota チェック対象の R2 バケット名。 |
NixOS の flake リポジトリで publish workflow を使う場合は、そのリポジトリの GitHub Environment production に次を登録する。ADMIN_TOKEN は Worker に登録した値と同じにする。
| 名前 | 種別 | 用途 |
|---|---|---|
CACHE_PRIVATE_KEY |
Secret | Nix binary cache の署名秘密鍵。 |
ADMIN_TOKEN |
Secret | Worker 管理 API の認証。 |
R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY |
Secret | R2 S3 API の認証情報。 |
API_BASE_URL |
Variable | デプロイ済み Worker の URL。 |
CLOUDFLARE_ACCOUNT_ID |
Variable | Cloudflare アカウント ID。 |
R2_BUCKET_NAME |
Variable | publish 先の R2 バケット名。 |
R2・KV・D1 の binding ID の設定や鍵生成は セットアップガイド を参照。
nix-cache-info(Priority/WantMassQuery設定可)- 署名付き
.narinfo(Ed25519 /nix-store --generate-binary-cache-key) - zstd 圧縮 NAR を
/nar/<file-hash>.nar.zstで配信 - HTTP
Range(bytes=start-end/bytes=start-/bytes=-suffix)に対応し206を返す hono/zod-openapiで自動生成された OpenAPI 3.0 スキーマを/api/openapi.jsonで公開
staging → ingest → finalizeの 3 段 publish、finalize が 1 つのdb.batch()でlatestを atomic に更新- 決定論的
build_id=sha256(host:system:gitRev:flakeLockHash:toplevelStorePath)[:36]— 再実行は冪等 - host ごとの build 履歴 + rollback root 登録
- host ごとの最新 3 世代と pin / rollback root を保持し、narinfo 非公開化から NAR 削除まで 1 時間の猶予を強制する世代 GC
- narinfo / NAR は Workers Cache(edge・Worker 手前)→ R2(正本)。404 も短 TTL の negative cache
- 5 分おきの Cron が Cloudflare GraphQL Analytics から R2 storage / Class A / Class B を読む
- 月次無料枠 80% で
warn、95% でkilled。killed中は read path が503を返して課金事故を防ぐ POST /api/quota/resetで手動解除
- Bearer 認証付き管理 API(
ADMIN_TOKEN)。未設定時は write 系を403で拒否 - Cloudflare Workers Builds が
mainpush で自動 deploy(CI deploy workflow も GitHub Secrets の Cloudflare token も不要) - publish ごとに R2 へ manifest を保存し cold-start 復元に使う
- Drizzle ORM schema / migration は
migrations/配下
{
nix.settings = {
extra-substituters = [ "https://cf-edgenix.<account>.workers.dev" ];
extra-trusted-public-keys = [ "nix-cache.example.com-1:xxxx=" ];
};
}sudo nixos-rebuild switch --flake .#<host> で反映。設定を書き換えず一度だけ試したい場合:
sudo nixos-rebuild switch --flake .#myhost \
--option extra-substituters "https://cf-edgenix.<account>.workers.dev" \
--option extra-trusted-public-keys "nix-cache.example.com-1:xxxx="nixos-rebuild は以下の順で(全て認証なしで)Worker を叩く:
GET /nix-cache-info— セッションごとに 1 回。StoreDirが一致しないと Nix はその cache を拒否する。GET /<store-hash>.narinfo— store path ごとに 1 回。404なら次の substituter にフォールバック。GET /nar/<file-hash>.nar.zst— narinfo がヒットしたときだけ取得。Range で途中再開可能。
疎通確認:
curl -sSf https://cf-edgenix.<account>.workers.dev/nix-cache-info
curl -sSfI https://cf-edgenix.<account>.workers.dev/<store-hash>.narinfoflowchart LR
Client[Nix client] --> WC[(Workers Cache<br/>edge)]
WC -. miss .-> W[Worker]
W --> R2[(R2: NAR_BUCKET)]
Workers Cache ヒット時は Worker 自体が起動しない(request collapsing 内蔵)。miss 時は R2 を直接読み、無ければ 404 を返して 60 秒だけ edge にキャッシュする。Worker 起動時の quota 判定は引き続き KV を読む。nix-cache-info は Workers Cache → KV → R2 → 既定値 の順で解決する。
flowchart LR
Client[Nix client] -->|GET/HEAD, Range| WC[(Workers Cache<br/>edge)]
WC -. miss .-> W[Worker]
W --> R2[(R2: NAR_BUCKET)]
Range: bytes=... をエッジまで通す(206 は edge に保存されず常に R2 から streaming)。full 200 は content-addressed かつ immutable として edge にキャッシュされる。miss 時は R2 から buffer せず streaming で返す。
flowchart LR
GHA[GitHub Actions<br/>publish 側 repo] --> P[closure 分類・new のみ NAR 化]
P -->|POST /api/publish/*| W[Worker]
P --> R2[(R2)]
W --> D1[(D1: CONTROL_DB)]
latest を動かすのは start → ingest × N → R2 upload → finalize の 1 ルートのみ。staging closure を R2 操作より先に登録し、GC と publish の競合を防ぐ。
full closure を分類し、external は manifest のみに記録する。owned のうち new だけ NAR 化する。NAR upload → narinfo upload → D1 確定の順序は scripts/publish.ts の batch 経路で保証し、test/publish/publish-script.test.ts で検証している。
flowchart LR
Repo[cf-edgeNix repo<br/>push to main] --> CFB[Cloudflare<br/>Workers Builds]
CFB --> W[Worker]
GitHub Actions の deploy workflow は持たない。
Workers Builds が push のたびに wrangler d1 migrations apply --remote && wrangler deploy を回す。
| 項目 | ファイル |
|---|---|
| 初回セットアップ(鍵生成・Cloudflare リソース・deploy・クライアント設定) | docs/setup.md |
| publish フロー / 冪等性 / 公開順序 / トラブルシューティング | docs/publish.md |
| エンドポイント一覧 | docs/api.md |
| Quota kill-switch 運用 | docs/quota.md |
| 設計仕様(フル) | docs/spec.md |
| 未解決の設計課題 | docs/fixme.md |
| 開発・テスト・環境変数リファレンス | CONTRIBUTING.md |
