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
7 changes: 5 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,12 @@ GITTENSORY_REVIEW_DRAFT=false
# # request-latency metrics to tell those two cases apart).
REDIS_URL=redis://redis:6379 # REQUIRED for the self-host review runtime. The default compose stack
# # starts Redis automatically; override for an external Redis.
# GITTENSORY_IMAGE=ghcr.io/jsonbored/gittensory-selfhost:latest # image used by scripts/deploy-selfhost-image.sh;
# GITTENSORY_IMAGE=ghcr.io/jsonbored/loopover-selfhost:latest # image used by scripts/deploy-selfhost-image.sh;
# # pin production rollouts to a release tag such as :orb-v0.1.0
# # or to an immutable @sha256 digest.
# # or to an immutable @sha256 digest. #4770: the pre-rename
# # ghcr.io/jsonbored/gittensory-selfhost name is a deprecated
# # alias of the same image during the deprecation window (#4777)
# # -- an existing pin to it keeps working unmodified.
# GITHUB_CACHE_TTL_SECONDS=20 # Enables the Redis-backed GitHub GET-response cache when >0; set 0
# # to disable. NOT a per-entry TTL duration — each cached class
# # resolves its own TTL below (GITHUB_BRANCH_PROTECTION_CACHE_TTL_SECONDS
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/orb-beta-release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Automated ORB (self-host container image, ghcr.io/jsonbored/gittensory-selfhost) beta channel.
# Automated ORB (self-host container image, ghcr.io/jsonbored/loopover-selfhost -- #4770: the pre-rename
# ghcr.io/jsonbored/gittensory-selfhost name remains a deprecated alias of the same build during the
# deprecation window tracked by #4777) beta channel.
# Daily (or on demand via workflow_dispatch), checks whether any image-relevant commit has landed
# since the last orb-v tag (scripts/check-orb-release-due.mjs / scripts/orb-release-core.mjs) and,
# if so, cuts the next `orb-vX.Y.Z-beta.N` tag and dispatches release-selfhost.yml to build + publish
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/orb-stable-release-pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Automated ORB (self-host container image, ghcr.io/jsonbored/gittensory-selfhost) STABLE-channel Release PR --
# Automated ORB (self-host container image, ghcr.io/jsonbored/loopover-selfhost -- #4770: the pre-rename
# ghcr.io/jsonbored/gittensory-selfhost name remains a deprecated alias of the same build during the
# deprecation window tracked by #4777) STABLE-channel Release PR --
# the release-please-equivalent for ORB, whose cross-cutting image-relevant scoping (src/** shared with
# UI/MCP-only subtrees it must exclude -- see orb-release-core.mjs's IMAGE_RELEVANT_PREFIXES/EXCLUDED_PREFIXES)
# doesn't fit release-please's directory-component model the way packages/gittensory-mcp and
Expand Down
25 changes: 22 additions & 3 deletions .github/workflows/release-selfhost.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
#
# git tag orb-v0.1.0 && git push origin orb-v0.1.0
#
# Pull: docker pull ghcr.io/<owner>/gittensory-selfhost:orb-v0.1.0
# Pull: docker pull ghcr.io/<owner>/loopover-selfhost:orb-v0.1.0
#
# #4770: `ghcr.io/<owner>/gittensory-selfhost` is a deprecated alias of the SAME image, published from this
# same build/push so both names resolve to the byte-identical digest -- see the "Image metadata" step below.
# Self-hosters pinned to the old name keep working unmodified during the deprecation window; do not remove
# that alias without the sign-off tracked by #4777.
#
# Prerelease tags (#1937): orb-v0.1.0-rc.1 / orb-v0.1.0-beta.1 run the identical pipeline but never move
# `latest` and are marked prerelease on the GitHub Release -- for beta-testing an image before it becomes
Expand Down Expand Up @@ -217,11 +222,21 @@ jobs:
echo "GTORBTAGS"
} >> "$GITHUB_OUTPUT"

# #4770: publish under BOTH the new "loopover-selfhost" name (primary going forward, listed first) and
# the legacy "gittensory-selfhost" name (deprecated alias) from this SAME buildx build/push below --
# docker/metadata-action applies the identical `tags:` set to every entry in a multi-line `images:`
# list, so both names resolve to the byte-identical multi-arch manifest with no second build and no
# extra registry round-trip. Self-hosters who pinned the old image reference in their own deployment
# config must keep working unmodified until the deprecation window closes. Removal of the alias is
# tracked by #4777 ("retire every deprecation shim") and requires an explicit, separately-announced
# window before it's dropped -- do not remove the second `images:` line without that sign-off.
- name: Image metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
with:
images: ghcr.io/${{ github.repository_owner }}/gittensory-selfhost
images: |
ghcr.io/${{ github.repository_owner }}/loopover-selfhost
ghcr.io/${{ github.repository_owner }}/gittensory-selfhost
tags: ${{ steps.tags.outputs.list }}
labels: |
org.opencontainers.image.title=gittensory-orb
Expand Down Expand Up @@ -333,12 +348,16 @@ jobs:
Gittensory Orb container image:

\`\`\`bash
docker pull ghcr.io/${REPOSITORY_OWNER_LOWER}/gittensory-selfhost:${RELEASE_TAG}
docker pull ghcr.io/${REPOSITORY_OWNER_LOWER}/loopover-selfhost:${RELEASE_TAG}
\`\`\`

Multi-arch (linux/amd64 + linux/arm64). See https://gittensory.aethereal.dev/docs/maintainer-self-hosting for setup.
Includes the Claude Code / Codex subscription CLIs by default; credentials stay runtime-only.
Sentry release id baked into the image: \`${RELEASE_ID}\`.

#4770: \`ghcr.io/${REPOSITORY_OWNER_LOWER}/gittensory-selfhost:${RELEASE_TAG}\` (the pre-rename name)
still resolves to this identical build/digest during the deprecation window -- no action needed if
you're already pinned to it. Track eventual removal via #4777.
EOF
)"
# #1937: a prerelease tag never becomes the repo's "Latest release" and is visibly marked as such
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -976,9 +976,14 @@ SENTRY_ORG_SLUG=<your-sentry-org-slug>
./scripts/deploy-selfhost-image.sh

# Pin an exact release tag or content digest
./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/gittensory-selfhost:orb-v0.1.0
GITTENSORY_IMAGE=ghcr.io/jsonbored/gittensory-selfhost@sha256:... ./scripts/deploy-selfhost-image.sh`}
./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/loopover-selfhost:orb-v0.1.0
GITTENSORY_IMAGE=ghcr.io/jsonbored/loopover-selfhost@sha256:... ./scripts/deploy-selfhost-image.sh`}
/>
<p>
<code>ghcr.io/jsonbored/gittensory-selfhost</code> (the pre-rename name) remains a
deprecated alias of the identical image during the deprecation window — an existing pin to
it keeps resolving unmodified.
</p>
<p>
The pull always runs with <code>--policy always</code>, so re-running the script against an
unchanged tag is safe: if the registry has nothing new, it just restarts the same image and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,14 @@ curl http://localhost:8787/ready`}
</p>
<CodeBlock
lang="bash"
code={`./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/gittensory-selfhost:orb-v0.1.0
GITTENSORY_IMAGE=ghcr.io/jsonbored/gittensory-selfhost@sha256:... ./scripts/deploy-selfhost-image.sh`}
code={`./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/loopover-selfhost:orb-v0.1.0
GITTENSORY_IMAGE=ghcr.io/jsonbored/loopover-selfhost@sha256:... ./scripts/deploy-selfhost-image.sh`}
/>
<p>
<code>ghcr.io/jsonbored/gittensory-selfhost</code> (the pre-rename name) remains available
as a deprecated alias of the identical image during the deprecation window — existing pins
keep working unmodified.
</p>
<Callout variant="note" title="Building from source instead">
Contributors and anyone customizing the Dockerfile can still build locally —{" "}
<code>docker compose up -d --build</code> builds the <code>gittensory</code> service from
Expand Down
14 changes: 10 additions & 4 deletions apps/gittensory-ui/src/routes/docs.self-hosting-releases.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ function SelfHostingReleases() {
/>
<CodeBlock
lang="bash"
code={`docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.1.0
docker pull ghcr.io/jsonbored/gittensory-selfhost:latest`}
code={`docker pull ghcr.io/jsonbored/loopover-selfhost:orb-v0.1.0
docker pull ghcr.io/jsonbored/loopover-selfhost:latest`}
/>
<Callout variant="note" title="Renamed from gittensory-selfhost">
<code>ghcr.io/jsonbored/loopover-selfhost</code> is the current name. The pre-rename{" "}
<code>ghcr.io/jsonbored/gittensory-selfhost</code> remains available as a deprecated alias
of the byte-identical image during the deprecation window — existing pins keep working
unmodified.
</Callout>

<h2>Prerelease (beta/rc) images</h2>
<p>
Expand All @@ -65,7 +71,7 @@ docker pull ghcr.io/jsonbored/gittensory-selfhost:latest`}
</p>
<CodeBlock
lang="bash"
code={`docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.1.0-rc.1`}
code={`docker pull ghcr.io/jsonbored/loopover-selfhost:orb-v0.1.0-rc.1`}
/>
<Callout variant="note">
Stable release behavior is unchanged: a plain <code>X.Y.Z</code> tag still moves{" "}
Expand Down Expand Up @@ -96,7 +102,7 @@ docker pull ghcr.io/jsonbored/gittensory-selfhost:latest`}
<CodeBlock
lang="bash"
code={`# Recommended: pull a published tag, restart, wait for healthy
./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/gittensory-selfhost:orb-v0.1.0
./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/loopover-selfhost:orb-v0.1.0
curl http://localhost:8787/ready

# Building from the current checkout instead of pulling
Expand Down
2 changes: 1 addition & 1 deletion orb-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"version": "0.6.0",
"description": "Source of truth for the self-hostable gittensory-orb container image's target release version (ghcr.io/jsonbored/gittensory-selfhost). Bumped by a maintainer when a feat/fix/breaking change since the last stable orb-v tag warrants moving to a new target version -- scripts/orb-release-core.mjs and .github/workflows/orb-beta-release.yml read this file to decide what version the next automated beta snapshot targets. Promoting a beta to a stable orb-vX.Y.Z release is still a manual `git tag` -- this manifest only drives the automated beta channel."
"description": "Source of truth for the self-hostable gittensory-orb container image's target release version (ghcr.io/jsonbored/loopover-selfhost -- #4770: ghcr.io/jsonbored/gittensory-selfhost is a deprecated alias of the same build/digest during the deprecation window tracked by #4777). Bumped by a maintainer when a feat/fix/breaking change since the last stable orb-v tag warrants moving to a new target version -- scripts/orb-release-core.mjs and .github/workflows/orb-beta-release.yml read this file to decide what version the next automated beta snapshot targets. Promoting a beta to a stable orb-vX.Y.Z release is still a manual `git tag` -- this manifest only drives the automated beta channel."
}
11 changes: 8 additions & 3 deletions scripts/deploy-selfhost-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
# ./scripts/deploy-selfhost-image.sh
#
# Pin production rollouts to a release tag or digest:
# ./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/gittensory-selfhost:orb-v0.1.0
# GITTENSORY_IMAGE=ghcr.io/jsonbored/gittensory-selfhost@sha256:... ./scripts/deploy-selfhost-image.sh
# ./scripts/deploy-selfhost-image.sh ghcr.io/jsonbored/loopover-selfhost:orb-v0.1.0
# GITTENSORY_IMAGE=ghcr.io/jsonbored/loopover-selfhost@sha256:... ./scripts/deploy-selfhost-image.sh
#
# #4770: ghcr.io/jsonbored/gittensory-selfhost (the pre-rename name) is a deprecated alias of the SAME
# build/digest during the deprecation window -- an existing GITTENSORY_IMAGE override or CLI argument
# pinned to that old name keeps resolving unmodified; only DEFAULT_IMAGE below moves to the new name.
# Removal of the alias is tracked by #4777.
#
# The image itself carries official release metadata. Set SENTRY_RELEASE only for custom images whose
# source maps were uploaded under that exact id.
Expand All @@ -17,7 +22,7 @@ set -euo pipefail
ENV_FILE="${SELFHOST_ENV_FILE:-.env}"
SERVICE="${SELFHOST_SERVICE:-loopover}"
HEALTH_TIMEOUT_SECONDS="${SELFHOST_HEALTH_TIMEOUT_SECONDS:-180}"
DEFAULT_IMAGE="ghcr.io/jsonbored/gittensory-selfhost:latest"
DEFAULT_IMAGE="ghcr.io/jsonbored/loopover-selfhost:latest"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/selfhost-deploy-common.sh
Expand Down
4 changes: 3 additions & 1 deletion scripts/orb-release-core.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Pure logic for the ORB (self-host container image, ghcr.io/jsonbored/gittensory-selfhost) automated beta
// Pure logic for the ORB (self-host container image, ghcr.io/jsonbored/loopover-selfhost -- #4770: the
// pre-rename ghcr.io/jsonbored/gittensory-selfhost name remains a deprecated alias of the same build
// during the deprecation window tracked by #4777) automated beta
// channel. Deliberately independent of scripts/mcp-release-core.mjs (no shared imports/state) even though the
// shape mirrors it closely -- ORB and the MCP package are versioned, tagged, and published on separate
// schedules by separate automation, and keeping them decoupled means neither can accidentally regress the
Expand Down
12 changes: 8 additions & 4 deletions test/unit/release-selfhost-notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,11 @@ describe('release-selfhost.yml "GitHub Release" step changelog generation', () =
});
const r = harness.run();
expect(r.status).toBe(0);
expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0");
expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/loopover-selfhost:orb-v0.2.0");
// #4770: the notes must also tell an operator that the pre-rename image name still resolves to
// this identical build during the deprecation window, and point at #4777 for eventual removal.
expect(r.notesPassed).toContain("ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0");
expect(r.notesPassed).toContain("#4777");
expect(r.notesPassed).toContain("## What's Changed");
expect(r.notesPassed).toContain("feat: something");
// The tag being released must never be diffed against itself.
Expand All @@ -156,7 +160,7 @@ describe('release-selfhost.yml "GitHub Release" step changelog generation', () =
const harness = createHarness({ tagList: ["orb-v0.2.0"], changelogBody: "## What's Changed\n* whatever" });
const r = harness.run();
expect(r.status).toBe(0);
expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0");
expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/loopover-selfhost:orb-v0.2.0");
expect(r.notesPassed).not.toContain("What's Changed");
expect(r.calls).not.toContain("gh api");
});
Expand All @@ -173,7 +177,7 @@ describe('release-selfhost.yml "GitHub Release" step changelog generation', () =
expect(r.notesPassed).toContain("https://github.com/JSONbored/gittensory/compare/orb-v0.1.0...orb-v0.2.0");
expect(r.notesPassed.length).toBeLessThan(121000);
// The fallback must not drop the operator-critical pull command along with the oversized changelog.
expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0");
expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/loopover-selfhost:orb-v0.2.0");
});

it("uses release create when the release does not exist yet, and release edit when it does", () => {
Expand All @@ -192,7 +196,7 @@ describe('release-selfhost.yml "GitHub Release" step changelog generation', () =
const harness = createHarness({ tagList: ["orb-v0.2.0", "orb-v0.1.0"] }); // changelogBody omitted -> API call exits 1
const r = harness.run();
expect(r.status).toBe(0);
expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/gittensory-selfhost:orb-v0.2.0");
expect(r.notesPassed).toContain("docker pull ghcr.io/jsonbored/loopover-selfhost:orb-v0.2.0");
// A silent empty changelog would be indistinguishable from a genuinely empty PR range -- the run
// log must say the API call itself failed.
expect(r.stdout).toContain("::warning::Fetching the release changelog");
Expand Down
21 changes: 20 additions & 1 deletion test/unit/selfhost-image-deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { spawnSync } from "node:child_process";
import { describe, expect, it } from "vitest";

const scriptPath = resolve("scripts/deploy-selfhost-image.sh");
const defaultImage = "ghcr.io/jsonbored/gittensory-selfhost:latest";
const defaultImage = "ghcr.io/jsonbored/loopover-selfhost:latest";
// #4770: the pre-rename name is a deprecated alias of the identical image during the deprecation
// window -- an operator who pinned it explicitly (CLI arg, env var, or .env value) must keep
// resolving to that exact string unmodified, even though DEFAULT_IMAGE above now points at the new name.
const legacyPinnedImage = "ghcr.io/jsonbored/gittensory-selfhost:orb-v0.1.0";

interface RunOptions {
args?: string[];
Expand Down Expand Up @@ -163,6 +167,21 @@ describe("self-host image deploy script", () => {
}
});

// REGRESSION (#4770): DEFAULT_IMAGE moved to the new "loopover-selfhost" name, but a self-hoster who
// explicitly pinned the pre-rename "gittensory-selfhost" image (as a CLI argument, GITTENSORY_IMAGE env
// var, or .env value) must keep resolving to that exact string, unmodified -- the deprecation alias is
// guaranteed by the release workflow's dual-tag push, not by rewriting an operator's existing pin here.
it("passes an explicit pre-rename image reference through unchanged despite the new default", () => {
const { harness, result } = runHarness({ args: [legacyPinnedImage] });
try {
expect(result.status, result.stderr).toBe(0);
expect(readFileSync(harness.envPath, "utf8")).toContain(`GITTENSORY_IMAGE=${legacyPinnedImage}`);
expect(harness.readImages()).toContain(`image: "${legacyPinnedImage}"`);
} finally {
harness.cleanup();
}
});

it("persists the image only after the service reports healthy", () => {
const image = "ghcr.io/jsonbored/gittensory-selfhost:ordered";
const { harness, result } = runHarness({ args: [image], envFile: "EXISTING=1\n" });
Expand Down
16 changes: 15 additions & 1 deletion test/unit/selfhost-sentry-release.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,21 @@ describe("self-host Sentry release wiring", () => {
// notes block doesn't go through it).
expect(releaseWorkflow).toContain('REPOSITORY_OWNER_LOWER="${REPOSITORY_OWNER,,}"');
expect(releaseWorkflow).toContain(
"docker pull ghcr.io/${REPOSITORY_OWNER_LOWER}/gittensory-selfhost:${RELEASE_TAG}",
"docker pull ghcr.io/${REPOSITORY_OWNER_LOWER}/loopover-selfhost:${RELEASE_TAG}",
);
// #4770: the release notes must also point out that the pre-rename image name still resolves to
// the identical build during the deprecation window (tracked for eventual removal by #4777).
expect(releaseWorkflow).toContain(
"ghcr.io/${REPOSITORY_OWNER_LOWER}/gittensory-selfhost:${RELEASE_TAG}",
);
expect(releaseWorkflow).toContain("#4777");
// The "Image metadata" step must push BOTH names from the same buildx build so the two tags share
// a byte-identical digest -- no second build, no drift between them.
expect(releaseWorkflow).toContain(
"ghcr.io/${{ github.repository_owner }}/loopover-selfhost",
);
expect(releaseWorkflow).toContain(
"ghcr.io/${{ github.repository_owner }}/gittensory-selfhost",
);
expect(releaseWorkflow).not.toContain('"selfhost-v*"');
expect(releaseWorkflow).not.toContain('VERSION="${REF_NAME#selfhost-v}"');
Expand Down
Loading