Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 11 additions & 0 deletions src/selfhost/setup-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ which are written to a file for you to load — then restart the container.</p>
</body></html>`;
}

/** 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 `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Gittensory self-host setup</title></head>
<body style="font-family:system-ui;max-width:40rem;margin:4rem auto;padding:0 1rem">
<h1>Gittensory self-host — brokered mode</h1>
<p>This instance is configured for the <strong>central Gittensory Orb App</strong> (<code>ORB_ENROLLMENT_SECRET</code> is set), so there is <strong>no GitHub App to create here</strong> — installation tokens are brokered from the Orb on demand.</p>
<p>To onboard: install the Gittensory Orb App on your repositories and complete enrollment to obtain your <code>ORB_ENROLLMENT_SECRET</code>. No further setup is needed on this page.</p>
</body></html>`;
}

/** 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");
Expand Down
10 changes: 10 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -244,6 +246,14 @@ async function main(): Promise<void> {
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;
Expand Down
8 changes: 8 additions & 0 deletions test/unit/selfhost-setup-wizard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
credentialsToEnv,
exchangeManifestCode,
isValidSetupAuthCookie,
renderBrokeredSetupPage,
renderSetupPage,
renderTokenEntryPage,
setupAuthCookieValue,
Expand Down Expand Up @@ -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);
Expand Down
Loading