| 1 | // Everything g1t deploys to Cloudflare, in one place. Read by |
| 2 | // scripts/deploy.mjs (plan, deploy), deploy/self-host/configs.mjs (what a |
| 3 | // self-hosted installation runs) and the tests in scripts/deploy/. |
| 4 | // docs/DEPLOYING.md explains each field and how to add a unit. |
| 5 | // |
| 6 | // What is written here is what the Wrangler configs cannot say. The rest is |
| 7 | // read from each unit's wrangler.jsonc, never copied: its D1 databases and |
| 8 | // migrations, the services it binds to, its KV, R2, queues and routes. The |
| 9 | // shared crates and packages a unit is built from are read from Cargo's and |
| 10 | // npm's workspace metadata. `worker` and `d1` are written here too, so the |
| 11 | // file reads as an inventory, and a test checks they match the configs. |
| 12 | { |
| 13 | // Deployed in this order. A stage starts only when the one before it |
| 14 | // succeeded. A unit binds only to units in its own stage or an earlier |
| 15 | // one (a test checks it), so new code never calls a service that has |
| 16 | // not shipped yet. Within a stage, units go out in parallel. |
| 17 | // |
| 18 | // migrations: every pending D1 migration, before any code. |
| 19 | // core: the services, reached through service bindings. |
| 20 | // edge: public endpoints other than the site: API, MCP, g1t.page, status |
| 21 | // (models is public too, but in core: agents binds to it). |
| 22 | // front: the site, sudo and the docs. |
| 23 | "stages": ["migrations", "core", "edge", "front"], |
| 24 | |
| 25 | // Names for the resources the configs refer to by id, for setup |
| 26 | // commands and the docs. A test checks every KV id in a config is here. |
| 27 | "resources": { |
| 28 | "kv": { |
| 29 | "16a4232cb746418db53782aa068be693": "g1t-actions-blobs", |
| 30 | "e627b571f07047e187c03e1fc2b3bbdd": "g1t-avatars", |
| 31 | "14bc5c233d4c46a5bbf23b5367cce5fd": "g1t-domains", |
| 32 | "be765052d0124c2a935b3db4dff99f1f": "g1t-repos-git-cache" |
| 33 | } |
| 34 | }, |
| 35 | |
| 36 | // Each deployable unit, by short name (`--only events,web`). |
| 37 | // |
| 38 | // kind: rust-worker (worker-build), ts-worker (Wrangler bundles it), |
| 39 | // react-router (vite build first), astro (astro build first). |
| 40 | // secrets: names only; set with `npx wrangler secret put NAME` in its folder. |
| 41 | // setup: one-time steps no config can say, for a first deploy. |
| 42 | // self_host: what deploy/self-host does with it: "run" (in the one |
| 43 | // workerd), "off" (bound to the off Worker), "separate" (a |
| 44 | // process of its own), or "none". |
| 45 | // inputs: files outside its folder it is built from that no workspace |
| 46 | // metadata names (a test finds such imports). |
| 47 | // image: a Containers image (docs/DEPLOYING.md, "The runner's images"): |
| 48 | // dockerfile the image a deploy ships: the base plus the binary |
| 49 | // crate the crate that binary is built from (and what it uses) |
| 50 | // base { context: the base's folder, lock: the file that |
| 51 | // records the base that was pushed }; the base is |
| 52 | // rebuilt only when its folder changes |
| 53 | // repository where both are pushed in Cloudflare's registry |
| 54 | "units": { |
| 55 | "events": { |
| 56 | "path": "services/events", |
| 57 | "kind": "rust-worker", |
| 58 | "worker": "g1t-events", |
| 59 | "d1": { "database": "g1t-events", "migrations": "migrations" }, |
| 60 | "stage": "core", |
| 61 | "secrets": [], |
| 62 | "setup": [ |
| 63 | "The dead-letter queue every queue consumer sends what it gave up on to, before any unit that names it deploys: npx wrangler queues create g1t-events-dlq" |
| 64 | ], |
| 65 | "self_host": "run" |
| 66 | }, |
| 67 | "identity": { |
| 68 | "path": "services/identity", |
| 69 | "kind": "rust-worker", |
| 70 | "worker": "g1t-identity", |
| 71 | "d1": { "database": "g1t", "migrations": "migrations" }, |
| 72 | "stage": "core", |
| 73 | "secrets": ["GITHUB_APP_CLIENT_SECRET", "IDENTITY_KEY"], |
| 74 | "setup": ["Email Sending on g1t.sh (the EMAIL binding)"], |
| 75 | "self_host": "run" |
| 76 | }, |
| 77 | "repos": { |
| 78 | "path": "services/repos", |
| 79 | "kind": "rust-worker", |
| 80 | "worker": "g1t-repos", |
| 81 | "d1": { "database": "g1t-repos", "migrations": "migrations" }, |
| 82 | "stage": "core", |
| 83 | "secrets": ["REPOS_KEY"], |
| 84 | "setup": [ |
| 85 | "The Artifacts namespace `g1t` (the ARTIFACTS binding)", |
| 86 | "The R2 bucket `g1t-git-packs` (GIT_PACKS) with its lifecycle rule: `npx wrangler r2 bucket create g1t-git-packs`, then `npx wrangler r2 bucket lifecycle add g1t-git-packs expire-packs packs/ --expire-days 7 --abort-multipart-days 1`", |
| 87 | "The R2 bucket for nightly backups: npx wrangler r2 bucket create g1t-backups" |
| 88 | ], |
| 89 | "self_host": "run" |
| 90 | }, |
| 91 | "work": { |
| 92 | "path": "services/work", |
| 93 | "kind": "rust-worker", |
| 94 | "worker": "g1t-work", |
| 95 | "d1": { "database": "g1t-work", "migrations": "migrations" }, |
| 96 | "stage": "core", |
| 97 | "secrets": [], |
| 98 | "self_host": "run" |
| 99 | }, |
| 100 | "search": { |
| 101 | "path": "services/search", |
| 102 | "kind": "rust-worker", |
| 103 | "worker": "g1t-search", |
| 104 | "d1": { "database": "g1t-search", "migrations": "migrations" }, |
| 105 | "stage": "core", |
| 106 | "secrets": [], |
| 107 | "self_host": "run" |
| 108 | }, |
| 109 | "projects": { |
| 110 | "path": "services/projects", |
| 111 | "kind": "ts-worker", |
| 112 | "worker": "g1t-projects", |
| 113 | "d1": { "database": "g1t-projects", "migrations": "migrations" }, |
| 114 | "stage": "core", |
| 115 | "secrets": [], |
| 116 | "self_host": "run" |
| 117 | }, |
| 118 | "chat": { |
| 119 | "path": "services/chat", |
| 120 | "kind": "ts-worker", |
| 121 | "worker": "g1t-chat", |
| 122 | "d1": { "database": "g1t-chat", "migrations": "migrations" }, |
| 123 | "stage": "core", |
| 124 | "secrets": [], |
| 125 | "setup": [ |
| 126 | "The D1 database, before the first deploy: npx wrangler d1 create g1t-chat, then put its id in services/chat/wrangler.jsonc" |
| 127 | ], |
| 128 | "self_host": "run" |
| 129 | }, |
| 130 | // Docs mode's service, which also hosts artifacts (folios, |
| 131 | // docs/ARTIFACTS_MODE.md). Its Worker is g1t-docs-service: g1t-docs |
| 132 | // is the documentation site (apps/docs). |
| 133 | "docs-service": { |
| 134 | "path": "services/docs", |
| 135 | "kind": "ts-worker", |
| 136 | "worker": "g1t-docs-service", |
| 137 | "d1": { "database": "g1t-docs", "migrations": "migrations" }, |
| 138 | "stage": "core", |
| 139 | "secrets": [], |
| 140 | "setup": [ |
| 141 | "The D1 database, before the first deploy: npx wrangler d1 create g1t-docs, then put its id in services/docs/wrangler.jsonc", |
| 142 | "The R2 bucket for files in pages: npx wrangler r2 bucket create g1t-docs-files", |
| 143 | "The queue the events service sends it merges and pushes on (pages whose cited code changed, projects' docs): npx wrangler queues create g1t-events-docs. The service also sends its own backfill jobs to it (JOBS)", |
| 144 | "The Vectorize index agents recall Docs from: npx wrangler vectorize create g1t-docs --dimensions=768 --metric=cosine, with string metadata indexes on workspace_id and space_id (npx wrangler vectorize create-metadata-index g1t-docs --property-name=<name> --type=string)", |
| 145 | "The Vectorize index for artifacts (folios), before the first deploy with FOLIO_VECTORS: npx wrangler vectorize create g1t-folios --dimensions=768 --metric=cosine, with string metadata indexes on workspace_id, scope and kind (npx wrangler vectorize create-metadata-index g1t-folios --property-name=<name> --type=string). Without it, artifacts are searched and recalled by words" |
| 146 | ], |
| 147 | "self_host": "run" |
| 148 | }, |
| 149 | "notify": { |
| 150 | "path": "services/notify", |
| 151 | "kind": "ts-worker", |
| 152 | "worker": "g1t-notify", |
| 153 | // No D1: each person's feed keeps its own state in its Durable |
| 154 | // Object's SQLite storage. |
| 155 | "stage": "core", |
| 156 | // The private half of the VAPID key pair browser pushes are signed |
| 157 | // with; without it, notifications are live in open tabs only. |
| 158 | "secrets": ["VAPID_PRIVATE_KEY"], |
| 159 | "setup": [ |
| 160 | "The VAPID key pair for browser push: `node scripts/ops/vapid-keys.mjs` prints both halves and stores nothing. Put the public half in VAPID_PUBLIC_KEY in services/notify/wrangler.jsonc, and the private half with `npx wrangler secret put VAPID_PRIVATE_KEY` in services/notify" |
| 161 | ], |
| 162 | "self_host": "run" |
| 163 | }, |
| 164 | "agents": { |
| 165 | "path": "services/agents", |
| 166 | "kind": "ts-worker", |
| 167 | "worker": "g1t-agents", |
| 168 | "d1": { "database": "g1t-agents", "migrations": "migrations" }, |
| 169 | "stage": "core", |
| 170 | "secrets": [], |
| 171 | "setup": [ |
| 172 | "The D1 database, before the first deploy: npx wrangler d1 create g1t-agents, then put its id in services/agents/wrangler.jsonc" |
| 173 | ], |
| 174 | // Replies route and gate model work exactly as runs do, with the |
| 175 | // runner's own modules: its routing policy and who may use hosted models. |
| 176 | "inputs": ["services/runner/src/model-env.ts", "services/runner/src/hosted.ts"], |
| 177 | "self_host": "run" |
| 178 | }, |
| 179 | "billing": { |
| 180 | "path": "services/billing", |
| 181 | "kind": "rust-worker", |
| 182 | "worker": "g1t-billing", |
| 183 | "d1": { "database": "g1t-billing", "migrations": "migrations" }, |
| 184 | "stage": "core", |
| 185 | "secrets": ["STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET", "CLOUDFLARE_USAGE_TOKEN"], |
| 186 | "self_host": "run" |
| 187 | }, |
| 188 | "integrations": { |
| 189 | "path": "services/integrations", |
| 190 | "kind": "rust-worker", |
| 191 | "worker": "g1t-integrations", |
| 192 | "d1": { "database": "g1t-integrations", "migrations": "migrations" }, |
| 193 | "stage": "core", |
| 194 | "secrets": ["INTEGRATIONS_KEY", "GITHUB_APP_PRIVATE_KEY", "GITHUB_APP_WEBHOOK_SECRET"], |
| 195 | "self_host": "run" |
| 196 | }, |
| 197 | "webhooks": { |
| 198 | "path": "services/webhooks", |
| 199 | "kind": "rust-worker", |
| 200 | "worker": "g1t-webhooks", |
| 201 | "d1": { "database": "g1t-webhooks", "migrations": "migrations" }, |
| 202 | "stage": "core", |
| 203 | "secrets": ["WEBHOOKS_KEY"], |
| 204 | "self_host": "run" |
| 205 | }, |
| 206 | "actions": { |
| 207 | "path": "services/actions", |
| 208 | "kind": "rust-worker", |
| 209 | "worker": "g1t-actions", |
| 210 | "d1": { "database": "g1t-actions", "migrations": "migrations" }, |
| 211 | "stage": "core", |
| 212 | "secrets": ["ACTIONS_KEY"], |
| 213 | "self_host": "run" |
| 214 | }, |
| 215 | "packages": { |
| 216 | "path": "services/packages", |
| 217 | "kind": "rust-worker", |
| 218 | "worker": "g1t-packages", |
| 219 | "d1": { "database": "g1t-packages", "migrations": "migrations" }, |
| 220 | "stage": "core", |
| 221 | "secrets": ["PACKAGES_TOKEN_SECRET", "R2_ACCESS_KEY_ID", "R2_SECRET_ACCESS_KEY"], |
| 222 | "setup": [ |
| 223 | "The D1 database: npx wrangler d1 create g1t-packages, its id in services/packages/wrangler.jsonc", |
| 224 | "The R2 bucket for packages' files: npx wrangler r2 bucket create g1t-packages", |
| 225 | "The events queue: npx wrangler queues create g1t-events-packages", |
| 226 | "For signed downloads: an R2 API token with read access to g1t-packages, as R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY" |
| 227 | ], |
| 228 | "self_host": "run" |
| 229 | }, |
| 230 | "security": { |
| 231 | "path": "services/security", |
| 232 | "kind": "rust-worker", |
| 233 | "worker": "g1t-security", |
| 234 | "d1": { "database": "g1t-security", "migrations": "migrations" }, |
| 235 | "stage": "core", |
| 236 | "secrets": [], |
| 237 | "self_host": "run" |
| 238 | }, |
| 239 | "deployments": { |
| 240 | "path": "services/deployments", |
| 241 | "kind": "ts-worker", |
| 242 | "worker": "g1t-deployments", |
| 243 | "d1": { "database": "g1t-deployments", "migrations": "migrations" }, |
| 244 | "stage": "core", |
| 245 | "secrets": ["CLOUDFLARE_API_TOKEN"], |
| 246 | "setup": [ |
| 247 | "Workers for Platforms, and the dispatch namespace: scripts/setup-deployments.sh", |
| 248 | "Custom domains (Cloudflare for SaaS on g1t.page): scripts/setup-custom-domains.sh" |
| 249 | ], |
| 250 | "self_host": "run" |
| 251 | }, |
| 252 | "runner": { |
| 253 | "path": "services/runner", |
| 254 | "kind": "ts-worker", |
| 255 | "worker": "g1t-runner", |
| 256 | "stage": "core", |
| 257 | "secrets": ["AI_GATEWAY_TOKEN"], |
| 258 | "setup": [ |
| 259 | "Containers on the account; Docker on the machine that builds a new image", |
| 260 | "The base image, once: node scripts/deploy.mjs build-base" |
| 261 | ], |
| 262 | "image": { |
| 263 | "dockerfile": "services/runner/Dockerfile", |
| 264 | // The binary the image adds to its base (scripts/build-runner.mjs). |
| 265 | "crate": "g1t-runner", |
| 266 | "base": { "context": "services/runner/base", "lock": "services/runner/base.json" }, |
| 267 | "repository": "g1t-runner" |
| 268 | }, |
| 269 | "self_host": "off" |
| 270 | }, |
| 271 | "context": { |
| 272 | "path": "services/context", |
| 273 | "kind": "ts-worker", |
| 274 | "worker": "g1t-context", |
| 275 | "d1": { "database": "g1t-context", "migrations": "migrations" }, |
| 276 | "stage": "core", |
| 277 | "secrets": [], |
| 278 | "setup": [ |
| 279 | "The Vectorize index: npx wrangler vectorize create g1t-context --dimensions=768 --metric=cosine, with metadata indexes on workspace, kind, project and private" |
| 280 | ], |
| 281 | "self_host": "off" |
| 282 | }, |
| 283 | "og": { |
| 284 | "path": "services/og", |
| 285 | "kind": "ts-worker", |
| 286 | "worker": "g1t-og", |
| 287 | "stage": "core", |
| 288 | "secrets": [], |
| 289 | "setup": ["Browser Rendering on the account (the BROWSER binding)"], |
| 290 | // The roadmap cards read the site's roadmap. |
| 291 | "inputs": ["apps/web/app/lib/roadmap.ts"], |
| 292 | "self_host": "none" |
| 293 | }, |
| 294 | "api": { |
| 295 | "path": "apps/api", |
| 296 | "kind": "rust-worker", |
| 297 | "worker": "g1t-api", |
| 298 | "stage": "edge", |
| 299 | // ACTIONS_OIDC_KEY signs workflow jobs' OIDC tokens; without it the |
| 300 | // issuer answers 404 and jobs are not offered tokens. |
| 301 | // ACTIONS_OIDC_KEY_PREVIOUS only while rotating. docs/DEPLOYING.md. |
| 302 | "secrets": ["ACTIONS_OIDC_KEY"], |
| 303 | "setup": [ |
| 304 | "The OIDC signing key for workflow jobs: an RSA key made with `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048`, stored with `npx wrangler secret put ACTIONS_OIDC_KEY` (docs/DEPLOYING.md, \"OIDC tokens for workflow jobs\")", |
| 305 | "The actions cache bucket's lifecycle rule limited to `c/`, so artifacts under `a/` are kept their retention-days (docs/DEPLOYING.md)" |
| 306 | ], |
| 307 | "self_host": "separate" |
| 308 | }, |
| 309 | "models": { |
| 310 | "path": "services/models", |
| 311 | "kind": "ts-worker", |
| 312 | "worker": "g1t-models", |
| 313 | // Core, though it is public at models.g1t.sh: the agents service |
| 314 | // reaches it by service binding for chat replies. It binds only to |
| 315 | // core services itself. |
| 316 | "stage": "core", |
| 317 | "secrets": ["AI_GATEWAY_TOKEN"], |
| 318 | "setup": ["The AI Gateway `g1t`"], |
| 319 | // Not run self-hosted, but bound to the off Worker: agents binds to it. |
| 320 | "self_host": "off" |
| 321 | }, |
| 322 | "pages": { |
| 323 | "path": "services/pages", |
| 324 | "kind": "ts-worker", |
| 325 | "worker": "g1t-pages", |
| 326 | "stage": "edge", |
| 327 | "secrets": [], |
| 328 | "setup": ["A proxied wildcard DNS record on g1t.page (`*`, AAAA 100::): scripts/setup-deployments.sh"], |
| 329 | "self_host": "none" |
| 330 | }, |
| 331 | "status": { |
| 332 | "path": "apps/status", |
| 333 | "kind": "ts-worker", |
| 334 | "worker": "g1t-status", |
| 335 | "d1": { "database": "g1t-status", "migrations": "migrations" }, |
| 336 | "stage": "edge", |
| 337 | "secrets": ["STATUS_SECRET"], |
| 338 | "setup": ["Email Sending on g1t.sh (the EMAIL binding)"], |
| 339 | "self_host": "separate" |
| 340 | }, |
| 341 | "web": { |
| 342 | "path": "apps/web", |
| 343 | "kind": "react-router", |
| 344 | "worker": "g1t", |
| 345 | "stage": "front", |
| 346 | // USERCONTENT_KEY signs the short-lived addresses of private |
| 347 | // repositories' files on g1tusercontent.com; without it they are |
| 348 | // served from g1t.sh instead. |
| 349 | "secrets": ["USERCONTENT_KEY"], |
| 350 | "setup": [ |
| 351 | "The R2 bucket g1t-downloads, for the self-hosted runner's releases: npx wrangler r2 bucket create g1t-downloads", |
| 352 | "The zone g1tusercontent.com on the account; the Worker's custom domain on it is made by the deploy", |
| 353 | "The key for private files' addresses: `node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\" | npx wrangler secret put USERCONTENT_KEY` in apps/web" |
| 354 | ], |
| 355 | "self_host": "run" |
| 356 | }, |
| 357 | "sudo": { |
| 358 | "path": "apps/sudo", |
| 359 | "kind": "react-router", |
| 360 | "worker": "g1t-sudo", |
| 361 | "stage": "front", |
| 362 | "secrets": [], |
| 363 | "setup": ["A Cloudflare Access application on sudo.g1t.sh; its AUD tag is ACCESS_AUD"], |
| 364 | "self_host": "none" |
| 365 | }, |
| 366 | "docs": { |
| 367 | "path": "apps/docs", |
| 368 | "kind": "astro", |
| 369 | "worker": "g1t-docs", |
| 370 | "stage": "front", |
| 371 | "secrets": [], |
| 372 | "self_host": "none" |
| 373 | } |
| 374 | } |
| 375 | } |