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
88 changes: 88 additions & 0 deletions apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,94 @@ function MaintainerSelfHosting() {
<Link to="/docs/tuning">Tuning your reviews</Link> for gate semantics and this section for
running the service yourself.
</p>

<h2>Moving a repo between hosted and self-host</h2>
<p>
&quot;Hosted&quot; here means the private managed-beta shared <code>gittensory</code> App
described in <Link to="/docs/github-app">GitHub App configuration</Link> — a repo installed
there is reviewed by gittensory&apos;s own cloud Worker and its own database.
&quot;Self-host&quot; means your own container from{" "}
<Link to="/docs/self-hosting-quickstart">Quickstart</Link>, with its own GitHub App (or
brokered Orb enrollment) and its own data store. There is{" "}
<strong>no automated migration path between the two</strong> today — moving a repo is a
manual App swap plus re-creating whatever settings you had, not a toggle.
</p>

<h3>Switching a repo from hosted to self-host</h3>
<ol>
<li>
Stand up your self-host instance first and confirm <code>/ready</code> is healthy — see{" "}
<Link to="/docs/self-hosting-quickstart">Quickstart</Link> — before touching the hosted
install, so the repo is never briefly reviewed by nothing.
</li>
<li>
Create <strong>your own</strong> GitHub App via the self-host{" "}
<Link to="/docs/self-hosting-github-app">setup wizard</Link> (or brokered Orb enrollment).
You cannot repoint the existing shared hosted App at your self-host container — the shared
App&apos;s credentials belong to gittensory&apos;s cloud Worker, and{" "}
<code>src/selfhost/setup-wizard.ts</code> always mints a distinct App tied to your
instance&apos;s own webhook URL.
</li>
<li>
Install your new self-host App on the repo, choosing only that repo (or the org, if you're
migrating several at once).
</li>
<li>
Uninstall the shared hosted App from that repo (repo Settings → Integrations → GitHub Apps
→ gittensory → Uninstall, or the equivalent org-level App settings page) once you&apos;ve
confirmed the self-host App is reviewing PRs correctly. Leaving both installed means two
reviewers post competing checks and comments on the same PRs.
</li>
</ol>

<h3>What does not carry over automatically</h3>
<p>
Hosted-side settings live in gittensory&apos;s own cloud database, keyed by repo full name —{" "}
<code>resolveRepositorySettings</code> (<code>src/settings/repository-settings.ts</code>)
reads them from <code>env.DB</code>, which is a completely different database instance than
your self-host container&apos;s. A self-host instance has no access to, and no import path
for, whatever thresholds, gate modes, or review-mode settings you configured on the hosted
side through the control panel or API. If you want the same behavior, you have to
re-configure it on the new instance from scratch — there is no export/import tool for this
today.
</p>
<p>
<strong>
One thing genuinely does carry over: a repo&apos;s own <code>.gittensory.yml</code>
</strong>{" "}
(config-as-code), because it lives in the repository&apos;s git history, not in either
service&apos;s database. <code>resolveRepositorySettings</code> overlays it on top of
whatever DB settings exist, on either hosted or self-host — so gate-mode overrides,
thresholds, and other settings expressed in that file apply identically the moment the new
App starts reviewing, with nothing to re-enter.
</p>
<Callout variant="warn" title="Review history does not move">
Past review comments, check-run history, and any per-PR state gittensory recorded while the
hosted App was active stay wherever they were created — GitHub comments and check runs are
never deleted or copied by an uninstall/install, but nothing in the self-host database is
backfilled from the hosted side. A migrated repo starts its self-host review history from
zero.
</Callout>
<p>
What stays identical for contributors either way: the review still posts as a{" "}
<code>gittensory[bot]</code>-style comment (under your own App&apos;s slug once you migrate,
not literally <code>gittensory[bot]</code>) plus the same check-run shape, and the gate
semantics in <Link to="/docs/tuning">Tuning your reviews</Link> and{" "}
<Link to="/docs/how-reviews-work">How reviews work</Link> are unchanged — only the
infrastructure and the settings storage location differ.
</p>

<h3>Switching a repo from self-host back to hosted</h3>
<p>
The reverse migration has the same shape and the same gap: uninstall your self-host App from
the repo, install the shared hosted App (if you have managed-beta access — see{" "}
<Link to="/docs/github-app">GitHub App configuration</Link>), and re-create any DB-backed
settings on the hosted side. <code>.gittensory.yml</code> again carries over for free since
it travels with the repo; nothing else does. Your self-host instance&apos;s data volumes are
untouched by this — see{" "}
<Link to="/docs/self-hosting-operations">Uninstalling and decommissioning</Link> if you also
intend to shut the instance down rather than keep it idle or reuse it for other repos.
</p>
</DocsPage>
);
}
115 changes: 113 additions & 2 deletions apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ export const Route = createFileRoute("/docs/self-hosting-operations")({
{
name: "description",
content:
"Operate the self-hosted Gittensory review service: readiness, metrics, logs, dashboards, jobs, queues, routine checks, and safe updates/rollback.",
"Operate the self-hosted Gittensory review service: readiness, metrics, logs, dashboards, jobs, queues, routine checks, safe updates/rollback, and clean uninstall/decommissioning.",
},
{ property: "og:title", content: "Self-host operations — Gittensory docs" },
{
property: "og:description",
content:
"Operate the self-hosted Gittensory review service: readiness, metrics, logs, dashboards, jobs, queues, routine checks, and safe updates/rollback.",
"Operate the self-hosted Gittensory review service: readiness, metrics, logs, dashboards, jobs, queues, routine checks, safe updates/rollback, and clean uninstall/decommissioning.",
},
{ property: "og:url", content: "/docs/self-hosting-operations" },
],
Expand Down Expand Up @@ -610,6 +610,117 @@ docker inspect --format '{{.Config.Image}}' "$(docker compose ps -q gittensory)"
If an operating check fails, go to{" "}
<Link to="/docs/self-hosting-troubleshooting">Self-host troubleshooting</Link>.
</p>

<h2>Uninstalling and decommissioning</h2>
<p>
Tearing an instance down cleanly touches four independent things: the GitHub App
installation, the data volumes, brokered-mode enrollment, and control-panel access. None of
this is scripted today — do each step deliberately, in this order, and decide what to keep
before you delete anything.
</p>

<h3>1. Revoke the GitHub App installation</h3>
<p>
Uninstalling stops GitHub from sending any further webhook events and immediately revokes
the App&apos;s installation tokens — nothing on the self-host side needs to be told; there
is no <code>installation</code> <code>deleted</code> webhook handler to run first. From the
repo or org: Settings → Integrations → GitHub Apps → your App → Uninstall. Do this before
stopping the container so you are not left with a dangling install pointed at a dead webhook
URL.
</p>
<p>
If you only want to pause reviews without losing the App&apos;s configuration (permissions,
webhook URL, private key), suspend the installation instead of uninstalling it — GitHub
stops delivering events to a suspended install but keeps everything else intact for a later
resume.
</p>

<h3>2. Decide what happens to the data volumes</h3>
<p>
Stopping the container does not delete anything — <code>docker compose stop</code> or{" "}
<code>docker compose down</code> (without <code>-v</code>) leaves every named volume (
<code>gittensory-data</code>, <code>gittensory-pg</code>, <code>qdrant-data</code>,{" "}
<code>gittensory-backups</code>, <code>grafana-data</code>, and the rest declared in{" "}
<code>docker-compose.yml</code>) on disk, along with the <code>./gittensory-config</code>{" "}
host directory (a bind mount, not a named volume, so it is never affected by <code>-v</code>{" "}
either way). Pick one:
</p>
<FeatureRow
items={[
{
title: "Keep (pause, don't decommission)",
description:
"docker compose stop. Volumes and .env stay as-is; restarting later resumes with the same data. Use this if you might come back.",
},
{
title: "Export, then delete",
description:
"Run the backup profile one last time (docker compose --profile backup up -d, then confirm with verify-backup.sh — see Backup and scaling) and copy the resulting archive off-host before removing anything.",
},
{
title: "Delete everything",
description:
"docker compose down -v removes every named volume permanently — the review database, vector index, Grafana dashboards state, and any local backup archives in gittensory-backups go with it. This does not touch ./gittensory-config (delete that host directory yourself if it should go too).",
},
]}
/>
<Callout variant="warn" title="down -v is irreversible without an existing backup">
If you have not exported a backup off-host first, <code>docker compose down -v</code>{" "}
permanently destroys review history, settings, and the vector index with no recovery path —
the volumes are the only copy. See{" "}
<Link to="/docs/self-hosting-backup-scaling">Backup and scaling</Link> before running it on
an instance you care about.
</Callout>

<h3>3. Deregister from the Orb broker (brokered mode only)</h3>
<p>
If this instance runs in brokered mode (<code>ORB_ENROLLMENT_SECRET</code> is set — see{" "}
<Link to="/docs/self-hosting-github-app">GitHub App and Orb</Link>), be aware there is{" "}
<strong>no self-service revocation endpoint today</strong> — the &quot;Minimum broker
safeguards&quot; checklist on that page lists a revocation path as a prerequisite for a
public brokered rollout that has not shipped yet. An enrollment record (
<code>orb_enrollments</code>) lives in gittensory&apos;s own central database, not your
container, and nothing in this codebase writes a <code>revoked_at</code> value to it outside
of tests. Practical steps until that exists:
</p>
<ul>
<li>
Uninstalling the GitHub App (step 1) stops new webhook traffic and installation-token
issuance from reaching your instance in practice, even though the enrollment row itself
stays marked enrolled centrally.
</li>
<li>
Stop the container and let <code>ORB_ENROLLMENT_SECRET</code> go with it — with nothing
polling or listening, the secret is inert even if it still resolves to a valid enrollment.
</li>
<li>
If the secret may have leaked or you want it invalidated outright rather than just
orphaned, treat this the same as any other suspected credential compromise: contact the
Orb operator to have the enrollment revoked centrally, since there is no in-product way to
do it yourself yet.
</li>
</ul>

<h3>4. Remove ADMIN_GITHUB_LOGINS access</h3>
<p>
<code>ADMIN_GITHUB_LOGINS</code> is read fresh from the environment on every control-panel
request (<code>isAuthorizedGitHubSessionLogin</code> in <code>src/auth/security.ts</code>) —
it is never cached at startup or baked into an issued session. To remove someone&apos;s
operator access, delete their login from the comma/whitespace-separated list in{" "}
<code>.env</code> and restart the <code>gittensory</code> service so the process picks up
the new value:
</p>
<CodeBlock
lang="bash"
code={`$EDITOR .env # remove the login from ADMIN_GITHUB_LOGINS
docker compose up -d --no-deps gittensory`}
/>
<p>
This takes effect on their very next control-panel request after the restart — no signed-in
session is grandfathered in, because authorization is re-checked against the current
allowlist every time, not read from the session itself. If you are decommissioning the whole
instance rather than removing one operator, this step is moot once the container is stopped.
</p>
</DocsPage>
);
}
Loading