Skip to content
409 linesCodeBlameRaw

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 pays1#!/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 pays10// 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 store13// 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 store15// (DOCS_S3_BUCKET);
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays16// - 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 address24// USERCONTENT_URL,
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store25// 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 member26// 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 pays27// 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
33import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
34import { dirname, join, relative, resolve } from "node:path";
35import { fileURLToPath } from "node:url";
36
37const here = dirname(fileURLToPath(import.meta.url));
38const root = resolve(here, "../..");
39const out = resolve(process.argv[2] ?? join(here, ".generated"));
40mkdirSync(out, { recursive: true });
41
42const 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 */
49export 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}
59const { api: API_URL, mcp: MCP_URL } = apiAddresses();
60
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays61// What runs, and what is off, is each unit's `self_host` in
62// deploy/stack.jsonc: the list hosted g1t deploys from.
63const 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). */
66export 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. */
71const OFF_NAMES = {
72 "g1t-runner": "Agents",
73 "g1t-context": "Context search and memory",
Chat and workspace agents: channels, DMs and named agents you talk to74 "g1t-models": "Hosted models",
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays75};
76const 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). */
81const 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 address82 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 pays84 "g1t-actions": "ACTIONS_KEY",
85 "g1t-integrations": "INTEGRATIONS_KEY",
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r986 "g1t-agents": "CONNECTORS_KEY",
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays87 "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 */
95const GITHUB_VARS = ["GITHUB_APP_ID", "GITHUB_APP_SLUG", "GITHUB_APP_CLIENT_ID"];
96const 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 member101/**
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 member105 * 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.109const 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 member110
Spend caps: a monthly budget for comped workspaces and a daily breaker on what g1t pays111/** Queues whose consumers are off: events stops sending to them. */
112const OFF_QUEUES = new Set(["g1t-events-runner", "g1t-events-context"]);
113
114/** Strips comments and trailing commas from JSONC. Strings are respected. */
115function 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
139const rel = (path) => relative(out, resolve(root, path)).replaceAll("\\", "/");
140
141function 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 pays145}
146
147function 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 pays196 }
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 member227 // 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 member233 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 address250 // 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 store277 // 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 store279 // 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 store281 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 pays290 // 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
296function offName(service) {
297 return `${service}-off`;
298}
299
300function write(name, config) {
301 const path = join(out, `${name}.json`);
302 writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
303 return path;
304}
305
306const files = [];
307for (const service of RUNNING) files.push(write(service.name, selfHosted(service)));
308
309const compatibility_date = "2026-09-26";
310files.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 pays314 compatibility_date,
315 vars: {
316 GITSTORE_URL: process.env.GITSTORE_URL ?? "http://gitstore:8080",
317 GITSTORE_SECRET: process.env.GITSTORE_SECRET ?? "",
318 },
319 }),
320);
321files.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);
333for (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.
345writeFileSync(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 member357// 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.
359const 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);
365writeFileSync(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 pays367// 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}
409console.log(`Wrote ${files.length} configs to ${out}`);

This file's history is long; its oldest lines are credited to the oldest commit read.