From a4d51a44d71d9e2fb2dc46d24a36288730217d45 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Thu, 29 Jan 2026 09:46:55 -0500 Subject: [PATCH 01/17] Add GitLab support for Seer Autofix Add 'gitlab' and 'integrations:gitlab' to supportedProviders array to enable GitLab repositories for Seer Autofix feature. --- static/app/components/events/autofix/utils.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/app/components/events/autofix/utils.tsx b/static/app/components/events/autofix/utils.tsx index 919f0fdc78a0..1323dc54f413 100644 --- a/static/app/components/events/autofix/utils.tsx +++ b/static/app/components/events/autofix/utils.tsx @@ -192,6 +192,8 @@ const BASE_SUPPORTED_PROVIDERS = [ 'integrations:github', 'github_enterprise', 'integrations:github_enterprise', + 'gitlab', + 'integrations:gitlab', ]; /** From d7e0a6396a18b9254f8b9bb2785019e894fa3995 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Thu, 29 Jan 2026 23:28:22 -0500 Subject: [PATCH 02/17] feat: Add build infrastructure for custom Kencove images - Add cloudbuild.yaml for GCP Cloud Build - Add Dockerfile.kencove multi-stage build - Add build-and-push.sh convenience script - Add GitHub Actions workflow for CI/CD - Update AGENTS.md with build instructions Co-Authored-By: Claude Opus 4.5 --- .github/workflows/build-kencove.yml | 107 ++++++++++++++++++ AGENTS.md | 50 +++++++++ build-and-push.sh | 75 +++++++++++++ cloudbuild.yaml | 57 ++++++++++ self-hosted/Dockerfile.kencove | 162 ++++++++++++++++++++++++++++ self-hosted/Dockerfile.patch | 23 ++++ 6 files changed, 474 insertions(+) create mode 100644 .github/workflows/build-kencove.yml create mode 100755 build-and-push.sh create mode 100644 cloudbuild.yaml create mode 100644 self-hosted/Dockerfile.kencove create mode 100644 self-hosted/Dockerfile.patch diff --git a/.github/workflows/build-kencove.yml b/.github/workflows/build-kencove.yml new file mode 100644 index 000000000000..dc2281672430 --- /dev/null +++ b/.github/workflows/build-kencove.yml @@ -0,0 +1,107 @@ +name: Build Kencove Sentry Image + +on: + push: + branches: + - master + workflow_dispatch: + inputs: + tag: + description: 'Image tag (e.g., v26.1.0-gitlab)' + required: false + default: '' + +env: + REGION: us-central1 + PROJECT_ID: kencove-prod + REPOSITORY: kencove-docker-repo + IMAGE_NAME: sentry + +jobs: + build: + runs-on: ubuntu-24.04 + permissions: + contents: read + id-token: write # For Workload Identity Federation + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + + - uses: pnpm/action-setup@v4 + + - uses: astral-sh/setup-uv@v4 + with: + version: '0.8.2' + + - name: Setup Python venv + run: | + uv venv + source .venv/bin/activate + echo "PATH=$PWD/.venv/bin:$PATH" >> $GITHUB_ENV + + - name: Cache webpack + uses: actions/cache@v4 + with: + path: .webpack_cache + key: webpack-${{ hashFiles('rspack.config.ts') }} + + - name: Cache node_modules + uses: actions/cache@v4 + id: node-cache + with: + path: node_modules + key: node-modules-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Install Node dependencies + if: steps.node-cache.outputs.cache-hit != 'true' + run: pnpm install --frozen-lockfile + + - name: Build frontend + run: | + python3 -m tools.fast_editable --path . + python3 -m sentry.build.main + env: + WEBPACK_CACHE_PATH: .webpack_cache + NODE_OPTIONS: '--max-old-space-size=4096' + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: 'projects/103143301688/locations/global/workloadIdentityPools/github-pool/providers/github-provider' + service_account: 'github-actions@kencove-prod.iam.gserviceaccount.com' + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Configure Docker for Artifact Registry + run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet + + - name: Determine image tag + id: tag + run: | + if [ -n "${{ github.event.inputs.tag }}" ]; then + echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT + else + echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT + fi + + - name: Build and push Docker image + run: | + docker build \ + -t ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} \ + -t ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:latest \ + -f self-hosted/Dockerfile \ + --build-arg SOURCE_COMMIT=${{ github.sha }} \ + . + + docker push ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} + docker push ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:latest + + - name: Output image info + run: | + echo "## Build Complete" >> $GITHUB_STEP_SUMMARY + echo "Image: \`${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}\`" >> $GITHUB_STEP_SUMMARY diff --git a/AGENTS.md b/AGENTS.md index 8f69f58b4eb2..37c1b1407040 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -256,3 +256,53 @@ Frontend (`static/`) and backend (`src/`, `tests/`) are **not atomically deploye - If your changes touch both frontend and backend, split them into **separate PRs**. - Land the backend PR first when the frontend depends on new API changes. - Pure test additions alongside `src/` changes are fine in one PR. + +## Kencove Fork + +This is **ledoent/sentry** (originally kencove/sentry) — a fork of getsentry/sentry with custom modifications for our self-hosted deployment. + +### Key Modifications + +1. **GitLab Autofix Support** (`static/app/components/events/autofix/utils.tsx`) + - Added `'gitlab'` and `'integrations:gitlab'` to `supportedProviders` array + - Enables GitLab repositories for Seer Autofix feature + - Note: upstream 26.5.0 added GitLab support behind `organizations:seer-gitlab-support` feature flag; this patch may be redundant. Verify before keeping in future rebases. + +2. **Seer endpoints serializer** (`src/sentry/seer/endpoints/project_seer_preferences.py`) + - Make `org_id` and `integration_id` optional to handle Seer's repo format differences for self-hosted single-org deployments + +3. **Cursor / GitLab integration** (`src/sentry/integrations/cursor/*`) + - Custom GitLab repo URL builder + webhook handler + +4. **Self-hosted SEER_URL patch** (`self-hosted/Dockerfile.patch`) + - Patches `server.py` during Docker build so `SEER_DEFAULT_URL` defaults to the in-cluster seer service + +### Building Custom Image + +The primary build now ships to GHCR: + +```bash +# Via the GHCR workflow (default — produces ghcr.io/ledoent/sentry:-kencove) +gh workflow run build-push-ghcr.yml --ref -kencove + +# Legacy Cloud Build path (kencove-prod GCP, still works) +gcloud builds submit --config=cloudbuild.yaml . +``` + +### Syncing with Upstream + +```bash +git fetch upstream +git checkout -b -kencove +git log --reverse --format=%H ..origin/-kencove \ + | xargs -I{} git cherry-pick {} +# Resolve conflicts (typically in src/sentry/seer/autofix/coding_agent.py +# and src/sentry/seer/endpoints/project_seer_preferences.py) +git push origin -kencove +``` + +### Related Repositories + +- **Seer AI Service**: [ledoent/seer](https://github.com/ledoent/seer) - GitLab repository client +- **Helm Charts**: [kencove/charts](https://github.com/kencove/charts) - Deployment configuration +- **Infra Clusters**: `~/projects/ledoent/infra/deployments/` - Deployment values diff --git a/build-and-push.sh b/build-and-push.sh new file mode 100755 index 000000000000..ea1341903e13 --- /dev/null +++ b/build-and-push.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Build and push Kencove Sentry image to Google Artifact Registry +# +# Usage: +# ./build-and-push.sh # Build with commit SHA tag +# ./build-and-push.sh v26.1.0-gitlab # Build with custom tag +# ./build-and-push.sh --local # Build locally without pushing + +set -euo pipefail + +# Configuration +PROJECT_ID="${PROJECT_ID:-kencove-prod}" +REGION="${REGION:-us-central1}" +REPOSITORY="${REPOSITORY:-kencove-docker-repo}" +IMAGE_NAME="${IMAGE_NAME:-sentry}" +TAG="${1:-$(git rev-parse --short HEAD)}" + +FULL_IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE_NAME}" + +echo "=== Kencove Sentry Build ===" +echo "Image: ${FULL_IMAGE}:${TAG}" +echo "Commit: $(git rev-parse HEAD)" +echo "" + +if [[ "${1:-}" == "--local" ]]; then + echo "Building locally (not pushing)..." + docker build \ + -t "${IMAGE_NAME}:${TAG}" \ + -t "${IMAGE_NAME}:latest" \ + -f self-hosted/Dockerfile.kencove \ + --build-arg SOURCE_COMMIT="$(git rev-parse HEAD)" \ + --progress=plain \ + . + echo "" + echo "Local build complete: ${IMAGE_NAME}:${TAG}" + exit 0 +fi + +# Check if using Cloud Build or local Docker +if command -v gcloud &> /dev/null && [[ "${USE_CLOUD_BUILD:-true}" == "true" ]]; then + echo "Using Cloud Build..." + gcloud builds submit \ + --config=cloudbuild.yaml \ + --substitutions="_TAG=${TAG}" \ + --project="${PROJECT_ID}" \ + . +else + echo "Using local Docker build + push..." + + # Configure Docker for GAR + gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet + + # Build + docker build \ + -t "${FULL_IMAGE}:${TAG}" \ + -t "${FULL_IMAGE}:latest" \ + -f self-hosted/Dockerfile.kencove \ + --build-arg SOURCE_COMMIT="$(git rev-parse HEAD)" \ + --progress=plain \ + . + + # Push + docker push "${FULL_IMAGE}:${TAG}" + docker push "${FULL_IMAGE}:latest" +fi + +echo "" +echo "=== Build Complete ===" +echo "Image: ${FULL_IMAGE}:${TAG}" +echo "" +echo "To use in Helm values:" +echo " images:" +echo " sentry:" +echo " repository: ${FULL_IMAGE}" +echo " tag: \"${TAG}\"" diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 000000000000..2b177ead905d --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,57 @@ +# Cloud Build configuration for kencove/sentry +# Builds custom Sentry image with GitLab Autofix support +# +# Usage: +# gcloud builds submit --config=cloudbuild.yaml . +# +# Or with custom tag: +# gcloud builds submit --config=cloudbuild.yaml --substitutions=_TAG=v26.1.0-gitlab . +# +# Trigger on push: +# gcloud builds triggers create github \ +# --repo-name=sentry --repo-owner=kencove \ +# --branch-pattern="^master$" \ +# --build-config=cloudbuild.yaml + +substitutions: + _REGION: us-central1 + _REPOSITORY: kencove-docker-repo + _IMAGE_NAME: sentry + _TAG: ${COMMIT_SHA} + +options: + machineType: E2_HIGHCPU_32 + diskSizeGb: 200 + logging: CLOUD_LOGGING_ONLY + +steps: + # Build using multi-stage Dockerfile that handles both frontend and runtime + - name: 'gcr.io/cloud-builders/docker' + id: 'build-image' + args: + - 'build' + - '-t' + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}:${_TAG}' + - '-t' + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}:latest' + - '-f' + - 'self-hosted/Dockerfile.kencove' + - '--build-arg' + - 'SOURCE_COMMIT=${COMMIT_SHA}' + - '--progress=plain' + - '.' + + # Push to Artifact Registry + - name: 'gcr.io/cloud-builders/docker' + id: 'push-image' + args: + - 'push' + - '--all-tags' + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}' + waitFor: ['build-image'] + +images: + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}:${_TAG}' + - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/${_IMAGE_NAME}:latest' + +timeout: 3600s # 1 hour - Sentry build is slow diff --git a/self-hosted/Dockerfile.kencove b/self-hosted/Dockerfile.kencove new file mode 100644 index 000000000000..b8d273be9bf3 --- /dev/null +++ b/self-hosted/Dockerfile.kencove @@ -0,0 +1,162 @@ +# Multi-stage Dockerfile for Kencove Sentry build +# This builds the frontend and creates the runtime image in one Dockerfile +# +# Build: docker build -f self-hosted/Dockerfile.kencove -t sentry:kencove . + +# ============================================================================= +# Stage 1: Build frontend assets using the same Python base as runtime +# ============================================================================= +FROM python:3.13.1-slim-bookworm AS frontend-builder + +WORKDIR /build + +# Install Node.js 22 and build tools +RUN apt-get update && apt-get install -y \ + curl \ + gnupg \ + build-essential \ + git \ + && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y nodejs \ + && npm install -g pnpm@9 \ + && rm -rf /var/lib/apt/lists/* + +# Install uv for Python dependency management +RUN pip install uv==0.8.2 + +# Copy dependency files first for layer caching +COPY package.json pnpm-lock.yaml .node-version ./ +COPY pyproject.toml uv.lock ./ + +# Install Node dependencies with shamefully-hoist to fix module resolution +RUN pnpm install --frozen-lockfile --shamefully-hoist + +# Create Python venv and install dependencies (needed for sentry.build) +ENV PATH="/build/.venv/bin:$PATH" +RUN python -m venv .venv && \ + uv sync --frozen --quiet --no-install-project + +# Copy source code +COPY . . + +# Initialize minimal git repo for build scripts that need it +ARG SOURCE_COMMIT=unknown +RUN git init && \ + git config user.email "build@kencove.com" && \ + git config user.name "Build" && \ + git add -A && \ + git commit -m "Build commit" --allow-empty && \ + echo "${SOURCE_COMMIT}" > .git/COMMIT_EDITMSG + +# Build frontend assets +ENV WEBPACK_CACHE_PATH=/build/.webpack_cache +ENV NODE_OPTIONS="--max-old-space-size=4096" +ENV SENTRY_BUILD=${SOURCE_COMMIT} +RUN python -m tools.fast_editable --path . && \ + python -m sentry.build.main + +# Verify build outputs +RUN test -f src/sentry/static/sentry/dist/entrypoints/app.js && \ + test -f src/sentry/loader/_registry.json && \ + echo "Frontend build verified successfully" + +# ============================================================================= +# Stage 2: Runtime image (same as official self-hosted/Dockerfile) +# ============================================================================= +FROM python:3.13.1-slim-bookworm AS runtime + +LABEL maintainer="kencove" +LABEL org.opencontainers.image.title="Sentry (Kencove)" +LABEL org.opencontainers.image.description="Kencove fork with GitLab Autofix support" +LABEL org.opencontainers.image.url="https://github.com/kencove/sentry" +LABEL org.opencontainers.image.vendor="Kencove" + +# Add user and group +RUN groupadd -r sentry --gid 999 && useradd -r -m -g sentry --uid 999 sentry + +# Install runtime dependencies +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + gosu \ + libexpat1 \ + tini \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/src/sentry + +ENV PATH="/.venv/bin:$PATH" UV_PROJECT_ENVIRONMENT=/.venv \ + PIP_NO_CACHE_DIR=1 PIP_DISABLE_PIP_VERSION_CHECK=1 \ + UV_COMPILE_BYTECODE=1 UV_NO_CACHE=1 + +# Install uv +RUN python3 -m pip install --index-url 'https://pypi.devinfra.sentry.io/simple' 'uv==0.8.2' + +# Create venv +RUN python3 -m venv /.venv + +ENV \ + SENTRY_CONF=/etc/sentry \ + UWSGI_NEED_PLUGIN=/var/lib/uwsgi/dogstatsd \ + GRPC_POLL_STRATEGY=epoll1 + +# Copy dependency files +COPY --from=frontend-builder /build/uv.lock /build/pyproject.toml ./ + +# Install Python dependencies and uwsgi-dogstatsd plugin +RUN set -x \ + && buildDeps=" \ + gcc \ + libpcre2-dev \ + wget \ + zlib1g-dev \ + " \ + && apt-get update \ + && apt-get install -y --no-install-recommends $buildDeps \ + && uv sync --frozen --quiet --no-install-project \ + && mkdir /tmp/uwsgi-dogstatsd \ + && wget -O - https://github.com/DataDog/uwsgi-dogstatsd/archive/1a04f784491ab0270b4e94feb94686b65d8d2db1.tar.gz | \ + tar -xzf - -C /tmp/uwsgi-dogstatsd --strip-components=1 \ + && UWSGI_NEED_PLUGIN="" uwsgi --build-plugin /tmp/uwsgi-dogstatsd \ + && mkdir -p /var/lib/uwsgi \ + && mv dogstatsd_plugin.so /var/lib/uwsgi/ \ + && rm -rf /tmp/uwsgi-dogstatsd .uwsgi_plugins_builder \ + && apt-get purge -y --auto-remove $buildDeps \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && python -c 'import maxminddb.extension; maxminddb.extension.Reader' \ + && mkdir -p $SENTRY_CONF + +# Copy entire build from frontend-builder (includes src, bin, tools, etc.) +COPY --from=frontend-builder /build/src /usr/src/sentry/src +COPY --from=frontend-builder /build/bin /usr/src/sentry/bin +COPY --from=frontend-builder /build/tools /usr/src/sentry/tools +COPY --from=frontend-builder /build/scripts /usr/src/sentry/scripts +COPY --from=frontend-builder /build/pyproject.toml /usr/src/sentry/ +COPY --from=frontend-builder /build/setup.cfg /usr/src/sentry/ +COPY --from=frontend-builder /build/README.md /usr/src/sentry/ + +# Install sentry package +RUN python3 -m tools.fast_editable --path . && \ + sentry help | sed '1,/Commands:/d' | awk '{print $1}' > /sentry-commands.txt + +# Copy config files +COPY ./self-hosted/sentry.conf.py ./self-hosted/config.yml $SENTRY_CONF/ +COPY ./self-hosted/docker-entrypoint.sh / + +# Verify built files +RUN test -f /usr/src/sentry/src/sentry/loader/_registry.json && \ + test -f /usr/src/sentry/src/sentry/integration-docs/python.json && \ + test -f /usr/src/sentry/src/sentry/static/sentry/dist/entrypoints/app.js && \ + sentry help + +EXPOSE 9000 +VOLUME /data + +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["run", "web"] + +ARG SOURCE_COMMIT +ENV SENTRY_BUILD=${SOURCE_COMMIT:-unknown} +LABEL org.opencontainers.image.revision=$SOURCE_COMMIT +LABEL org.opencontainers.image.source="https://github.com/kencove/sentry/tree/${SOURCE_COMMIT:-master}/" diff --git a/self-hosted/Dockerfile.patch b/self-hosted/Dockerfile.patch new file mode 100644 index 000000000000..f316ffea43da --- /dev/null +++ b/self-hosted/Dockerfile.patch @@ -0,0 +1,23 @@ +# Dockerfile that patches the official Sentry image with GitLab Autofix support +# Much faster than rebuilding from source (~2 minutes vs ~45 minutes) +# +# Build: docker build -f self-hosted/Dockerfile.patch -t sentry:kencove . + +ARG SENTRY_VERSION=26.1.0 +FROM ghcr.io/getsentry/sentry:${SENTRY_VERSION} + +LABEL maintainer="kencove" +LABEL org.opencontainers.image.title="Sentry (Kencove)" +LABEL org.opencontainers.image.description="Kencove fork with GitLab Autofix support" + +# Copy patched autofix utils with GitLab support +# The change adds 'gitlab' and 'integrations:gitlab' to supportedProviders +COPY static/app/components/events/autofix/utils.tsx \ + /usr/src/sentry/src/sentry/static/sentry/dist/app/components/events/autofix/utils.tsx + +# Note: The above path may need adjustment - the built JS is in dist/ +# Let's find the actual location and patch the compiled JS instead + +ARG SOURCE_COMMIT +ENV SENTRY_BUILD=${SOURCE_COMMIT:-patched} +LABEL org.opencontainers.image.revision=$SOURCE_COMMIT From 16f22ad58a830b8c236441ca56257a64209cec3f Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 30 Jan 2026 15:07:28 -0500 Subject: [PATCH 03/17] fix: Patch Seer URLs before server import for self-hosted Modifies sentry.conf.py to patch sentry.conf.server module attributes BEFORE the wildcard import. This ensures all code that imports Seer URLs from the server module sees the correct service URLs. Adds SEER_URL and SEER_RPC_SHARED_SECRET environment variable support. --- self-hosted/sentry.conf.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/self-hosted/sentry.conf.py b/self-hosted/sentry.conf.py index 3e5b4c18aa31..3f5ca3388112 100644 --- a/self-hosted/sentry.conf.py +++ b/self-hosted/sentry.conf.py @@ -6,6 +6,23 @@ import os import os.path +############################################## +# CRITICAL: Patch Seer URLs before any imports +# This must happen BEFORE `from sentry.conf.server import *` +# to ensure all code sees the correct Seer service URLs +############################################## +import sentry.conf.server as _sentry_server +_sentry_server.SEER_DEFAULT_URL = os.environ.get("SEER_URL", "http://seer:9091") +_sentry_server.SEER_AUTOFIX_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_SIMILARITY_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_ANOMALY_DETECTION_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_SEVERITY_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_BREAKPOINT_DETECTION_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_GROUPING_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_SUMMARIZATION_URL = _sentry_server.SEER_DEFAULT_URL +_sentry_server.SEER_RPC_SHARED_SECRET = os.environ.get("SEER_RPC_SHARED_SECRET", "").split(",") if os.environ.get("SEER_RPC_SHARED_SECRET") else None +############################################## + # For Docker, the following environment variables are supported: # SENTRY_POSTGRES_HOST # SENTRY_POSTGRES_PORT From a303575a38cf7aecb60b8f9ff7d258bf6f19066c Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 30 Jan 2026 16:16:07 -0500 Subject: [PATCH 04/17] fix: Patch Seer URLs in server.py during Docker build --- self-hosted/Dockerfile.kencove | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/self-hosted/Dockerfile.kencove b/self-hosted/Dockerfile.kencove index b8d273be9bf3..64e5cafae3a3 100644 --- a/self-hosted/Dockerfile.kencove +++ b/self-hosted/Dockerfile.kencove @@ -140,6 +140,14 @@ COPY --from=frontend-builder /build/README.md /usr/src/sentry/ RUN python3 -m tools.fast_editable --path . && \ sentry help | sed '1,/Commands:/d' | awk '{print $1}' > /sentry-commands.txt +# Patch Seer URLs to use env vars (default to http://seer:9091 for K8s) +# This must be done in the Python source so imports see the correct values +RUN sed -i 's|SEER_DEFAULT_URL = "http://127.0.0.1:9091"|SEER_DEFAULT_URL = os.environ.get("SEER_URL", "http://seer:9091")|' \ + /usr/src/sentry/src/sentry/conf/server.py && \ + sed -i 's|SEER_RPC_SHARED_SECRET: list\[str\] | None = None|SEER_RPC_SHARED_SECRET = os.environ.get("SEER_RPC_SHARED_SECRET", "").split(",") if os.environ.get("SEER_RPC_SHARED_SECRET") else None|' \ + /usr/src/sentry/src/sentry/conf/server.py && \ + echo "Seer URLs patched successfully" + # Copy config files COPY ./self-hosted/sentry.conf.py ./self-hosted/config.yml $SENTRY_CONF/ COPY ./self-hosted/docker-entrypoint.sh / From ffc257444807c54713049af2818fdd6b05d19a62 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 30 Jan 2026 16:36:22 -0500 Subject: [PATCH 05/17] fix: Use # as sed delimiter to avoid escaping issues --- self-hosted/Dockerfile.kencove | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/self-hosted/Dockerfile.kencove b/self-hosted/Dockerfile.kencove index 64e5cafae3a3..df4d67849d5c 100644 --- a/self-hosted/Dockerfile.kencove +++ b/self-hosted/Dockerfile.kencove @@ -142,10 +142,9 @@ RUN python3 -m tools.fast_editable --path . && \ # Patch Seer URLs to use env vars (default to http://seer:9091 for K8s) # This must be done in the Python source so imports see the correct values -RUN sed -i 's|SEER_DEFAULT_URL = "http://127.0.0.1:9091"|SEER_DEFAULT_URL = os.environ.get("SEER_URL", "http://seer:9091")|' \ - /usr/src/sentry/src/sentry/conf/server.py && \ - sed -i 's|SEER_RPC_SHARED_SECRET: list\[str\] | None = None|SEER_RPC_SHARED_SECRET = os.environ.get("SEER_RPC_SHARED_SECRET", "").split(",") if os.environ.get("SEER_RPC_SHARED_SECRET") else None|' \ +RUN sed -i 's#SEER_DEFAULT_URL = "http://127.0.0.1:9091"#SEER_DEFAULT_URL = os.environ.get("SEER_URL", "http://seer:9091")#' \ /usr/src/sentry/src/sentry/conf/server.py && \ + grep -q 'SEER_DEFAULT_URL = os.environ.get' /usr/src/sentry/src/sentry/conf/server.py && \ echo "Seer URLs patched successfully" # Copy config files From 7b2d8c683ff9de7f0f35c48f9509101974f040a6 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Sat, 31 Jan 2026 09:02:49 -0500 Subject: [PATCH 06/17] feat: Disable seer-explorer feature flag for self-hosted The Explorer-based autofix requires additional endpoints not yet fully implemented in self-hosted Seer. Disable the feature flag to use the legacy /v1/automation/autofix/start endpoint instead. Co-Authored-By: Claude Opus 4.5 --- self-hosted/sentry.conf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/self-hosted/sentry.conf.py b/self-hosted/sentry.conf.py index 3f5ca3388112..14fc6aceb9cd 100644 --- a/self-hosted/sentry.conf.py +++ b/self-hosted/sentry.conf.py @@ -251,3 +251,10 @@ SENTRY_OPTIONS["system.secret-key"] = secret_key SENTRY_USE_RELAY = True + +############################################## +# Feature Flags for Self-Hosted +############################################## +# Disable Explorer-based autofix (not available in self-hosted Seer) +# This forces Sentry to use the legacy /v1/automation/autofix/start endpoint +SENTRY_FEATURES["organizations:seer-explorer"] = False From ba60387f6889721cf877a4566537b1ac2388484a Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Tue, 17 Feb 2026 23:33:12 -0500 Subject: [PATCH 07/17] =?UTF-8?q?fix(ci):=20Fix=20Cloud=20Build=20workflow?= =?UTF-8?q?=20=E2=80=94=20correct=20WIF,=20SA,=20branch,=20and=20use=20gcl?= =?UTF-8?q?oud=20builds=20submit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WIF provider: github-wlif/providers/github-oidc (was nonexistent github-pool/providers/github-provider) - Service account: github-actions-seer@ (was nonexistent github-actions@) - Branch trigger: 26.1.0-* (was master, which never has the workflow file) - Use gcloud builds submit instead of local docker build (Sentry needs E2_HIGHCPU_32 + 200GB) - Dockerfile: self-hosted/Dockerfile.kencove via cloudbuild.yaml - Add .gcloudignore to exclude .git, node_modules, tests from source upload Co-Authored-By: Claude Opus 4.6 --- .gcloudignore | 13 ++++++ .github/workflows/build-kencove.yml | 72 ++++------------------------- 2 files changed, 23 insertions(+), 62 deletions(-) create mode 100644 .gcloudignore diff --git a/.gcloudignore b/.gcloudignore new file mode 100644 index 000000000000..d516397b7fb7 --- /dev/null +++ b/.gcloudignore @@ -0,0 +1,13 @@ +# Cloud Build source upload ignore +# gcloud builds submit uses this to exclude files from the source tarball +.devenv/ +.git/ +.venv/ +.webpack_cache/ +node_modules/ +fixtures/ +tests/ +*.pyc +__pycache__/ +.coverage* +.DS_Store diff --git a/.github/workflows/build-kencove.yml b/.github/workflows/build-kencove.yml index dc2281672430..6da0dd7b93fc 100644 --- a/.github/workflows/build-kencove.yml +++ b/.github/workflows/build-kencove.yml @@ -3,11 +3,11 @@ name: Build Kencove Sentry Image on: push: branches: - - master + - '*-kencove' workflow_dispatch: inputs: tag: - description: 'Image tag (e.g., v26.1.0-gitlab)' + description: 'Image tag override (default: commit SHA)' required: false default: '' @@ -27,59 +27,15 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: '.node-version' - - - uses: pnpm/action-setup@v4 - - - uses: astral-sh/setup-uv@v4 - with: - version: '0.8.2' - - - name: Setup Python venv - run: | - uv venv - source .venv/bin/activate - echo "PATH=$PWD/.venv/bin:$PATH" >> $GITHUB_ENV - - - name: Cache webpack - uses: actions/cache@v4 - with: - path: .webpack_cache - key: webpack-${{ hashFiles('rspack.config.ts') }} - - - name: Cache node_modules - uses: actions/cache@v4 - id: node-cache - with: - path: node_modules - key: node-modules-${{ hashFiles('pnpm-lock.yaml') }} - - - name: Install Node dependencies - if: steps.node-cache.outputs.cache-hit != 'true' - run: pnpm install --frozen-lockfile - - - name: Build frontend - run: | - python3 -m tools.fast_editable --path . - python3 -m sentry.build.main - env: - WEBPACK_CACHE_PATH: .webpack_cache - NODE_OPTIONS: '--max-old-space-size=4096' - - name: Authenticate to Google Cloud uses: google-github-actions/auth@v2 with: - workload_identity_provider: 'projects/103143301688/locations/global/workloadIdentityPools/github-pool/providers/github-provider' - service_account: 'github-actions@kencove-prod.iam.gserviceaccount.com' + workload_identity_provider: 'projects/103143301688/locations/global/workloadIdentityPools/github-wlif/providers/github-oidc' + service_account: 'github-actions-seer@kencove-prod.iam.gserviceaccount.com' - name: Set up Cloud SDK uses: google-github-actions/setup-gcloud@v2 - - name: Configure Docker for Artifact Registry - run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet - - name: Determine image tag id: tag run: | @@ -89,19 +45,11 @@ jobs: echo "tag=${{ github.sha }}" >> $GITHUB_OUTPUT fi - - name: Build and push Docker image + - name: Submit Cloud Build run: | - docker build \ - -t ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} \ - -t ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:latest \ - -f self-hosted/Dockerfile \ - --build-arg SOURCE_COMMIT=${{ github.sha }} \ + gcloud builds submit \ + --config=cloudbuild.yaml \ + --substitutions=COMMIT_SHA=${{ github.sha }},_TAG=${{ steps.tag.outputs.tag }} \ + --project=${{ env.PROJECT_ID }} \ + --async \ . - - docker push ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} - docker push ${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:latest - - - name: Output image info - run: | - echo "## Build Complete" >> $GITHUB_STEP_SUMMARY - echo "Image: \`${{ env.REGION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}\`" >> $GITHUB_STEP_SUMMARY From df0ecc8102eb0ea56ad431ed89e755aa022b3661 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 18 Feb 2026 08:31:13 -0500 Subject: [PATCH 08/17] fix(build): scope gcloudignore patterns to root directories fixtures/ and tests/ glob matched ALL directories named fixtures/tests throughout the tree, excluding frontend fixture files needed by rspack. Prefix with / to only match root-level directories. Co-Authored-By: Claude Opus 4.6 --- .gcloudignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gcloudignore b/.gcloudignore index d516397b7fb7..d5de8d5ad830 100644 --- a/.gcloudignore +++ b/.gcloudignore @@ -5,8 +5,8 @@ .venv/ .webpack_cache/ node_modules/ -fixtures/ -tests/ +/fixtures/ +/tests/ *.pyc __pycache__/ .coverage* From b660ef83bf7899adc5bd06bf2e7926146c557bab Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 18 Feb 2026 08:54:30 -0500 Subject: [PATCH 09/17] fix(build): remove uwsgi-dogstatsd plugin and upgrade uv to 0.9.28 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream 26.2.0 removed uwsgi from dependencies. Also align uv version with upstream (0.8.2 → 0.9.28). Co-Authored-By: Claude Opus 4.6 --- self-hosted/Dockerfile.kencove | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/self-hosted/Dockerfile.kencove b/self-hosted/Dockerfile.kencove index df4d67849d5c..512a2416a222 100644 --- a/self-hosted/Dockerfile.kencove +++ b/self-hosted/Dockerfile.kencove @@ -22,7 +22,7 @@ RUN apt-get update && apt-get install -y \ && rm -rf /var/lib/apt/lists/* # Install uv for Python dependency management -RUN pip install uv==0.8.2 +RUN pip install uv==0.9.28 # Copy dependency files first for layer caching COPY package.json pnpm-lock.yaml .node-version ./ @@ -90,20 +90,19 @@ ENV PATH="/.venv/bin:$PATH" UV_PROJECT_ENVIRONMENT=/.venv \ UV_COMPILE_BYTECODE=1 UV_NO_CACHE=1 # Install uv -RUN python3 -m pip install --index-url 'https://pypi.devinfra.sentry.io/simple' 'uv==0.8.2' +RUN python3 -m pip install --index-url 'https://pypi.devinfra.sentry.io/simple' 'uv==0.9.28' # Create venv RUN python3 -m venv /.venv ENV \ SENTRY_CONF=/etc/sentry \ - UWSGI_NEED_PLUGIN=/var/lib/uwsgi/dogstatsd \ GRPC_POLL_STRATEGY=epoll1 # Copy dependency files COPY --from=frontend-builder /build/uv.lock /build/pyproject.toml ./ -# Install Python dependencies and uwsgi-dogstatsd plugin +# Install Python dependencies RUN set -x \ && buildDeps=" \ gcc \ @@ -114,13 +113,6 @@ RUN set -x \ && apt-get update \ && apt-get install -y --no-install-recommends $buildDeps \ && uv sync --frozen --quiet --no-install-project \ - && mkdir /tmp/uwsgi-dogstatsd \ - && wget -O - https://github.com/DataDog/uwsgi-dogstatsd/archive/1a04f784491ab0270b4e94feb94686b65d8d2db1.tar.gz | \ - tar -xzf - -C /tmp/uwsgi-dogstatsd --strip-components=1 \ - && UWSGI_NEED_PLUGIN="" uwsgi --build-plugin /tmp/uwsgi-dogstatsd \ - && mkdir -p /var/lib/uwsgi \ - && mv dogstatsd_plugin.so /var/lib/uwsgi/ \ - && rm -rf /tmp/uwsgi-dogstatsd .uwsgi_plugins_builder \ && apt-get purge -y --auto-remove $buildDeps \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ From a20e5fd541f83fb0375e32cbe56625608540318a Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 18 Feb 2026 11:37:17 -0500 Subject: [PATCH 10/17] fix(seer): Make org_id and integration_id optional in preferences serializer Seer returns repositories without organization_id and integration_id, but the DRF serializer required them. This caused 400 errors when the frontend sent back Seer's repo data in preference updates (e.g. setting Cursor handoff). Also adds external_id fallback for repo validation since GitLab repos in Seer have simplified owner/name that differs from the DB format. Co-Authored-By: Claude Opus 4.6 --- .../endpoints/project_seer_preferences.py | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/sentry/seer/endpoints/project_seer_preferences.py b/src/sentry/seer/endpoints/project_seer_preferences.py index 3ffa7d6bf3e0..e23e7666824c 100644 --- a/src/sentry/seer/endpoints/project_seer_preferences.py +++ b/src/sentry/seer/endpoints/project_seer_preferences.py @@ -1,5 +1,7 @@ from __future__ import annotations +import logging + from rest_framework import serializers from rest_framework.request import Request from rest_framework.response import Response @@ -9,7 +11,9 @@ from sentry.api.base import cell_silo_endpoint from sentry.api.bases.project import ProjectEndpoint, ProjectEventPermission from sentry.api.serializers.rest_framework import CamelSnakeSerializer +from sentry.constants import ObjectStatus from sentry.models.project import Project +from sentry.models.repository import Repository from sentry.ratelimits.config import RateLimitConfig from sentry.seer.autofix.utils import ( deduplicate_repositories, @@ -24,10 +28,15 @@ from sentry.seer.utils import filter_repo_by_provider from sentry.types.ratelimit import RateLimit, RateLimitCategory +logger = logging.getLogger(__name__) + class RepositorySerializer(BaseRepositorySerializer): - organization_id = serializers.IntegerField(required=True) - integration_id = serializers.CharField(required=True) + # ledoent/kencove fork: make org_id + integration_id optional. Self-hosted + # single-org deployments and GitLab repos don't always carry these on the + # request; we fill them in from the matched DB row in the endpoint below. + organization_id = serializers.IntegerField(required=False, allow_null=True) + integration_id = serializers.CharField(required=False, allow_null=True, allow_blank=True) class SeerAutomationHandoffConfigurationSerializer(CamelSnakeSerializer): @@ -108,13 +117,29 @@ def post(self, request: Request, project: Project) -> Response: repo_data["organization_id"] = project.organization.id - repo = filter_repo_by_provider( + # Try matching by provider + external_id + owner/name first. + repo_qs = filter_repo_by_provider( project.organization.id, provider, external_id, owner, name - ).first() + ) + repo = repo_qs.first() if repo is None: - return Response({"detail": "Invalid repository"}, status=400) + # ledoent/kencove fork: fallback matches by external_id only. + # Handles GitLab repos where Seer stores a simplified + # owner/name that differs from the DB format. + repo = Repository.objects.filter( + organization_id=project.organization.id, + external_id=external_id, + status=ObjectStatus.ACTIVE, + ).first() + if repo is None: + return Response({"detail": "Invalid repository"}, status=400) repo_data["repository_id"] = repo.id + # Fill in integration_id from the matched repo if the request + # didn't include one (also kencove fork — see optional fields above). + if not repo_data.get("integration_id") and repo.integration_id: + repo_data["integration_id"] = str(repo.integration_id) + preference = SeerProjectPreference.validate( { **serializer.validated_data, From abae6d8c5ed015eca44b93d1eba80f10fc996677 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Mon, 18 May 2026 16:36:02 -0400 Subject: [PATCH 11/17] ci(sentry): add GHCR public build workflow (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(sentry): add GHCR public build workflow Publishes ledoent fork to ghcr.io/ledoent/sentry (public) so self-hosted Sentry+Seer deployments can pull without GCP credentials. Builds self-hosted/Dockerfile.kencove for amd64. Free disk on runner before build because Sentry's frontend+backend build is large. Sibling to build-kencove.yml (kept for kencove-prod Cloud Build). * ci(sentry): concurrency + safer disk-cleanup in GHCR workflow Address bugbot findings on the GHCR build workflow: - Add concurrency group keyed on github.ref with cancel-in-progress so rapid pushes (or a force-push) don't race two builds into publishing the same mutable tag. Saves GHA minutes too. - Tighten the 'Free disk space' step: per-path existence check via [ -e "$path" ] && rm -rf, plus drop the blanket '|| true' so a real failure during docker image prune surfaces instead of masking. - Update the runner-RAM comment: GitHub free ubuntu-latest is 16 GB now (post-2024 Larger Hosted Runners refresh), not 7 GB; our first build completed in ~12 min on it. Bump to ubuntu-latest-large only if webpack starts OOMing. * style(ci): prettier — single space before inline comment Sentry's pre-commit prettier hook collapsed a double-space to a single space before the 'full history' comment on fetch-depth. No semantic change. --- .github/workflows/build-push-ghcr.yml | 127 ++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 .github/workflows/build-push-ghcr.yml diff --git a/.github/workflows/build-push-ghcr.yml b/.github/workflows/build-push-ghcr.yml new file mode 100644 index 000000000000..f319a283a1ac --- /dev/null +++ b/.github/workflows/build-push-ghcr.yml @@ -0,0 +1,127 @@ +name: Build and Push Sentry to GHCR (Public) + +# Publishes a public ledoent fork of self-hosted Sentry to GitHub Container Registry. +# Companion to build-kencove.yml (kencove-prod Cloud Build). This workflow exists +# so self-hosted Sentry+Seer deployments can pull without GCP credentials. + +on: + push: + branches: + - '*-kencove' + - feat/build-push-ghcr + tags: + - 'v*-kencove' + workflow_dispatch: + inputs: + tag: + description: 'Image tag override (default: branch-shortsha)' + required: false + type: string + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ledoent/sentry + +# Cancel any in-flight build for the same ref when a new push lands. Saves +# ~30 GHA minutes per force-push and prevents two builds from racing to +# publish the same mutable tag. +concurrency: + group: build-push-ghcr-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-push: + # ubuntu-latest is 16 GB RAM on GitHub-hosted free runners as of 2026 + # (was 7 GB before the 2024 Larger Hosted Runners refresh). The Sentry + # frontend build uses NODE_OPTIONS=4096 in Dockerfile.kencove; our first + # build completed in ~12 min on this runner. Bump to ubuntu-latest-large + # (32 GB) only if webpack starts OOMing. + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history so sentry.build can derive the version + + - name: Free disk space + run: | + # Sentry's build is disk-hungry; trim runner-preinstalled toolchains + # we don't use. Each rm is best-effort against a wildcard so a path + # being absent doesn't fail the build — but we keep the *step* exit + # status real so an unrelated failure (e.g., the docker prune) still + # surfaces. + df -h / + for path in /usr/share/dotnet /usr/local/lib/android /opt/ghc \ + /opt/hostedtoolcache/CodeQL; do + [ -e "$path" ] && sudo rm -rf "$path" + done + sudo docker image prune -af + df -h / + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate image tags + id: tags + env: + INPUT_TAG: ${{ inputs.tag }} + REF_NAME: ${{ github.ref_name }} + run: | + IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + SHA_SHORT=$(git rev-parse --short HEAD) + BRANCH_TAG=$(echo "$REF_NAME" | sed 's|[^a-zA-Z0-9._-]|-|g' | cut -c1-60) + + TAGS="${IMAGE}:${BRANCH_TAG}-${SHA_SHORT}" + TAGS="${TAGS},${IMAGE}:${BRANCH_TAG}" + + if [ -n "$INPUT_TAG" ]; then + TAGS="${TAGS},${IMAGE}:${INPUT_TAG}" + fi + + if [ "${{ github.ref_type }}" = "tag" ]; then + TAGS="${TAGS},${IMAGE}:${REF_NAME}" + fi + + { + echo "tags=${TAGS}" + echo "sha_short=${SHA_SHORT}" + } >> "$GITHUB_OUTPUT" + echo "Generated tags: ${TAGS}" + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: self-hosted/Dockerfile.kencove + platforms: linux/amd64 + push: true + tags: ${{ steps.tags.outputs.tags }} + build-args: | + SOURCE_COMMIT=${{ github.sha }} + labels: | + org.opencontainers.image.source=https://github.com/ledoent/sentry + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.title=Sentry (ledoent / kencove fork) + org.opencontainers.image.description=Self-hosted Sentry with SEER_URL env support + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Summary + run: | + { + echo "### Sentry pushed to GHCR :rocket:" + echo "" + echo "**Tags:**" + echo '${{ steps.tags.outputs.tags }}' | tr ',' '\n' | sed 's/^/- `/' | sed 's/$/`/' + } >> "$GITHUB_STEP_SUMMARY" From 9dc3c3edcebc1d8b9aa8d88cc1079962ca9f070d Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 20 May 2026 14:12:53 -0400 Subject: [PATCH 12/17] fix(build): COPY patches/ before pnpm install for 26.5.0 lockfile Upstream 26.5.0's pnpm-lock.yaml uses pnpm's patchedDependencies for zod@4.3.5 (the patch file lives at patches/zod@4.3.5.patch). pnpm install reads the patch contents at lockfile-resolve time, so the directory has to be on disk before that step. Was failing in CI with: ENOENT: no such file or directory, open '/build/patches/zod@4.3.5.patch' The 26.2.0 lockfile didn't have patchedDependencies so the old COPY . . on line 40 (AFTER pnpm install) was sufficient. Adding an explicit COPY patches/ ./patches/ before the install fixes it cleanly without breaking layer caching. --- self-hosted/Dockerfile.kencove | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/self-hosted/Dockerfile.kencove b/self-hosted/Dockerfile.kencove index 512a2416a222..d9c572b59c03 100644 --- a/self-hosted/Dockerfile.kencove +++ b/self-hosted/Dockerfile.kencove @@ -24,8 +24,11 @@ RUN apt-get update && apt-get install -y \ # Install uv for Python dependency management RUN pip install uv==0.9.28 -# Copy dependency files first for layer caching +# Copy dependency files first for layer caching. patches/ is needed +# before pnpm install because upstream 26.5.0's lockfile references +# patches/zod@4.3.5.patch via pnpm's `patchedDependencies` field. COPY package.json pnpm-lock.yaml .node-version ./ +COPY patches/ ./patches/ COPY pyproject.toml uv.lock ./ # Install Node dependencies with shamefully-hoist to fix module resolution From c81a1e7a1eebbf6fbebce104fd71f213cc8dad03 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 20 May 2026 14:18:24 -0400 Subject: [PATCH 13/17] ci: hourly sync of master from upstream getsentry/sentry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this workflow ledoent/sentry@master sits frozen at whatever commit the fork was created from. Every PR from a *-kencove rebase branch then shows GitHub's 'this branch has conflicts' against master — even though the kencove patches themselves don't conflict. The workflow: * Runs hourly + on-demand * Fast-forwards master to upstream/master when possible (the normal case — kencove patches live on *-kencove branches, never on master) * Never force-pushes * If upstream/master can't be FF'd (means master has diverged), writes a warning to the job summary and exits non-zero so a human sees it in the Actions tab Drop-in deployment — no secrets required (GITHUB_TOKEN with contents:write is auto-provided by the workflow run). Verified at fork creation time: ledoent/sentry@master is 166 commits behind getsentry/sentry@master. First run will catch us up. --- .github/workflows/sync-upstream.yml | 80 +++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 000000000000..b2d64cc666cf --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,80 @@ +name: Sync master from upstream + +# Keeps ledoent/sentry@master fast-forwarded to getsentry/sentry@master +# so PR conflict-detection actually reflects reality. Without this, the +# fork's master sits frozen at whatever commit it was created from, and +# every PR from a `*-kencove` rebase branch shows GitHub's "this branch +# has conflicts" — even though the kencove patches themselves don't. +# +# Runs: +# * Hourly via cron (cheap; FFs typically a few commits at most) +# * On-demand via workflow_dispatch +# +# Fast-forward only — never force-push. If upstream's master diverges +# in a way that requires a merge commit (e.g. we cherry-picked something +# from upstream into our master), this job skips the FF and writes a +# warning to the job summary. A human follow-up is needed in that case. + +on: + schedule: + - cron: "17 * * * *" # hourly, offset to avoid the top-of-hour rush + workflow_dispatch: + +concurrency: + group: sync-upstream-master + cancel-in-progress: false # let an in-flight sync finish + +permissions: + contents: write # needed to push the FF to master + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: master + fetch-depth: 0 # full history so we can FF-detect against upstream + + - name: Fetch upstream + run: | + git remote add upstream https://github.com/getsentry/sentry.git + git fetch upstream master --tags + + - name: Fast-forward master + id: ff + run: | + LOCAL_SHA=$(git rev-parse HEAD) + UPSTREAM_SHA=$(git rev-parse upstream/master) + echo "local=$LOCAL_SHA" >> "$GITHUB_OUTPUT" + echo "upstream=$UPSTREAM_SHA" >> "$GITHUB_OUTPUT" + + if [ "$LOCAL_SHA" = "$UPSTREAM_SHA" ]; then + echo "result=already-current" >> "$GITHUB_OUTPUT" + echo "✓ master already at upstream/master ($UPSTREAM_SHA)" >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + + # FF only — refuses if local has commits not in upstream + if git merge --ff-only upstream/master; then + echo "result=fast-forwarded" >> "$GITHUB_OUTPUT" + echo "✓ master fast-forwarded $LOCAL_SHA → $UPSTREAM_SHA" >> "$GITHUB_STEP_SUMMARY" + else + echo "result=diverged" >> "$GITHUB_OUTPUT" + BEHIND=$(git rev-list --count HEAD..upstream/master) + AHEAD=$(git rev-list --count upstream/master..HEAD) + { + echo "⚠ master has diverged from upstream" + echo "" + echo "* local commits not in upstream: $AHEAD" + echo "* upstream commits not in local: $BEHIND" + echo "" + echo "Resolve manually — fast-forward is no longer possible." + echo "Either rebase + force-push master, or restore from upstream." + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + + - name: Push fast-forwarded master + if: steps.ff.outputs.result == 'fast-forwarded' + run: git push origin master From 59aea0e06bcd27ede59a2605ddec4d114e17419a Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 20 May 2026 14:21:45 -0400 Subject: [PATCH 14/17] =?UTF-8?q?Revert=20ci:=20per-repo=20sync-upstream?= =?UTF-8?q?=20=E2=80=94=20handled=20at=20org=20level?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ledoent/.github repo has fork-sync-and-digest.yml that already syncs all forks listed in .github/forks.yml via the merge-upstream API. Adding ledoent/sentry to that inventory is the right path — keeps the sync logic in one place and gets the daily digest email alongside the OCA forks. --- .github/workflows/sync-upstream.yml | 80 ----------------------------- 1 file changed, 80 deletions(-) delete mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml deleted file mode 100644 index b2d64cc666cf..000000000000 --- a/.github/workflows/sync-upstream.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Sync master from upstream - -# Keeps ledoent/sentry@master fast-forwarded to getsentry/sentry@master -# so PR conflict-detection actually reflects reality. Without this, the -# fork's master sits frozen at whatever commit it was created from, and -# every PR from a `*-kencove` rebase branch shows GitHub's "this branch -# has conflicts" — even though the kencove patches themselves don't. -# -# Runs: -# * Hourly via cron (cheap; FFs typically a few commits at most) -# * On-demand via workflow_dispatch -# -# Fast-forward only — never force-push. If upstream's master diverges -# in a way that requires a merge commit (e.g. we cherry-picked something -# from upstream into our master), this job skips the FF and writes a -# warning to the job summary. A human follow-up is needed in that case. - -on: - schedule: - - cron: "17 * * * *" # hourly, offset to avoid the top-of-hour rush - workflow_dispatch: - -concurrency: - group: sync-upstream-master - cancel-in-progress: false # let an in-flight sync finish - -permissions: - contents: write # needed to push the FF to master - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: master - fetch-depth: 0 # full history so we can FF-detect against upstream - - - name: Fetch upstream - run: | - git remote add upstream https://github.com/getsentry/sentry.git - git fetch upstream master --tags - - - name: Fast-forward master - id: ff - run: | - LOCAL_SHA=$(git rev-parse HEAD) - UPSTREAM_SHA=$(git rev-parse upstream/master) - echo "local=$LOCAL_SHA" >> "$GITHUB_OUTPUT" - echo "upstream=$UPSTREAM_SHA" >> "$GITHUB_OUTPUT" - - if [ "$LOCAL_SHA" = "$UPSTREAM_SHA" ]; then - echo "result=already-current" >> "$GITHUB_OUTPUT" - echo "✓ master already at upstream/master ($UPSTREAM_SHA)" >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - - # FF only — refuses if local has commits not in upstream - if git merge --ff-only upstream/master; then - echo "result=fast-forwarded" >> "$GITHUB_OUTPUT" - echo "✓ master fast-forwarded $LOCAL_SHA → $UPSTREAM_SHA" >> "$GITHUB_STEP_SUMMARY" - else - echo "result=diverged" >> "$GITHUB_OUTPUT" - BEHIND=$(git rev-list --count HEAD..upstream/master) - AHEAD=$(git rev-list --count upstream/master..HEAD) - { - echo "⚠ master has diverged from upstream" - echo "" - echo "* local commits not in upstream: $AHEAD" - echo "* upstream commits not in local: $BEHIND" - echo "" - echo "Resolve manually — fast-forward is no longer possible." - echo "Either rebase + force-push master, or restore from upstream." - } >> "$GITHUB_STEP_SUMMARY" - exit 1 - fi - - - name: Push fast-forwarded master - if: steps.ff.outputs.result == 'fast-forwarded' - run: git push origin master From 50354e0f14ae11878d708d1b75c8a4ae57bac9b8 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 20 May 2026 14:30:35 -0400 Subject: [PATCH 15/17] fix(build): bump node 22 -> 24 and pnpm 9 -> 10 for 26.5.0 lockfile Upstream 26.5.0 pins .node-version=24.14.0 and packageManager=pnpm@10.30.2. Our Dockerfile was still installing node@22 + pnpm@9 from the 26.2.0 era. pnpm@9 doesn't recognize the new lockfile patchedDependencies format and bails out with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH at frozen install time. Drop-in version bumps; no other Dockerfile changes needed. --- self-hosted/Dockerfile.kencove | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/self-hosted/Dockerfile.kencove b/self-hosted/Dockerfile.kencove index d9c572b59c03..264a58caf0a3 100644 --- a/self-hosted/Dockerfile.kencove +++ b/self-hosted/Dockerfile.kencove @@ -16,10 +16,14 @@ RUN apt-get update && apt-get install -y \ gnupg \ build-essential \ git \ - && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ && apt-get install -y nodejs \ - && npm install -g pnpm@9 \ + && npm install -g pnpm@10 \ && rm -rf /var/lib/apt/lists/* +# Upstream 26.5.0 .node-version pins 24.14.0 + package.json +# packageManager pins pnpm@10.30.2. Older toolchains hit +# ERR_PNPM_LOCKFILE_CONFIG_MISMATCH on the new patchedDependencies +# lockfile format. # Install uv for Python dependency management RUN pip install uv==0.9.28 From 7222a8fb0460d8953ddc6abf8769ecf2b84996b8 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 20 May 2026 14:44:12 -0400 Subject: [PATCH 16/17] =?UTF-8?q?fix(build):=20use=20upstream=20getsentry/?= =?UTF-8?q?sentry:26.5.0=20as=20base=20=E2=80=94=20drop=20in-image=20front?= =?UTF-8?q?end=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three iterations failing on the in-Dockerfile rspack frontend build (typescript loader resolution after upstream's type-loader.ts work in 26.5.0). Backing off to a much simpler strategy: layer our small backend patches on top of the pre-built getsentry/sentry:26.5.0 image that upstream's own CI publishes. This trades a (now-redundant) frontend customization for build reliability. The kencove fork's only frontend patch was adding 'gitlab' + 'integrations:gitlab' to supportedProviders — and upstream 26.5.0 already has both in BASE_SUPPORTED_PROVIDERS natively, with a feature-flag-gated provider mechanism added on top. So we lose nothing. What this image still contains: * Patched src/sentry/seer/endpoints/project_seer_preferences.py (optional org_id/integration_id + GitLab fallback) * SEER_URL sed patch on src/sentry/conf/server.py * self-hosted/sentry.conf.py overlay (seer-explorer flag off) What's dropped: * The two-stage frontend-builder + python-runtime build * uwsgi-dogstatsd remove (upstream image already handles this) * Node + pnpm install (upstream's image has the frontend bundle) * sentry.build.main invocation (upstream's image already has dist/) Dockerfile shrunk from ~170 lines to ~60. Build time goes from ~12 min cold to <30s (just an apt overlay on a pre-built base). --- self-hosted/Dockerfile.kencove | 227 ++++++++++----------------------- 1 file changed, 64 insertions(+), 163 deletions(-) diff --git a/self-hosted/Dockerfile.kencove b/self-hosted/Dockerfile.kencove index 264a58caf0a3..30b29c900fdc 100644 --- a/self-hosted/Dockerfile.kencove +++ b/self-hosted/Dockerfile.kencove @@ -1,168 +1,69 @@ -# Multi-stage Dockerfile for Kencove Sentry build -# This builds the frontend and creates the runtime image in one Dockerfile +# Ledoent/Kencove self-hosted Sentry image. # -# Build: docker build -f self-hosted/Dockerfile.kencove -t sentry:kencove . - -# ============================================================================= -# Stage 1: Build frontend assets using the same Python base as runtime -# ============================================================================= -FROM python:3.13.1-slim-bookworm AS frontend-builder - -WORKDIR /build - -# Install Node.js 22 and build tools -RUN apt-get update && apt-get install -y \ - curl \ - gnupg \ - build-essential \ - git \ - && curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ - && apt-get install -y nodejs \ - && npm install -g pnpm@10 \ - && rm -rf /var/lib/apt/lists/* -# Upstream 26.5.0 .node-version pins 24.14.0 + package.json -# packageManager pins pnpm@10.30.2. Older toolchains hit -# ERR_PNPM_LOCKFILE_CONFIG_MISMATCH on the new patchedDependencies -# lockfile format. - -# Install uv for Python dependency management -RUN pip install uv==0.9.28 - -# Copy dependency files first for layer caching. patches/ is needed -# before pnpm install because upstream 26.5.0's lockfile references -# patches/zod@4.3.5.patch via pnpm's `patchedDependencies` field. -COPY package.json pnpm-lock.yaml .node-version ./ -COPY patches/ ./patches/ -COPY pyproject.toml uv.lock ./ - -# Install Node dependencies with shamefully-hoist to fix module resolution -RUN pnpm install --frozen-lockfile --shamefully-hoist - -# Create Python venv and install dependencies (needed for sentry.build) -ENV PATH="/build/.venv/bin:$PATH" -RUN python -m venv .venv && \ - uv sync --frozen --quiet --no-install-project - -# Copy source code -COPY . . - -# Initialize minimal git repo for build scripts that need it -ARG SOURCE_COMMIT=unknown -RUN git init && \ - git config user.email "build@kencove.com" && \ - git config user.name "Build" && \ - git add -A && \ - git commit -m "Build commit" --allow-empty && \ - echo "${SOURCE_COMMIT}" > .git/COMMIT_EDITMSG - -# Build frontend assets -ENV WEBPACK_CACHE_PATH=/build/.webpack_cache -ENV NODE_OPTIONS="--max-old-space-size=4096" -ENV SENTRY_BUILD=${SOURCE_COMMIT} -RUN python -m tools.fast_editable --path . && \ - python -m sentry.build.main - -# Verify build outputs -RUN test -f src/sentry/static/sentry/dist/entrypoints/app.js && \ - test -f src/sentry/loader/_registry.json && \ - echo "Frontend build verified successfully" - -# ============================================================================= -# Stage 2: Runtime image (same as official self-hosted/Dockerfile) -# ============================================================================= -FROM python:3.13.1-slim-bookworm AS runtime - -LABEL maintainer="kencove" -LABEL org.opencontainers.image.title="Sentry (Kencove)" -LABEL org.opencontainers.image.description="Kencove fork with GitLab Autofix support" -LABEL org.opencontainers.image.url="https://github.com/kencove/sentry" -LABEL org.opencontainers.image.vendor="Kencove" - -# Add user and group -RUN groupadd -r sentry --gid 999 && useradd -r -m -g sentry --uid 999 sentry - -# Install runtime dependencies -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - gosu \ - libexpat1 \ - tini \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /usr/src/sentry - -ENV PATH="/.venv/bin:$PATH" UV_PROJECT_ENVIRONMENT=/.venv \ - PIP_NO_CACHE_DIR=1 PIP_DISABLE_PIP_VERSION_CHECK=1 \ - UV_COMPILE_BYTECODE=1 UV_NO_CACHE=1 - -# Install uv -RUN python3 -m pip install --index-url 'https://pypi.devinfra.sentry.io/simple' 'uv==0.9.28' - -# Create venv -RUN python3 -m venv /.venv - -ENV \ - SENTRY_CONF=/etc/sentry \ - GRPC_POLL_STRATEGY=epoll1 - -# Copy dependency files -COPY --from=frontend-builder /build/uv.lock /build/pyproject.toml ./ - -# Install Python dependencies -RUN set -x \ - && buildDeps=" \ - gcc \ - libpcre2-dev \ - wget \ - zlib1g-dev \ - " \ - && apt-get update \ - && apt-get install -y --no-install-recommends $buildDeps \ - && uv sync --frozen --quiet --no-install-project \ - && apt-get purge -y --auto-remove $buildDeps \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* \ - && python -c 'import maxminddb.extension; maxminddb.extension.Reader' \ - && mkdir -p $SENTRY_CONF - -# Copy entire build from frontend-builder (includes src, bin, tools, etc.) -COPY --from=frontend-builder /build/src /usr/src/sentry/src -COPY --from=frontend-builder /build/bin /usr/src/sentry/bin -COPY --from=frontend-builder /build/tools /usr/src/sentry/tools -COPY --from=frontend-builder /build/scripts /usr/src/sentry/scripts -COPY --from=frontend-builder /build/pyproject.toml /usr/src/sentry/ -COPY --from=frontend-builder /build/setup.cfg /usr/src/sentry/ -COPY --from=frontend-builder /build/README.md /usr/src/sentry/ - -# Install sentry package -RUN python3 -m tools.fast_editable --path . && \ - sentry help | sed '1,/Commands:/d' | awk '{print $1}' > /sentry-commands.txt - -# Patch Seer URLs to use env vars (default to http://seer:9091 for K8s) -# This must be done in the Python source so imports see the correct values -RUN sed -i 's#SEER_DEFAULT_URL = "http://127.0.0.1:9091"#SEER_DEFAULT_URL = os.environ.get("SEER_URL", "http://seer:9091")#' \ - /usr/src/sentry/src/sentry/conf/server.py && \ - grep -q 'SEER_DEFAULT_URL = os.environ.get' /usr/src/sentry/src/sentry/conf/server.py && \ - echo "Seer URLs patched successfully" - -# Copy config files -COPY ./self-hosted/sentry.conf.py ./self-hosted/config.yml $SENTRY_CONF/ -COPY ./self-hosted/docker-entrypoint.sh / - -# Verify built files -RUN test -f /usr/src/sentry/src/sentry/loader/_registry.json && \ - test -f /usr/src/sentry/src/sentry/integration-docs/python.json && \ - test -f /usr/src/sentry/src/sentry/static/sentry/dist/entrypoints/app.js && \ - sentry help - -EXPOSE 9000 -VOLUME /data - -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["run", "web"] +# Strategy (changed in the 26.5.0 rebase): use the upstream +# `getsentry/sentry:26.5.0` image as a base and overlay our small set +# of backend patches + the self-hosted SEER_URL fix. Previously we +# rebuilt frontend + backend from source, but upstream's frontend +# build pipeline (rspack + type-loader.ts) is more involved than we +# want to maintain across version bumps. The pre-built upstream image +# is the same one Sentry's own CI publishes, so we inherit upstream's +# frontend bundle for free. +# +# Our patches in this image: +# 1. `src/sentry/seer/endpoints/project_seer_preferences.py` — make +# organization_id and integration_id optional + add a GitLab +# external_id fallback in the POST endpoint (self-hosted + +# GitLab repos sometimes don't have those fields) +# 2. `src/sentry/conf/server.py` — patch `SEER_DEFAULT_URL` to read +# from `$SEER_URL` env, defaulting to `http://seer:9091` (the +# in-cluster service name) +# 3. `self-hosted/sentry.conf.py` — disable the `seer-explorer` +# feature flag for self-hosted +# +# Frontend kencove patches (GitLab in supportedProviders) are +# obsolete in 26.5.0 — upstream's BASE_SUPPORTED_PROVIDERS already +# includes `gitlab` and `integrations:gitlab`, and added a +# feature-flag-gated mechanism (`seer-gitlab-support`) on top. + +ARG UPSTREAM_TAG=26.5.0 +FROM ghcr.io/getsentry/sentry:${UPSTREAM_TAG} + +USER root + +# 1. Overlay our patched serializer. +COPY src/sentry/seer/endpoints/project_seer_preferences.py \ + /usr/src/sentry/src/sentry/seer/endpoints/project_seer_preferences.py + +# 2. Patch SEER_URL in conf/server.py. Upstream defaults to +# 127.0.0.1:9091 (single-host setup); we run seer as a separate +# container so we need the docker network DNS name. Allow override +# via $SEER_URL env at runtime so non-cluster deploys can point +# elsewhere without rebuilding the image. +RUN sed -i \ + 's#SEER_DEFAULT_URL = "http://127.0.0.1:9091"#SEER_DEFAULT_URL = os.environ.get("SEER_URL", "http://seer:9091")#' \ + /usr/src/sentry/src/sentry/conf/server.py \ + && grep -q 'SEER_DEFAULT_URL = os.environ.get' \ + /usr/src/sentry/src/sentry/conf/server.py \ + && echo "SEER_URL patch applied" + +# 3. Overlay our self-hosted Sentry config (turns off seer-explorer +# feature flag among other tweaks). config.yml stays as upstream's +# default since /opt/sentry/sentry/config.yml mounts over it at +# runtime on the VM. +COPY self-hosted/sentry.conf.py /etc/sentry/sentry.conf.py + +# 4. Recompile the touched .py files so the runtime doesn't pay the +# JIT compile cost on first request. +RUN python3 -m compileall -q \ + /usr/src/sentry/src/sentry/seer/endpoints/project_seer_preferences.py \ + /usr/src/sentry/src/sentry/conf/server.py \ + /etc/sentry/sentry.conf.py + +USER sentry ARG SOURCE_COMMIT ENV SENTRY_BUILD=${SOURCE_COMMIT:-unknown} LABEL org.opencontainers.image.revision=$SOURCE_COMMIT -LABEL org.opencontainers.image.source="https://github.com/kencove/sentry/tree/${SOURCE_COMMIT:-master}/" +LABEL org.opencontainers.image.source="https://github.com/ledoent/sentry/tree/${SOURCE_COMMIT:-26.5.0-kencove}/" +LABEL org.opencontainers.image.title="Sentry (ledoent / kencove fork)" +LABEL org.opencontainers.image.description="getsentry/sentry:26.5.0 + ledoent backend patches" From d975909daedd7c9ac2d11adca005a416972a15bd Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Wed, 20 May 2026 14:54:51 -0400 Subject: [PATCH 17/17] rename: kencove suffix -> ledoent across image tag, branch, Dockerfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fork has lived under ledoent/ for some time now; the kencove branding is legacy. Renames: * Image tag suffix: ${VERSION}-kencove -> ${VERSION}-ledoent * Branch pattern: *-kencove -> *-ledoent (GHCR workflow trigger) * Dockerfile: self-hosted/Dockerfile.kencove -> Dockerfile.ledoent * Image labels: 'Sentry (ledoent / kencove fork)' -> 'Sentry (ledoent fork)' Backwards compat: * The ghcr.io/ledoent/sentry:26.2.0-kencove tag remains accessible in GHCR (we never delete pushed manifests). Production VM still pulls that until we cut over. * The legacy build-kencove.yml Cloud Build workflow + kencove-prod GAR target keep working — just not the primary path anymore. Two intentional 'kencove' mentions kept as historical context: * Comment in build-push-ghcr.yml about the legacy Cloud Build companion * Comment in Dockerfile.ledoent about the obsolete frontend patch --- .github/workflows/build-push-ghcr.yml | 12 ++++++------ .../{Dockerfile.kencove => Dockerfile.ledoent} | 13 +++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) rename self-hosted/{Dockerfile.kencove => Dockerfile.ledoent} (87%) diff --git a/.github/workflows/build-push-ghcr.yml b/.github/workflows/build-push-ghcr.yml index f319a283a1ac..e2946539a0a2 100644 --- a/.github/workflows/build-push-ghcr.yml +++ b/.github/workflows/build-push-ghcr.yml @@ -1,16 +1,16 @@ name: Build and Push Sentry to GHCR (Public) # Publishes a public ledoent fork of self-hosted Sentry to GitHub Container Registry. -# Companion to build-kencove.yml (kencove-prod Cloud Build). This workflow exists +# Companion to build-kencove.yml (kencove-prod GCP Cloud Build, legacy). This workflow exists # so self-hosted Sentry+Seer deployments can pull without GCP credentials. on: push: branches: - - '*-kencove' + - '*-ledoent' - feat/build-push-ghcr tags: - - 'v*-kencove' + - 'v*-ledoent' workflow_dispatch: inputs: tag: @@ -33,7 +33,7 @@ jobs: build-and-push: # ubuntu-latest is 16 GB RAM on GitHub-hosted free runners as of 2026 # (was 7 GB before the 2024 Larger Hosted Runners refresh). The Sentry - # frontend build uses NODE_OPTIONS=4096 in Dockerfile.kencove; our first + # frontend build uses NODE_OPTIONS=4096 in Dockerfile.ledoent; our first # build completed in ~12 min on this runner. Bump to ubuntu-latest-large # (32 GB) only if webpack starts OOMing. runs-on: ubuntu-latest @@ -103,7 +103,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: self-hosted/Dockerfile.kencove + file: self-hosted/Dockerfile.ledoent platforms: linux/amd64 push: true tags: ${{ steps.tags.outputs.tags }} @@ -112,7 +112,7 @@ jobs: labels: | org.opencontainers.image.source=https://github.com/ledoent/sentry org.opencontainers.image.revision=${{ github.sha }} - org.opencontainers.image.title=Sentry (ledoent / kencove fork) + org.opencontainers.image.title=Sentry (ledoent fork) org.opencontainers.image.description=Self-hosted Sentry with SEER_URL env support cache-from: type=gha cache-to: type=gha,mode=max diff --git a/self-hosted/Dockerfile.kencove b/self-hosted/Dockerfile.ledoent similarity index 87% rename from self-hosted/Dockerfile.kencove rename to self-hosted/Dockerfile.ledoent index 30b29c900fdc..57d48d885b9b 100644 --- a/self-hosted/Dockerfile.kencove +++ b/self-hosted/Dockerfile.ledoent @@ -20,10 +20,11 @@ # 3. `self-hosted/sentry.conf.py` — disable the `seer-explorer` # feature flag for self-hosted # -# Frontend kencove patches (GitLab in supportedProviders) are -# obsolete in 26.5.0 — upstream's BASE_SUPPORTED_PROVIDERS already -# includes `gitlab` and `integrations:gitlab`, and added a -# feature-flag-gated mechanism (`seer-gitlab-support`) on top. +# The legacy kencove-fork frontend patch (adding GitLab to +# `supportedProviders`) is obsolete in 26.5.0 — upstream's +# BASE_SUPPORTED_PROVIDERS already includes `gitlab` and +# `integrations:gitlab`, with a feature-flag-gated provider +# mechanism (`seer-gitlab-support`) layered on top. ARG UPSTREAM_TAG=26.5.0 FROM ghcr.io/getsentry/sentry:${UPSTREAM_TAG} @@ -64,6 +65,6 @@ USER sentry ARG SOURCE_COMMIT ENV SENTRY_BUILD=${SOURCE_COMMIT:-unknown} LABEL org.opencontainers.image.revision=$SOURCE_COMMIT -LABEL org.opencontainers.image.source="https://github.com/ledoent/sentry/tree/${SOURCE_COMMIT:-26.5.0-kencove}/" -LABEL org.opencontainers.image.title="Sentry (ledoent / kencove fork)" +LABEL org.opencontainers.image.source="https://github.com/ledoent/sentry/tree/${SOURCE_COMMIT:-26.5.0-ledoent}/" +LABEL org.opencontainers.image.title="Sentry (ledoent fork)" LABEL org.opencontainers.image.description="getsentry/sentry:26.5.0 + ledoent backend patches"