From c33f2f18360a84632549d522ff1c39dcf6d5192b Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 4 Jul 2026 12:21:24 -0700 Subject: [PATCH 1/2] docs(selfhost): document the real update and rollback flow (#1823) Add an "Updating and rolling back" section to the self-host operations docs grounded in the actual deploy-selfhost-image.sh and deploy-selfhost-prebuilt.sh scripts: what each restarts (app service only, --no-deps), what is preserved automatically (.env, config mount, data volumes, compose overrides), and how to pin or roll back a version. States plainly that there is no dedicated rollback script and explains the forward-only migration caveat operators must check before rolling back across a schema change. Also corrects the releases page's "Upgrade flow" and "Rollback" sections, which described a generic docker compose pull/up flow that doesn't match either real script, and cross-links both pages to the new section. --- .../routes/docs.self-hosting-operations.tsx | 116 +++++++++++++++++- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx index ac3e6a9888..1e6aaae576 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx @@ -1,7 +1,7 @@ import { createFileRoute, Link } from "@tanstack/react-router"; import { DocsPage } from "@/components/site/docs-page"; -import { CodeBlock, FeatureRow } from "@/components/site/primitives"; +import { Callout, CodeBlock, FeatureRow } from "@/components/site/primitives"; export const Route = createFileRoute("/docs/self-hosting-operations")({ head: () => ({ @@ -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, and routine checks.", + "Operate the self-hosted Gittensory review service: readiness, metrics, logs, dashboards, jobs, queues, routine checks, and safe updates/rollback.", }, { 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, and routine checks.", + "Operate the self-hosted Gittensory review service: readiness, metrics, logs, dashboards, jobs, queues, routine checks, and safe updates/rollback.", }, { property: "og:url", content: "/docs/self-hosting-operations" }, ], @@ -275,6 +275,116 @@ volumes:
  • Backups are recent and restore-tested.
  • +

    Updating and rolling back

    +

    + Both update paths below only ever restart the gittensory app service ( + --no-deps) — they never touch other compose-profile services (Postgres, Redis, + Qdrant, Grafana, and friends), and they never touch .env keys other than the + one they persist for next time. That means .env, the{" "} + gittensory-config/ mount, every data volume, and any{" "} + docker-compose.override.yml are preserved automatically across an update — you + don't need to back those up or re-supply them just to run either script. +

    + +

    Path 1: pull a published image

    +

    + scripts/deploy-selfhost-image.sh pulls a tag or digest, restarts only the{" "} + gittensory service, waits for it to report healthy via{" "} + docker inspect's health status (configurable timeout, default 180s), and + then persists the resolved image reference back to GITTENSORY_IMAGE in{" "} + .env so the next plain invocation reuses it. +

    + +

    + The pull always runs with --policy always, so re-running the script against an + unchanged tag is safe: if the registry has nothing new, it just restarts the same image and + the health-check wait passes immediately. +

    + +

    Path 2: build from the current git checkout

    +

    + scripts/deploy-selfhost-prebuilt.sh is for a source-based deploy (this is how{" "} + GITTENSORY_VERSION ends up as a short git SHA instead of an image tag). It + builds the bundle inside a Dockerized Node container — the host itself never needs Node or + npm installed — then restarts only the gittensory service the same way as the + image path. +

    + +

    + SENTRY_RELEASE defaults to{" "} + gittensory-selfhost@<short git SHA of the current HEAD> unless you + override it, so each deploy from a new commit gets a distinct release id automatically. When{" "} + SENTRY_AUTH_TOKEN, SENTRY_ORG, and SENTRY_PROJECT are + all configured, the script also injects and uploads Sentry source maps for that release + before restarting the service (set SELFHOST_SKIP_SENTRY_UPLOAD=1 to skip this + even when those three are present). +

    + +

    Rollback: no dedicated command today

    +

    + There is no rollback script. Rolling back means re-running one of the two + scripts above pointed at an older target: +

    + + + This repo has no down-migration convention — scripts/check-migrations.mjs only + enforces a contiguous, non-colliding numbering, not a reverse path. If a migration has + already run forward against the live database, rolling back the app code is{" "} + not safe in general: older code can break against a newer schema (a + dropped/renamed column, a NOT NULL column it never writes, a changed constraint), even + though the migration itself succeeded. Before rolling back across a migration boundary, + check whether everything the newer migration(s) did is purely additive (new nullable column, + new table, new index) and, specifically, whether the code you're rolling back to actually + still runs against that schema — additive is usually fine; anything the old code can't + tolerate is not. Take a fresh backup first regardless — see{" "} + Backup and scaling — and if in doubt, + restore that backup to a scratch database and boot the older code against it before doing + the same on the live instance. + + +

    Before and after any update

    +

    Before updating:

    + +

    + After updating, work through the same checks as any other health pass — see{" "} + Health endpoints and Useful commands above: confirm{" "} + /ready returns 200, docker compose ps shows the service{" "} + healthy, and tail recent logs for startup errors or an unexpected absence of{" "} + selfhost_listening / selfhost_migrations_applied. +

    +

    If an operating check fails, go to{" "} Self-host troubleshooting. From 78cf57cdaf42da1be49440d352a293c3e5433f58 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sat, 4 Jul 2026 12:32:57 -0700 Subject: [PATCH 2/2] docs(selfhost): name preserved auth/Grafana state and add release-id check Name the app's /data volume (where Codex/Claude Code auth material lives) and Grafana's grafana-data volume explicitly as preserved state, since #1823 calls both out by name. Also add a post-update check for confirming the deployed release id, since neither /health nor /ready reports a version -- .env and docker inspect are the only truthful ways to verify it. --- .../routes/docs.self-hosting-operations.tsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx index 1e6aaae576..1a868046bf 100644 --- a/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-operations.tsx @@ -278,12 +278,16 @@ volumes:

    Updating and rolling back

    Both update paths below only ever restart the gittensory app service ( - --no-deps) — they never touch other compose-profile services (Postgres, Redis, - Qdrant, Grafana, and friends), and they never touch .env keys other than the - one they persist for next time. That means .env, the{" "} - gittensory-config/ mount, every data volume, and any{" "} - docker-compose.override.yml are preserved automatically across an update — you - don't need to back those up or re-supply them just to run either script. + --no-deps) — they never touch other compose-profile services or their state + (Postgres, Redis, Qdrant, and Grafana's own grafana-data volume), and they + never touch .env keys other than the one they persist for next time. That means{" "} + .env, the gittensory-config/ mount, every data volume — including + the app's own /data volume where Codex/Claude Code auth material lives — + and any docker-compose.override.yml are preserved automatically across an + update. You don't need to back those up or re-supply them just to run either script, + and you only need to recreate a profile service yourself if you're deliberately + upgrading that service (its own image tag in docker-compose.yml, or a + Postgres/Redis/Qdrant major-version bump) rather than the app.

    Path 1: pull a published image

    @@ -384,6 +388,17 @@ GITTENSORY_IMAGE=ghcr.io/jsonbored/gittensory-selfhost@sha256:... ./scripts/depl healthy, and tail recent logs for startup errors or an unexpected absence of{" "} selfhost_listening / selfhost_migrations_applied.

    +

    + Neither /health nor /ready reports a version, so confirm the + deployed release directly — GITTENSORY_IMAGE or SENTRY_RELEASE in{" "} + .env records what the deploy script just resolved, and{" "} + docker inspect confirms what the running container actually has: +

    +

    If an operating check fails, go to{" "}