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 today | 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; | |
| 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; | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 12 | // - the packages service keeps files in S3-compatible storage (MinIO) |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 13 | // instead of R2, and the repos service its nightly backups (a bucket of |
| 14 | // their own, BACKUP_S3_BUCKET); | |
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 15 | // - services that are off in this phase (agents, the context hub, the |
| 16 | // g1t.page dispatcher, model proxy) are bound to workers/off instead, and | |
| 17 | // events stop queueing work for them; | |
| 18 | // - URLs that name g1t.sh name PUBLIC_URL instead, and billing is free. | |
| 19 | // | |
| 20 | // Usage: node configs.mjs [outDir] | |
| 21 | // Environment: PUBLIC_URL, GITSTORE_URL, GITSTORE_SECRET, MAIL_URL, | |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 22 | // ACTIONS_KEY, INTEGRATIONS_KEY, WEBHOOKS_KEY, IDENTITY_KEY, |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 23 | // PACKAGES_TOKEN_SECRET, S3_ENDPOINT, S3_BUCKET, BACKUP_S3_BUCKET, S3_REGION, |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 24 | // 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 look | 25 | // your own GitHub App: GITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_APP_CLIENT_ID, |
| 26 | // 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 today | 27 | // |
| 28 | // The output is for `wrangler dev` (see start.sh): every Worker in one | |
| 29 | // workerd, the site first, with D1, KV and Queues kept on disk. | |
| 30 | ||
| 31 | import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; | |
| 32 | import { dirname, join, relative, resolve } from "node:path"; | |
| 33 | import { fileURLToPath } from "node:url"; | |
| 34 | ||
| 35 | const here = dirname(fileURLToPath(import.meta.url)); | |
| 36 | const root = resolve(here, "../.."); | |
| 37 | const out = resolve(process.argv[2] ?? join(here, ".generated")); | |
| 38 | mkdirSync(out, { recursive: true }); | |
| 39 | ||
| 40 | const PUBLIC_URL = (process.env.PUBLIC_URL ?? "http://localhost:8787").replace(/\/$/, ""); | |
| 41 | ||
| Self-hosting: the clone pack cache on S3 (MinIO, expiring), the API and MCP on their own port with PUBLIC_URL-derived addresses across the site, API and mail, scheduler --once, and smoke.sh covering pull requests from branches and forks and the merge queue | 42 | /** |
| 43 | * Where the API (REST, OAuth) is reached: API_URL, or PUBLIC_URL's host on | |
| 44 | * API_PORT (8789). The MCP server is a path on it: MCP_URL, or | |
| 45 | * `<API_URL>/mcp`. The API's OAuth issuer is API_URL. | |
| 46 | */ | |
| 47 | export function apiAddresses(env = process.env, publicUrl = PUBLIC_URL) { | |
| 48 | let api = (env.API_URL ?? "").trim().replace(/\/$/, ""); | |
| 49 | if (!api) { | |
| 50 | const url = new URL(publicUrl); | |
| 51 | url.port = env.API_PORT || "8789"; | |
| 52 | api = url.origin; | |
| 53 | } | |
| 54 | const mcp = (env.MCP_URL ?? "").trim().replace(/\/$/, "") || `${api}/mcp`; | |
| 55 | return { api, mcp }; | |
| 56 | } | |
| 57 | const { api: API_URL, mcp: MCP_URL } = apiAddresses(); | |
| 58 | ||
| Deploys as code: a manifest of every Worker, a deploy tool that ships only what changed in parallel stages, and a g1t Actions workflow | 59 | // What runs, and what is off, is each unit's `self_host` in |
| 60 | // deploy/stack.jsonc: the list hosted g1t deploys from. | |
| 61 | const STACK = Object.values(parseJsonc(readFileSync(join(root, "deploy/stack.jsonc"), "utf8")).units); | |
| 62 | ||
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 63 | /** 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 workflow | 64 | export const RUNNING = STACK.filter((unit) => unit.self_host === "run") |
| 65 | .map((unit) => ({ name: unit.worker, dir: unit.path, web: unit.kind === "react-router" })) | |
| 66 | .sort((a, b) => Number(b.web) - Number(a.web)); | |
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 67 | |
| Deploys as code: a manifest of every Worker, a deploy tool that ships only what changed in parallel stages, and a g1t Actions workflow | 68 | /** What the off Worker calls each service that is off in phase 1. */ |
| 69 | const OFF_NAMES = { | |
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 70 | "g1t-runner": "Agents", |
| 71 | "g1t-context": "Context search and memory", | |
| 72 | }; | |
| Deploys as code: a manifest of every Worker, a deploy tool that ships only what changed in parallel stages, and a g1t Actions workflow | 73 | const OFF = Object.fromEntries( |
| 74 | STACK.filter((unit) => unit.self_host === "off").map((unit) => [unit.worker, OFF_NAMES[unit.worker] ?? unit.worker]), | |
| 75 | ); | |
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 76 | |
| 77 | /** Sealing keys, by the service that holds each (hosted: Wrangler secrets). */ | |
| 78 | const SECRETS = { | |
| 79 | "g1t-actions": "ACTIONS_KEY", | |
| 80 | "g1t-integrations": "INTEGRATIONS_KEY", | |
| 81 | "g1t-webhooks": "WEBHOOKS_KEY", | |
| 82 | }; | |
| 83 | ||
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 84 | /** |
| 85 | * g1t.sh's GitHub App is its own: an installation registers one of its | |
| 86 | * own, or has none, and then no GitHub buttons appear. Its public settings | |
| 87 | * replace the hosted vars; its secrets go only to the service that uses each. | |
| 88 | */ | |
| 89 | const GITHUB_VARS = ["GITHUB_APP_ID", "GITHUB_APP_SLUG", "GITHUB_APP_CLIENT_ID"]; | |
| 90 | const GITHUB_SECRETS = { | |
| 91 | "g1t-identity": ["GITHUB_APP_CLIENT_SECRET", "IDENTITY_KEY", "REGISTRATION_MODE"], | |
| 92 | "g1t-integrations": ["GITHUB_APP_PRIVATE_KEY", "GITHUB_APP_WEBHOOK_SECRET"], | |
| 93 | }; | |
| 94 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 95 | /** |
| 96 | * Services whose cron triggers scheduler.mjs runs here: sweeps and | |
| 97 | * reminders that need nothing self-hosting lacks. Not run: actions (its | |
| 98 | * minute would start scheduled workflows with no runner to take them), | |
| 99 | * billing (reconciles against Cloudflare and Stripe), deployments (calls | |
| 100 | * Cloudflare's API) and the services that are off. | |
| 101 | */ | |
| 102 | const SELF_HOST_CRONS = new Set(["g1t-repos", "g1t-events", "g1t-identity", "g1t-security", "g1t-webhooks", "g1t-packages"]); | |
| 103 | ||
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 104 | /** Queues whose consumers are off: events stops sending to them. */ |
| 105 | const OFF_QUEUES = new Set(["g1t-events-runner", "g1t-events-context"]); | |
| 106 | ||
| 107 | /** Strips comments and trailing commas from JSONC. Strings are respected. */ | |
| 108 | function parseJsonc(text) { | |
| 109 | let result = ""; | |
| 110 | let inString = false; | |
| 111 | for (let i = 0; i < text.length; i++) { | |
| 112 | const char = text[i]; | |
| 113 | if (inString) { | |
| 114 | result += char; | |
| 115 | if (char === "\\") result += text[++i]; | |
| 116 | else if (char === '"') inString = false; | |
| 117 | } else if (char === '"') { | |
| 118 | inString = true; | |
| 119 | result += char; | |
| 120 | } else if (char === "/" && text[i + 1] === "/") { | |
| 121 | while (i < text.length && text[i] !== "\n") i++; | |
| 122 | result += "\n"; | |
| 123 | } else if (char === "/" && text[i + 1] === "*") { | |
| 124 | i = text.indexOf("*/", i + 2) + 1; | |
| 125 | } else { | |
| 126 | result += char; | |
| 127 | } | |
| 128 | } | |
| 129 | return JSON.parse(result.replace(/,(\s*[}\]])/g, "$1")); | |
| 130 | } | |
| 131 | ||
| 132 | const rel = (path) => relative(out, resolve(root, path)).replaceAll("\\", "/"); | |
| 133 | ||
| 134 | function hostedUrl(value) { | |
| Self-hosting: the clone pack cache on S3 (MinIO, expiring), the API and MCP on their own port with PUBLIC_URL-derived addresses across the site, API and mail, scheduler --once, and smoke.sh covering pull requests from branches and forks and the merge queue | 135 | return typeof value === "string" |
| 136 | ? value.replace(/https:\/\/api\.g1t\.sh/g, API_URL).replace(/https:\/\/g1t\.sh/g, PUBLIC_URL) | |
| 137 | : value; | |
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 138 | } |
| 139 | ||
| 140 | function selfHosted(service) { | |
| 141 | const hosted = parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8")); | |
| 142 | const config = { | |
| 143 | name: hosted.name, | |
| 144 | compatibility_date: hosted.compatibility_date, | |
| 145 | compatibility_flags: hosted.compatibility_flags, | |
| 146 | rules: hosted.rules, | |
| 147 | vars: {}, | |
| 148 | }; | |
| 149 | ||
| 150 | if (service.web) { | |
| 151 | // The site as React Router built it (apps/web/build), not its sources. | |
| 152 | config.main = rel(`${service.dir}/build/server/index.js`); | |
| 153 | config.no_bundle = true; | |
| 154 | config.rules = [{ type: "ESModule", globs: ["**/*.js", "**/*.mjs"] }]; | |
| 155 | config.assets = { directory: rel(`${service.dir}/build/client`) }; | |
| 156 | } else { | |
| 157 | config.main = rel(join(service.dir, hosted.main)); | |
| 158 | } | |
| 159 | ||
| 160 | for (const [key, value] of Object.entries(hosted.vars ?? {})) config.vars[key] = hostedUrl(value); | |
| 161 | ||
| 162 | if (hosted.d1_databases) { | |
| 163 | config.d1_databases = hosted.d1_databases.map((db) => ({ | |
| 164 | binding: db.binding, | |
| 165 | database_name: db.database_name, | |
| 166 | database_id: db.database_id, | |
| 167 | migrations_dir: rel(join(service.dir, db.migrations_dir ?? "migrations")), | |
| 168 | })); | |
| 169 | } | |
| 170 | if (hosted.kv_namespaces) config.kv_namespaces = hosted.kv_namespaces.map(({ binding, id }) => ({ binding, id })); | |
| 171 | if (hosted.triggers) config.triggers = hosted.triggers; | |
| 172 | ||
| 173 | if (hosted.queues) { | |
| 174 | config.queues = {}; | |
| 175 | if (hosted.queues.producers) { | |
| 176 | config.queues.producers = hosted.queues.producers.filter((producer) => !OFF_QUEUES.has(producer.queue)); | |
| 177 | } | |
| 178 | if (hosted.queues.consumers) config.queues.consumers = hosted.queues.consumers; | |
| 179 | } | |
| 180 | ||
| 181 | config.services = (hosted.services ?? []).map((binding) => | |
| 182 | OFF[binding.service] ? { binding: binding.binding, service: offName(binding.service) } : binding, | |
| 183 | ); | |
| 184 | ||
| 185 | // Cloudflare-only bindings, and what stands in for them. | |
| 186 | if (hosted.artifacts) { | |
| 187 | for (const artifacts of hosted.artifacts) { | |
| 188 | config.services.push({ binding: artifacts.binding, service: "g1t-artifacts" }); | |
| 189 | } | |
| 190 | } | |
| 191 | if (hosted.send_email) { | |
| 192 | for (const email of hosted.send_email) config.services.push({ binding: email.name, service: "g1t-mail" }); | |
| 193 | } | |
| 194 | ||
| 195 | // Secrets the hosted services hold, given here from the environment, each | |
| 196 | // only to the service that uses it. | |
| 197 | const secret = SECRETS[hosted.name]; | |
| 198 | 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 look | 199 | for (const name of GITHUB_VARS) { |
| 200 | if (name in config.vars) config.vars[name] = process.env[name] ?? ""; | |
| 201 | } | |
| 202 | for (const name of GITHUB_SECRETS[hosted.name] ?? []) { | |
| 203 | if (process.env[name]) config.vars[name] = process.env[name]; | |
| 204 | } | |
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 205 | |
| 206 | // Self-hosted g1t charges nothing: billing records usage at cost and never | |
| 207 | // stops work for it. | |
| 208 | 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 look | 209 | // Anyone may register on an installation of your own unless you set |
| 210 | // REGISTRATION_MODE=invite; invites then work as on g1t.sh, and the owners | |
| 211 | // of INVITE_STAFF_WORKSPACES (yours, not g1t.sh's) invite without limit. | |
| 212 | if (hosted.name === "g1t-identity") { | |
| 213 | config.vars.REGISTRATION_MODE = process.env.REGISTRATION_MODE || "open"; | |
| 214 | config.vars.INVITE_STAFF_WORKSPACES = process.env.INVITE_STAFF_WORKSPACES ?? ""; | |
| 215 | 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 quotas | 216 | // Access requests are summarised to your own address, not g1t.sh's. |
| 217 | 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 look | 218 | } |
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 219 | // Packages' files go to the compose file's MinIO (or any S3-compatible |
| 220 | // store) instead of R2, with no request size limit, and package | |
| 221 | // addresses start with this installation's host. | |
| 222 | if (hosted.name === "g1t-packages") { | |
| 223 | Object.assign(config.vars, { | |
| 224 | BLOB_STORE: "s3", | |
| 225 | S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://minio:9000", | |
| 226 | S3_BUCKET: process.env.S3_BUCKET ?? "g1t-packages", | |
| 227 | S3_REGION: process.env.S3_REGION ?? "us-east-1", | |
| 228 | S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "", | |
| 229 | S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "", | |
| 230 | S3_PUBLIC_ENDPOINT: process.env.S3_PUBLIC_ENDPOINT ?? "", | |
| 231 | MAX_REQUEST_BYTES: "0", | |
| 232 | STORAGE_LIMITS: "off", | |
| 233 | REGISTRY_HOST: new URL(PUBLIC_URL).host, | |
| 234 | PACKAGES_TOKEN_SECRET: process.env.PACKAGES_TOKEN_SECRET ?? "", | |
| 235 | }); | |
| 236 | delete config.vars.R2_ACCOUNT_ID; | |
| 237 | delete config.vars.R2_BUCKET; | |
| 238 | } | |
| Self-hosting: the clone pack cache on S3 (MinIO, expiring), the API and MCP on their own port with PUBLIC_URL-derived addresses across the site, API and mail, scheduler --once, and smoke.sh covering pull requests from branches and forks and the merge queue | 239 | // Where this installation is reached, for the links and addresses each |
| 240 | // shows: the site's clone URLs, meta tags and agent setup, the API's | |
| 241 | // OAuth issuer and MCP server, and identity's mail. No social cards: the | |
| 242 | // card service (services/og) is not run here. | |
| 243 | if (service.web) Object.assign(config.vars, { SITE_URL: PUBLIC_URL, API_URL, MCP_URL, OG_URL: "" }); | |
| 244 | if (hosted.name === "g1t-api") Object.assign(config.vars, { SITE_URL: PUBLIC_URL, API_URL, MCP_URL }); | |
| 245 | if (hosted.name === "g1t-identity") config.vars.SITE_URL = PUBLIC_URL; | |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 246 | // Nightly backups' bundles go to a bucket of their own on the same |
| Self-hosting: the clone pack cache on S3 (MinIO, expiring), the API and MCP on their own port with PUBLIC_URL-derived addresses across the site, API and mail, scheduler --once, and smoke.sh covering pull requests from branches and forks and the merge queue | 247 | // S3-compatible store, instead of the BACKUPS R2 bucket, and so do the |
| 248 | // packs kept for fresh clones (src/pack_cache.rs), instead of GIT_PACKS. | |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 249 | if (hosted.name === "g1t-repos") { |
| 250 | Object.assign(config.vars, { | |
| 251 | BACKUP_STORE: "s3", | |
| 252 | BACKUP_S3_BUCKET: process.env.BACKUP_S3_BUCKET ?? "g1t-backups", | |
| Self-hosting: the clone pack cache on S3 (MinIO, expiring), the API and MCP on their own port with PUBLIC_URL-derived addresses across the site, API and mail, scheduler --once, and smoke.sh covering pull requests from branches and forks and the merge queue | 253 | PACK_STORE: "s3", |
| 254 | PACK_S3_BUCKET: process.env.PACK_S3_BUCKET ?? "g1t-git-packs", | |
| Merge branch 'worktree-agent-ac5b181a013e54348' | 255 | S3_ENDPOINT: process.env.S3_ENDPOINT ?? "http://minio:9000", |
| 256 | S3_REGION: process.env.S3_REGION ?? "us-east-1", | |
| 257 | S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID ?? "", | |
| 258 | S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY ?? "", | |
| 259 | }); | |
| 260 | } | |
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 261 | // Nothing to deploy to: deployments are off (no Cloudflare API token). |
| 262 | if (hosted.name === "g1t-deployments") delete config.vars.CUSTOM_HOSTNAMES_ZONE_ID; | |
| 263 | ||
| 264 | return config; | |
| 265 | } | |
| 266 | ||
| 267 | function offName(service) { | |
| 268 | return `${service}-off`; | |
| 269 | } | |
| 270 | ||
| 271 | function write(name, config) { | |
| 272 | const path = join(out, `${name}.json`); | |
| 273 | writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`); | |
| 274 | return path; | |
| 275 | } | |
| 276 | ||
| 277 | const files = []; | |
| 278 | for (const service of RUNNING) files.push(write(service.name, selfHosted(service))); | |
| 279 | ||
| 280 | const compatibility_date = "2026-09-26"; | |
| 281 | files.push( | |
| 282 | write("g1t-artifacts", { | |
| 283 | name: "g1t-artifacts", | |
| 284 | main: rel("deploy/self-host/workers/artifacts/index.js"), | |
| 285 | compatibility_date, | |
| 286 | vars: { | |
| 287 | GITSTORE_URL: process.env.GITSTORE_URL ?? "http://gitstore:8080", | |
| 288 | GITSTORE_SECRET: process.env.GITSTORE_SECRET ?? "", | |
| 289 | }, | |
| 290 | }), | |
| 291 | ); | |
| 292 | files.push( | |
| 293 | write("g1t-mail", { | |
| 294 | name: "g1t-mail", | |
| 295 | main: rel("deploy/self-host/workers/mail/index.js"), | |
| 296 | compatibility_date, | |
| 297 | vars: { | |
| 298 | PUBLIC_URL, | |
| 299 | MAIL_URL: process.env.MAIL_URL ?? "", | |
| 300 | MAIL_FROM: process.env.MAIL_FROM ?? "", | |
| 301 | }, | |
| 302 | }), | |
| 303 | ); | |
| 304 | for (const [service, feature] of Object.entries(OFF)) { | |
| 305 | files.push( | |
| 306 | write(offName(service), { | |
| 307 | name: offName(service), | |
| 308 | main: rel("deploy/self-host/workers/off/index.js"), | |
| 309 | compatibility_date, | |
| 310 | vars: { OFF_NAME: feature }, | |
| 311 | }), | |
| 312 | ); | |
| 313 | } | |
| 314 | ||
| 315 | // The order Wrangler takes them in: the site first, as the one that serves. | |
| 316 | writeFileSync(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 quotas | 317 | |
| Self-hosting: the clone pack cache on S3 (MinIO, expiring), the API and MCP on their own port with PUBLIC_URL-derived addresses across the site, API and mail, scheduler --once, and smoke.sh covering pull requests from branches and forks and the merge queue | 318 | // The API (REST, MCP and OAuth) is served on a port of its own (API_PORT), |
| 319 | // by a second `wrangler dev` (start.sh): not in workers.txt. Its service | |
| 320 | // bindings reach the Workers above through Wrangler's dev registry, as | |
| 321 | // any two `wrangler dev` sessions on one machine do. | |
| 322 | { | |
| 323 | const api = STACK.find((unit) => unit.worker === "g1t-api"); | |
| 324 | write("g1t-api", selfHosted({ name: api.worker, dir: api.path, web: false })); | |
| 325 | writeFileSync(join(out, "api.json"), `${JSON.stringify({ api: API_URL, mcp: MCP_URL }, null, 2)}\n`); | |
| 326 | } | |
| 327 | ||
| Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member | 328 | // What scheduler.mjs runs: each service's own crons, as hosted g1t's Cron |
| 329 | // Triggers run them, for the services in SELF_HOST_CRONS. | |
| 330 | const schedules = RUNNING.filter((service) => SELF_HOST_CRONS.has(service.name)) | |
| 331 | .map((service) => ({ | |
| 332 | worker: service.name, | |
| 333 | crons: parseJsonc(readFileSync(join(root, service.dir, "wrangler.jsonc"), "utf8")).triggers?.crons ?? [], | |
| 334 | })) | |
| 335 | .filter((schedule) => schedule.crons.length > 0); | |
| 336 | writeFileSync(join(out, "schedules.json"), `${JSON.stringify(schedules, null, 2)}\n`); | |
| 337 | ||
| status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas | 338 | // The status page runs in a workerd of its own (status.sh, the `status` |
| 339 | // service in docker-compose.yml), so it stays up when the site does not: | |
| 340 | // not in workers.txt. It checks the site from inside Compose | |
| 341 | // (STATUS_CHECK_URL) and links to it at PUBLIC_URL. Parts this | |
| 342 | // installation does not run (the API, MCP, docs, g1t.page, the model | |
| 343 | // proxy, billing) are left off its page; a public repository of yours in | |
| 344 | // STATUS_PROBE_REPO adds the git check. | |
| 345 | { | |
| 346 | const hosted = parseJsonc(readFileSync(join(root, "apps/status/wrangler.jsonc"), "utf8")); | |
| 347 | const db = hosted.d1_databases[0]; | |
| 348 | write("g1t-status", { | |
| 349 | name: hosted.name, | |
| 350 | main: rel(join("apps/status", hosted.main)), | |
| 351 | compatibility_date: hosted.compatibility_date, | |
| 352 | rules: hosted.rules, | |
| 353 | triggers: hosted.triggers, | |
| 354 | d1_databases: [ | |
| 355 | { | |
| 356 | binding: db.binding, | |
| 357 | database_name: db.database_name, | |
| 358 | database_id: db.database_id, | |
| 359 | migrations_dir: rel(join("apps/status", db.migrations_dir)), | |
| 360 | }, | |
| 361 | ], | |
| 362 | vars: { | |
| 363 | SITE_URL: (process.env.STATUS_CHECK_URL ?? "http://g1t:8787").replace(/\/$/, ""), | |
| 364 | PUBLIC_SITE_URL: PUBLIC_URL, | |
| 365 | API_URL: "", | |
| 366 | MCP_URL: "", | |
| 367 | DOCS_URL: "", | |
| 368 | PAGES_URL: "", | |
| 369 | MODELS_URL: "", | |
| 370 | PROBE_REPO: process.env.STATUS_PROBE_REPO ?? "", | |
| 371 | SUPPORT_URL: `${PUBLIC_URL}/support`, | |
| 372 | OG_IMAGE: "", | |
| 373 | // Its own address, for links made outside a request. No email | |
| 374 | // binding here: subscribing by email is off, the feeds work. | |
| 375 | STATUS_URL: `http://localhost:${process.env.STATUS_PORT ?? "8788"}`, | |
| 376 | STATUS_ALERT_EMAIL: "", | |
| 377 | }, | |
| 378 | }); | |
| 379 | } | |
| Running g1t yourself: the design, a docker compose proof, and a guide to what works today | 380 | console.log(`Wrote ${files.length} configs to ${out}`); |
This file's history is long; its oldest lines are credited to the oldest commit read.