Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 1 | #!/usr/bin/env node |
| 2 | // Writes the Wrangler configs a self-hosted g1t runs with, derived from the | |
| 3 | // hosted ones, so the two never drift apart. | |
| 4 | // | |
| 5 | // Each hosted service's wrangler.jsonc is read and changed only where | |
| 6 | // Cloudflare-only things live: | |
| 7 | // | |
| 8 | // - account, routes, placement, observability and builds are dropped; | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 9 | // - GITSTORE (git storage, Cloudflare Artifacts) becomes a service binding to workers/gitstore, |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 10 | // which keeps repositories in the git store (gitstore/server.mjs); |
| 11 | // - EMAIL (Email Sending) becomes a service binding to workers/mail; | |
| Merge branch 'worktree-agent-af58ac8933b0dd125' | 12 | // - the packages service keeps files in S3-compatible storage (RustFS) |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 13 | // instead of R2, the repos service its nightly backups (a bucket of |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 14 | // their own, BACKUP_S3_BUCKET), and the artifacts service the files in pages |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 15 | // (DOCS_S3_BUCKET); |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 16 | // - services that are off in this phase (agents, the context hub, the |
| 17 | // g1t.page dispatcher, model proxy) are bound to workers/off instead, and | |
| 18 | // events stop queueing work for them; | |
| 19 | // - URLs that name g1t.sh name PUBLIC_URL instead, and billing is free. | |
| 20 | // | |
| 21 | // Usage: node configs.mjs [outDir] | |
| 22 | // Environment: PUBLIC_URL, GITSTORE_URL, GITSTORE_SECRET, MAIL_URL, | |
| Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's. | 23 | // ACTIONS_KEY, INTEGRATIONS_KEY, WEBHOOKS_KEY, IDENTITY_KEY, USERCONTENT_KEY, REPOS_KEY, CONNECTORS_KEY, |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 24 | // USERCONTENT_URL, |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 25 | // PACKAGES_TOKEN_SECRET, S3_ENDPOINT, S3_BUCKET, BACKUP_S3_BUCKET, DOCS_S3_BUCKET, S3_REGION, |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 26 | // S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_PUBLIC_ENDPOINT, and optionally |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 27 | // your own GitHub App: GITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_APP_CLIENT_ID, |
| 28 | // GITHUB_APP_CLIENT_SECRET, GITHUB_APP_PRIVATE_KEY, GITHUB_APP_WEBHOOK_SECRET. | |
| 29 | // | |
| 30 | // The output is for `wrangler dev` (see start.sh): every Worker in one | |
| 31 | // workerd, the site first, with D1, KV and Queues kept on disk. | |
| 32 | ||
| 33 | import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; | |
| 34 | import { dirname, join, relative, resolve } from "node:path"; | |
| 35 | import { fileURLToPath } from "node:url"; | |
| 36 | ||
| 37 | const here = dirname(fileURLToPath(import.meta.url)); | |
| 38 | const root = resolve(here, "../.."); | |
| 39 | const out = resolve(process.argv[2] ?? join(here, ".generated")); | |
| 40 | mkdirSync(out, { recursive: true }); | |
| 41 | ||
| 42 | const PUBLIC_URL = (process.env.PUBLIC_URL ?? "http://localhost:8787").replace(/\/$/, ""); | |
| 43 | ||
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 44 | /** |
| 45 | * Where the API (REST, OAuth) is reached: API_URL, or PUBLIC_URL's host on | |
| 46 | * API_PORT (8789). The MCP server is a path on it: MCP_URL, or | |
| 47 | * `<API_URL>/mcp`. The API's OAuth issuer is API_URL. | |
| 48 | */ | |
| 49 | export function apiAddresses(env = process.env, publicUrl = PUBLIC_URL) { | |
| 50 | let api = (env.API_URL ?? "").trim().replace(/\/$/, ""); | |
| 51 | if (!api) { | |
| 52 | const url = new URL(publicUrl); | |
| 53 | url.port = env.API_PORT || "8789"; | |
| 54 | api = url.origin; | |
| 55 | } | |
| 56 | const mcp = (env.MCP_URL ?? "").trim().replace(/\/$/, "") || `${api}/mcp`; | |
| 57 | return { api, mcp }; | |
| 58 | } | |
| 59 | const { api: API_URL, mcp: MCP_URL } = apiAddresses(); | |
| 60 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 61 | // What runs, and what is off, is each unit's `self_host` in |
| 62 | // deploy/stack.jsonc: the list hosted g1t deploys from. | |
| 63 | const STACK = Object.values(parseJsonc(readFileSync(join(root, "deploy/stack.jsonc"), "utf8")).units); | |
| 64 | ||
| 65 | /** Services that run, in the order Wrangler is given them (the site first). */ | |
| 66 | export const RUNNING = STACK.filter((unit) => unit.self_host === "run") | |
| 67 | .map((unit) => ({ name: unit.worker, dir: unit.path, web: unit.kind === "react-router" })) | |
| 68 | .sort((a, b) => Number(b.web) - Number(a.web)); | |
| 69 | ||
| 70 | /** What the off Worker calls each service that is off in phase 1. */ | |
| 71 | const OFF_NAMES = { | |
| 72 | "g1t-runner": "Agents", | |
| 73 | "g1t-context": "Context search and memory", | |
| Chat and workspace agents: channels, DMs and named agents you talk to | 74 | "g1t-models": "Hosted models", |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 75 | }; |
| 76 | const OFF = Object.fromEntries( | |
| 77 | STACK.filter((unit) => unit.self_host === "off").map((unit) => [unit.worker, OFF_NAMES[unit.worker] ?? unit.worker]), | |
| 78 | ); | |
| 79 | ||
| 80 | /** Sealing keys, by the service that holds each (hosted: Wrangler secrets). */ | |
| 81 | const SECRETS = { | |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 82 | g1t: "USERCONTENT_KEY", |
| Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's. | 83 | "g1t-repos": "REPOS_KEY", |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 84 | "g1t-actions": "ACTIONS_KEY", |
| 85 | "g1t-integrations": "INTEGRATIONS_KEY", | |
| Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9 | 86 | "g1t-agents": "CONNECTORS_KEY", |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 87 | "g1t-webhooks": "WEBHOOKS_KEY", |
| 88 | }; | |
| 89 | ||
| 90 | /** | |
| 91 | * g1t.sh's GitHub App is its own: an installation registers one of its | |
| 92 | * own, or has none, and then no GitHub buttons appear. Its public settings | |
| 93 | * replace the hosted vars; its secrets go only to the service that uses each. | |
| 94 | */ | |
| 95 | const GITHUB_VARS = ["GITHUB_APP_ID", "GITHUB_APP_SLUG", "GITHUB_APP_CLIENT_ID"]; | |
| 96 | const GITHUB_SECRETS = { | |
| 97 | "g1t-identity": ["GITHUB_APP_CLIENT_SECRET", "IDENTITY_KEY", "REGISTRATION_MODE"], | |
| 98 | "g1t-integrations": ["GITHUB_APP_PRIVATE_KEY", "GITHUB_APP_WEBHOOK_SECRET"], | |
| 99 | }; | |
| 100 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 101 | /** |
| 102 | * Services whose cron triggers scheduler.mjs runs here: sweeps and | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 103 | * reminders that need nothing self-hosting lacks (the artifacts service's is |
| The docs service answers every artifacts call: docs can be made, listed, shared, moved, trashed, restored, searched, versioned and edited live in their own rooms, agents read, write and recall them only where their person and everyone in the conversation can, and folio events go out on the bus, while Docs' pages keep working as before. | 104 | * emptying artifacts' trash after 30 days). Not run: actions (its |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 105 | * minute would start scheduled workflows with no runner to take them), |
| 106 | * billing (reconciles against Cloudflare and Stripe), deployments (calls | |
| 107 | * Cloudflare's API) and the services that are off. | |
| 108 | */ | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 109 | const SELF_HOST_CRONS = new Set(["g1t-repos", "g1t-events", "g1t-identity", "g1t-security", "g1t-webhooks", "g1t-packages", "g1t-artifacts"]); |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 110 | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 111 | /** Queues whose consumers are off: events stops sending to them. */ |
| 112 | const OFF_QUEUES = new Set(["g1t-events-runner", "g1t-events-context"]); | |
| 113 | ||
| 114 | /** Strips comments and trailing commas from JSONC. Strings are respected. */ | |
| 115 | function parseJsonc(text) { | |
| 116 | let result = ""; | |
| 117 | let inString = false; | |
| 118 | for (let i = 0; i < text.length; i++) { | |
| 119 | const char = text[i]; | |
| 120 | if (inString) { | |
| 121 | result += char; | |
| 122 | if (char === "\\") result += text[++i]; | |
| 123 | else if (char === '"') inString = false; | |
| 124 | } else if (char === '"') { | |
| 125 | inString = true; | |
| 126 | result += char; | |
| 127 | } else if (char === "/" && text[i + 1] === "/") { | |
| 128 | while (i < text.length && text[i] !== "\n") i++; | |
| 129 | result += "\n"; | |
| 130 | } else if (char === "/" && text[i + 1] === "*") { | |
| 131 | i = text.indexOf("*/", i + 2) + 1; | |
| 132 | } else { | |
| 133 | result += char; | |
| 134 | } | |
| 135 | } | |
| 136 | return JSON.parse(result.replace(/,(\s*[}\]])/g, "$1")); | |
| 137 | } | |
| 138 | ||
| 139 | const rel = (path) => relative(out, resolve(root, path)).replaceAll("\\", "/"); | |
| 140 | ||
| 141 | function hostedUrl(value) { | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 142 | return typeof value === "string" |
| 143 | ? value.replace(/https:\/\/api\.g1t\.sh/g, API_URL).replace(/https:\/\/g1t\.sh/g, PUBLIC_URL) | |
| 144 | : value; | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 145 | } |
| 146 | ||
| 147 | function selfHosted(service) { | |
| 148 | const hosted = parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8")); | |
| 149 | const config = { | |
| 150 | name: hosted.name, | |
| 151 | compatibility_date: hosted.compatibility_date, | |
| 152 | compatibility_flags: hosted.compatibility_flags, | |
| 153 | rules: hosted.rules, | |
| 154 | vars: {}, | |
| 155 | }; | |
| 156 | ||
| 157 | if (service.web) { | |
| 158 | // The site as React Router built it (apps/web/build), not its sources. | |
| 159 | config.main = rel(`${service.dir}/build/server/index.js`); | |
| 160 | config.no_bundle = true; | |
| 161 | config.rules = [{ type: "ESModule", globs: ["**/*.js", "**/*.mjs"] }]; | |
| 162 | config.assets = { directory: rel(`${service.dir}/build/client`) }; | |
| 163 | } else { | |
| 164 | config.main = rel(join(service.dir, hosted.main)); | |
| 165 | } | |
| 166 | ||
| 167 | for (const [key, value] of Object.entries(hosted.vars ?? {})) config.vars[key] = hostedUrl(value); | |
| 168 | ||
| 169 | if (hosted.d1_databases) { | |
| 170 | config.d1_databases = hosted.d1_databases.map((db) => ({ | |
| 171 | binding: db.binding, | |
| 172 | database_name: db.database_name, | |
| 173 | database_id: db.database_id, | |
| 174 | migrations_dir: rel(join(service.dir, db.migrations_dir ?? "migrations")), | |
| 175 | })); | |
| 176 | } | |
| 177 | if (hosted.kv_namespaces) config.kv_namespaces = hosted.kv_namespaces.map(({ binding, id }) => ({ binding, id })); | |
| 178 | if (hosted.triggers) config.triggers = hosted.triggers; | |
| 179 | ||
| 180 | if (hosted.queues) { | |
| 181 | config.queues = {}; | |
| 182 | if (hosted.queues.producers) { | |
| 183 | config.queues.producers = hosted.queues.producers.filter((producer) => !OFF_QUEUES.has(producer.queue)); | |
| 184 | } | |
| 185 | if (hosted.queues.consumers) config.queues.consumers = hosted.queues.consumers; | |
| 186 | } | |
| 187 | ||
| 188 | config.services = (hosted.services ?? []).map((binding) => | |
| 189 | OFF[binding.service] ? { binding: binding.binding, service: offName(binding.service) } : binding, | |
| 190 | ); | |
| 191 | ||
| 192 | // Cloudflare-only bindings, and what stands in for them. | |
| 193 | if (hosted.artifacts) { | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 194 | for (const store of hosted.artifacts) { |
| 195 | config.services.push({ binding: store.binding, service: "g1t-gitstore" }); | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 196 | } |
| 197 | } | |
| 198 | if (hosted.send_email) { | |
| 199 | for (const email of hosted.send_email) config.services.push({ binding: email.name, service: "g1t-mail" }); | |
| 200 | } | |
| 201 | ||
| 202 | // Secrets the hosted services hold, given here from the environment, each | |
| 203 | // only to the service that uses it. | |
| 204 | const secret = SECRETS[hosted.name]; | |
| 205 | if (secret && process.env[secret]) config.vars[secret] = process.env[secret]; | |
| 206 | for (const name of GITHUB_VARS) { | |
| 207 | if (name in config.vars) config.vars[name] = process.env[name] ?? ""; | |
| 208 | } | |
| 209 | for (const name of GITHUB_SECRETS[hosted.name] ?? []) { | |
| 210 | if (process.env[name]) config.vars[name] = process.env[name]; | |
| 211 | } | |
| 212 | ||
| 213 | // Self-hosted g1t charges nothing: billing records usage at cost and never | |
| 214 | // stops work for it. | |
| 215 | if (hosted.name === "g1t-billing") config.vars.FREE_WHILE_BUILDING = "true"; | |
| 216 | // Anyone may register on an installation of your own unless you set | |
| 217 | // REGISTRATION_MODE=invite; invites then work as on g1t.sh, and the owners | |
| 218 | // of INVITE_STAFF_WORKSPACES (yours, not g1t.sh's) invite without limit. | |
| 219 | if (hosted.name === "g1t-identity") { | |
| 220 | config.vars.REGISTRATION_MODE = process.env.REGISTRATION_MODE || "open"; | |
| 221 | config.vars.INVITE_STAFF_WORKSPACES = process.env.INVITE_STAFF_WORKSPACES ?? ""; | |
| 222 | if (process.env.INVITES_PER_USER) config.vars.INVITES_PER_USER = process.env.INVITES_PER_USER; | |
| 223 | // Access requests are summarised to your own address, not g1t.sh's. | |
| 224 | config.vars.WAITLIST_NOTIFY_EMAIL = process.env.WAITLIST_NOTIFY_EMAIL ?? ""; | |
| 225 | } | |
| Merge branch 'worktree-agent-af58ac8933b0dd125' | 226 | // Packages' files go to the compose file's RustFS (or any S3-compatible |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 227 | // store) instead of R2, with no request size limit, and package |
| 228 | // addresses start with this installation's host. | |
| 229 | if (hosted.name === "g1t-packages") { | |
| 230 | Object.assign(config.vars, { | |
| 231 | BLOB_STORE: "s3", | |
| Merge branch 'worktree-agent-af58ac8933b0dd125' | 232 | S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000", |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 233 | S3_BUCKET: process.env.S3_BUCKET ?? "g1t-packages", |
| 234 | S3_REGION: process.env.S3_REGION ?? "us-east-1", | |
| 235 | S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "", | |
| 236 | S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "", | |
| 237 | S3_PUBLIC_ENDPOINT: process.env.S3_PUBLIC_ENDPOINT ?? "", | |
| 238 | MAX_REQUEST_BYTES: "0", | |
| 239 | STORAGE_LIMITS: "off", | |
| 240 | REGISTRY_HOST: new URL(PUBLIC_URL).host, | |
| 241 | PACKAGES_TOKEN_SECRET: process.env.PACKAGES_TOKEN_SECRET ?? "", | |
| 242 | }); | |
| 243 | delete config.vars.R2_ACCOUNT_ID; | |
| 244 | delete config.vars.R2_BUCKET; | |
| 245 | } | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 246 | // Where this installation is reached, for the links and addresses each |
| 247 | // shows: the site's clone URLs, meta tags and agent setup, the API's | |
| 248 | // OAuth issuer and MCP server, and identity's mail. No social cards: the | |
| 249 | // card service (services/og) is not run here. | |
| Merge g1tusercontent.com: registry answers run nothing in a browser, the site's pages run only their own scripts, repository files and avatars on their own origin, raw files rate limited per address | 250 | // Repository files and avatars: USERCONTENT_URL, a host of its own that |
| 251 | // reaches this same site, or, empty, a path on it (PUBLIC_URL/-/usercontent). | |
| 252 | if (service.web) { | |
| 253 | Object.assign(config.vars, { SITE_URL: PUBLIC_URL, API_URL, MCP_URL, OG_URL: "", USERCONTENT_URL: (process.env.USERCONTENT_URL ?? "").trim() }); | |
| 254 | } | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 255 | if (hosted.name === "g1t-api") Object.assign(config.vars, { SITE_URL: PUBLIC_URL, API_URL, MCP_URL }); |
| The phone app with your own g1t, on g1t.sh/download until Google Play, and Node 24 (#5) | 256 | // Notifications on the g1t phone app go through Expo's push service (and |
| 257 | // from there Google's and Apple's), with no setup here, unless | |
| 258 | // PHONE_PUSH=off: notify then sends phones nothing, and the site's | |
| 259 | // /.well-known/g1t.json tells the app so. | |
| 260 | if (service.web || hosted.name === "g1t-notify") config.vars.PHONE_PUSH = process.env.PHONE_PUSH === "off" ? "off" : "on"; | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 261 | if (hosted.name === "g1t-identity") config.vars.SITE_URL = PUBLIC_URL; |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 262 | // Nightly backups' bundles go to a bucket of their own on the same |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 263 | // S3-compatible store, instead of the BACKUPS R2 bucket, and so do the |
| 264 | // packs kept for fresh clones (src/pack_cache.rs), instead of GIT_PACKS. | |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 265 | if (hosted.name === "g1t-repos") { |
| 266 | Object.assign(config.vars, { | |
| 267 | BACKUP_STORE: "s3", | |
| 268 | BACKUP_S3_BUCKET: process.env.BACKUP_S3_BUCKET ?? "g1t-backups", | |
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 269 | PACK_STORE: "s3", |
| 270 | PACK_S3_BUCKET: process.env.PACK_S3_BUCKET ?? "g1t-git-packs", | |
| Merge branch 'worktree-agent-af58ac8933b0dd125' | 271 | S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000", |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 272 | S3_REGION: process.env.S3_REGION ?? "us-east-1", |
| 273 | S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "", | |
| 274 | S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "", | |
| 275 | }); | |
| 276 | } | |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 277 | // Files people put in Docs pages go to a bucket of their own on the same |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 278 | // S3-compatible store, instead of the FILES R2 bucket (services/artifacts |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 279 | // src/files.ts, `s3FileStore`). |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 280 | if (hosted.name === "g1t-artifacts") { |
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 281 | Object.assign(config.vars, { |
| 282 | DOCS_FILES: "s3", | |
| 283 | DOCS_S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000", | |
| 284 | DOCS_S3_BUCKET: process.env.DOCS_S3_BUCKET ?? "g1t-docs-files", | |
| 285 | DOCS_S3_REGION: process.env.S3_REGION ?? "us-east-1", | |
| 286 | DOCS_S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "", | |
| 287 | DOCS_S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "", | |
| 288 | }); | |
| 289 | } | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 290 | // Nothing to deploy to: deployments are off (no Cloudflare API token). |
| 291 | if (hosted.name === "g1t-deployments") delete config.vars.CUSTOM_HOSTNAMES_ZONE_ID; | |
| 292 | ||
| 293 | return config; | |
| 294 | } | |
| 295 | ||
| 296 | function offName(service) { | |
| 297 | return `${service}-off`; | |
| 298 | } | |
| 299 | ||
| 300 | function write(name, config) { | |
| 301 | const path = join(out, `${name}.json`); | |
| 302 | writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`); | |
| 303 | return path; | |
| 304 | } | |
| 305 | ||
| 306 | const files = []; | |
| 307 | for (const service of RUNNING) files.push(write(service.name, selfHosted(service))); | |
| 308 | ||
| 309 | const compatibility_date = "2026-09-26"; | |
| 310 | files.push( | |
| The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook. | 311 | write("g1t-gitstore", { |
| 312 | name: "g1t-gitstore", | |
| 313 | main: rel("deploy/self-host/workers/gitstore/index.js"), | |
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 314 | compatibility_date, |
| 315 | vars: { | |
| 316 | GITSTORE_URL: process.env.GITSTORE_URL ?? "http://gitstore:8080", | |
| 317 | GITSTORE_SECRET: process.env.GITSTORE_SECRET ?? "", | |
| 318 | }, | |
| 319 | }), | |
| 320 | ); | |
| 321 | files.push( | |
| 322 | write("g1t-mail", { | |
| 323 | name: "g1t-mail", | |
| 324 | main: rel("deploy/self-host/workers/mail/index.js"), | |
| 325 | compatibility_date, | |
| 326 | vars: { | |
| 327 | PUBLIC_URL, | |
| 328 | MAIL_URL: process.env.MAIL_URL ?? "", | |
| 329 | MAIL_FROM: process.env.MAIL_FROM ?? "", | |
| 330 | }, | |
| 331 | }), | |
| 332 | ); | |
| 333 | for (const [service, feature] of Object.entries(OFF)) { | |
| 334 | files.push( | |
| 335 | write(offName(service), { | |
| 336 | name: offName(service), | |
| 337 | main: rel("deploy/self-host/workers/off/index.js"), | |
| 338 | compatibility_date, | |
| 339 | vars: { OFF_NAME: feature }, | |
| 340 | }), | |
| 341 | ); | |
| 342 | } | |
| 343 | ||
| 344 | // The order Wrangler takes them in: the site first, as the one that serves. | |
| 345 | writeFileSync(join(out, "workers.txt"), `${files.map((file) => relative(out, file)).join("\n")}\n`); | |
| 346 | ||
| Merge branch 'worktree-agent-aaf03bdceac799c89' | 347 | // The API (REST, MCP and OAuth) is served on a port of its own (API_PORT), |
| 348 | // by a second `wrangler dev` (start.sh): not in workers.txt. Its service | |
| 349 | // bindings reach the Workers above through Wrangler's dev registry, as | |
| 350 | // any two `wrangler dev` sessions on one machine do. | |
| 351 | { | |
| 352 | const api = STACK.find((unit) => unit.worker === "g1t-api"); | |
| 353 | write("g1t-api", selfHosted({ name: api.worker, dir: api.path, web: false })); | |
| 354 | writeFileSync(join(out, "api.json"), `${JSON.stringify({ api: API_URL, mcp: MCP_URL }, null, 2)}\n`); | |
| 355 | } | |
| 356 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 357 | // What scheduler.mjs runs: each service's own crons, as hosted g1t's Cron |
| 358 | // Triggers run them, for the services in SELF_HOST_CRONS. | |
| 359 | const schedules = RUNNING.filter((service) => SELF_HOST_CRONS.has(service.name)) | |
| 360 | .map((service) => ({ | |
| 361 | worker: service.name, | |
| 362 | crons: parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8")).triggers?.crons ?? [], | |
| 363 | })) | |
| 364 | .filter((schedule) => schedule.crons.length > 0); | |
| 365 | writeFileSync(join(out, "schedules.json"), `${JSON.stringify(schedules, null, 2)}\n`); | |
| 366 | ||
| Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays | 367 | // The status page runs in a workerd of its own (status.sh, the `status` |
| 368 | // service in docker-compose.yml), so it stays up when the site does not: | |
| 369 | // not in workers.txt. It checks the site from inside Compose | |
| 370 | // (STATUS_CHECK_URL) and links to it at PUBLIC_URL. Parts this | |
| 371 | // installation does not run (the API, MCP, docs, g1t.page, the model | |
| 372 | // proxy, billing) are left off its page; a public repository of yours in | |
| 373 | // STATUS_PROBE_REPO adds the git check. | |
| 374 | { | |
| 375 | const hosted = parseJsonc(readFileSync(join(root, "apps/status/wrangler.jsonc"), "utf8")); | |
| 376 | const db = hosted.d1_databases[0]; | |
| 377 | write("g1t-status", { | |
| 378 | name: hosted.name, | |
| 379 | main: rel(join("apps/status", hosted.main)), | |
| 380 | compatibility_date: hosted.compatibility_date, | |
| 381 | rules: hosted.rules, | |
| 382 | triggers: hosted.triggers, | |
| 383 | d1_databases: [ | |
| 384 | { | |
| 385 | binding: db.binding, | |
| 386 | database_name: db.database_name, | |
| 387 | database_id: db.database_id, | |
| 388 | migrations_dir: rel(join("apps/status", db.migrations_dir)), | |
| 389 | }, | |
| 390 | ], | |
| 391 | vars: { | |
| 392 | SITE_URL: (process.env.STATUS_CHECK_URL ?? "http://g1t:8787").replace(/\/$/, ""), | |
| 393 | PUBLIC_SITE_URL: PUBLIC_URL, | |
| 394 | API_URL: "", | |
| 395 | MCP_URL: "", | |
| 396 | DOCS_URL: "", | |
| 397 | PAGES_URL: "", | |
| 398 | MODELS_URL: "", | |
| 399 | PROBE_REPO: process.env.STATUS_PROBE_REPO ?? "", | |
| 400 | SUPPORT_URL: `${PUBLIC_URL}/support`, | |
| 401 | OG_IMAGE: "", | |
| 402 | // Its own address, for links made outside a request. No email | |
| 403 | // binding here: subscribing by email is off, the feeds work. | |
| 404 | STATUS_URL: `http://localhost:${process.env.STATUS_PORT ?? "8788"}`, | |
| 405 | STATUS_ALERT_EMAIL: "", | |
| 406 | }, | |
| 407 | }); | |
| 408 | } | |
| 409 | console.log(`Wrote ${files.length} configs to ${out}`); |
This file's history is long; its oldest lines are credited to the oldest commit read.