Skip to content
402 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.

Running g1t yourself: the design, a docker compose proof, and a guide to what works today1#!/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;
9// - ARTIFACTS (git storage) becomes a service binding to workers/artifacts,
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
14// their own, BACKUP_S3_BUCKET), and the docs service the files in pages
15// (DOCS_S3_BUCKET);
Running g1t yourself: the design, a docker compose proof, and a guide to what works today16// - 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 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 address23// ACTIONS_KEY, INTEGRATIONS_KEY, WEBHOOKS_KEY, IDENTITY_KEY, USERCONTENT_KEY,
24// 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
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look27// 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.
Running g1t yourself: the design, a docker compose proof, and a guide to what works today29//
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
Deploys as code: a manifest of every Worker, a deploy tool that ships only what changed in parallel stages, and a g1t Actions workflow61// 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
Running g1t yourself: the design, a docker compose proof, and a guide to what works today65/** Services that run, in the order Wrangler is given them (the site first). */
Deploys as code: a manifest of every Worker, a deploy tool that ships only what changed in parallel stages, and a g1t Actions workflow66export 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));
Running g1t yourself: the design, a docker compose proof, and a guide to what works today69
Deploys as code: a manifest of every Worker, a deploy tool that ships only what changed in parallel stages, and a g1t Actions workflow70/** What the off Worker calls each service that is off in phase 1. */
71const OFF_NAMES = {
Running g1t yourself: the design, a docker compose proof, and a guide to what works today72 "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",
Running g1t yourself: the design, a docker compose proof, and a guide to what works today75};
Deploys as code: a manifest of every Worker, a deploy tool that ships only what changed in parallel stages, and a g1t Actions workflow76const OFF = Object.fromEntries(
77 STACK.filter((unit) => unit.self_host === "off").map((unit) => [unit.worker, OFF_NAMES[unit.worker] ?? unit.worker]),
78);
Running g1t yourself: the design, a docker compose proof, and a guide to what works today79
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",
Running g1t yourself: the design, a docker compose proof, and a guide to what works today83 "g1t-actions": "ACTIONS_KEY",
84 "g1t-integrations": "INTEGRATIONS_KEY",
85 "g1t-webhooks": "WEBHOOKS_KEY",
86};
87
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look88/**
89 * g1t.sh's GitHub App is its own: an installation registers one of its
90 * own, or has none, and then no GitHub buttons appear. Its public settings
91 * replace the hosted vars; its secrets go only to the service that uses each.
92 */
93const GITHUB_VARS = ["GITHUB_APP_ID", "GITHUB_APP_SLUG", "GITHUB_APP_CLIENT_ID"];
94const GITHUB_SECRETS = {
95 "g1t-identity": ["GITHUB_APP_CLIENT_SECRET", "IDENTITY_KEY", "REGISTRATION_MODE"],
96 "g1t-integrations": ["GITHUB_APP_PRIVATE_KEY", "GITHUB_APP_WEBHOOK_SECRET"],
97};
98
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member99/**
100 * Services whose cron triggers scheduler.mjs runs here: sweeps and
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 * reminders that need nothing self-hosting lacks (the docs service's is
102 * 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 member103 * minute would start scheduled workflows with no runner to take them),
104 * billing (reconciles against Cloudflare and Stripe), deployments (calls
105 * Cloudflare's API) and the services that are off.
106 */
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.107const SELF_HOST_CRONS = new Set(["g1t-repos", "g1t-events", "g1t-identity", "g1t-security", "g1t-webhooks", "g1t-packages", "g1t-docs-service"]);
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member108
Running g1t yourself: the design, a docker compose proof, and a guide to what works today109/** Queues whose consumers are off: events stops sending to them. */
110const OFF_QUEUES = new Set(["g1t-events-runner", "g1t-events-context"]);
111
112/** Strips comments and trailing commas from JSONC. Strings are respected. */
113function parseJsonc(text) {
114 let result = "";
115 let inString = false;
116 for (let i = 0; i < text.length; i++) {
117 const char = text[i];
118 if (inString) {
119 result += char;
120 if (char === "\\") result += text[++i];
121 else if (char === '"') inString = false;
122 } else if (char === '"') {
123 inString = true;
124 result += char;
125 } else if (char === "/" && text[i + 1] === "/") {
126 while (i < text.length && text[i] !== "\n") i++;
127 result += "\n";
128 } else if (char === "/" && text[i + 1] === "*") {
129 i = text.indexOf("*/", i + 2) + 1;
130 } else {
131 result += char;
132 }
133 }
134 return JSON.parse(result.replace(/,(\s*[}\]])/g, "$1"));
135}
136
137const rel = (path) => relative(out, resolve(root, path)).replaceAll("\\", "/");
138
139function hostedUrl(value) {
Merge branch 'worktree-agent-aaf03bdceac799c89'140 return typeof value === "string"
141 ? value.replace(/https:\/\/api\.g1t\.sh/g, API_URL).replace(/https:\/\/g1t\.sh/g, PUBLIC_URL)
142 : value;
Running g1t yourself: the design, a docker compose proof, and a guide to what works today143}
144
145function selfHosted(service) {
146 const hosted = parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8"));
147 const config = {
148 name: hosted.name,
149 compatibility_date: hosted.compatibility_date,
150 compatibility_flags: hosted.compatibility_flags,
151 rules: hosted.rules,
152 vars: {},
153 };
154
155 if (service.web) {
156 // The site as React Router built it (apps/web/build), not its sources.
157 config.main = rel(`${service.dir}/build/server/index.js`);
158 config.no_bundle = true;
159 config.rules = [{ type: "ESModule", globs: ["**/*.js", "**/*.mjs"] }];
160 config.assets = { directory: rel(`${service.dir}/build/client`) };
161 } else {
162 config.main = rel(join(service.dir, hosted.main));
163 }
164
165 for (const [key, value] of Object.entries(hosted.vars ?? {})) config.vars[key] = hostedUrl(value);
166
167 if (hosted.d1_databases) {
168 config.d1_databases = hosted.d1_databases.map((db) => ({
169 binding: db.binding,
170 database_name: db.database_name,
171 database_id: db.database_id,
172 migrations_dir: rel(join(service.dir, db.migrations_dir ?? "migrations")),
173 }));
174 }
175 if (hosted.kv_namespaces) config.kv_namespaces = hosted.kv_namespaces.map(({ binding, id }) => ({ binding, id }));
176 if (hosted.triggers) config.triggers = hosted.triggers;
177
178 if (hosted.queues) {
179 config.queues = {};
180 if (hosted.queues.producers) {
181 config.queues.producers = hosted.queues.producers.filter((producer) => !OFF_QUEUES.has(producer.queue));
182 }
183 if (hosted.queues.consumers) config.queues.consumers = hosted.queues.consumers;
184 }
185
186 config.services = (hosted.services ?? []).map((binding) =>
187 OFF[binding.service] ? { binding: binding.binding, service: offName(binding.service) } : binding,
188 );
189
190 // Cloudflare-only bindings, and what stands in for them.
191 if (hosted.artifacts) {
192 for (const artifacts of hosted.artifacts) {
193 config.services.push({ binding: artifacts.binding, service: "g1t-artifacts" });
194 }
195 }
196 if (hosted.send_email) {
197 for (const email of hosted.send_email) config.services.push({ binding: email.name, service: "g1t-mail" });
198 }
199
200 // Secrets the hosted services hold, given here from the environment, each
201 // only to the service that uses it.
202 const secret = SECRETS[hosted.name];
203 if (secret && process.env[secret]) config.vars[secret] = process.env[secret];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look204 for (const name of GITHUB_VARS) {
205 if (name in config.vars) config.vars[name] = process.env[name] ?? "";
206 }
207 for (const name of GITHUB_SECRETS[hosted.name] ?? []) {
208 if (process.env[name]) config.vars[name] = process.env[name];
209 }
Running g1t yourself: the design, a docker compose proof, and a guide to what works today210
211 // Self-hosted g1t charges nothing: billing records usage at cost and never
212 // stops work for it.
213 if (hosted.name === "g1t-billing") config.vars.FREE_WHILE_BUILDING = "true";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look214 // Anyone may register on an installation of your own unless you set
215 // REGISTRATION_MODE=invite; invites then work as on g1t.sh, and the owners
216 // of INVITE_STAFF_WORKSPACES (yours, not g1t.sh's) invite without limit.
217 if (hosted.name === "g1t-identity") {
218 config.vars.REGISTRATION_MODE = process.env.REGISTRATION_MODE || "open";
219 config.vars.INVITE_STAFF_WORKSPACES = process.env.INVITE_STAFF_WORKSPACES ?? "";
220 if (process.env.INVITES_PER_USER) config.vars.INVITES_PER_USER = process.env.INVITES_PER_USER;
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas221 // Access requests are summarised to your own address, not g1t.sh's.
222 config.vars.WAITLIST_NOTIFY_EMAIL = process.env.WAITLIST_NOTIFY_EMAIL ?? "";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look223 }
Merge branch 'worktree-agent-af58ac8933b0dd125'224 // 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 member225 // store) instead of R2, with no request size limit, and package
226 // addresses start with this installation's host.
227 if (hosted.name === "g1t-packages") {
228 Object.assign(config.vars, {
229 BLOB_STORE: "s3",
Merge branch 'worktree-agent-af58ac8933b0dd125'230 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 member231 S3_BUCKET: process.env.S3_BUCKET ?? "g1t-packages",
232 S3_REGION: process.env.S3_REGION ?? "us-east-1",
233 S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
234 S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
235 S3_PUBLIC_ENDPOINT: process.env.S3_PUBLIC_ENDPOINT ?? "",
236 MAX_REQUEST_BYTES: "0",
237 STORAGE_LIMITS: "off",
238 REGISTRY_HOST: new URL(PUBLIC_URL).host,
239 PACKAGES_TOKEN_SECRET: process.env.PACKAGES_TOKEN_SECRET ?? "",
240 });
241 delete config.vars.R2_ACCOUNT_ID;
242 delete config.vars.R2_BUCKET;
243 }
Merge branch 'worktree-agent-aaf03bdceac799c89'244 // Where this installation is reached, for the links and addresses each
245 // shows: the site's clone URLs, meta tags and agent setup, the API's
246 // OAuth issuer and MCP server, and identity's mail. No social cards: the
247 // 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 address248 // Repository files and avatars: USERCONTENT_URL, a host of its own that
249 // reaches this same site, or, empty, a path on it (PUBLIC_URL/-/usercontent).
250 if (service.web) {
251 Object.assign(config.vars, { SITE_URL: PUBLIC_URL, API_URL, MCP_URL, OG_URL: "", USERCONTENT_URL: (process.env.USERCONTENT_URL ?? "").trim() });
252 }
Merge branch 'worktree-agent-aaf03bdceac799c89'253 if (hosted.name === "g1t-api") Object.assign(config.vars, { SITE_URL: PUBLIC_URL, API_URL, MCP_URL });
254 if (hosted.name === "g1t-identity") config.vars.SITE_URL = PUBLIC_URL;
Merge branch 'worktree-agent-ac5b181a013e54348'255 // Nightly backups' bundles go to a bucket of their own on the same
Merge branch 'worktree-agent-aaf03bdceac799c89'256 // S3-compatible store, instead of the BACKUPS R2 bucket, and so do the
257 // packs kept for fresh clones (src/pack_cache.rs), instead of GIT_PACKS.
Merge branch 'worktree-agent-ac5b181a013e54348'258 if (hosted.name === "g1t-repos") {
259 Object.assign(config.vars, {
260 BACKUP_STORE: "s3",
261 BACKUP_S3_BUCKET: process.env.BACKUP_S3_BUCKET ?? "g1t-backups",
Merge branch 'worktree-agent-aaf03bdceac799c89'262 PACK_STORE: "s3",
263 PACK_S3_BUCKET: process.env.PACK_S3_BUCKET ?? "g1t-git-packs",
Merge branch 'worktree-agent-af58ac8933b0dd125'264 S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000",
Merge branch 'worktree-agent-ac5b181a013e54348'265 S3_REGION: process.env.S3_REGION ?? "us-east-1",
266 S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
267 S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
268 });
269 }
Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store270 // Files people put in Docs pages go to a bucket of their own on the same
271 // S3-compatible store, instead of the FILES R2 bucket (services/docs
272 // src/files.ts, `s3FileStore`).
273 if (hosted.name === "g1t-docs-service") {
274 Object.assign(config.vars, {
275 DOCS_FILES: "s3",
276 DOCS_S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://rustfs:9000",
277 DOCS_S3_BUCKET: process.env.DOCS_S3_BUCKET ?? "g1t-docs-files",
278 DOCS_S3_REGION: process.env.S3_REGION ?? "us-east-1",
279 DOCS_S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "",
280 DOCS_S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "",
281 });
282 }
Running g1t yourself: the design, a docker compose proof, and a guide to what works today283 // Nothing to deploy to: deployments are off (no Cloudflare API token).
284 if (hosted.name === "g1t-deployments") delete config.vars.CUSTOM_HOSTNAMES_ZONE_ID;
285
286 return config;
287}
288
289function offName(service) {
290 return `${service}-off`;
291}
292
293function write(name, config) {
294 const path = join(out, `${name}.json`);
295 writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
296 return path;
297}
298
299const files = [];
300for (const service of RUNNING) files.push(write(service.name, selfHosted(service)));
301
302const compatibility_date = "2026-09-26";
303files.push(
304 write("g1t-artifacts", {
305 name: "g1t-artifacts",
306 main: rel("deploy/self-host/workers/artifacts/index.js"),
307 compatibility_date,
308 vars: {
309 GITSTORE_URL: process.env.GITSTORE_URL ?? "http://gitstore:8080",
310 GITSTORE_SECRET: process.env.GITSTORE_SECRET ?? "",
311 },
312 }),
313);
314files.push(
315 write("g1t-mail", {
316 name: "g1t-mail",
317 main: rel("deploy/self-host/workers/mail/index.js"),
318 compatibility_date,
319 vars: {
320 PUBLIC_URL,
321 MAIL_URL: process.env.MAIL_URL ?? "",
322 MAIL_FROM: process.env.MAIL_FROM ?? "",
323 },
324 }),
325);
326for (const [service, feature] of Object.entries(OFF)) {
327 files.push(
328 write(offName(service), {
329 name: offName(service),
330 main: rel("deploy/self-host/workers/off/index.js"),
331 compatibility_date,
332 vars: { OFF_NAME: feature },
333 }),
334 );
335}
336
337// The order Wrangler takes them in: the site first, as the one that serves.
338writeFileSync(join(out, "workers.txt"), `${files.map((file) => relative(out, file)).join("\n")}\n`);
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas339
Merge branch 'worktree-agent-aaf03bdceac799c89'340// The API (REST, MCP and OAuth) is served on a port of its own (API_PORT),
341// by a second `wrangler dev` (start.sh): not in workers.txt. Its service
342// bindings reach the Workers above through Wrangler's dev registry, as
343// any two `wrangler dev` sessions on one machine do.
344{
345 const api = STACK.find((unit) => unit.worker === "g1t-api");
346 write("g1t-api", selfHosted({ name: api.worker, dir: api.path, web: false }));
347 writeFileSync(join(out, "api.json"), `${JSON.stringify({ api: API_URL, mcp: MCP_URL }, null, 2)}\n`);
348}
349
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member350// What scheduler.mjs runs: each service's own crons, as hosted g1t's Cron
351// Triggers run them, for the services in SELF_HOST_CRONS.
352const schedules = RUNNING.filter((service) => SELF_HOST_CRONS.has(service.name))
353 .map((service) => ({
354 worker: service.name,
355 crons: parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8")).triggers?.crons ?? [],
356 }))
357 .filter((schedule) => schedule.crons.length > 0);
358writeFileSync(join(out, "schedules.json"), `${JSON.stringify(schedules, null, 2)}\n`);
359
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas360// The status page runs in a workerd of its own (status.sh, the `status`
361// service in docker-compose.yml), so it stays up when the site does not:
362// not in workers.txt. It checks the site from inside Compose
363// (STATUS_CHECK_URL) and links to it at PUBLIC_URL. Parts this
364// installation does not run (the API, MCP, docs, g1t.page, the model
365// proxy, billing) are left off its page; a public repository of yours in
366// STATUS_PROBE_REPO adds the git check.
367{
368 const hosted = parseJsonc(readFileSync(join(root, "apps/status/wrangler.jsonc"), "utf8"));
369 const db = hosted.d1_databases[0];
370 write("g1t-status", {
371 name: hosted.name,
372 main: rel(join("apps/status", hosted.main)),
373 compatibility_date: hosted.compatibility_date,
374 rules: hosted.rules,
375 triggers: hosted.triggers,
376 d1_databases: [
377 {
378 binding: db.binding,
379 database_name: db.database_name,
380 database_id: db.database_id,
381 migrations_dir: rel(join("apps/status", db.migrations_dir)),
382 },
383 ],
384 vars: {
385 SITE_URL: (process.env.STATUS_CHECK_URL ?? "http://g1t:8787").replace(/\/$/, ""),
386 PUBLIC_SITE_URL: PUBLIC_URL,
387 API_URL: "",
388 MCP_URL: "",
389 DOCS_URL: "",
390 PAGES_URL: "",
391 MODELS_URL: "",
392 PROBE_REPO: process.env.STATUS_PROBE_REPO ?? "",
393 SUPPORT_URL: `${PUBLIC_URL}/support`,
394 OG_IMAGE: "",
395 // Its own address, for links made outside a request. No email
396 // binding here: subscribing by email is off, the feeds work.
397 STATUS_URL: `http://localhost:${process.env.STATUS_PORT ?? "8788"}`,
398 STATUS_ALERT_EMAIL: "",
399 },
400 });
401}
Running g1t yourself: the design, a docker compose proof, and a guide to what works today402console.log(`Wrote ${files.length} configs to ${out}`);

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