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
401 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.

Merge branch 'worktree-agent-a1b995daa94e4e1b7'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,
Merge branch 'worktree-agent-a1b995daa94e4e1b7'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 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);
The agents, models and context services are deleted. Nothing calls them any more: workspace agents, the hosted model proxy at models.g1t.sh with its AI Gateway, and the context hub with its search index are gone with the features they served. The deploy stack no longer builds or deploys them, so it no longer needs CONNECTORS_KEY, AI_GATEWAY_TOKEN, ANTHROPIC_API_KEY or WORKERS_AI_TOKEN, and a self-hosted install no longer generates CONNECTORS_KEY or stubs them out. Their databases, queues, index, gateway and domain stay in Cloudflare until they are deleted by hand after this is deployed.16// - services that are off in this phase (the cloud runner, the g1t.page
17// dispatcher) are bound to workers/off instead, and events stop queueing
18// work for them;
Merge branch 'worktree-agent-a1b995daa94e4e1b7'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,
The agents, models and context services are deleted. Nothing calls them any more: workspace agents, the hosted model proxy at models.g1t.sh with its AI Gateway, and the context hub with its search index are gone with the features they served. The deploy stack no longer builds or deploys them, so it no longer needs CONNECTORS_KEY, AI_GATEWAY_TOKEN, ANTHROPIC_API_KEY or WORKERS_AI_TOKEN, and a self-hosted install no longer generates CONNECTORS_KEY or stubs them out. Their databases, queues, index, gateway and domain stay in Cloudflare until they are deleted by hand after this is deployed.23// ACTIONS_KEY, INTEGRATIONS_KEY, WEBHOOKS_KEY, IDENTITY_KEY, USERCONTENT_KEY, REPOS_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,
Merge branch 'worktree-agent-a1b995daa94e4e1b7'26// 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
Merge branch 'worktree-agent-a1b995daa94e4e1b7'61// 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 = {
The agents, models and context services are deleted. Nothing calls them any more: workspace agents, the hosted model proxy at models.g1t.sh with its AI Gateway, and the context hub with its search index are gone with the features they served. The deploy stack no longer builds or deploys them, so it no longer needs CONNECTORS_KEY, AI_GATEWAY_TOKEN, ANTHROPIC_API_KEY or WORKERS_AI_TOKEN, and a self-hosted install no longer generates CONNECTORS_KEY or stubs them out. Their databases, queues, index, gateway and domain stay in Cloudflare until they are deleted by hand after this is deployed.72 "g1t-runner": "Cloud runners",
Merge branch 'worktree-agent-a1b995daa94e4e1b7'73};
74const OFF = Object.fromEntries(
75 STACK.filter((unit) => unit.self_host === "off").map((unit) => [unit.worker, OFF_NAMES[unit.worker] ?? unit.worker]),
76);
77
78/** Sealing keys, by the service that holds each (hosted: Wrangler secrets). */
79const 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 address80 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.81 "g1t-repos": "REPOS_KEY",
Merge branch 'worktree-agent-a1b995daa94e4e1b7'82 "g1t-actions": "ACTIONS_KEY",
83 "g1t-integrations": "INTEGRATIONS_KEY",
84 "g1t-webhooks": "WEBHOOKS_KEY",
85};
86
87/**
88 * g1t.sh's GitHub App is its own: an installation registers one of its
89 * own, or has none, and then no GitHub buttons appear. Its public settings
90 * replace the hosted vars; its secrets go only to the service that uses each.
91 */
92const GITHUB_VARS = ["GITHUB_APP_ID", "GITHUB_APP_SLUG", "GITHUB_APP_CLIENT_ID"];
93const GITHUB_SECRETS = {
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.94 "g1t-identity": ["GITHUB_APP_CLIENT_SECRET", "IDENTITY_KEY"],
Merge branch 'worktree-agent-a1b995daa94e4e1b7'95 "g1t-integrations": ["GITHUB_APP_PRIVATE_KEY", "GITHUB_APP_WEBHOOK_SECRET"],
96};
97
98/**
99 * 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.100 * 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.101 * emptying artifacts' trash after 30 days). Not run: actions (its
Merge branch 'worktree-agent-a1b995daa94e4e1b7'102 * minute would start scheduled workflows with no runner to take them),
103 * billing (reconciles against Cloudflare and Stripe), deployments (calls
104 * Cloudflare's API) and the services that are off.
105 */
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.106const SELF_HOST_CRONS = new Set(["g1t-repos", "g1t-events", "g1t-identity", "g1t-security", "g1t-webhooks", "g1t-packages", "g1t-artifacts"]);
Merge branch 'worktree-agent-a1b995daa94e4e1b7'107
108/** Queues whose consumers are off: events stops sending to them. */
The agents, models and context services are deleted. Nothing calls them any more: workspace agents, the hosted model proxy at models.g1t.sh with its AI Gateway, and the context hub with its search index are gone with the features they served. The deploy stack no longer builds or deploys them, so it no longer needs CONNECTORS_KEY, AI_GATEWAY_TOKEN, ANTHROPIC_API_KEY or WORKERS_AI_TOKEN, and a self-hosted install no longer generates CONNECTORS_KEY or stubs them out. Their databases, queues, index, gateway and domain stay in Cloudflare until they are deleted by hand after this is deployed.109const OFF_QUEUES = new Set(["g1t-events-runner"]);
Merge branch 'worktree-agent-a1b995daa94e4e1b7'110
111/** Strips comments and trailing commas from JSONC. Strings are respected. */
112function parseJsonc(text) {
113 let result = "";
114 let inString = false;
115 for (let i = 0; i < text.length; i++) {
116 const char = text[i];
117 if (inString) {
118 result += char;
119 if (char === "\\") result += text[++i];
120 else if (char === '"') inString = false;
121 } else if (char === '"') {
122 inString = true;
123 result += char;
124 } else if (char === "/" && text[i + 1] === "/") {
125 while (i < text.length && text[i] !== "\n") i++;
126 result += "\n";
127 } else if (char === "/" && text[i + 1] === "*") {
128 i = text.indexOf("*/", i + 2) + 1;
129 } else {
130 result += char;
131 }
132 }
133 return JSON.parse(result.replace(/,(\s*[}\]])/g, "$1"));
134}
135
136const rel = (path) => relative(out, resolve(root, path)).replaceAll("\\", "/");
137
138function hostedUrl(value) {
Merge branch 'worktree-agent-aaf03bdceac799c89'139 return typeof value === "string"
140 ? value.replace(/https:\/\/api\.g1t\.sh/g, API_URL).replace(/https:\/\/g1t\.sh/g, PUBLIC_URL)
141 : value;
Merge branch 'worktree-agent-a1b995daa94e4e1b7'142}
143
144function selfHosted(service) {
145 const hosted = parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8"));
146 const config = {
147 name: hosted.name,
148 compatibility_date: hosted.compatibility_date,
149 compatibility_flags: hosted.compatibility_flags,
150 rules: hosted.rules,
151 vars: {},
152 };
153
154 if (service.web) {
155 // The site as React Router built it (apps/web/build), not its sources.
156 config.main = rel(`${service.dir}/build/server/index.js`);
157 config.no_bundle = true;
158 config.rules = [{ type: "ESModule", globs: ["**/*.js", "**/*.mjs"] }];
159 config.assets = { directory: rel(`${service.dir}/build/client`) };
160 } else {
161 config.main = rel(join(service.dir, hosted.main));
162 }
163
164 for (const [key, value] of Object.entries(hosted.vars ?? {})) config.vars[key] = hostedUrl(value);
165
166 if (hosted.d1_databases) {
167 config.d1_databases = hosted.d1_databases.map((db) => ({
168 binding: db.binding,
169 database_name: db.database_name,
170 database_id: db.database_id,
171 migrations_dir: rel(join(service.dir, db.migrations_dir ?? "migrations")),
172 }));
173 }
174 if (hosted.kv_namespaces) config.kv_namespaces = hosted.kv_namespaces.map(({ binding, id }) => ({ binding, id }));
175 if (hosted.triggers) config.triggers = hosted.triggers;
176
177 if (hosted.queues) {
178 config.queues = {};
179 if (hosted.queues.producers) {
180 config.queues.producers = hosted.queues.producers.filter((producer) => !OFF_QUEUES.has(producer.queue));
181 }
182 if (hosted.queues.consumers) config.queues.consumers = hosted.queues.consumers;
183 }
184
185 config.services = (hosted.services ?? []).map((binding) =>
186 OFF[binding.service] ? { binding: binding.binding, service: offName(binding.service) } : binding,
187 );
188
189 // Cloudflare-only bindings, and what stands in for them.
190 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.191 for (const store of hosted.artifacts) {
192 config.services.push({ binding: store.binding, service: "g1t-gitstore" });
Merge branch 'worktree-agent-a1b995daa94e4e1b7'193 }
194 }
195 if (hosted.send_email) {
196 for (const email of hosted.send_email) config.services.push({ binding: email.name, service: "g1t-mail" });
197 }
198
199 // Secrets the hosted services hold, given here from the environment, each
200 // only to the service that uses it.
201 const secret = SECRETS[hosted.name];
202 if (secret && process.env[secret]) config.vars[secret] = process.env[secret];
203 for (const name of GITHUB_VARS) {
204 if (name in config.vars) config.vars[name] = process.env[name] ?? "";
205 }
206 for (const name of GITHUB_SECRETS[hosted.name] ?? []) {
207 if (process.env[name]) config.vars[name] = process.env[name];
208 }
209
210 // Self-hosted g1t charges nothing: billing records usage at cost and never
211 // stops work for it.
212 if (hosted.name === "g1t-billing") config.vars.FREE_WHILE_BUILDING = "true";
Merge branch 'worktree-agent-af58ac8933b0dd125'213 // Packages' files go to the compose file's RustFS (or any S3-compatible
Merge branch 'worktree-agent-a1b995daa94e4e1b7'214 // store) instead of R2, with no request size limit, and package
215 // addresses start with this installation's host.
216 if (hosted.name === "g1t-packages") {
217 Object.assign(config.vars, {
218 BLOB_STORE: "s3",
Merge branch 'worktree-agent-af58ac8933b0dd125'219 S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000",
Merge branch 'worktree-agent-a1b995daa94e4e1b7'220 S3_BUCKET: process.env.S3_BUCKET ?? "g1t-packages",
221 S3_REGION: process.env.S3_REGION ?? "us-east-1",
222 S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
223 S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
224 S3_PUBLIC_ENDPOINT: process.env.S3_PUBLIC_ENDPOINT ?? "",
225 MAX_REQUEST_BYTES: "0",
226 STORAGE_LIMITS: "off",
227 REGISTRY_HOST: new URL(PUBLIC_URL).host,
228 PACKAGES_TOKEN_SECRET: process.env.PACKAGES_TOKEN_SECRET ?? "",
229 });
230 delete config.vars.R2_ACCOUNT_ID;
231 delete config.vars.R2_BUCKET;
232 }
Merge branch 'worktree-agent-aaf03bdceac799c89'233 // Where this installation is reached, for the links and addresses each
234 // shows: the site's clone URLs, meta tags and agent setup, the API's
235 // OAuth issuer and MCP server, and identity's mail. No social cards: the
236 // 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 address237 // Repository files and avatars: USERCONTENT_URL, a host of its own that
238 // reaches this same site, or, empty, a path on it (PUBLIC_URL/-/usercontent).
239 if (service.web) {
240 Object.assign(config.vars, { SITE_URL: PUBLIC_URL, API_URL, MCP_URL, OG_URL: "", USERCONTENT_URL: (process.env.USERCONTENT_URL ?? "").trim() });
241 }
Merge branch 'worktree-agent-aaf03bdceac799c89'242 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)243 // Notifications on the g1t phone app go through Expo's push service (and
244 // from there Google's and Apple's), with no setup here, unless
245 // PHONE_PUSH=off: notify then sends phones nothing, and the site's
246 // /.well-known/g1t.json tells the app so.
247 if (service.web || hosted.name === "g1t-notify") config.vars.PHONE_PUSH = process.env.PHONE_PUSH === "off" ? "off" : "on";
Merge branch 'worktree-agent-aaf03bdceac799c89'248 if (hosted.name === "g1t-identity") config.vars.SITE_URL = PUBLIC_URL;
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.249 // Cloudflare Turnstile on sign-up, only with both of your widget's keys.
250 if (service.web) {
251 config.vars.TURNSTILE_SITE_KEY = process.env.TURNSTILE_SITE_KEY ?? "";
252 if (process.env.TURNSTILE_SECRET) config.vars.TURNSTILE_SECRET = process.env.TURNSTILE_SECRET;
253 }
Merge branch 'worktree-agent-ac5b181a013e54348'254 // Nightly backups' bundles go to a bucket of their own on the same
Merge branch 'worktree-agent-aaf03bdceac799c89'255 // S3-compatible store, instead of the BACKUPS R2 bucket, and so do the
256 // packs kept for fresh clones (src/pack_cache.rs), instead of GIT_PACKS.
Merge branch 'worktree-agent-ac5b181a013e54348'257 if (hosted.name === "g1t-repos") {
258 Object.assign(config.vars, {
259 BACKUP_STORE: "s3",
260 BACKUP_S3_BUCKET: process.env.BACKUP_S3_BUCKET ?? "g1t-backups",
Merge branch 'worktree-agent-aaf03bdceac799c89'261 PACK_STORE: "s3",
262 PACK_S3_BUCKET: process.env.PACK_S3_BUCKET ?? "g1t-git-packs",
Merge branch 'worktree-agent-af58ac8933b0dd125'263 S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000",
Merge branch 'worktree-agent-ac5b181a013e54348'264 S3_REGION: process.env.S3_REGION ?? "us-east-1",
265 S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
266 S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
267 });
268 }
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store269 // 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.270 // 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 store271 // 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.272 if (hosted.name === "g1t-artifacts") {
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store273 Object.assign(config.vars, {
274 DOCS_FILES: "s3",
275 DOCS_S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000",
276 DOCS_S3_BUCKET: process.env.DOCS_S3_BUCKET ?? "g1t-docs-files",
277 DOCS_S3_REGION: process.env.S3_REGION ?? "us-east-1",
278 DOCS_S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
279 DOCS_S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
280 });
281 }
Merge branch 'worktree-agent-a1b995daa94e4e1b7'282 // Nothing to deploy to: deployments are off (no Cloudflare API token).
283 if (hosted.name === "g1t-deployments") delete config.vars.CUSTOM_HOSTNAMES_ZONE_ID;
284
285 return config;
286}
287
288function offName(service) {
289 return `${service}-off`;
290}
291
292function write(name, config) {
293 const path = join(out, `${name}.json`);
294 writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
295 return path;
296}
297
298const files = [];
299for (const service of RUNNING) files.push(write(service.name, selfHosted(service)));
300
301const compatibility_date = "2026-09-26";
302files.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.303 write("g1t-gitstore", {
304 name: "g1t-gitstore",
305 main: rel("deploy/self-host/workers/gitstore/index.js"),
Merge branch 'worktree-agent-a1b995daa94e4e1b7'306 compatibility_date,
307 vars: {
308 GITSTORE_URL: process.env.GITSTORE_URL ?? "http://gitstore:8080",
309 GITSTORE_SECRET: process.env.GITSTORE_SECRET ?? "",
310 },
311 }),
312);
313files.push(
314 write("g1t-mail", {
315 name: "g1t-mail",
316 main: rel("deploy/self-host/workers/mail/index.js"),
317 compatibility_date,
318 vars: {
319 PUBLIC_URL,
320 MAIL_URL: process.env.MAIL_URL ?? "",
321 MAIL_FROM: process.env.MAIL_FROM ?? "",
322 },
323 }),
324);
325for (const [service, feature] of Object.entries(OFF)) {
326 files.push(
327 write(offName(service), {
328 name: offName(service),
329 main: rel("deploy/self-host/workers/off/index.js"),
330 compatibility_date,
331 vars: { OFF_NAME: feature },
332 }),
333 );
334}
335
336// The order Wrangler takes them in: the site first, as the one that serves.
337writeFileSync(join(out, "workers.txt"), `${files.map((file) => relative(out, file)).join("\n")}\n`);
338
Merge branch 'worktree-agent-aaf03bdceac799c89'339// The API (REST, MCP and OAuth) is served on a port of its own (API_PORT),
340// by a second `wrangler dev` (start.sh): not in workers.txt. Its service
341// bindings reach the Workers above through Wrangler's dev registry, as
342// any two `wrangler dev` sessions on one machine do.
343{
344 const api = STACK.find((unit) => unit.worker === "g1t-api");
345 write("g1t-api", selfHosted({ name: api.worker, dir: api.path, web: false }));
346 writeFileSync(join(out, "api.json"), `${JSON.stringify({ api: API_URL, mcp: MCP_URL }, null, 2)}\n`);
347}
348
Merge branch 'worktree-agent-a1b995daa94e4e1b7'349// What scheduler.mjs runs: each service's own crons, as hosted g1t's Cron
350// Triggers run them, for the services in SELF_HOST_CRONS.
351const schedules = RUNNING.filter((service) => SELF_HOST_CRONS.has(service.name))
352 .map((service) => ({
353 worker: service.name,
354 crons: parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8")).triggers?.crons ?? [],
355 }))
356 .filter((schedule) => schedule.crons.length > 0);
357writeFileSync(join(out, "schedules.json"), `${JSON.stringify(schedules, null, 2)}\n`);
358
359// The status page runs in a workerd of its own (status.sh, the `status`
360// service in docker-compose.yml), so it stays up when the site does not:
361// not in workers.txt. It checks the site from inside Compose
362// (STATUS_CHECK_URL) and links to it at PUBLIC_URL. Parts this
363// installation does not run (the API, MCP, docs, g1t.page, the model
364// proxy, billing) are left off its page; a public repository of yours in
365// STATUS_PROBE_REPO adds the git check.
366{
367 const hosted = parseJsonc(readFileSync(join(root, "apps/status/wrangler.jsonc"), "utf8"));
368 const db = hosted.d1_databases[0];
369 write("g1t-status", {
370 name: hosted.name,
371 main: rel(join("apps/status", hosted.main)),
372 compatibility_date: hosted.compatibility_date,
373 rules: hosted.rules,
374 triggers: hosted.triggers,
375 d1_databases: [
376 {
377 binding: db.binding,
378 database_name: db.database_name,
379 database_id: db.database_id,
380 migrations_dir: rel(join("apps/status", db.migrations_dir)),
381 },
382 ],
383 vars: {
384 SITE_URL: (process.env.STATUS_CHECK_URL ?? "http://g1t:8787").replace(/\/$/, ""),
385 PUBLIC_SITE_URL: PUBLIC_URL,
386 API_URL: "",
387 MCP_URL: "",
388 DOCS_URL: "",
389 PAGES_URL: "",
390 MODELS_URL: "",
391 PROBE_REPO: process.env.STATUS_PROBE_REPO ?? "",
392 SUPPORT_URL: `${PUBLIC_URL}/support`,
393 OG_IMAGE: "",
394 // Its own address, for links made outside a request. No email
395 // binding here: subscribing by email is off, the feeds work.
396 STATUS_URL: `http://localhost:${process.env.STATUS_PORT ?? "8788"}`,
397 STATUS_ALERT_EMAIL: "",
398 },
399 });
400}
401console.log(`Wrote ${files.length} configs to ${out}`);

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