Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
404 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.

Download ZIP reads blobs eight batches at a time and deflates files together1#!/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,
Download ZIP reads blobs eight batches at a time and deflates files together10// 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);
Download ZIP reads blobs eight batches at a time and deflates files together16// - 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,
Download ZIP reads blobs eight batches at a time and deflates files together26// S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_PUBLIC_ENDPOINT, and optionally
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
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
Download ZIP reads blobs eight batches at a time and deflates files together61// 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",
Download ZIP reads blobs eight batches at a time and deflates files together75};
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",
Download ZIP reads blobs eight batches at a time and deflates files together84 "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",
Download ZIP reads blobs eight batches at a time and deflates files together87 "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 = {
Merge branch 'open-signups'97 "g1t-identity": ["GITHUB_APP_CLIENT_SECRET", "IDENTITY_KEY"],
Download ZIP reads blobs eight batches at a time and deflates files together98 "g1t-integrations": ["GITHUB_APP_PRIVATE_KEY", "GITHUB_APP_WEBHOOK_SECRET"],
99};
100
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
Download ZIP reads blobs eight batches at a time and deflates files together105 * 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"]);
Download ZIP reads blobs eight batches at a time and deflates files together110
111/** 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;
Download ZIP reads blobs eight batches at a time and deflates files together145}
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" });
Download ZIP reads blobs eight batches at a time and deflates files together196 }
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";
Merge branch 'worktree-agent-af58ac8933b0dd125'216 // Packages' files go to the compose file's RustFS (or any S3-compatible
Download ZIP reads blobs eight batches at a time and deflates files together217 // store) instead of R2, with no request size limit, and package
218 // addresses start with this installation's host.
219 if (hosted.name === "g1t-packages") {
220 Object.assign(config.vars, {
221 BLOB_STORE: "s3",
Merge branch 'worktree-agent-af58ac8933b0dd125'222 S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000",
Download ZIP reads blobs eight batches at a time and deflates files together223 S3_BUCKET: process.env.S3_BUCKET ?? "g1t-packages",
224 S3_REGION: process.env.S3_REGION ?? "us-east-1",
225 S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
226 S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
227 S3_PUBLIC_ENDPOINT: process.env.S3_PUBLIC_ENDPOINT ?? "",
228 MAX_REQUEST_BYTES: "0",
229 STORAGE_LIMITS: "off",
230 REGISTRY_HOST: new URL(PUBLIC_URL).host,
231 PACKAGES_TOKEN_SECRET: process.env.PACKAGES_TOKEN_SECRET ?? "",
232 });
233 delete config.vars.R2_ACCOUNT_ID;
234 delete config.vars.R2_BUCKET;
235 }
Merge branch 'worktree-agent-aaf03bdceac799c89'236 // Where this installation is reached, for the links and addresses each
237 // shows: the site's clone URLs, meta tags and agent setup, the API's
238 // OAuth issuer and MCP server, and identity's mail. No social cards: the
239 // 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 address240 // Repository files and avatars: USERCONTENT_URL, a host of its own that
241 // reaches this same site, or, empty, a path on it (PUBLIC_URL/-/usercontent).
242 if (service.web) {
243 Object.assign(config.vars, { SITE_URL: PUBLIC_URL, API_URL, MCP_URL, OG_URL: "", USERCONTENT_URL: (process.env.USERCONTENT_URL ?? "").trim() });
244 }
Merge branch 'worktree-agent-aaf03bdceac799c89'245 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)246 // Notifications on the g1t phone app go through Expo's push service (and
247 // from there Google's and Apple's), with no setup here, unless
248 // PHONE_PUSH=off: notify then sends phones nothing, and the site's
249 // /.well-known/g1t.json tells the app so.
250 if (service.web || hosted.name === "g1t-notify") config.vars.PHONE_PUSH = process.env.PHONE_PUSH === "off" ? "off" : "on";
Merge branch 'worktree-agent-aaf03bdceac799c89'251 if (hosted.name === "g1t-identity") config.vars.SITE_URL = PUBLIC_URL;
Merge branch 'open-signups'252 // Cloudflare Turnstile on sign-up, only with both of your widget's keys.
253 if (service.web) {
254 config.vars.TURNSTILE_SITE_KEY = process.env.TURNSTILE_SITE_KEY ?? "";
255 if (process.env.TURNSTILE_SECRET) config.vars.TURNSTILE_SECRET = process.env.TURNSTILE_SECRET;
256 }
Merge branch 'worktree-agent-ac5b181a013e54348'257 // Nightly backups' bundles go to a bucket of their own on the same
Merge branch 'worktree-agent-aaf03bdceac799c89'258 // S3-compatible store, instead of the BACKUPS R2 bucket, and so do the
259 // packs kept for fresh clones (src/pack_cache.rs), instead of GIT_PACKS.
Merge branch 'worktree-agent-ac5b181a013e54348'260 if (hosted.name === "g1t-repos") {
261 Object.assign(config.vars, {
262 BACKUP_STORE: "s3",
263 BACKUP_S3_BUCKET: process.env.BACKUP_S3_BUCKET ?? "g1t-backups",
Merge branch 'worktree-agent-aaf03bdceac799c89'264 PACK_STORE: "s3",
265 PACK_S3_BUCKET: process.env.PACK_S3_BUCKET ?? "g1t-git-packs",
Merge branch 'worktree-agent-af58ac8933b0dd125'266 S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000",
Merge branch 'worktree-agent-ac5b181a013e54348'267 S3_REGION: process.env.S3_REGION ?? "us-east-1",
268 S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
269 S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
270 });
271 }
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store272 // 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.273 // 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 store274 // 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.275 if (hosted.name === "g1t-artifacts") {
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store276 Object.assign(config.vars, {
277 DOCS_FILES: "s3",
278 DOCS_S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000",
279 DOCS_S3_BUCKET: process.env.DOCS_S3_BUCKET ?? "g1t-docs-files",
280 DOCS_S3_REGION: process.env.S3_REGION ?? "us-east-1",
281 DOCS_S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
282 DOCS_S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
283 });
284 }
Download ZIP reads blobs eight batches at a time and deflates files together285 // Nothing to deploy to: deployments are off (no Cloudflare API token).
286 if (hosted.name === "g1t-deployments") delete config.vars.CUSTOM_HOSTNAMES_ZONE_ID;
287
288 return config;
289}
290
291function offName(service) {
292 return `${service}-off`;
293}
294
295function write(name, config) {
296 const path = join(out, `${name}.json`);
297 writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
298 return path;
299}
300
301const files = [];
302for (const service of RUNNING) files.push(write(service.name, selfHosted(service)));
303
304const compatibility_date = "2026-09-26";
305files.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.306 write("g1t-gitstore", {
307 name: "g1t-gitstore",
308 main: rel("deploy/self-host/workers/gitstore/index.js"),
Download ZIP reads blobs eight batches at a time and deflates files together309 compatibility_date,
310 vars: {
311 GITSTORE_URL: process.env.GITSTORE_URL ?? "http://gitstore:8080",
312 GITSTORE_SECRET: process.env.GITSTORE_SECRET ?? "",
313 },
314 }),
315);
316files.push(
317 write("g1t-mail", {
318 name: "g1t-mail",
319 main: rel("deploy/self-host/workers/mail/index.js"),
320 compatibility_date,
321 vars: {
322 PUBLIC_URL,
323 MAIL_URL: process.env.MAIL_URL ?? "",
324 MAIL_FROM: process.env.MAIL_FROM ?? "",
325 },
326 }),
327);
328for (const [service, feature] of Object.entries(OFF)) {
329 files.push(
330 write(offName(service), {
331 name: offName(service),
332 main: rel("deploy/self-host/workers/off/index.js"),
333 compatibility_date,
334 vars: { OFF_NAME: feature },
335 }),
336 );
337}
338
339// The order Wrangler takes them in: the site first, as the one that serves.
340writeFileSync(join(out, "workers.txt"), `${files.map((file) => relative(out, file)).join("\n")}\n`);
341
Merge branch 'worktree-agent-aaf03bdceac799c89'342// The API (REST, MCP and OAuth) is served on a port of its own (API_PORT),
343// by a second `wrangler dev` (start.sh): not in workers.txt. Its service
344// bindings reach the Workers above through Wrangler's dev registry, as
345// any two `wrangler dev` sessions on one machine do.
346{
347 const api = STACK.find((unit) => unit.worker === "g1t-api");
348 write("g1t-api", selfHosted({ name: api.worker, dir: api.path, web: false }));
349 writeFileSync(join(out, "api.json"), `${JSON.stringify({ api: API_URL, mcp: MCP_URL }, null, 2)}\n`);
350}
351
Download ZIP reads blobs eight batches at a time and deflates files together352// What scheduler.mjs runs: each service's own crons, as hosted g1t's Cron
353// Triggers run them, for the services in SELF_HOST_CRONS.
354const schedules = RUNNING.filter((service) => SELF_HOST_CRONS.has(service.name))
355 .map((service) => ({
356 worker: service.name,
357 crons: parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8")).triggers?.crons ?? [],
358 }))
359 .filter((schedule) => schedule.crons.length > 0);
360writeFileSync(join(out, "schedules.json"), `${JSON.stringify(schedules, null, 2)}\n`);
361
362// The status page runs in a workerd of its own (status.sh, the `status`
363// service in docker-compose.yml), so it stays up when the site does not:
364// not in workers.txt. It checks the site from inside Compose
365// (STATUS_CHECK_URL) and links to it at PUBLIC_URL. Parts this
366// installation does not run (the API, MCP, docs, g1t.page, the model
367// proxy, billing) are left off its page; a public repository of yours in
368// STATUS_PROBE_REPO adds the git check.
369{
370 const hosted = parseJsonc(readFileSync(join(root, "apps/status/wrangler.jsonc"), "utf8"));
371 const db = hosted.d1_databases[0];
372 write("g1t-status", {
373 name: hosted.name,
374 main: rel(join("apps/status", hosted.main)),
375 compatibility_date: hosted.compatibility_date,
376 rules: hosted.rules,
377 triggers: hosted.triggers,
378 d1_databases: [
379 {
380 binding: db.binding,
381 database_name: db.database_name,
382 database_id: db.database_id,
383 migrations_dir: rel(join("apps/status", db.migrations_dir)),
384 },
385 ],
386 vars: {
387 SITE_URL: (process.env.STATUS_CHECK_URL ?? "http://g1t:8787").replace(/\/$/, ""),
388 PUBLIC_SITE_URL: PUBLIC_URL,
389 API_URL: "",
390 MCP_URL: "",
391 DOCS_URL: "",
392 PAGES_URL: "",
393 MODELS_URL: "",
394 PROBE_REPO: process.env.STATUS_PROBE_REPO ?? "",
395 SUPPORT_URL: `${PUBLIC_URL}/support`,
396 OG_IMAGE: "",
397 // Its own address, for links made outside a request. No email
398 // binding here: subscribing by email is off, the feeds work.
399 STATUS_URL: `http://localhost:${process.env.STATUS_PORT ?? "8788"}`,
400 STATUS_ALERT_EMAIL: "",
401 },
402 });
403}
404console.log(`Wrote ${files.length} configs to ${out}`);

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