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:
+ 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.
+
+ 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.
+
+ 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).
+
+ There is no rollback script. Rolling back means re-running one of the two
+ scripts above pointed at an older target:
+
deploy-selfhost-image.sh with the prior tag or digest (
+ docker inspect on the running container, or your own deploy log, has the
+ digest you were on before the update).
+ git checkout the prior commit, then re-run{" "}
+ deploy-selfhost-prebuilt.sh.
+ 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 updating:
+git status is clean (no uncommitted local changes the
+ build would silently pick up or drop).
+
+ 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:
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.
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{" "}