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
34 changes: 30 additions & 4 deletions apps/gittensory-ui/src/routes/docs.self-hosting-github-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,37 @@ GITHUB_WEBHOOK_SECRET=<same-secret-configured-on-the-app>`}
<CodeBlock
filename=".env"
code={`ORB_ENROLLMENT_SECRET=<issued-once-by-orb>
ORB_BROKER_URL=https://gittensory-api.aethereal.dev`}
ORB_BROKER_URL=https://gittensory-api.aethereal.dev
ORB_RELAY_MODE=pull # or omit for push (the default) -- see "Choosing a relay mode" below`}
/>

<h2>Choosing a relay mode: pull vs. push</h2>
<p>
Brokered mode still needs a way for GitHub webhook events to reach your self-host through
the broker. <code>ORB_RELAY_MODE</code> picks how:
</p>
<FeatureRow
items={[
{
title: "pull (recommended for NAT/tailnet — no public ingress needed)",
description:
"The container polls the broker outbound on a short interval and drains queued events -- no inbound endpoint is ever exposed, and PUBLIC_API_ORIGIN is not required. A failed registration attempt is non-fatal (logged as a warning, not an error): the drain loop keeps retrying on its own schedule and events still arrive once it succeeds.",
},
{
title: "push (the default — requires a stable public origin)",
description:
"The broker calls your self-host directly at PUBLIC_API_ORIGIN, which must be a real, internet-reachable, TLS-terminated URL -- the broker validates it server-side at registration time and rejects a loopback or private address outright. A failed registration is fatal: the container looks healthy but never receives an event, since there's no fallback delivery path.",
},
]}
/>
<Callout variant="note">
Brokered mode is useful when the self-host should not hold a GitHub App private key. It
still needs a reachable webhook path or relay mode, depending on the network setup.
If you're not behind a stable public ingress — a home connection, a NAT without port
forwarding, a tailnet-only deployment — set <code>ORB_RELAY_MODE=pull</code>. It needs no
DNS record, TLS certificate, or firewall rule of its own, and tolerates a transient broker
outage more gracefully (see the release checklist's known-warnings table below). Use push
only once you already have a stable, publicly reachable HTTPS origin for this instance — the
Direct App setup wizard, for instance, always requires one anyway, so an operator running
Direct App today has it available for brokered push mode too.
</Callout>
<Callout variant="warn" title="Brokered mode operational risks">
Before enabling this for anyone outside a controlled managed-beta cohort, weigh: (1){" "}
Expand Down Expand Up @@ -240,7 +266,7 @@ ORB_BROKER_URL=https://gittensory-api.aethereal.dev`}
See <Link to="/docs/self-hosting-troubleshooting">Troubleshooting</Link> for what a degraded
brokered relay looks like in logs today, and{" "}
<Link to="/docs/self-hosting-release-checklist">the beta release checklist</Link>'s
brokered-mode scenario for the smoke test that exercises this path.
brokered-mode scenario for the smoke tests that exercise both relay modes.
</p>

<h2>Webhook checks</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,31 @@ SELFHOST_SMOKE_FORBID_EVENTS="selfhost_orb_export_error,selfhost_orb_relay_regis
<h2>2. Brokered mode (private / managed-beta only)</h2>
<p>
<code>ORB_ENROLLMENT_SECRET</code> set — the container gets tokens from the central Orb
instead of its own App key. A working push-mode registration logs{" "}
<code>selfhost_orb_relay_register</code>; a broken one is fatal for push mode (logged at{" "}
<code>error</code>, not <code>warn</code>).
instead of its own App key. Relay mode changes what "working" means: push mode (
<code>ORB_RELAY_MODE</code> unset, the default) needs a real public{" "}
<code>PUBLIC_API_ORIGIN</code> and a failed registration is release-blocking (logged at{" "}
<code>error</code>); pull mode (<code>ORB_RELAY_MODE=pull</code>) needs no inbound endpoint
at all and tolerates a failed registration (logged at <code>warn</code>) since the drain
loop keeps retrying regardless. Run BOTH scenarios — they exercise genuinely different code
paths, not just different env (see{" "}
<Link to="/docs/self-hosting-github-app">choosing a relay mode</Link>).
</p>
<CodeBlock
lang="bash"
code={`SELFHOST_SMOKE_EXTRA_ENV="ORB_ENROLLMENT_SECRET=\${TEST_ENROLLMENT_SECRET}
code={`# Push mode (default) -- requires a real, internet-reachable PUBLIC_API_ORIGIN; the Orb
# SSRF-validates it server-side at registration time, so a loopback/private origin is rejected.
SELFHOST_SMOKE_EXTRA_ENV="ORB_ENROLLMENT_SECRET=\${TEST_ENROLLMENT_SECRET}
PUBLIC_API_ORIGIN=https://selfhost-smoke.example" \\
SELFHOST_SMOKE_EXPECT_EVENTS="selfhost_orb_relay_register" \\
SELFHOST_SMOKE_FORBID_EVENTS="selfhost_orb_relay_register_failed" \\
./scripts/smoke-selfhost.sh gittensory:rc-candidate

# Pull mode -- no PUBLIC_API_ORIGIN needed; the container polls the broker outbound instead of
# exposing an inbound endpoint. The right fit for NAT/tailnet operators with no public ingress.
SELFHOST_SMOKE_EXTRA_ENV="ORB_ENROLLMENT_SECRET=\${TEST_ENROLLMENT_SECRET}
ORB_RELAY_MODE=pull" \\
SELFHOST_SMOKE_EXPECT_EVENTS="selfhost_orb_relay_register" \\
SELFHOST_SMOKE_FORBID_EVENTS="selfhost_orb_relay_register_failed" \\
./scripts/smoke-selfhost.sh gittensory:rc-candidate`}
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,34 @@ curl -X DELETE "$QDRANT_URL/collections/gittensory"`}
the fastest way to confirm whether the loop is even running, before digging into the error
counters.
</p>
<p>
<code>selfhost_orb_relay_register_failed</code> means the container could not announce
itself to the broker at boot. What it means depends on <code>ORB_RELAY_MODE</code> (see{" "}
<Link to="/docs/self-hosting-github-app">choosing a relay mode</Link>):
</p>
<ul>
<li>
<strong>Push mode</strong> (default) — logged at <code>error</code> and{" "}
<strong>release-blocking</strong>: the container looks healthy but never receives an
event, since there is no fallback delivery path. Check that <code>PUBLIC_API_ORIGIN</code>{" "}
is a real, internet-reachable, TLS-terminated URL — the broker rejects a loopback or
private address at registration time, so a misconfigured or unreachable origin fails here
every time, not intermittently.
</li>
<li>
<strong>Pull mode</strong> — logged at <code>warn</code> and non-fatal: the drain loop (
<code>gittensory_orb_relay_drains_total</code>) keeps retrying on its own schedule
regardless, so a transient failure here recovers on its own once the broker is reachable
again. A registration failure that never clears across many retries still points at{" "}
<code>ORB_ENROLLMENT_SECRET</code> being wrong, revoked, or not yet provisioned
server-side.
</li>
</ul>
<p>
Either mode: confirm <code>ORB_BROKER_URL</code> is reachable from inside the container
first — a broker that is down or unreachable produces the same registration-failed event
regardless of relay mode or credential correctness.
</p>

<h2>AI provider circuit breaker keeps opening</h2>
<p>
Expand Down
Loading