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
53 changes: 41 additions & 12 deletions .github/workflows/release-selfhost.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,23 @@ jobs:
id: version
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
EVENT_NAME: ${{ github.event_name }}
REF_NAME: ${{ github.ref_name }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "v=${INPUT_VERSION}" >> "$GITHUB_OUTPUT"
set -euo pipefail
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
VERSION="$INPUT_VERSION"
else
echo "v=${GITHUB_REF_NAME#selfhost-v}" >> "$GITHUB_OUTPUT"
case "$REF_NAME" in
selfhost-v*) VERSION="${REF_NAME#selfhost-v}" ;;
*) echo "expected a selfhost-v<semver> tag, got $REF_NAME" >&2; exit 1 ;;
esac
fi
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${INPUT_VERSION}"
else
VERSION="${GITHUB_REF_NAME#selfhost-v}"
if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "expected semver version X.Y.Z, got $VERSION" >&2
exit 1
fi
echo "v=${VERSION}" >> "$GITHUB_OUTPUT"
echo "release=gittensory-selfhost@${VERSION}" >> "$GITHUB_OUTPUT"

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
Expand All @@ -67,16 +73,21 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
SENTRY_URL: ${{ vars.SENTRY_URL }}
SENTRY_RELEASE: ${{ steps.version.outputs.release }}
SENTRY_REPOSITORY: ${{ github.repository }}
SENTRY_COMMIT_SHA: ${{ github.sha }}
run: |
set -euo pipefail
test -n "$SENTRY_AUTH_TOKEN"
test -n "$SENTRY_ORG"
test -n "$SENTRY_PROJECT"
npx -y @sentry/cli@latest releases new "$SENTRY_RELEASE"
npx -y @sentry/cli@latest releases set-commits "$SENTRY_RELEASE" --auto
npx -y @sentry/cli@latest sourcemaps inject dist
if [ -z "${SENTRY_URL:-}" ]; then unset SENTRY_URL; fi
npx -y @sentry/cli@3.6.0 releases new "$SENTRY_RELEASE"
npx -y @sentry/cli@3.6.0 releases set-commits "$SENTRY_RELEASE" --commit "$SENTRY_REPOSITORY@$SENTRY_COMMIT_SHA" --ignore-missing
npx -y @sentry/cli@3.6.0 sourcemaps inject dist
node scripts/validate-selfhost-sourcemap.mjs
npx -y @sentry/cli@latest sourcemaps upload --release="$SENTRY_RELEASE" dist
npx -y @sentry/cli@3.6.0 sourcemaps upload --release="$SENTRY_RELEASE" --validate --wait --strict dist

- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
Expand Down Expand Up @@ -121,8 +132,26 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
SENTRY_URL: ${{ vars.SENTRY_URL }}
SENTRY_RELEASE: ${{ steps.version.outputs.release }}
run: |
set -euo pipefail
if [ -z "${SENTRY_URL:-}" ]; then unset SENTRY_URL; fi
npx -y @sentry/cli@3.6.0 releases finalize "$SENTRY_RELEASE"

- name: Validate Sentry release
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
SENTRY_URL: ${{ vars.SENTRY_URL }}
SENTRY_RELEASE: ${{ steps.version.outputs.release }}
run: npx -y @sentry/cli@latest releases finalize "$SENTRY_RELEASE"
SENTRY_REPOSITORY: ${{ github.repository }}
SENTRY_COMMIT_SHA: ${{ github.sha }}
SENTRY_REQUIRE_COMMITS: "true"
SENTRY_REQUIRE_DEPLOY: "false"
SENTRY_REQUIRE_FINALIZED: "true"
run: node review-enrichment/scripts/validate-sentry-release.mjs

- name: GitHub Release
if: github.event_name == 'push'
Expand Down
13 changes: 8 additions & 5 deletions docs/self-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,15 @@ The maintainer release workflow expects:
| Secret `SENTRY_AUTH_TOKEN` | Sentry auth token allowed to create releases and upload source maps |
| Variable `SENTRY_ORG` | Sentry organization slug |
| Variable `SENTRY_PROJECT` | Sentry project slug |
| Variable `SENTRY_URL` | Optional Sentry API URL; defaults to `https://sentry.io` |
| Sentry GitHub integration | Installed for `JSONbored/gittensory`, with the code mapping above |

The workflow builds `dist/server.mjs` with `dist/server.mjs.map`, validates the `sourceMappingURL` and embedded
`sourcesContent`, injects Sentry debug ids, creates release `gittensory-selfhost@<version>`, associates commits with
`set-commits --auto`, uploads the source maps, and then builds the image from that injected `dist/server.mjs`.
`dist/server.mjs.map` is **not** copied into the runtime image and is not served by the app; it only exists as a
private Sentry release artifact.
the tagged commit, uploads the source maps with Sentry validation/waiting enabled, finalizes the release, and then
validates through the Sentry API that the exact release exists, is finalized, and includes the release commit. It then
builds the image from that injected `dist/server.mjs`. `dist/server.mjs.map` is **not** copied into the runtime image
and is not served by the app; it only exists as a private Sentry release artifact.

For a custom image, source maps only work when the deployed JS bundle is the exact post-injection bundle whose map was
uploaded. If you build locally and do not upload maps, leave `SENTRY_RELEASE` unset. Events still report to Sentry,
Expand All @@ -296,9 +298,10 @@ If a new event still shows `/app/dist/server.mjs`:
1. Confirm the event's `release` exactly matches the release that has the uploaded artifact bundle.
2. Confirm the image was built from the injected `dist/server.mjs`, not from a later Docker-internal rebuild.
3. Confirm the Sentry code mapping is `/app` → `.` on branch `main`.
4. Confirm `dist/server.mjs` had `//# sourceMappingURL=server.mjs.map` before upload and the map includes
4. Confirm the release workflow's `Validate Sentry release` step passed for that exact `gittensory-selfhost@<version>`.
5. Confirm `dist/server.mjs` had `//# sourceMappingURL=server.mjs.map` before upload and the map includes
`sourcesContent`.
5. Trigger a fresh event after the upload; old events may need reprocessing before they pick up newly uploaded maps.
6. Trigger a fresh event after the upload; old events may need reprocessing before they pick up newly uploaded maps.

---

Expand Down
1 change: 1 addition & 0 deletions review-enrichment/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ RUN useradd --create-home --uid 10001 rees
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/node_modules ./node_modules
COPY --from=build --chown=rees:rees /app/scripts ./scripts
COPY --from=build --chown=rees:rees /app/dist ./dist
COPY --from=build --chown=rees:rees /app/package.json ./package.json
USER rees
Expand Down
12 changes: 10 additions & 2 deletions review-enrichment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ Set these Railway service variables:
| `SENTRY_ENVIRONMENT` | Optional; defaults to Railway's environment name, then `production`. |
| `SENTRY_TRACES_SAMPLE_RATE` | Optional; defaults to `0`, so errors report without tracing. |
| `SENTRY_RELEASE` | Optional override. Only set it when that exact REES bundle is uploaded. |
| `SENTRY_URL` | Optional Sentry API URL; defaults to `https://sentry.io`. |
| `SENTRY_REPOSITORY` | Optional; defaults to `JSONbored/gittensory` for commit association. |
| `REES_SENTRY_UPLOAD_STRICT` | Optional. Set `true` to fail startup if source-map upload fails. |
| `REES_SENTRY_VALIDATE_RELEASE` | Optional. Set `false` only to disable post-upload release validation. |

By default the release id is `gittensory-rees@<RAILWAY_GIT_COMMIT_SHA>`, using Railway's Git metadata. The Sentry
GitHub code mapping should be:
Expand All @@ -78,6 +80,11 @@ Do **not** pass `SENTRY_AUTH_TOKEN` as a Docker build arg. Railway deploys this
can leak through image metadata. Keeping the upload at runtime means Sentry sees the same `dist/` files that the service
executes, without exposing source maps over HTTP.

After upload, startup validates the exact `gittensory-rees@<RAILWAY_GIT_COMMIT_SHA>` release through the Sentry API:
the release must exist, be finalized, include the deployed commit, and include the Railway deploy id/environment. If
`REES_SENTRY_UPLOAD_STRICT=true`, a failed upload or failed validation stops the Railway deployment; otherwise it logs a
`rees_sentry_sourcemap_upload_failed` warning so the problem is visible without blocking startup.

Analyzer failures are still fail-open: the `/v1/enrich` response marks the analyzer as `degraded` and returns a partial
brief. When Sentry is enabled, those degradations are captured as `rees_analyzer_degraded` events with tags for
`analyzer`, `repo`, `pullNumber`, `headSha`, `release`, `environment`, and `timeoutMs`. Use those tags to spot a broken
Expand All @@ -88,5 +95,6 @@ If Sentry still shows frames such as `/app/dist/server.js`, check:
1. The event's `release` is `gittensory-rees@<same Railway commit sha>` or your exact `SENTRY_RELEASE` override.
2. The Sentry release has an artifact bundle uploaded for the REES project.
3. Railway has `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, and `SENTRY_PROJECT` set on the REES service.
4. The Sentry code mapping is `/app` → `review-enrichment` on branch `main`.
5. `npm --prefix review-enrichment run validate:sourcemaps` passes locally.
4. Startup logs include `sentry_release_validation_complete` for the same release id and Railway deployment id.
5. The Sentry code mapping is `/app` → `review-enrichment` on branch `main`.
6. `npm --prefix review-enrichment run validate:sourcemaps` passes locally.
Loading
Loading