diff --git a/docker-compose.yml b/docker-compose.yml index 680036af9d..c79f475ef7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,6 +51,10 @@ services: # Uncomment for Ollama AI (--profile ollama): # AI_PROVIDER: ollama # AI_BASE_URL: http://ollama:11434/v1 + # BROKERED mode — use the central Gittensory Orb App instead of creating your own GitHub App. Install the + # Orb App on your repos + set ORB_ENROLLMENT_SECRET in .env (loaded above); the engine then brokers + # short-lived GitHub tokens from the Orb on demand (no own App private key). See .env.example. + # ORB_BROKER_URL: https://gittensory-api.aethereal.dev # override only for a private Orb deployment volumes: - gittensory-data:/data depends_on: diff --git a/src/selfhost/setup-wizard.ts b/src/selfhost/setup-wizard.ts index 3a4a09de2e..2ee0c6ad03 100644 --- a/src/selfhost/setup-wizard.ts +++ b/src/selfhost/setup-wizard.ts @@ -53,6 +53,17 @@ which are written to a file for you to load — then restart the container.

`; } +/** Setup page shown in BROKERED mode (ORB_ENROLLMENT_SECRET is set): there is no own GitHub App to create — + * the central Gittensory Orb App provides installation tokens on demand via the enrollment secret. */ +export function renderBrokeredSetupPage(): string { + return `Gittensory self-host setup + +

Gittensory self-host — brokered mode

+

This instance is configured for the central Gittensory Orb App (ORB_ENROLLMENT_SECRET is set), so there is no GitHub App to create here — installation tokens are brokered from the Orb on demand.

+

To onboard: install the Gittensory Orb App on your repositories and complete enrollment to obtain your ORB_ENROLLMENT_SECRET. No further setup is needed on this page.

+`; +} + /** Signed cookie value proving the setup flow was started by someone who knows the operator token. */ export function setupAuthCookieValue(secret: string, state: string): string { const mac = createHmac("sha256", secret).update(state).digest("base64url"); diff --git a/src/server.ts b/src/server.ts index eec227a28b..579b1d6380 100644 --- a/src/server.ts +++ b/src/server.ts @@ -18,11 +18,13 @@ import { credentialsToEnv, exchangeManifestCode, isValidSetupAuthCookie, + renderBrokeredSetupPage, renderSetupPage, renderTokenEntryPage, setupAuthCookieValue, timingSafeStrEqual, } from "./selfhost/setup-wizard"; +import { isOrbBrokerMode } from "./orb/broker-client"; import { exportOrbBatch } from "./selfhost/orb-collector"; import { createD1Adapter, nodeSqliteDriver } from "./selfhost/d1-adapter"; import { readiness } from "./selfhost/health"; @@ -244,6 +246,14 @@ async function main(): Promise { return new Response(JSON.stringify(r), { status: r.ok ? 200 : 503, headers: { "content-type": "application/json" } }); } if (path === "/metrics") return new Response(await renderMetrics(), { headers: { "content-type": "text/plain; version=0.0.4" } }); + // Brokered mode (ORB_ENROLLMENT_SECRET set): the central Orb App provides credentials on demand, so + // there is no own GitHub App to create — short-circuit the setup wizard to a brokered-mode page rather + // than walking the operator through (and overriding with) an own-App setup they don't need. + if ((path === "/setup" || path === "/setup/callback") && isOrbBrokerMode({ ORB_ENROLLMENT_SECRET: process.env.ORB_ENROLLMENT_SECRET })) { + return new Response(renderBrokeredSetupPage(), { + headers: { "content-type": "text/html; charset=utf-8", "Referrer-Policy": "no-referrer" }, + }); + } // First-run GitHub App setup wizard — only while no App is configured (can't rebind a live install). if ((path === "/setup" || path === "/setup/callback") && !process.env.GITHUB_APP_ID) { const setupToken = process.env.SELFHOST_SETUP_TOKEN; diff --git a/test/unit/selfhost-setup-wizard.test.ts b/test/unit/selfhost-setup-wizard.test.ts index e259f40926..4b627ecc04 100644 --- a/test/unit/selfhost-setup-wizard.test.ts +++ b/test/unit/selfhost-setup-wizard.test.ts @@ -5,6 +5,7 @@ import { credentialsToEnv, exchangeManifestCode, isValidSetupAuthCookie, + renderBrokeredSetupPage, renderSetupPage, renderTokenEntryPage, setupAuthCookieValue, @@ -34,6 +35,13 @@ describe("setup-wizard (#981 GitHub App Manifest)", () => { expect(html).toContain("nonce-abc"); // state is baked into the manifest value }); + it("renders a brokered-mode page that does NOT create a GitHub App", () => { + const html = renderBrokeredSetupPage(); + expect(html).toContain("brokered mode"); + expect(html).toContain("ORB_ENROLLMENT_SECRET"); + expect(html).not.toContain("github.com/settings/apps/new"); // no own-App creation form in brokered mode + }); + it("signs the setup cookie so only token-authorized setup visits can finish the callback", () => { const cookie = setupAuthCookieValue("operator-token", "nonce-abc"); expect(isValidSetupAuthCookie("operator-token", "nonce-abc", cookie)).toBe(true);