Repository: getsentry/self-hosted Branch: master Commit: 6365d51868a1 Files: 119 Total size: 278.1 KB Directory structure: gitextract_uhvx5ma5/ ├── .craft.yml ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── config.yml │ │ ├── feature-request.yml │ │ ├── problem-report.yml │ │ └── release.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── changelog-preview.yml │ ├── enforce-license-compliance.yml │ ├── fast-revert.yml │ ├── lock.yml │ ├── pre-commit.yml │ ├── release.yml │ ├── shellcheck.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .python-version ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── _integration-test/ │ ├── conftest.py │ ├── custom-ca-roots/ │ │ ├── custom-ca-roots-test.py │ │ └── docker-compose.test.yml │ ├── fixtures/ │ │ ├── envelope-with-profile │ │ └── envelope-with-transaction │ ├── nodejs/ │ │ ├── .gitignore │ │ ├── instrument.js │ │ ├── package.json │ │ └── user-feedback.js │ ├── test_01_basics.py │ └── test_02_backup.py ├── _unit-test/ │ ├── _test_setup.sh │ ├── bootstrap-s3-nodestore-test.sh │ ├── bootstrap-s3-profiles-test.sh │ ├── create-docker-volumes-test.sh │ ├── ensure-relay-credentials-test.sh │ ├── error-handling-test.sh │ ├── geoip-test.sh │ ├── js-sdk-assets-test.sh │ ├── merge-env-file-test.sh │ ├── migrate-pgbouncer-test.sh │ ├── multiple-seaweedfs-bucket-test.sh │ └── snapshots/ │ └── sentry-envelope-f73e4da437c42a1d28b86a81ebcff35d ├── action.yaml ├── certificates/ │ └── .gitignore ├── clickhouse/ │ ├── Dockerfile │ ├── config.xml │ └── default-password.xml ├── codecov.yml ├── cron/ │ ├── Dockerfile │ └── entrypoint.sh ├── docker-compose.yml ├── geoip/ │ └── GeoLite2-City.mmdb.empty ├── get-compose-action/ │ └── action.yaml ├── install/ │ ├── _detect-container-engine.sh │ ├── _lib.sh │ ├── _logging.sh │ ├── _min-requirements.sh │ ├── bootstrap-s3-nodestore.sh │ ├── bootstrap-s3-profiles.sh │ ├── bootstrap-snuba.sh │ ├── build-docker-images.sh │ ├── check-latest-commit.sh │ ├── check-memcached-backend.sh │ ├── check-minimum-requirements.sh │ ├── create-docker-volumes.sh │ ├── dc-detect-version.sh │ ├── detect-platform.sh │ ├── ensure-correct-permissions-profiles-dir.sh │ ├── ensure-files-from-examples.sh │ ├── ensure-relay-credentials.sh │ ├── error-handling.sh │ ├── generate-secret-key.sh │ ├── geoip.sh │ ├── migrate-pgbouncer.sh │ ├── parse-cli.sh │ ├── set-up-and-migrate-database.sh │ ├── setup-js-sdk-assets.sh │ ├── turn-things-off.sh │ ├── update-docker-images.sh │ ├── upgrade-clickhouse.sh │ ├── upgrade-postgres.sh │ └── wrap-up.sh ├── install.sh ├── jq/ │ └── Dockerfile ├── nginx.conf ├── optional-modifications/ │ ├── README.md │ └── patches/ │ └── external-kafka/ │ ├── config.example.yml.patch │ ├── docker-compose.yml.patch │ └── sentry.conf.example.py.patch ├── pyproject.toml ├── redis.conf ├── relay/ │ └── config.example.yml ├── scripts/ │ ├── _lib.sh │ ├── backup.sh │ ├── bump-version.sh │ ├── post-release.sh │ ├── reset.sh │ └── restore.sh ├── sentry/ │ ├── Dockerfile │ ├── config.example.yml │ ├── enhance-image.example.sh │ ├── entrypoint.sh │ ├── requirements.example.txt │ └── sentry.conf.example.py ├── sentry-admin.sh ├── symbolicator/ │ └── config.example.yml ├── unit-test.sh └── workstation/ ├── 200_download-self-hosted.sh ├── 201_install-self-hosted.sh ├── 299_setup-completed.sh ├── README.md ├── commands.sh ├── postinstall/ │ └── Dockerfile └── preinstall/ └── Dockerfile ================================================ FILE CONTENTS ================================================ ================================================ FILE: .craft.yml ================================================ minVersion: 2.21.6 changelogPolicy: auto preReleaseCommand: bash scripts/bump-version.sh artifactProvider: name: none targets: - name: github versioning: policy: calver ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_style = space insert_final_newline = true [*.sh] indent_size = 2 [*.yml] indent_size = 2 [nginx/*.conf] indent_style = tab ================================================ FILE: .gitattributes ================================================ /.gitattributes export-ignore /.gitignore export-ignore /.github export-ignore /.editorconfig export-ignore /.craft.yml export-ignore ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Report a security vulnerability url: https://sentry.io/security/#vulnerability-disclosure about: Please see our guide for responsible disclosure. ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yml ================================================ name: 💡 Feature Request description: Tell us about a problem our software could solve but doesn't. body: - type: textarea id: problem attributes: label: Problem Statement description: What problem could `self-hosted` solve that it doesn't? placeholder: |- I want to make whirled peas, but `self-hosted` doesn't blend. validations: required: true - type: textarea id: expected attributes: label: Solution Brainstorm description: We know you have bright ideas to share ... share away, friend. placeholder: |- Add a blender to `self-hosted`. validations: required: false - type: markdown attributes: value: |- ## Thanks Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/problem-report.yml ================================================ name: 🐞 Problem Report description: Tell us about something that's not working the way you expect. body: - type: input id: self_hosted_version attributes: label: Self-Hosted Version placeholder: 21.7.0 ← should look like this (check the footer) description: What version of self-hosted Sentry are you running? validations: required: true - type: input id: cpu_architecture attributes: label: CPU Architecture placeholder: x86_64 ← should look like this description: | What cpu architecture are you running self-hosted on? e.g: (docker info --format '{{.Architecture}}') validations: required: true - type: input id: docker_version attributes: label: Docker Version placeholder: 20.10.16 ← should look like this (docker --version) description: | What version of docker are you using to run self-hosted? e.g: (docker --version) validations: required: true - type: input id: docker_compose_version attributes: label: Docker Compose Version placeholder: 2.6.0 ← should look like this (docker compose version) description: | What version of docker compose are you using to run self-hosted? e.g: (docker compose version) validations: required: true - type: checkboxes id: machine_specification attributes: label: Machine Specification description: Make sure your system meets the [minimum system requirements of Sentry](https://develop.sentry.dev/self-hosted/#required-minimum-system-resources). options: - label: My system meets the minimum system requirements of Sentry required: true validations: required: true - type: input id: installation_type attributes: label: Installation Type placeholder: Fresh install / Upgrade from 24.8.0 to 25.5.1 description: | Are you filing this issue for a fresh install or an upgrade? validations: required: true - type: textarea id: repro attributes: label: Steps to Reproduce description: How can we see what you're seeing? Specific is terrific. placeholder: |- 1. foo 2. bar 3. baz validations: required: true - type: textarea id: expected attributes: label: Expected Result description: | What did you expect to happen? validations: required: true - type: textarea id: actual attributes: label: Actual Result description: | Logs? Screenshots? Yes, please. e.g.: - latest install logs: `ls -1 sentry_install_log-*.txt | tail -1 | xargs cat` - `docker compose logs` output placeholder: |- e.g.: - logs output validations: required: true - type: input id: event_id attributes: label: Event ID description: | If you opted into sending errors to our error monitoring and the error has an event ID, enter it here! placeholder: c2d85058-d3b0-4d85-a509-e2ba965845d7 - type: markdown attributes: value: |- ## Thanks Check our [triage docs](https://open.sentry.io/triage/) for what to expect next. If you're reporting a security issue, please follow our [security policy](https://github.com/getsentry/.github/blob/main/SECURITY.md) instead. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/release.yml ================================================ name: 📦 Release Issue description: Start a new self-hosted Sentry release title: Release YY.M.N body: - type: textarea attributes: label: Body description: "Edit YY.M.N in the title and three times in the first line of the body, then submit. 👍" value: | [previous YY.M.N](https://github.com/getsentry/self-hosted/issues) | ***YY.M.N*** | [next YY.M.N](https://github.com/getsentry/self-hosted/issues) - [ ] Release all components (_replace items with [publish repo issue links](https://github.com/getsentry/publish/issues)_). - [ ] [`relay`](https://github.com/getsentry/relay/actions/workflows/release_binary.yml) - [ ] [`sentry`](https://github.com/getsentry/sentry/actions/workflows/release.yml) - [ ] [`snuba`](https://github.com/getsentry/snuba/actions/workflows/release.yml) - [ ] [`symbolicator`](https://github.com/getsentry/symbolicator/actions/workflows/release.yml) - [ ] [`vroom`](https://github.com/getsentry/vroom/actions/workflows/release.yaml) - [ ] [`uptime-checker`](https://github.com/getsentry/uptime-checker/actions/workflows/release.yml) - [ ] [`taskbroker`](https://github.com/getsentry/taskbroker/actions/workflows/release.yml) - [ ] Release self-hosted. - [ ] [Prepare the `self-hosted` release](https://github.com/getsentry/self-hosted/actions/workflows/release.yml) (_replace with publish issue repo link_). - [ ] Check to make sure the new release branch in self-hosted includes the appropriate CalVer images. - [ ] Accept (publish) the release. - [ ] Edit [release notes](https://github.com/getsentry/self-hosted/releases). - [ ] Follow up. - [ ] [Create the next release issue](https://github.com/getsentry/self-hosted/issues/new?assignees=&labels=&projects=&template=release.yml) and link it from this one. - _replace with link_ - [ ] Update the [release issue template](https://github.com/getsentry/self-hosted/blob/master/.github/ISSUE_TEMPLATE/release.yml). - [ ] Create a PR to update relocation release tests to add the new version. - _replace with link_ validations: required: true ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ### Legal Boilerplate Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms. ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: docker directory: "/" schedule: interval: daily open-pull-requests-limit: 0 # only security updates reviewers: - "@getsentry/dev-infra" - "@getsentry/security" - package-ecosystem: "github-actions" directory: "/" schedule: # Check for updates to GitHub Actions every week interval: "weekly" reviewers: - "@getsentry/dev-infra" - "@getsentry/security" ================================================ FILE: .github/workflows/changelog-preview.yml ================================================ name: Changelog Preview on: pull_request_target: types: - opened - synchronize - reopened - edited - labeled - unlabeled permissions: contents: write pull-requests: write statuses: write jobs: changelog-preview: uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2 secrets: inherit ================================================ FILE: .github/workflows/enforce-license-compliance.yml ================================================ name: Enforce License Compliance on: push: branches: [master] pull_request: branches: [master] permissions: contents: read jobs: enforce-license-compliance: if: github.repository_owner == 'getsentry' runs-on: ubuntu-latest steps: - name: 'Enforce License Compliance' uses: getsentry/action-enforce-license-compliance@main with: fossa_api_key: ${{ secrets.FOSSA_API_KEY }} ================================================ FILE: .github/workflows/fast-revert.yml ================================================ on: pull_request_target: types: [labeled] workflow_dispatch: inputs: pr: required: true description: pr number co_authored_by: required: true description: '`name ` for triggering user' # disable all permissions -- we use the PAT's permissions instead permissions: {} jobs: revert: runs-on: ubuntu-latest if: | github.event_name == 'workflow_dispatch' || github.event.label.name == 'Trigger: Revert' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ secrets.BUMP_SENTRY_TOKEN }} - uses: getsentry/action-fast-revert@35b4b6c1f8f91b5911159568b3b15e531b5b8174 # v2.0.1 with: pr: ${{ github.event.number || github.event.inputs.pr }} co_authored_by: ${{ github.event.inputs.co_authored_by || format('{0} <{1}+{0}@users.noreply.github.com>', github.event.sender.login, github.event.sender.id) }} committer_name: getsentry-bot committer_email: bot@sentry.io token: ${{ secrets.BUMP_SENTRY_TOKEN }} - name: comment on failure run: | curl \ --silent \ -X POST \ -H 'Authorization: token ${{ secrets.BUMP_SENTRY_TOKEN }}' \ -d'{"body": "revert failed (conflict? already reverted?) -- [check the logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})"}' \ https://api.github.com/repositories/${{ github.event.repository.id }}/issues/${{ github.event.number || github.event.inputs.pr }}/comments if: failure() ================================================ FILE: .github/workflows/lock.yml ================================================ name: 'Lock closed issues/PRs' on: schedule: - cron: '11 3 * * *' workflow_dispatch: jobs: lock: if: github.repository_owner == 'getsentry' runs-on: ubuntu-latest steps: - uses: getsentry/forked-action-lock-threads@master with: github-token: ${{ github.token }} issue-lock-inactive-days: 15 issue-lock-reason: '' pr-lock-inactive-days: 15 pr-lock-reason: '' ================================================ FILE: .github/workflows/pre-commit.yml ================================================ name: pre-commit on: pull_request: push: branches: [master] permissions: contents: read jobs: pre-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: 3.x - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch: inputs: version: description: Version to release (or "auto") required: false force: description: Force a release even when there are release-blockers (optional) required: false schedule: # We want the release to be at 10 or 11am Pacific Time # We also make this an hour after all others such as Sentry, # Snuba, and Relay to make sure their releases finish. - cron: "0 18 15 * *" permissions: contents: read jobs: release: if: github.repository_owner == 'getsentry' runs-on: ubuntu-latest name: "Release a new version" steps: - name: Get auth token id: token uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 with: app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.token.outputs.token }} fetch-depth: 0 - name: Prepare release id: prepare-release uses: getsentry/craft@013a7b2113c2cac0ff32d5180cfeaefc7c9ce5b6 # v2.24.1 env: GITHUB_TOKEN: ${{ steps.token.outputs.token }} with: version: ${{ github.event.inputs.version }} force: ${{ github.event.inputs.force }} outputs: release-version: ${{ env.RELEASE_VERSION }} dogfood-release: if: github.repository_owner == 'getsentry' runs-on: ubuntu-latest name: Create release on self-hosted dogfood instance needs: release steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - uses: getsentry/action-release@dab6548b3c03c4717878099e43782cf5be654289 # v3.5.0 env: SENTRY_ORG: self-hosted SENTRY_PROJECT: installer SENTRY_URL: https://self-hosted.getsentry.net/ SENTRY_AUTH_TOKEN: ${{ secrets.SELF_HOSTED_RELEASE_TOKEN }} with: environment: production version: ${{ needs.release.outputs.release-version }} ignore_empty: true ignore_missing: true ================================================ FILE: .github/workflows/shellcheck.yml ================================================ name: "ShellCheck" on: push: paths: - "**.sh" branches: [master] pull_request: paths: - "**.sh" branches: [master] permissions: contents: read jobs: shellcheck: name: ShellCheck runs-on: ubuntu-latest steps: - name: Repository checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - name: Run ShellCheck run: | git diff --name-only -z `git merge-base origin/master HEAD` -- \ install/_lib.sh \ 'optional-modifications/**.sh' \ 'scripts/**.sh' \ unit-test.sh \ 'workstation/**.sh' \ | xargs -0 -r -- \ shellcheck \ --shell=bash \ --format=json1 \ --external-sources \ | jq -r ' .comments | map(.level |= if ([.] | inside(["info", "style"])) then "notice" else . end) | .[] as $note | "::\($note.level) file=\($note.file),line=\($note.line),endLine=\($note.endLine),col=\($note.column),endColumn=\($note.endColumn)::[SC\($note.code)] \($note.message)" ' \ | grep . >&2 && exit 1 exit 0 ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: # Run CI on all pushes to the master and release/** branches, and on all new # pull requests, and on all pushes to pull requests (even if a pull request # is not against master). push: branches: - "master" - "release/**" pull_request: schedule: - cron: "0 0,12 * * *" concurrency: group: ${{ github.ref_name || github.sha }} cancel-in-progress: true permissions: contents: read defaults: run: shell: bash jobs: unit-test: if: github.repository_owner == 'getsentry' runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] name: ${{ matrix.os == 'ubuntu-24.04-arm' && 'unit tests (arm64)' || 'unit tests' }} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Unit Tests run: ./unit-test.sh integration-test: if: github.repository_owner == 'getsentry' runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-24.04, ubuntu-24.04-arm] container_engine: ['docker'] # TODO: add 'podman' into the list compose_profiles: ['feature-complete', 'errors-only'] name: ${{ format('integration test{0}{1}{2}', matrix.os == 'ubuntu-24.04-arm' && ' (arm64)' || '', matrix.container_engine == 'podman' && ' (podman)' || '', matrix.compose_profiles == 'errors-only' && ' (errors-only)' || '') }} env: REPORT_SELF_HOSTED_ISSUES: 0 SELF_HOSTED_TESTING_DSN: ${{ vars.SELF_HOSTED_TESTING_DSN }} CONTAINER_ENGINE_PODMAN: ${{ matrix.container_engine == 'podman' && '1' || '0' }} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Podman if: matrix.container_engine == 'podman' run: | sudo apt-get update sudo apt-get install -y --no-install-recommends podman # TODO: Replace below with podman-compose # We need this commit to be able to work: https://github.com/containers/podman-compose/commit/8206cc3ea277eee6c2e87d4cd66eba8eae3d44eb pip3 install --user https://github.com/containers/podman-compose/archive/main.tar.gz echo "PODMAN_COMPOSE_PROVIDER=podman-compose" >> $GITHUB_ENV echo "PODMAN_COMPOSE_WARNING_LOGS=false" >> $GITHUB_ENV - name: Use action from local checkout uses: './' with: compose_profiles: ${{ matrix.compose_profiles }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} ================================================ FILE: .gitignore ================================================ # Error reporting choice cache .reporterrors # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt sentry_install_log*.txt sentry_reset_log*.txt sentry_restore_log*.txt sentry_backup_log*.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Sphinx documentation docs/_build/ # PyBuilder target/ # Ipython Notebook .ipynb_checkpoints # https://docs.docker.com/compose/extends/ docker-compose.override.yml # https://docs.docker.com/compose/environment-variables/#using-the---env-file--option .env.custom *.tar data/ # Editor / IDE .vscode/tags .idea # custom Sentry config sentry/sentry.conf.py sentry/config.yml sentry/*.bak sentry/backup.json sentry/enhance-image.sh sentry/requirements.txt relay/credentials.json relay/config.yml symbolicator/config.yml geoip/GeoIP.conf geoip/*.mmdb geoip/.geoipupdate.lock # integration testing _integration-test/custom-ca-roots/nginx/* sentry/test-custom-ca-roots.py # OSX minutia .DS_Store ================================================ FILE: .pre-commit-config.yaml ================================================ exclude: '\.patch$' repos: - repo: local hooks: # Based on https://github.com/scop/pre-commit-shfmt/blob/main/.pre-commit-hooks.yaml # Customized to also work on ARM, and give diff for CI on failure. - id: shfmt name: shfmt description: Format shell source code language: docker_image entry: --net none mvdan/shfmt:v3.5.1 args: [-w, -d] files: .*\.sh stages: [commit, merge-commit, push, manual] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: - id: check-case-conflict - id: check-executables-have-shebangs - id: check-merge-conflict - id: check-symlinks - id: end-of-file-fixer - id: trailing-whitespace - id: check-yaml ================================================ FILE: .python-version ================================================ 3.12 ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 26.3.1 - No documented changes. ## 26.3.0 ### New Features ✨ - Reorder pull images by @aldy505 in [#4202](https://github.com/getsentry/self-hosted/pull/4202) ### Bug Fixes 🐛 - Manual image tags rollback to nightly by @aldy505 in [#4204](https://github.com/getsentry/self-hosted/pull/4204) ### Internal Changes 🔧 #### Deps - Bump actions/setup-node from 6.2.0 to 6.3.0 by @dependabot in [#4206](https://github.com/getsentry/self-hosted/pull/4206) - Bump getsentry/craft from 2.21.7 to 2.23.2 by @dependabot in [#4207](https://github.com/getsentry/self-hosted/pull/4207) - Bump minimatch from 9.0.5 to 9.0.7 in /_integration-test/nodejs by @dependabot in [#4189](https://github.com/getsentry/self-hosted/pull/4189) ## 26.2.1 ### Bug Fixes 🐛 #### Release - Restore version bumping for releases by @BYK in [#4191](https://github.com/getsentry/self-hosted/pull/4191) - Be explicit about pre release and post release command by @hubertdeng123 in [#4190](https://github.com/getsentry/self-hosted/pull/4190) #### Other - Prevent script injection vulnerability in get-compose-action by @fix-it-felix-sentry in [#4179](https://github.com/getsentry/self-hosted/pull/4179) ### Internal Changes 🔧 - (deps) Bump getsentry/craft from 2.21.4 to 2.21.7 by @dependabot in [#4188](https://github.com/getsentry/self-hosted/pull/4188) ### Other - Use docker-compose shipped in GHA runners by @aminvakil in [#4184](https://github.com/getsentry/self-hosted/pull/4184) ## 26.1.0 ### New Features ✨ - Switch nodestore-s3 package to getsentry org by @aldy505 in [#4119](https://github.com/getsentry/self-hosted/pull/4119) ### Build / dependencies / internal 🔧 - (deps) Bump astral-sh/setup-uv from 7.1.5 to 7.1.6 by @dependabot in [#4100](https://github.com/getsentry/self-hosted/pull/4100) ### Other - Include SDK version 10 when using local JS SDK assets by @hsgt-brice in [#4130](https://github.com/getsentry/self-hosted/pull/4130) ## 25.12.1 ### Build / dependencies / internal 🔧 - chore(deps): bump seaweedfs version to 3.97 by @kostirez1 in [#4120](https://github.com/getsentry/self-hosted/pull/4120) ## 25.12.0 ### New Features ✨ - feat(release): Manually run post release script by @hubertdeng123 in [#4073](https://github.com/getsentry/self-hosted/pull/4073) - feat: bump action-setup-venv to use working-directory by @aldy505 in [#4098](https://github.com/getsentry/self-hosted/pull/4098) ### Bug Fixes 🐛 - fix: Provide useful info on permission errors by @BYK in [#4096](https://github.com/getsentry/self-hosted/pull/4096) - fix: missing 'SENTRY_SYSTEM_SECRET_KEY' declaration on Docker Compose block by @aldy505 in [#4087](https://github.com/getsentry/self-hosted/pull/4087) - fix: grep seaweedfs bucket name instead of using awk by @aldy505 in [#4076](https://github.com/getsentry/self-hosted/pull/4076) ### Build / dependencies / internal 🔧 #### Deps - build(deps): bump actions/create-github-app-token from 2.2.0 to 2.2.1 by @dependabot in [#4090](https://github.com/getsentry/self-hosted/pull/4090) - build(deps): bump actions/setup-node from 4.4.0 to 6.1.0 by @dependabot in [#4091](https://github.com/getsentry/self-hosted/pull/4091) - build(deps): bump astral-sh/setup-uv from 7.1.4 to 7.1.5 by @dependabot in [#4093](https://github.com/getsentry/self-hosted/pull/4093) - chore: guard unit-test.sh from being invoked by users by @aldy505 in [#4085](https://github.com/getsentry/self-hosted/pull/4085) - chore: ask installation type on problem report template by @aldy505 in [#4086](https://github.com/getsentry/self-hosted/pull/4086) ### Other - Revert "Revert "ref: migrate to uv (#4061)"" by @aldy505 in [#4097](https://github.com/getsentry/self-hosted/pull/4097) - Revert "ref: migrate to uv (#4061)" by @BYK in [#4094](https://github.com/getsentry/self-hosted/pull/4094) - test: integration test for user feedback by @aldy505 in [#3880](https://github.com/getsentry/self-hosted/pull/3880) - ref: migrate to uv by @aldy505 in [#4061](https://github.com/getsentry/self-hosted/pull/4061) - Add shm_size configuration back to postgres by @max-wittig in [#4072](https://github.com/getsentry/self-hosted/pull/4072) ## 25.11.1 ### Build / dependencies / internal 🔧 - chore: remove some more unused directories by @aldy505 in [#4046](https://github.com/getsentry/self-hosted/pull/4046) - build(deps): bump actions/create-github-app-token from 2.1.4 to 2.2.0 by @dependabot in [#4058](https://github.com/getsentry/self-hosted/pull/4058) - build(deps): bump actions/checkout from 5 to 6 by @dependabot in [#4059](https://github.com/getsentry/self-hosted/pull/4059) - chore: uptime checker is missing on the release issue by @aldy505 in [#4047](https://github.com/getsentry/self-hosted/pull/4047) ### Bug Fixes 🐛 - fix(profiling): Ingest profile file path by @Zylphrex in [#4060](https://github.com/getsentry/self-hosted/pull/4060) - fix: ensure seaweedfs lifecycle policy is set correctly by @kodebach in [#4040](https://github.com/getsentry/self-hosted/pull/4040) - fix: missing SYMBOLICATOR_STATSD_ADDR environment var for symbolicator-cleanup by @aldy505 in [#4042](https://github.com/getsentry/self-hosted/pull/4042) ### Other - Add `dcx` shortcut for docker compose exec with HTTP proxy env vars by @copilot-swe-agent in [#4067](https://github.com/getsentry/self-hosted/pull/4067) ## 25.11.0 ### Various fixes & improvements - feat: statsd configuration through environment variables (#4031) by @aldy505 - ref: sound SENTRY_DISALLOWED_IPS on the configuration file (#3981) by @aldy505 - fix: broken link to Errors-Only Mode docs (#4032) by @mariansimecek - Fix Clickhouse max_server_memory_usage_to_ram_ratio setting (#4025) by @otoriphoenix - Increase default max_suspicious_broken_parts to 100 (#4011) by @stevenobird - fix(install): add migrate-pgbouncer.sh to install.sh (#4030) by @kodebach - fix: remove snuba uptime results consumer (#4027) by @aldy505 ## 25.10.0 ### Various fixes & improvements - fix: geoip standalone script should check on CONTAINER_ENGINE variable first (#3982) by @aldy505 - fix: missing `-dir` flag for seaweedfs (#3991) by @aldy505 - Remove symbolicator volume once (#3994) by @aminvakil - Remove symbolicator external volume (#3992) by @aminvakil - chore(spans): Remove old snuba-spans consumer (#3989) by @jjbayer - Bump redis 6.2.20-alpine (#3988) by @aminvakil - ref: add `continue-on-error` for codecov action on self-hosted integration tests (#3978) by @aldy505 - ref: use dedicated `healthcheck` command for symbolicator & remove cron for `symbolicator-cleanup` (#3979) by @aldy505 - fix(actions): include arch and compose_profiles information on cache keys (#3974) by @aldy505 - ref: Remove proxy_next_upstream directives (#3973) by @aminvakil - fix: Unset the proxy when performing the seaweedfs health check (#3959) by @SteppingHat - fix: logic error in s3 install script (#3965) by @kodebach - Fix swap allocation in integration test (#3972) by @aminvakil - chore(tasks) Remove reference to celery (#3962) by @markstory - Respect uppercase proxy variables (#3949) by @aminvakil - chore(tasks): Remove the worker and cron containers (#3946) by @markstory - fix: install behind a proxy (#3944) by @moroine ## 25.9.0 ### Various fixes & improvements - fix: able to setup nodestore multiple times (#3940) by @aldy505 - build(deps): bump actions/create-github-app-token from 2.1.1 to 2.1.4 (#3936) by @dependabot - docs: provide information for SENTRY_AIR_GAP flag on Django config file (#3935) by @aldy505 - feat: Use S3 node store with seaweedfs (#3498) by @BYK - feat(tasks): Remove taskworker option override and add worker healthcheck (#3933) by @markstory - feat: install script to migrate sentry.conf.py config to use pgbouncer (#3898) by @aldy505 - chore(deps): bump clickhouse to 25.3 (#3878) by @aldy505 - feat: enable `issue-views` flag (#3922) by @aldy505 - feat: query against `eap` dataset instead of `metrics` dataset for spans (#3923) by @aldy505 - build(deps): bump actions/setup-python from 5 to 6 (#3927) by @dependabot - Add restart policy to pgbouncer service (#3925) by @frederikspang - fix(tests): skip logs event test for errors-only (#3915) by @aldy505 - Improve nginx depends_on policy (#3914) by @aminvakil - test: run errors-only integration tests (#3910) by @aldy505 - feat: enable Logs feature (#3912) by @aldy505 - fix: ensuring vroom permission should be skipped on errors-only (#3911) by @aldy505 - chore(deps): bump patches version (#3879) by @aldy505 - Revert "increase postgres max_connections above 100 connections (#2740)" (#3899) by @aminvakil - Add pgbouncer (#3884) by @frederikspang - chore: resolve GHA code scanning alerts (#3889) by @aldy505 - fix(enhancement): search for permissions on docker container instead of host and combine it in one command for performance enhancement (#3890) by @LvckyAPI - build(deps): bump actions/create-github-app-token from 2.1.0 to 2.1.1 (#3885) by @dependabot - build(deps): bump actions/checkout from 4 to 5 (#3883) by @dependabot ## 25.8.0 ### Various fixes & improvements - feat: Relay healthcheck (#3875) by @aldy505 - fix: setup swapfile only if runner architecture is X64 or X86 (#3876) by @aldy505 - Set minimum bash version to 4.4.0 (#3873) by @aminvakil - fix: adjust file healthcheck durations (#3874) by @mzglinski - feat: healthchecks for sentry components (#3859) by @mzglinski - fix(eap): Fix dataset parameter to target spans (#3866) by @phacops - build(deps): bump actions/create-github-app-token from 2.0.6 to 2.1.0 (#3865) by @dependabot - fix(scripts): use `env` to find `bash` interpreter (#3861) by @Zaczero - fix(scripts): every known flags should be shifted before executing the sentry command (#3831) by @aldy505 - fix: uptime checker image should be bumped to the tagged release (#3858) by @aldy505 - fix(enhancement): ensure correct ownership check before setting permissions of profiles (#3855) by @LvckyAPI - chore(features): cleanup feature flags grouped by its' category (#3843) by @aldy505 - fix: add schedulers for generic metrics subscriptions (#3847) by @mzglinski - feat: Continue using celery in self-hosted for now (#3845) by @markstory - feat(features): add `profiling-view` flag (#3837) by @aldy505 - Potential fix for code scanning alert no. 12: Workflow does not contain permissions (#3822) by @aldy505 - docs: clearly state that `system.internal-url-prefix` shouldn't be changed (#3829) by @aldy505 - feat(install): Adds support for podman(compose) (#3673) by @DuncanConroy - fix(action): missing project directory path for failure inspection (#3825) by @aldy505 - Cleanup unused feature flags (#3820) by @doc-sheet - feat: inspect docker compose failure on self-hosted e2e action (#3817) by @aldy505 ## 25.7.0 ### Various fixes & improvements - feat: Swap `trace-view-v1` feature flag with `visibility-explore-view` (#3801) by @aldy505 - fix: set harakiri Django option to 30s (#3792) by @aldy505 - feat(images):Cutover images to ghcr (#3800) by @hubertdeng123 - docs: encourage community patches (#3794) by @aldy505 - feat: run EAP-related containers (#3778) by @aldy505 - feat(uptime): Enable uptime in self-hosted (#3787) by @evanpurkhiser - feat: make `system.secret-key` configurable from environment variables (#3783) by @aldy505 - ci: run tests on arm64 (#3750) by @aldy505 ## 25.6.2 ### Various fixes & improvements - fix: Increase timeout for flakey test (#3781) by @tobias-wilfert - chore: provide detailed note for sentry endpoint settings (#3780) by @aldy505 ## 25.6.1 ### Various fixes & improvements - fix(taskworker) Remove num-brokers (#3769) by @markstory - feat: enable customization sentry DSN endpoint (#3747) by @yildizozgur - ref(js-assets): Simplify how we call nginx container (#3761) by @BYK - Revert "fix(vroom): Explicitly set PROFILES_DIR for upcoming change" (#3760) by @hubertdeng123 - fix(vroom): Explicitly set PROFILES_DIR for upcoming change (#3759) by @BYK ## 25.6.0 ### Various fixes & improvements - enable shell linter for more scripts (#3748) by @doc-sheet - feat: migrate to arm64-compatible smtp image (#3746) by @ezhevita - Introduce patches with external kafka (#3521) by @aldy505 - add shellcheck action to lint bash scripts (#3710) by @doc-sheet - tests: Install version 2.x of Python SDK (#3745) by @sentrivana - feat(features): enable continuous profiling (#3742) by @aldy505 - feat: Add taskbroker + worker + scheduler (#3738) by @markstory - fix(profiles): Run the profile chunks consumer (#3739) by @phacops - chore: prune removed feature flags on main repository (#3731) by @aldy505 - remove index workaround (#3730) by @asottile-sentry - Make usage of Python SDK future proof (#3714) by @antonpirker ## 25.5.1 ### Various fixes & improvements - Add missing lib script to sentry-admin.sh (#3693) by @djakielski - chore: cleanup obsolete feature flags (#3701) by @doc-sheet ## 25.5.0 ### Various fixes & improvements - build(deps): bump actions/create-github-app-token from 2.0.2 to 2.0.6 (#3690) by @dependabot - Resolve datetime deprecation warnings (#3686) by @emmanuel-ferdman - ref: remove SENTRY_USE_BIG_INTS (always True) (#3687) by @asottile-sentry ## 25.4.0 ### Stand-alone Docker Compose Fixes By: @aminvakil (#3658, #3654) ### Various fixes & improvements - chore(relay): specify spool.enveloppe.max_backpressure_memory_percent configuration for handling relay's failing healthcheck (#3635) by @aldy505 - build(deps): bump actions/create-github-app-token from 1.12.0 to 2.0.2 (#3649) by @dependabot - build(deps): bump actions/create-github-app-token from 1.11.7 to 1.12.0 (#3639) by @dependabot - Minimum requirements for 'errors-only' profile (#3634) by @madest92 - build(deps): bump actions/create-github-app-token from 1.11.6 to 1.11.7 (#3632) by @dependabot - feat(sentry): add dynamic sampling feature to config (#3631) by @aldy505 - docs(config): add example config for Google Auth (#3623) by @junsung-cho - fix: js-sdk directory/file permission should be set correctly (#3616) by @aldy505 - feat(features): enable session replay canvas (#3619) by @aldy505 ## 25.3.0 ### Various fixes & improvements - feat(features): enable trace view (#3617) by @aldy505 - feat: provide monitoring-related configurations (#3611) by @aldy505 - Enforce license compliance only on getsentry repository (#3606) by @aminvakil - Fix unbound variable error in install script (#3601) by @brettdh - Add --short to docker-compose version (#3605) by @aminvakil - ref: Less complicated docker compose detection (#3604) by @BYK - Use docker-compose if version is gte docker compose (#3595) by @aminvakil - build(deps): bump actions/create-github-app-token from 1.11.3 to 1.11.6 (#3598) by @dependabot - build(deps): bump getsentry/action-release from 1 to 3 (#3599) by @dependabot - Bump docker-compose 2.33.1 (#3597) by @aminvakil - refactor: move system.url-prefix under systems settings section (#3588) by @leeoocca ## 25.2.0 ### Various fixes & improvements - build(deps): bump actions/create-github-app-token from 1.11.2 to 1.11.3 (#3569) by @dependabot - feat: merge `.env` and `.env.custom` file during installation (#3564) by @aldy505 - build(deps): bump actions/create-github-app-token from 1.11.1 to 1.11.2 (#3561) by @dependabot - feat: Require both inputs to be set on action (#3554) by @BYK - ref: Simpler and more accurate cache keys (#3553) by @BYK - Hand off open-source to dev-infra (#3549) by @chadwhitacre - ci: Remove obsolete `dcr up -w` from import test (#3544) by @BYK - fix: github.action_path may not have trailing slash (#3547) by @BYK - chore: Remove upgrade test (#3541) by @hubertdeng123 - fix: Use correct path for get compose action (#3539) by @hubertdeng123 - fix: Caching of sentry migrations should cover additional folders (#3542) by @hubertdeng123 - ci: Move self-contained action reference to master branch (#3538) by @BYK - breaking: Upgrade min Compose version to 2.23.2 (#3535) by @BYK - ci: Even better cache keys and granular caching (#3534) by @BYK - test: Reorganize backup/restore tests for speed and reliability (#3537) by @BYK ## 25.1.0 ### Various fixes & improvements - ci: Use generic Docker volume cache action (#3524) by @BYK - ci: Less volatile cache keys (#3522) by @BYK - docs: include regular env file on wrap-up (#3523) by @aldy505 - ci: Faster and smarter backup/restore tests (#3516) by @BYK - fix: Fix the new e2e action to be portable (#3520) by @BYK - ci: Move e2e test action into the repo (#3519) by @BYK - ci: Only test on compose 2.26 w/ customizations (#3506) by @BYK - ci: Skip DB ops during install completely on cache hit (#3496) by @BYK - chore: Remove everything zookeeper (#3499) by @hubertdeng123 - ci: Cache postgres volume after first migration (#3488) by @BYK - fix: Remove the extra space in the log file names (#3212) by @melnele - ref(snuba): Combine bootstrap & migrate for faster bootstrap (#3491) by @BYK - ref(geoip): Remove geoipupdate from compose (#3490) by @BYK - build(deps): bump actions/create-github-app-token from 1.11.0 to 1.11.1 (#3492) by @dependabot ## 24.12.1 ### Various fixes & improvements - chore: clearer message for errors-only mode (#3487) by @aldy505 - chore(relay): provide opt-in max_memory_percent config as workaround for failing healthcheck (#3486) by @aldy505 - fix(nginx): _assets should rewrite to _static/sentry/dist (#3483) by @BYK ## 24.12.0 - No documented changes. ## 24.11.2 ### Various fixes & improvements - fix(redis): Actually use custom config (#3459) by @BYK - feat(release): Replace release bot with GH app (#3458) by @Jeffreyhung - chore(issue-template): ask for machine specification and provide link to security policy (#3447) by @aldy505 - add sentry/backup.json to gitignore (#3450) by @niklassc7 - ref: remove suggested fix (#3446) by @aldy505 ## 24.11.1 ### Various fixes & improvements - fix(redis): Use a safer eviction rule (#3432) by @BYK - feat: add Redis configuration for improved memory management (#3427) by @Hassanzadeh-sd - build(deps): bump codecov/codecov-action from 4 to 5 (#3429) by @dependabot ## 24.11.0 ### Various fixes & improvements - feat(healthcheck): Improve redis healthcheck (#3422) by @hubertdeng123 - fix: missing mime types and turning off autoindex for js-sdk endpoint (#3395) by @aldy505 - fix: Use js.sentry-cdn.com for JS SDK downloads (#3417) by @BYK - fix(loader): provide js sdk assets from 4.x (#3415) by @aldy505 - Revert "Revert "ref(feedback): remove issue platform flags after releasing issue types"" (#3403) by @BYK - Revert "ref(feedback): remove issue platform flags after releasing issue types" (#3402) by @BYK - ref(feedback): remove issue platform flags after releasing issue types (#3397) by @aliu39 - fix(sentry-admin): Do not wait for command finish to display output (#3390) by @Makhonya ## 24.10.0 ### Various fixes & improvements - chore: Disable codecov for master/release branches (#3384) by @hubertdeng123 - chore: replace old URLs of the repo with the new docs (#3375) by @victorelec14 - ref: span normalization allowed host config (#3245) by @aldy505 - docs: explicitly specify `mail.use-{tls,ssl}` is mutually exclusive (#3368) by @aldy505 - ref: allow hosted js sdk bundles (#3365) by @aldy505 - fix(clickhouse): Allow nullable key (#3354) by @nikhars ## 24.9.0 ### Various fixes & improvements - docs: link to develop docs (#3307) by @joshuarli - fix: more leeway for minimum RAM (#3290) by @joshuarli - Mandate minimum requirements for ram/cpu (#3275) by @hubertdeng123 - ref(feedback): cleanup topic rollout option (#3276) by @aliu39 - Update release template (#3270) by @hubertdeng123 ## 24.8.0 ### Various fixes & improvements - Migrate to zookeeper-less kafka (#3263) by @hubertdeng123 - Revert "ref(feedback): cleanup topic rollout option" (#3262) by @aliu39 - ref(feedback): cleanup topic rollout option (#3256) by @aliu39 - Remove cdc and wal2json and use the default postgres entrypoint (#3260) by @beezz - add `-euo pipefail` to enhance-image.example.sh (#3246) by @asottile-sentry - remove python-dev (#3242) by @asottile-sentry - feat: enable user feedback feature (#3193) by @aldy505 - Use CDN by default for JS SDK Loader (#3213) by @stayallive ## 24.7.1 ### Various fixes & improvements - Fix: errors only config flag (#3220) by @hubertdeng123 - Add errors only self-hosted infrastructure (#3190) by @hubertdeng123 - feat(generic-metrics): Add gauges to docker compose, re-try (#3177) by @ayirr7 ## 24.7.0 ### Various fixes & improvements - Check postgres os before proceeding with install (#3197) by @hubertdeng123 - Update sentry-admin.sh to select its own working directory (#3184) by @theoriginalgri - feat: add insights feature flags (#3152) by @aldy505 - feat(relay): Forward /api/0/relays/* to inner relays (#3144) by @iambriccardo ## 24.6.0 ### Various fixes & improvements - Use general kafka topic creation in self-hosted (#3121) by @hubertdeng123 - Use non-alpine postgres (#3116) by @hubertdeng123 - Bump Python SDK version used in tests (#3108) by @sentrivana ## 24.5.1 ### Various fixes & improvements - Update consumer flags (#3112) by @hubertdeng123 - feat: Add crons task consumers (#3106) by @wedamija - Update minimum docker compose requirement (#3078) by @JannKleen - Different approach to editing permissions of docker volumes (#3084) by @hubertdeng123 - ref(spans): Add new feature flags needed (#3092) by @phacops - chore: Add comment explaining the one liner in clickhouse config (#3085) by @hubertdeng123 - Fix install: use dynamic docker root dir instead of hardcoded one (#3064) by @boutetnico - Typo in config.example.yml (#3063) by @luchaninov ## 24.5.0 ### Various fixes & improvements - fix: Make docker volume script respect compose project name (#3039) by @hubertdeng123 - remove ref to skip writes (#3041) by @john-z-yang - Add clickhouse healthchecks to upgrade (#3024) by @hubertdeng123 - Upgrade clickhouse to 23.8 (#3009) by @hubertdeng123 - fix: use nginx realip module (#2977) by @oioki - Add upgrade test (#3012) by @hubertdeng123 - Bump kafka and zookeeper versions (#2988) by @hubertdeng123 ## 24.4.2 ### Various fixes & improvements - Edit test file name (#3002) by @hubertdeng123 - Revert "Sampling: Run e2e tests every 5 minutes" (#2999) by @hubertdeng123 - Fix master test failures (#3000) by @hubertdeng123 - Sampling: Run e2e tests every 5 minutes (#2994) by @hubertdeng123 - Tweak e2e test github action (#2987) by @hubertdeng123 - fix(performance): Add spans-first-ui flag to enable starfish/performance module views in ui (#2993) by @edwardgou-sentry - Bump docker compose version in CI (#2980) by @hubertdeng123 - Upgrade postgres to 14.11 (#2975) by @mdtro - Add workstation configuration (#2968) by @azaslavsky ## 24.4.1 ### Various fixes & improvements - chore(deps): bump memcached and redis to latest patch versions (#2973) by @mdtro - Use docker compose exec to create additional kafka topics (#2904) by @saz - Add example to docker compose version in problem report (#2959) by @edgariscoding - Port last integration tests to python (#2966) by @hubertdeng123 ## 24.4.0 ### Various fixes & improvements - Use python for e2e tests (#2953) by @hubertdeng123 - feat: adds group attributes consumer (#2927) by @scefali - fix(spans): Adds organizations:standalone-span-ingestion flag to default config (#2936) by @edwardgou-sentry - Bump ubuntu version for tests (#2923) by @hubertdeng123 - Write Customization tests in python (#2918) by @hubertdeng123 - feat(clickhouse): Added max_suspicious_broken_parts to the config.xml (#2853) by @victorelec14 - Port backup tests to python (#2907) by @hubertdeng123 - Fix defunct java processes (#2914) by @hubertdeng123 - Integration tests in python (#2892) by @hubertdeng123 - feat: run outcomes-billing consumer (#2909) by @lynnagara - Remove duplicate feature flags (#2899) by @JannKleen ## 24.3.0 ### Various fixes & improvements - feat(spans): Ingest spans (#2861) by @phacops - Integration test improvements (#2858) by @hubertdeng123 - increase postgres max_connections above 100 connections (#2740) by @erfantkerfan - deps: bump maxmind/geoipupdate to 6.1.0 (#2859) by @victorelec14 - Enable proxy buffering in nginx (#2844) by @RexTim - Add snuba rust consumers (#2831) by @hubertdeng123 - simplify if for open-ai-suggestion (#2732) by @LvckyAPI - Upgrade to FSL-1.1 (#2835) by @chadwhitacre - chore: provide clearer csrf url example (#2833) by @aldy505 - chore: Use django ORM to perform sql commands (#2827) by @hubertdeng123 - revert changes in 3067683f6c0e1c6dd9ceb72cb5155c1dbf3bf501 (#2829) by @hubertdeng123 - use rust consumers in self-hosted (3067683f) by @hubertdeng123 ## 24.2.0 ### Various fixes & improvements - Bump nginx version (#2797) by @hubertdeng123 - build(deps): bump pre-commit/action from 3.0.0 to 3.0.1 (#2788) by @dependabot - Tweak postgres indexing fix (#2792) by @hubertdeng123 - fix: DB migration script (#2779) by @hubertdeng123 ## 24.1.2 ### Various fixes & improvements - Check memcached backend in Django (#2778) by @chadwhitacre - Fix groupedmessage indexing error (#2777) by @hubertdeng123 - build(deps): bump actions/setup-python from 4 to 5 (#2644) by @dependabot - feat: provide csrf settings information for sentry config (#2762) by @aldy505 - Fix apt config generation when http_proxy is set (#2725) (#2734) by @lemrouch ## 24.1.1 ### Various fixes & improvements - Revert "Move open ai key from env variables" (#2724) by @hubertdeng123 - Fix cache error self hosted (#2722) by @hubertdeng123 ## 24.1.0 ### Various fixes & improvements - Enable crons (#2712) by @hubertdeng123 - Parameterize backup restore script (#2412) by @hubertdeng123 - Run tests only on getsentry repository (#2681) by @aminvakil - Tweak the template now that we can see it (#2670) by @chadwhitacre - Nginx client request body is buffered to a temporary file (#2630) by @zKoz210 ## 23.12.1 ### Various fixes & improvements - Make a release issue template (#2666) by @chadwhitacre ## 23.12.0 ### Various fixes & improvements - test(backup): Use --no-prompt for backup tests (#2618) by @azaslavsky ## 23.11.2 - No documented changes. ## 23.11.1 ### Various fixes & improvements - feat: Add sentry-admin.sh tool (#2594) by @azaslavsky - Patch for dev self-hosted environments (#2592) by @hubertdeng123 - Relicense under FSL-1.0-Apache-2.0 (#2586) by @chadwhitacre - Bump minimum ram usage (#2585) by @hubertdeng123 ## 23.11.0 ### Various fixes & improvements - feat: provide a toggle to enable discord integration (#2548) by @aldy505 - ref: fix a typo (#2556) by @asottile-sentry - ref: use `git branch --show-current` instead of sed (#2550) by @asottile-sentry - Remove sessions infra (#2514) by @hubertdeng123 - Upgrade Clickhouse to 21.8 (#2536) by @hubertdeng123 - [Snyk] Security upgrade debian from bullseye-slim to bookworm-20231009-slim (#2511) by @Indigi-managed - snuba: Remove deprecated CLI arg (#2515) by @lynnagara ## 23.10.1 ### Various fixes & improvements - Revert "feat: upgrade to zookeeper-less kafka (#2445)" (#2500) by @hubertdeng123 - Add fast revert GH workflow (#2499) by @hubertdeng123 - build(deps): bump actions/checkout from 3 to 4 (#2493) by @dependabot - configure dependabot (#2491) by @mdtro - deps: bump nginx to 1.25.2 (#2490) by @mdtro - feat: upgrade to zookeeper-less kafka (#2445) by @joshuarli - Update outdated install option in README (#2440) by @hubertdeng123 ## 23.10.0 ### Various fixes & improvements - Switch geoipupdate image to ghcr.io (#2442) by @hkraal - Add system.url-prefix to config for visibility (#2426) by @hubertdeng123 - Remove CSPMiddleware since it is enabled by default in the upstream sentry (#2434) by @oioki - Update nginx.conf (#2455) by @mwarkentin - Update Redis container image to 6.2.13 (#2432) by @mencarellic ## 23.9.1 ### Various fixes & improvements - fix: e2e test jq bug (#2410) by @azaslavsky - feat(backup): Support new backup script (#2407) by @azaslavsky - Decrease frequency of e2e tests (#2383) by @hubertdeng123 - Reduce logs coming from clickhouse (#2382) by @hubertdeng123 - Increase frequency of e2e test runs (#2375) by @hubertdeng123 - Remove nginx content-disposition hack for safari (#2381) by @hubertdeng123 - Attempt to fix integration test flakiness (#2372) by @hubertdeng123 - change health check for kafka service (#2371) by @johnatannvmd - Add metrics and generic metrics backend (#2355) by @hubertdeng123 - Bump self-hosted e2e action commit sha (#2369) by @hubertdeng123 ## 23.8.0 ### Various fixes & improvements - Add issue platform infra (#2309) by @hubertdeng123 ## 23.7.2 ### Various fixes & improvements - Ignore fixture-custom-ca-roots service in integration test (#2321) by @hubertdeng123 - Update GeoIpUpdate to v6.0.0 (#2287) by @victorelec14 - Bump healthcheck timeout (#2300) by @hubertdeng123 ## 23.7.1 ### Various fixes & improvements - Resolve Safari Content-Disposition header bug (#2297) by @azaslavsky - feat: vroom cleanup script that respects default retention days (#2211) by @aldy505 ## 23.7.0 ### Various fixes & improvements - Remove nc -q option (#2275) by @hubertdeng123 - Move open ai key from env variables (#2274) by @hubertdeng123 - Fix command called in reset script (#2254) by @stayallive - Remove stale-bot in self-hosted (#2255) by @hubertdeng123 - Update geoipupdate to 5.1.1 (#2236) by @williamdes ## 23.6.2 ### Various fixes & improvements - Update nginx to 1.25.1 (#2235) by @williamdes - Fix error fingerprinting (#2237) by @chadwhitacre - A couple unit testing improvements (#2238) by @chadwhitacre - Fix #1684 (#2234) by @azaslavsky - Update memcached to 1.6.21 (#2231) by @williamdes - Update redis to 6.2.12 (#2230) by @williamdes - ref: Move all consumers to unified consumer CLI (#2224) by @hubertdeng123 - Revert "ref: Move most consumers to unified consumer CLI" (#2223) by @hubertdeng123 - ref: Move most consumers to unified consumer CLI (#2203) by @untitaker - Release 23.6.1 cleanup (#2209) by @hubertdeng123 ## 23.6.1 ### Various fixes & improvements - Fix bump version script (#2207) by @hubertdeng123 ## 23.6.0 ### Various fixes & improvements - Remove docker compose v1 (#2187) by @hubertdeng123 - ref(compose): Separate ingest consumers (#2193) by @jan-auer - feat(profiling): Run profiling on self-hosted (#2154) by @phacops ## 23.5.2 - No documented changes. ## 23.5.1 ### Various fixes & improvements - fix(suggested-fix): key should be 'key', not 'token' (#2146) by @aldy505 ## 23.5.0 ### Various fixes & improvements - Add no strict offset reset options to consumers (#2144) by @hubertdeng123 - Add settings for enabling CSP to config file (#2134) by @hubertdeng123 - feat: add suggested fix feature (#2115) by @aldy505 - adding ulimits for zookeeper, kafka, and web (#2123) by @jamincollins - Uninstall Docker Compose v1 from CI so it's not used for tests (#2114) by @hubertdeng123 - Fixed docker compose issue in backup/restore (#2110) by @montaniasystemab - Enable upstream keepalive (#2099) by @otbutz - Bump commit sha for e2e test action (#2104) by @hubertdeng123 - Use docker compose exec to account for differences in container names for Postgres upgrade (#2096) by @hubertdeng123 - Change symbolicator to use CalVer for release (#2091) by @hubertdeng123 ## 23.4.0 ### Postgres 14 Upgrade We've now included an upgrade from Postgres 9.6 to 14.5 that will automatically be run via the `./install.sh` script. By: @hubertdeng123 (#2074) ### Various fixes & improvements - Remove clean function testing line (#2082) by @hubertdeng123 - Fix command to get docker compose version in problem report template (#2080) by @hubertdeng123 - Tweak permissioning of backup file in backup script to read/write for all users (#2043) by @hubertdeng123 - Remove commit-batch-size parameter (#2058) by @hubertdeng123 - Support external sourcemaps bigger, than 1Mb (#2050) by @le0pard - Add github setup instructions to config.example.yml (#2051) by @tm1000 - ref(snuba): Use snuba self-hosted settings (#2039) by @enochtangg ## 23.3.1 ### Various fixes & improvements - Bump Kafka version to keep up with SaaS (#2037) by @chadwhitacre - Add Backup/restore scripts (#2029) by @hubertdeng123 - Add opt in error monitoring to reset and clean scripts (#2021) by @hubertdeng123 ## 23.3.0 ### Various fixes & improvements - Remove ZooKeeper snapshot (#2020) by @dereckson - feat(snuba): Add snuba sessions subscription service (#2006) by @klboke - Add backup/restore integration tests (#2012) by @hubertdeng123 - ref(snuba): Remove snuba-cleanup, snuba-transactions-cleanup jobs (#2003) by @klboke - ref(replays): Remove the session-replay-ui flag (#2010) by @ryan953 - Remove broken replay integration test (#2011) by @hubertdeng123 - Bump self-hosted-e2e-tests action commit sha (#2008) by @hubertdeng123 - Revert symbolicator tests (#2004) by @hubertdeng123 - post-process-forwarder: Update CLI command (#1999) by @lynnagara - feat(replays): add replays to self hosted (#1990) by @JoshFerge - Remove issue status helper automation (#1989) by @hubertdeng123 - Add proxy buffer size config to fix Bad Gateway (#1984) by @SCjona - Reference paths relative to project root (#1800) by @spawnia - Run close stale issues/PRs only on getsentry (#1969) by @aminvakil ## 23.2.0 ### Various fixes & improvements - Run lock issues/PRs only on getsentry (#1966) by @aminvakil - Updates Redis to 6.2.10 (#1937) by @danielhartnell - Handle missing example files gracefully (#1950) by @chadwhitacre - Fix post-release.sh for `git pull` (#1938) by @BYK - Manually change 23.1.1 to nightly (#1936) by @hubertdeng123 ## 23.1.1 ### Various fixes & improvements - ci: Add test for symbolicator pipeline (#1916) by @ethanhs ## 23.1.0 ### Various fixes & improvements - ci: Check health of services after running integration tests and fix snuba-replacer (#1897) by @ethanhs - Add wal2json debugging (#1906) by @chadwhitacre - Pick up CI bugfix (#1905) by @chadwhitacre - ref: Move jq build to error-handling.sh, and use proxy config (#1895) by @ethanhs - fix(CI): use default curl retry mechanism for wal2json install (#1890) by @volokluev - ref: Retry wal2json download in installer (#1881) by @ethanhs - ci: Remove GCB and update Github Action SHA (#1880) by @ethanhs ## 22.12.0 ### Various fixes & improvements - Build each service image individually (#1858) by @ethanhs - Set higher kafka healthcheck timeout and fix clickhouse timeout (#1855) by @ethanhs - Add --skip-sse42-requirements to install.sh and enable SKIP_SSE42_REQUIREMENTS override (#1790) by @erinaceous - Fix commit-log-topic parameter configuration problem (#1817) by @klboke - Add .idea to .gitignore (#1803) by @spawnia - Add USE_X_FORWARDED_HOST to example config (#1804) by @crinjes - (fix): Fix contributor PR e2e tests (#1820) by @hubertdeng123 ## 22.11.0 ### Various fixes & improvements - Fix jq usage (#1814) by @ethanhs - Try adding end to end tests using new action (#1806) by @ethanhs - Add context line, error msg to envelope (#1784) by @hubertdeng123 - Update to actions/checkoutv3 to address upcoming github deprecations (#1792) by @mattgauntseo-sentry - ref: upgrade actions/setup-python to avoid set-output deprecation (#1789) by @asottile-sentry - Enforce error reporting (#1777) by @hubertdeng123 - Upload end of log as breadcrumbs, use exceptions and stacktrace (#1775) by @ethanhs - Fix sentry release for dogfood instance (#1768) by @hubertdeng123 - Add pre-commit config (#1738) by @ethanhs - Do not send event on INT signal (#1773) by @hubertdeng123 ## 22.10.0 ### Various fixes & improvements - Split post process forwarders (#1759) by @chadwhitacre - Revert "Enforce error reporting for self-hosted" (#1755) by @hubertdeng123 - Enforce error reporting for self-hosted (#1753) by @hubertdeng123 - ref: Remove unused scripts and code (#1710) by @BYK - Check to see if docker compose exists, else error out (#1733) by @hubertdeng123 - Fix minimum version requirements for docker and docker compose (#1732) by @hubertdeng123 - Factor out clean and use it in unit-test (#1731) by @chadwhitacre - Reorganize unit test layout (#1729) by @hubertdeng123 - Request event ID in issue template (#1723) by @ethanhs - Tag releases with sentry-cli (#1718) by @hubertdeng123 - Send full logs as an attachment to our dogfood instance (#1715) by @hubertdeng123 ## 22.9.0 ### Various fixes & improvements - Fix traceback hash for error monitoring (#1700) by @hubertdeng123 - Add section about error monitoring to the README (#1699) by @ethanhs - Switch from .reporterrors file to flag + envvar (#1697) by @chadwhitacre - Rename flag to --skip-user-creation (#1696) by @chadwhitacre - Default to not sending data to Sentry for now (#1695) by @chadwhitacre - fix(e2e tests): Pull branch that initially triggers gcp build for PRs (#1694) by @hubertdeng123 - fix(e2e tests): Add .reporterrors file for GCP run of e2e tests (#1691) by @hubertdeng123 - Error monitoring of the self-hosted installer (#1679) by @ethanhs - added docker commands in the description (#1673) by @victorelec14 - Use docker-compose 2.7.0 instead of 2.2.3 in CI (#1591) by @aminvakil ## 22.8.0 - No documented changes. ## 22.7.0 ### Various fixes & improvements - ref: use sort -V to check minimum versions (#1553) by @ethanhs - Get more data from users in issue templates (#1497) by @aminvakil - Add ARM support (#1538) by @chadwhitacre - do not use gosu for snuba-transactions-cleanup and snuba-cleanup (#1564) by @goganchic - ref: Replace regex with --short flag to get compose version (#1551) by @ethanhs - Improve installation through proxy (#1543) by @goganchic - Cleanup .env{,.custom} handling (#1539) by @chadwhitacre - Bump nginx:1.22.0-alpine (#1506) by @aminvakil - Run release a new version job only on getsentry (#1529) by @aminvakil ## 22.6.0 ### Various fixes & improvements - fix "services.web.healthcheck.retries must be a number" (#1482) by @yuval1986 - Add volume for nginx cache (#1511) by @glensc - snuba: New subscriptions infrastructure rollout (#1507) by @lynnagara - Ease modification of base image (#1479) by @spawnia ## 22.5.0 ### Various fixes & improvements - ref: reset user to root for installation (#1469) by @asottile-sentry - Document From email display name (#1446) by @chadwhitacre - Bring in CLA Lite (#1439) by @chadwhitacre - fix: replace git.io links with redirect targets (#1430) by @asottile-sentry ## 22.4.0 ### Various fixes & improvements - Use better API key when available (#1408) by @chadwhitacre - Use a custom action (#1407) by @chadwhitacre - Add some debug logging (#1340) by @chadwhitacre - meta(gha): Deploy workflow enforce-license-compliance.yml (#1388) by @chadwhitacre - Turn off containers under old name as well (#1384) by @chadwhitacre ## 22.3.0 ### Various fixes & improvements - Run CI every night (#1334) by @aminvakil - Docker-Compose: Avoid setting hostname to '' (#1365) by @glensc - meta(gha): Deploy workflow enforce-license-compliance.yml (#1375) by @chadwhitacre - ci: Change stale GitHub workflow to run once a day (#1371) by @kamilogorek - ci: Temporary fix for interactive prompt on createuser (#1370) by @BYK - meta(gha): Deploy workflow enforce-license-compliance.yml (#1347) by @chadwhitacre - Add SaaS nudge to README (#1327) by @chadwhitacre ## 22.2.0 ### Various fixes & improvements - fix: unbound variable _group in reset/dc-detect-version script (#1283) (#1284) by @lovetodream - Remove routing helper (#1323) by @chadwhitacre - Bump nginx:1.21.6-alpine (#1319) by @aminvakil - Add a cloudbuild.yaml for GCB (#1315) by @chadwhitacre - Update set-up-and-migrate-database.sh (#1308) by @drmrbrewer - Pull relay explicitly to avoid garbage in creds (#1301) by @chadwhitacre - Improve logging of docker versions and relay creds (#1298) by @chadwhitacre - Remove file again (#1299) by @chadwhitacre - Clean up relay credentials generation (#1289) by @chadwhitacre - Add CI compose version 1.29.2 / 2.0.1 / 2.2.3 (#1290) by @chadwhitacre - Revert "Add CI compose version 1.29.2 / 2.0.1 / 2.2.3 (#1251)" (#1272) by @chadwhitacre - Add CI compose version 1.29.2 / 2.0.1 / 2.2.3 (#1251) by @aminvakil ## 22.1.0 ### Various fixes & improvements - Make healthcheck variables configurable in .env (#1248) by @aminvakil - Take some actions to avoid unhealthy containers (#1241) by @chadwhitacre - Install: setup umask (#1222) by @glensc - Deprecated /docker-entrypoint.sh call (#1218) by @marcinroman - Bump nginx:1.21.5-alpine (#1230) by @aminvakil - Fix reset.sh docker-compose call (#1215) by @aminvakil - Set worker_processes to auto (#1207) by @aminvakil ## 21.12.0 ### Support Docker Compose v2 (ongoing) Self-hosted Sentry mostly works with Docker Compose v2 (in addition to v1 >= 1.28.0). There is [one more bug](https://github.com/getsentry/self-hosted/issues/1133) we are trying to squash. By: @chadwhitacre (#1179) ### Prevent Component Drift When a user runs the `install.sh` script, they get the latest version of the Sentry, Snuba, Relay and Symbolicator projects. However there is no guarantee they have pulled the latest `self-hosted` version first, and running an old one may cause problems. To mitigate this, we now perform a check during installation that the user is on the latest commit if they are on the `master` branch. You can disable this check with `--skip-commit-check`. By: @chadwhitacre (#1191), @aminvakil (#1186) ### React to log4shell Self-hosted Sentry is [not vulnerable](https://github.com/getsentry/self-hosted/issues/1196) to the [log4shell](https://log4shell.com/) vulnerability. By: @chadwhitacre (#1203) ### Forum → Issues In the interest of reducing sources of truth, providing better support, and restarting the fire of the self-hosted Sentry community, we [deprecated the Discourse forum in favor of GitHub Issues](https://github.com/getsentry/self-hosted/issues/1151). By: @chadwhitacre (#1167, #1160, #1159) ### Rename onpremise to self-hosted (ongoing) In the beginning we used the term "on-premise" and over time we introduced the term "self-hosted." In an effort to regain some consistency for both branding and developer mental overhead purposes, we are standardizing on the term "self-hosted." This release includes a fair portion of the work towards this across multiple repos, hopefully a future release will include the remainder. Some orphaned containers / volumes / networks are [expected](https://github.com/getsentry/self-hosted/pull/1169#discussion_r756401917). You may clean them up with `docker-compose down --remove-orphans`. By: @chadwhitacre (#1169) ### Add support for custom DotEnv file There are several ways to [configure self-hosted Sentry](https://develop.sentry.dev/self-hosted/#configuration) and one of them is the `.env` file. In this release we add support for a `.env.custom` file that is git-ignored to make it easier for you to override keys configured this way with custom values. Thanks to @Sebi94nbg for the contribution! By: @Sebi94nbg (#1113) ### Various fixes & improvements - Revert "Rename onpremise to self-hosted" (5495fe2e) by @chadwhitacre - Rename onpremise to self-hosted (9ad05d87) by @chadwhitacre ## 21.11.0 ### Various fixes & improvements - Fix #1079 - bug in reset.sh (#1134) by @chadwhitacre - ci: Enable parallel tests again, increase timeouts (#1125) by @BYK - fix: Hide compose errors during version check (#1124) by @BYK - build: Omit nightly bump commit from changelog (#1120) by @BYK - build: Set master version to nightly (d3e77857) ## 21.10.0 ### Support for Docker Compose v2 (ongoing) You asked for it and you did it! Sentry self-hosted now can work with Docker Compose v2 thanks to our community's contributions. PRs: #1116 ### Various fixes & improvements - docs: simplify Linux `sudo` instructions in README (#1096) - build: Set master version to nightly (58874cf9) ## 21.9.0 - fix(healthcheck): Increase retries to 5 (#1072) - fix(requirements): Make compose version check bw-compatible (#1068) - ci: Test with the required minimum docker-compose (#1066) Run tests using docker-compose `1.28.0` instead of latest - fix(clickhouse): Use correct HTTP port for healthcheck (#1069) Fixes the regular `Unexpected packet` errors in Clickhouse ## 21.8.0 - feat: Support custom CA roots ([#27062](https://github.com/getsentry/sentry/pull/27062)), see the [docs](https://develop.sentry.dev/self-hosted/custom-ca-roots/) for more details. - fix: Fix `curl` image to version 7.77.0 - upgrade: docker-compose version to 1.29.2 - feat: Leverage health checks for depends_on ## 21.7.0 - No documented changes. ## 21.6.3 - No documented changes. ## 21.6.2 - BREAKING CHANGE: The frontend bundle will be loaded asynchronously (via [#25744](https://github.com/getsentry/sentry/pull/25744)). This is a breaking change that can affect custom plugins that access certain globals in the django template. Please see https://forum.sentry.io/t/breaking-frontend-changes-for-custom-plugins/14184 for more information. ## 21.6.1 - No documented changes. ## 21.6.0 - feat: Add healthchecks for redis, memcached and postgres (#975) ================================================ FILE: CONTRIBUTING.md ================================================ ## Testing ### Running Tests with Pytest We use pytest for running tests. To run the tests: 1) Ensure that you are in the root directory of the project. 2) Run the following command: ```bash pytest ``` This will automatically discover and run all test cases in the project. ================================================ FILE: LICENSE.md ================================================ # Functional Source License, Version 1.1, Apache 2.0 Future License ## Abbreviation FSL-1.1-Apache-2.0 ## Notice Copyright 2016-2024 Functional Software, Inc. dba Sentry ## Terms and Conditions ### Licensor ("We") The party offering the Software under these Terms and Conditions. ### The Software The "Software" is each version of the software that we make available under these Terms and Conditions, as indicated by our inclusion of these Terms and Conditions with the Software. ### License Grant Subject to your compliance with this License Grant and the Patents, Redistribution and Trademark clauses below, we hereby grant you the right to use, copy, modify, create derivative works, publicly perform, publicly display and redistribute the Software for any Permitted Purpose identified below. ### Permitted Purpose A Permitted Purpose is any purpose other than a Competing Use. A Competing Use means making the Software available to others in a commercial product or service that: 1. substitutes for the Software; 2. substitutes for any other product or service we offer using the Software that exists as of the date we make the Software available; or 3. offers the same or substantially similar functionality as the Software. Permitted Purposes specifically include using the Software: 1. for your internal use and access; 2. for non-commercial education; 3. for non-commercial research; and 4. in connection with professional services that you provide to a licensee using the Software in accordance with these Terms and Conditions. ### Patents To the extent your use for a Permitted Purpose would necessarily infringe our patents, the license grant above includes a license under our patents. If you make a claim against any party that the Software infringes or contributes to the infringement of any patent, then your patent license to the Software ends immediately. ### Redistribution The Terms and Conditions apply to all copies, modifications and derivatives of the Software. If you redistribute any copies, modifications or derivatives of the Software, you must include a copy of or a link to these Terms and Conditions and not remove any copyright notices provided in or with the Software. ### Disclaimer THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT. IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE. ### Trademarks Except for displaying the License Details and identifying us as the origin of the Software, you have no right under these Terms and Conditions to use our trademarks, trade names, service marks or product names. ## Grant of Future License We hereby irrevocably grant you an additional license to use the Software under the Apache License, Version 2.0 that is effective on the second anniversary of the date we make the Software available. On or after that date, you may use the Software under the Apache License, Version 2.0, in which case the following will apply: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Self-Hosted Sentry 26.3.1 [Sentry](https://sentry.io/), feature-complete and packaged up for low-volume deployments and proofs-of-concept. Documentation [here](https://develop.sentry.dev/self-hosted/). ================================================ FILE: _integration-test/conftest.py ================================================ import os from os.path import join import subprocess import pytest SENTRY_CONFIG_PY = "sentry/sentry.conf.py" SENTRY_TEST_HOST = os.getenv("SENTRY_TEST_HOST", "http://localhost:9000") TEST_USER = "test@example.com" TEST_PASS = "test123TEST" @pytest.fixture(scope="session", autouse=True) def configure_self_hosted_environment(request): subprocess.run( ["docker", "compose", "--ansi", "never", "up", "--wait"], check=True, capture_output=True, ) # Create test user subprocess.run( [ "docker", "compose", "exec", "-T", "web", "sentry", "createuser", "--force-update", "--superuser", "--email", TEST_USER, "--password", TEST_PASS, "--no-input", ], check=True, text=True, ) @pytest.fixture() def setup_backup_restore_env_variables(): os.environ["SENTRY_DOCKER_IO_DIR"] = os.path.join(os.getcwd(), "sentry") os.environ["SKIP_USER_CREATION"] = "1" ================================================ FILE: _integration-test/custom-ca-roots/custom-ca-roots-test.py ================================================ import unittest import requests class CustomCATests(unittest.TestCase): def test_valid_self_signed(self): self.assertEqual(requests.get("https://self.test").text, "ok") def test_invalid_self_signed(self): with self.assertRaises(requests.exceptions.SSLError): requests.get("https://fail.test") if __name__ == "__main__": unittest.main() ================================================ FILE: _integration-test/custom-ca-roots/docker-compose.test.yml ================================================ version: '3.4' services: fixture-custom-ca-roots: image: nginx:1.21.0-alpine restart: unless-stopped volumes: - ./_integration-test/custom-ca-roots/nginx:/etc/nginx:ro networks: default: aliases: - self.test - fail.test ================================================ FILE: _integration-test/fixtures/envelope-with-profile ================================================ {"event_id":"66578634d48d433db0ad52882d1efe5b","sent_at":"2023-05-17T14:54:31.057Z","sdk":{"name":"sentry.javascript.node","version":"7.46.0"},"trace":{"environment":"production","transaction":"fib: sourcemaps here","public_key":"05ab86aebbe14a24bcab62caa839cf27","trace_id":"33321bfbd5304bcc9663d1b53b08f84e","sample_rate":"1"}} {"type":"transaction"} {"contexts":{"profile":{"profile_id":"e73aaf1f29b24812be60132f32d09f92"},"trace":{"op":"test","span_id":"b38f2b24537c3858","trace_id":"33321bfbd5304bcc9663d1b53b08f84e"},"runtime":{"name":"node","version":"v16.16.0"},"app":{"app_start_time":"2023-05-17T14:54:27.678Z","app_memory":57966592},"os":{"kernel_version":"22.3.0","name":"macOS","version":"13.2","build":"22D49"},"device":{"boot_time":"2023-05-12T15:08:41.047Z","arch":"arm64","memory_size":34359738368,"free_memory":6861651968,"processor_count":10,"cpu_description":"Apple M1 Pro","processor_frequency":24},"culture":{"locale":"en-US","timezone":"America/New_York"}},"spans":[],"start_timestamp":1684335267.744,"tags":{},"timestamp":1684335271.033,"transaction":"fib: sourcemaps here","type":"transaction","transaction_info":{"source":"custom"},"platform":"node","server_name":"TK6G745PW1.local","event_id":"66578634d48d433db0ad52882d1efe5b","environment":"production","sdk":{"integrations":["InboundFilters","FunctionToString","Console","Http","OnUncaughtException","OnUnhandledRejection","ContextLines","LocalVariables","Context","Modules","RequestData","LinkedErrors","ProfilingIntegration"],"name":"sentry.javascript.node","version":"7.46.0","packages":[{"name":"npm:@sentry/node","version":"7.46.0"}]},"debug_meta":{"images":[]},"modules":{}} {"type":"profile"} {"event_id":"e73aaf1f29b24812be60132f32d09f92","timestamp":"2023-05-17T14:54:27.744Z","platform":"node","version":"1","release":"","environment":"production","runtime":{"name":"node","version":"16.16.0"},"os":{"name":"darwin","version":"22.3.0","build_number":"Darwin Kernel Version 22.3.0: Thu Jan 5 20:48:54 PST 2023; root:xnu-8792.81.2~2/RELEASE_ARM64_T6000"},"device":{"locale":"en_US.UTF-8","model":"arm64","manufacturer":"Darwin","architecture":"arm64","is_emulator":false},"debug_meta":{"images":[]},"profile":{"samples":[{"stack_id":0,"thread_id":"0","elapsed_since_start_ns":125000},{"stack_id":0,"thread_id":"0","elapsed_since_start_ns":13958000}],"frames":[{"lineno":14129,"colno":17,"function":"startProfiling","abs_path":"/Users/jonasbadalic/code/node-profiler/lib/index.js"}],"stacks":[[0]],"thread_metadata":{"0":{"name":"main"}}},"transaction":{"name":"fib: sourcemaps here","id":"66578634d48d433db0ad52882d1efe5b","trace_id":"33321bfbd5304bcc9663d1b53b08f84e","active_thread_id":"0"}} ================================================ FILE: _integration-test/fixtures/envelope-with-transaction ================================================ {"event_id":"66578634d48d433db0ad52882d1efe5b","sent_at":"2023-05-17T14:54:31.057Z","sdk":{"name":"sentry.javascript.node","version":"7.46.0"},"trace":{"environment":"production","transaction":"fib: sourcemaps here","public_key":"05ab86aebbe14a24bcab62caa839cf27","trace_id":"33321bfbd5304bcc9663d1b53b08f84e","sample_rate":"1"}} {"type":"transaction"} {"contexts":{"trace":{"op":"test","span_id":"b38f2b24537c3858","trace_id":"33321bfbd5304bcc9663d1b53b08f84e"},"runtime":{"name":"node","version":"v16.16.0"},"app":{"app_start_time":"2023-05-17T14:54:27.678Z","app_memory":57966592},"os":{"kernel_version":"22.3.0","name":"macOS","version":"13.2","build":"22D49"},"device":{"boot_time":"2023-05-12T15:08:41.047Z","arch":"arm64","memory_size":34359738368,"free_memory":6861651968,"processor_count":10,"cpu_description":"Apple M1 Pro","processor_frequency":24},"culture":{"locale":"en-US","timezone":"America/New_York"}},"spans":[],"start_timestamp":1684335267.744,"tags":{},"timestamp":1684335271.033,"transaction":"fib: sourcemaps here","type":"transaction","transaction_info":{"source":"custom"},"platform":"node","server_name":"TK6G745PW1.local","event_id":"66578634d48d433db0ad52882d1efe5b","environment":"production","sdk":{"integrations":["InboundFilters","FunctionToString","Console","Http","OnUncaughtException","OnUnhandledRejection","ContextLines","LocalVariables","Context","Modules","RequestData","LinkedErrors","ProfilingIntegration"],"name":"sentry.javascript.node","version":"7.46.0","packages":[{"name":"npm:@sentry/node","version":"7.46.0"}]},"debug_meta":{"images":[]},"modules":{}} ================================================ FILE: _integration-test/nodejs/.gitignore ================================================ node_modules ================================================ FILE: _integration-test/nodejs/instrument.js ================================================ import * as Sentry from "@sentry/node"; Sentry.init({ dsn: process.env.SENTRY_DSN, sampleRate: 1.0, tracesSampleRate: 1.0, enableLogs: true, profileLifecycle: "manual", sendClientReports: true, sendDefaultPii: true, debug: true, }); ================================================ FILE: _integration-test/nodejs/package.json ================================================ { "name": "sentry-self-hosted-integration-test-nodejs", "version": "0.0.0", "description": "Scripts to run for features only available on Nodejs SDK", "author": "Sentry ", "type": "module", "scripts": { "start": "node --import ./instrument.js index.js" }, "dependencies": { "@sentry/node": "^10.5.0" } } ================================================ FILE: _integration-test/nodejs/user-feedback.js ================================================ import * as Sentry from "@sentry/node"; Sentry.captureFeedback({ message: "I love your startup!", name: "John Doe", email: "john@example.com", url: "https://example.com", }); Sentry.flush(5000); ================================================ FILE: _integration-test/test_01_basics.py ================================================ import datetime import json import os import re import shutil import subprocess import sys import time from functools import lru_cache from typing import Callable import httpx import pytest import sentry_sdk from sentry_sdk import logger as sentry_logger from bs4 import BeautifulSoup from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509.oid import NameOID SENTRY_CONFIG_PY = "sentry/sentry.conf.py" SENTRY_TEST_HOST = os.getenv("SENTRY_TEST_HOST", "http://localhost:9000") TEST_USER = "test@example.com" TEST_PASS = "test123TEST" TIMEOUT_SECONDS = 120 def poll_for_response( request: str, client: httpx.Client, validator: Callable = None ) -> httpx.Response: for i in range(TIMEOUT_SECONDS): response = client.get( request, follow_redirects=True, headers={"Referer": SENTRY_TEST_HOST} ) if response.status_code == 200: if validator is None or validator(response.text): break time.sleep(1) else: raise AssertionError( "timeout waiting for response status code 200 or valid data" ) return response @lru_cache def get_sentry_dsn(client: httpx.Client) -> str: response = poll_for_response( f"{SENTRY_TEST_HOST}/api/0/projects/sentry/internal/keys/", client, lambda x: len(json.loads(x)[0]["dsn"]["public"]) > 0, ) sentry_dsn = json.loads(response.text)[0]["dsn"]["public"] return sentry_dsn @pytest.fixture() def client_login(): client = httpx.Client() response = client.get(SENTRY_TEST_HOST, follow_redirects=True) parser = BeautifulSoup(response.text, "html.parser") login_csrf_token = parser.find("input", {"name": "csrfmiddlewaretoken"})["value"] login_response = client.post( f"{SENTRY_TEST_HOST}/auth/login/sentry/", follow_redirects=True, data={ "op": "login", "username": TEST_USER, "password": TEST_PASS, "csrfmiddlewaretoken": login_csrf_token, }, headers={"Referer": f"{SENTRY_TEST_HOST}/auth/login/sentry/"}, ) assert login_response.status_code == 200 yield (client, login_response) def test_initial_redirect(): initial_auth_redirect = httpx.get(SENTRY_TEST_HOST, follow_redirects=True) assert initial_auth_redirect.url == f"{SENTRY_TEST_HOST}/auth/login/sentry/" def test_asset_internal_rewrite(): """Tests whether we correctly map `/_assets/*` to `/_static/dist/sentry` as we don't have a CDN setup in self-hosted.""" response = httpx.get(f"{SENTRY_TEST_HOST}/_assets/entrypoints/app.js") assert response.status_code == 200 assert response.headers["Content-Type"] == "text/javascript" def test_login(client_login): client, login_response = client_login parser = BeautifulSoup(login_response.text, "html.parser") script_tag = parser.find( "script", string=lambda x: x and "window.__initialData" in x ) assert script_tag is not None json_data = json.loads(script_tag.text.split("=", 1)[1].strip().rstrip(";")) assert json_data["isAuthenticated"] is True assert json_data["user"]["username"] == "test@example.com" assert json_data["user"]["isSuperuser"] is True assert login_response.cookies["sc"] is not None # Set up initial/required settings (InstallWizard request) client.headers.update({"X-CSRFToken": login_response.cookies["sc"]}) response = client.put( f"{SENTRY_TEST_HOST}/api/0/internal/options/?query=is:required", follow_redirects=True, headers={"Referer": SENTRY_TEST_HOST}, data={ "mail.use-tls": False, "mail.username": "", "mail.port": 25, "system.admin-email": "test@example.com", "mail.password": "", "system.url-prefix": SENTRY_TEST_HOST, "auth.allow-registration": False, "beacon.anonymous": True, }, ) assert response.status_code == 200 def test_receive_event(client_login): event_id = None client, _ = client_login sentry_sdk.init(dsn=get_sentry_dsn(client)) event_id = sentry_sdk.capture_exception(Exception("a failure")) assert event_id is not None response = poll_for_response( f"{SENTRY_TEST_HOST}/api/0/projects/sentry/internal/events/{event_id}/", client ) response_json = json.loads(response.text) assert response_json["eventID"] == event_id assert response_json["metadata"]["value"] == "a failure" def test_cleanup_crons_running(): docker_services = subprocess.check_output( [ "docker", "compose", "--ansi", "never", "ps", "-a", ], text=True, ) pattern = re.compile( r"(\-cleanup\s+running)|(\-cleanup[_-].+\s+Up\s+)", re.MULTILINE ) cleanup_crons = pattern.findall(docker_services) assert len(cleanup_crons) > 0 def test_custom_certificate_authorities(): # Set environment variable os.environ["COMPOSE_FILE"] = ( "docker-compose.yml:_integration-test/custom-ca-roots/docker-compose.test.yml" ) test_nginx_conf_path = "_integration-test/custom-ca-roots/nginx" custom_certs_path = "certificates" # Generate tightly constrained CA ca_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) ca_name = x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, "TEST CA *DO NOT TRUST*")] ) ca_cert = ( x509.CertificateBuilder() .subject_name(ca_name) .issuer_name(ca_name) .public_key(ca_key.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(datetime.datetime.now(datetime.timezone.utc)) .not_valid_after( datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1) ) .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) .add_extension( x509.KeyUsage( digital_signature=False, key_encipherment=False, content_commitment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False, ), critical=True, ) .add_extension( x509.NameConstraints([x509.DNSName("self.test")], None), critical=True ) .add_extension( x509.SubjectKeyIdentifier.from_public_key(ca_key.public_key()), critical=False, ) .sign(private_key=ca_key, algorithm=hashes.SHA256()) ) ca_key_path = f"{test_nginx_conf_path}/ca.key" ca_crt_path = f"{test_nginx_conf_path}/ca.crt" with open(ca_key_path, "wb") as key_file: key_file.write( ca_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) ) with open(ca_crt_path, "wb") as cert_file: cert_file.write(ca_cert.public_bytes(serialization.Encoding.PEM)) # Create custom certs path and copy ca.crt os.makedirs(custom_certs_path, exist_ok=True) shutil.copyfile(ca_crt_path, f"{custom_certs_path}/test-custom-ca-roots.crt") # Generate server key and certificate self_test_key_path = os.path.join(test_nginx_conf_path, "self.test.key") self_test_csr_path = os.path.join(test_nginx_conf_path, "self.test.csr") self_test_cert_path = os.path.join(test_nginx_conf_path, "self.test.crt") self_test_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) self_test_req = ( x509.CertificateSigningRequestBuilder() .subject_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "Self Signed with CA Test Server" ) ] ) ) .add_extension( x509.SubjectAlternativeName([x509.DNSName("self.test")]), critical=False ) .sign(self_test_key, hashes.SHA256()) ) self_test_cert = ( x509.CertificateBuilder() .subject_name( x509.Name( [ x509.NameAttribute( NameOID.COMMON_NAME, "Self Signed with CA Test Server" ) ] ) ) .issuer_name(ca_cert.issuer) .serial_number(x509.random_serial_number()) .not_valid_before(datetime.datetime.now(datetime.timezone.utc)) .not_valid_after( datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1) ) .public_key(self_test_req.public_key()) .add_extension( x509.SubjectAlternativeName([x509.DNSName("self.test")]), critical=False ) .add_extension( x509.SubjectKeyIdentifier.from_public_key(self_test_req.public_key()), critical=False, ) .add_extension( x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( ca_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value ), critical=False, ) .sign(private_key=ca_key, algorithm=hashes.SHA256()) ) # Save server key, CSR, and certificate with open(self_test_key_path, "wb") as key_file: key_file.write( self_test_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) ) with open(self_test_csr_path, "wb") as csr_file: csr_file.write(self_test_req.public_bytes(serialization.Encoding.PEM)) with open(self_test_cert_path, "wb") as cert_file: cert_file.write(self_test_cert.public_bytes(serialization.Encoding.PEM)) # Generate server key and certificate for fake.test fake_test_key_path = os.path.join(test_nginx_conf_path, "fake.test.key") fake_test_cert_path = os.path.join(test_nginx_conf_path, "fake.test.crt") fake_test_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) fake_test_cert = ( x509.CertificateBuilder() .subject_name( x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, "Self Signed Test Server")] ) ) .issuer_name( x509.Name( [x509.NameAttribute(NameOID.COMMON_NAME, "Self Signed Test Server")] ) ) .serial_number(x509.random_serial_number()) .not_valid_before(datetime.datetime.now(datetime.timezone.utc)) .not_valid_after( datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1) ) .public_key(fake_test_key.public_key()) .add_extension( x509.SubjectAlternativeName([x509.DNSName("fake.test")]), critical=False ) .add_extension( x509.SubjectKeyIdentifier.from_public_key(fake_test_key.public_key()), critical=False, ) .sign(private_key=fake_test_key, algorithm=hashes.SHA256()) ) # Save server key and certificate for fake.test with open(fake_test_key_path, "wb") as key_file: key_file.write( fake_test_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) ) # Our asserts for this test case must be executed within the web container, so we are copying a python test script into the mounted sentry directory with open(fake_test_cert_path, "wb") as cert_file: cert_file.write(fake_test_cert.public_bytes(serialization.Encoding.PEM)) shutil.copyfile( "_integration-test/custom-ca-roots/custom-ca-roots-test.py", "sentry/test-custom-ca-roots.py", ) subprocess.run( [ "docker", "compose", "--ansi", "never", "up", "--wait", "fixture-custom-ca-roots", ], check=True, ) subprocess.run( [ "docker", "compose", "--ansi", "never", "run", "--no-deps", "web", "python3", "/etc/sentry/test-custom-ca-roots.py", ], check=True, ) subprocess.run( [ "docker", "compose", "--ansi", "never", "rm", "-s", "-f", "-v", "fixture-custom-ca-roots", ], check=True, ) # Remove files os.remove(f"{custom_certs_path}/test-custom-ca-roots.crt") os.remove("sentry/test-custom-ca-roots.py") # Unset environment variable if "COMPOSE_FILE" in os.environ: del os.environ["COMPOSE_FILE"] @pytest.mark.skipif(os.environ.get("COMPOSE_PROFILES") != "feature-complete", reason="Only run if feature-complete") def test_receive_transaction_events(client_login): client, _ = client_login sentry_sdk.init( dsn=get_sentry_dsn(client), profiles_sample_rate=1.0, traces_sample_rate=1.0 ) def placeholder_fn(): sum = 0 for i in range(5): sum += i time.sleep(0.25) with sentry_sdk.start_transaction(op="task", name="Test Transactions"): placeholder_fn() poll_for_response( f"{SENTRY_TEST_HOST}/api/0/organizations/sentry/events/?dataset=profiles&field=profile.id&project=1&statsPeriod=1h", client, lambda x: len(json.loads(x)["data"]) > 0, ) poll_for_response( f"{SENTRY_TEST_HOST}/api/0/organizations/sentry/events/?dataset=spans&field=id&project=1&statsPeriod=1h", client, lambda x: len(json.loads(x)["data"]) > 0, ) @pytest.mark.skipif(os.environ.get("COMPOSE_PROFILES") != "feature-complete", reason="Only run if feature-complete") def test_receive_user_feedback_events(client_login): client, _ = client_login sentry_dsn = get_sentry_dsn(client) # Execute `node --import instrument.js user-feedback.js` on the `nodejs` directory with the `SENTRY_DSN` env var set env = os.environ.copy() env["SENTRY_DSN"] = sentry_dsn subprocess.run( ["node", "--import", "./instrument.js", "./user-feedback.js"], check=True, shell=False, env=env, cwd="_integration-test/nodejs", stdout=sys.stdout, stderr=sys.stderr, timeout=60, ) poll_for_response( f"{SENTRY_TEST_HOST}/api/0/organizations/sentry/issues/?query=issue.category%3Afeedback", client, lambda x: len(json.loads(x)) > 0, ) poll_for_response( f"{SENTRY_TEST_HOST}/api/0/organizations/sentry/events/?dataset=issuePlatform&field=message&field=title&field=timestamp&project=1&statsPeriod=1h", client, lambda x: len(json.loads(x)["data"]) > 0, ) @pytest.mark.skipif(os.environ.get("COMPOSE_PROFILES") != "feature-complete", reason="Only run if feature-complete") def test_receive_logs_events(client_login): client, _ = client_login sentry_sdk.init( dsn=get_sentry_dsn(client), profiles_sample_rate=1.0, traces_sample_rate=1.0, enable_logs=True, ) sentry_logger.trace('Starting database connection {database}', database="users") sentry_logger.debug('Cache miss for user {user_id}', user_id=123) sentry_logger.info('Updated global cache') sentry_logger.warning('Rate limit reached for endpoint {endpoint}', endpoint='/api/results/') sentry_logger.error('Failed to process payment. Order: {order_id}. Amount: {amount}', order_id="or_2342", amount=99.99) sentry_logger.fatal('Database {database} connection pool exhausted', database="users") sentry_logger.error( 'Payment processing failed', attributes={ 'payment.provider': 'stripe', 'payment.method': 'credit_card', 'payment.currency': 'USD', 'user.subscription_tier': 'premium' } ) poll_for_response( f"{SENTRY_TEST_HOST}/api/0/organizations/sentry/events/?dataset=ourlogs&field=sentry.item_id&field=project.id&field=trace&field=severity_number&field=severity&field=timestamp&field=timestamp_precise&field=observed_timestamp&field=message&project=1&statsPeriod=1h", client, lambda x: len(json.loads(x)["data"]) > 0, ) def test_customizations(): commands = [ [ "docker", "compose", "--ansi", "never", "run", "--no-deps", "web", "bash", "-c", "if [ ! -e /created-by-enhance-image ]; then exit 1; fi", ], [ "docker", "compose", "--ansi", "never", "run", "--no-deps", "--entrypoint=/etc/sentry/entrypoint.sh", "sentry-cleanup", "bash", "-c", "if [ ! -e /created-by-enhance-image ]; then exit 1; fi", ], [ "docker", "compose", "--ansi", "never", "run", "--no-deps", "web", "python", "-c", "import ldap", ], [ "docker", "compose", "--ansi", "never", "run", "--no-deps", "--entrypoint=/etc/sentry/entrypoint.sh", "sentry-cleanup", "python", "-c", "import ldap", ], ] for command in commands: result = subprocess.run(command, check=False) assert result.returncode == 0 ================================================ FILE: _integration-test/test_02_backup.py ================================================ import os from os.path import join import subprocess def test_sentry_admin(setup_backup_restore_env_variables): sentry_admin_sh = os.path.join(os.getcwd(), "sentry-admin.sh") output = subprocess.run( [sentry_admin_sh, "--help"], check=True, capture_output=True, encoding="utf8" ).stdout assert "Usage: ./sentry-admin.sh" in output assert "SENTRY_DOCKER_IO_DIR" in output output = subprocess.run( [sentry_admin_sh, "permissions", "--help"], check=True, capture_output=True, encoding="utf8", ).stdout assert "Usage: ./sentry-admin.sh permissions" in output def test_01_backup(setup_backup_restore_env_variables): # Docker was giving me permission issues when trying to create this file and write to it even after giving read + write access # to group and owner. Instead, try creating the empty file and then give everyone write access to the backup file file_path = os.path.join(os.getcwd(), "sentry", "backup.json") sentry_admin_sh = os.path.join(os.getcwd(), "sentry-admin.sh") open(file_path, "a", encoding="utf8").close() os.chmod(file_path, 0o666) assert os.path.getsize(file_path) == 0 subprocess.run( [ sentry_admin_sh, "export", "global", "/sentry-admin/backup.json", "--no-prompt", ], check=True, ) assert os.path.getsize(file_path) > 0 def test_02_import(setup_backup_restore_env_variables): # Bring postgres down and recreate the docker volume subprocess.run(["docker", "compose", "--ansi", "never", "down"], check=True) # We reset all DB-related volumes here and not just Postgres although the backups # are only for Postgres. The reason is to get a "clean slate" as we need the Kafka # and Clickhouse volumes to be back to their initial state as well (without any events) # We cannot just rm and create them as they still need the migrations. for name in ("postgres", "clickhouse", "kafka"): subprocess.run(["docker", "volume", "rm", f"sentry-{name}"], check=True) subprocess.run(["docker", "volume", "create", f"sentry-{name}"], check=True) subprocess.run( [ "rsync", "-aW", "--super", "--numeric-ids", "--no-compress", "--mkpath", join(os.environ["RUNNER_TEMP"], "volumes", f"sentry-{name}", ""), f"/var/lib/docker/volumes/sentry-{name}/", ], check=True, capture_output=True, ) sentry_admin_sh = os.path.join(os.getcwd(), "sentry-admin.sh") subprocess.run( [ sentry_admin_sh, "import", "global", "/sentry-admin/backup.json", "--no-prompt", ], check=True, ) # TODO: Check something actually restored here like the test user we have from earlier ================================================ FILE: _unit-test/_test_setup.sh ================================================ set -euo pipefail source install/_lib.sh _ORIGIN=$(pwd) rm -rf /tmp/sentry-self-hosted-test-sandbox.* _SANDBOX="$(mktemp -d /tmp/sentry-self-hosted-test-sandbox.XXX)" source install/detect-platform.sh docker build -t sentry-self-hosted-jq-local --platform="$DOCKER_PLATFORM" jq report_success() { echo "$(basename $0) - Success 👍" } teardown() { test "${DEBUG:-}" || rm -rf "$_SANDBOX" cd "$_ORIGIN" } setup() { # Clone the local repo into a temp dir. FWIW `git clone --local` breaks for # me because it depends on hard-linking, which doesn't work across devices, # and I happen to have my workspace and /tmp on separate devices. git -c advice.detachedHead=false clone --depth=1 "file://$_ORIGIN" "$_SANDBOX" # Now propagate any local changes from the working copy to the sandbox. This # provides a pretty nice dev experience: edit the files in the working copy, # then run `DEBUG=1 some-test.sh` to leave the sandbox up for interactive # dev/debugging. git status --porcelain | while read line; do # $line here is something like `M some-script.sh`. local filepath="$(cut -f2 -d' ' <(echo $line))" local filestatus="$(cut -f1 -d' ' <(echo $line))" case $filestatus in D) rm "$_SANDBOX/$filepath" ;; A | M | AM | ??) ln -sf "$(realpath $filepath)" "$_SANDBOX/$filepath" ;; **) echo "Wuh? $line" exit 77 ;; esac done cd "$_SANDBOX" trap teardown EXIT } setup ================================================ FILE: _unit-test/bootstrap-s3-nodestore-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh source install/dc-detect-version.sh source install/create-docker-volumes.sh # Set the flag to apply automatic updates export APPLY_AUTOMATIC_CONFIG_UPDATES=1 # Here we're just gonna test to run it multiple times # Only to make sure it doesn't break for i in $(seq 1 5); do source install/bootstrap-s3-nodestore.sh done report_success ================================================ FILE: _unit-test/bootstrap-s3-profiles-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh source install/dc-detect-version.sh source install/create-docker-volumes.sh source install/ensure-files-from-examples.sh export COMPOSE_PROFILES="feature-complete" $dc pull vroom source install/ensure-correct-permissions-profiles-dir.sh # Generate some random files on `sentry-vroom` volume for testing $dc run --rm --no-deps -v sentry-vroom:/var/vroom/sentry-profiles --entrypoint /bin/bash vroom -c ' for i in $(seq 1 1000); do echo This is test file $i > /var/vroom/sentry-profiles/test_file_$i.txt done ' # Set the flag to apply automatic updates export APPLY_AUTOMATIC_CONFIG_UPDATES=1 # Here we're just gonna test to run it multiple times # Only to make sure it doesn't break for i in $(seq 1 5); do source install/bootstrap-s3-profiles.sh done # Ensure that the files have been migrated to SeaweedFS migrated_files_count=$($dc exec seaweedfs s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=seaweedfs:8333 --host-bucket="seaweedfs:8333/%(bucket)" ls s3://profiles/ | wc -l) if [[ "$migrated_files_count" -ne 1000 ]]; then echo "Error: Expected 1000 migrated files, but found $migrated_files_count" exit 1 fi # Manual cleanup, otherwise `create-docker-volumes.sh` will fail $dc down -v --remove-orphans report_success ================================================ FILE: _unit-test/create-docker-volumes-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh get_volumes() { # If grep returns no strings, we still want to return without error docker volume ls --quiet | { grep '^sentry-.*' || true; } | sort } # Maybe they exist prior, maybe they don't. Script is idempotent. expected_volumes="sentry-clickhouse sentry-data sentry-kafka sentry-postgres sentry-redis sentry-seaweedfs" before=$(get_volumes) test "$before" == "" || test "$before" == "$expected_volumes" source install/create-docker-volumes.sh after=$(get_volumes) test "$after" == "$expected_volumes" report_success ================================================ FILE: _unit-test/ensure-relay-credentials-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh source install/dc-detect-version.sh # using _file format for these variables since there is a creds defined in dc-detect-version.sh cfg_file=relay/config.yml creds_file=relay/credentials.json # Relay files don't exist in a clean clone. test ! -f $cfg_file test ! -f $creds_file # Running the install script adds them. source install/ensure-relay-credentials.sh test -f $cfg_file test -f $creds_file test "$(jq -r 'keys[2]' "$creds_file")" = "secret_key" # If the files exist we don't touch it. echo GARBAGE >$cfg_file echo MOAR GARBAGE >$creds_file source install/ensure-relay-credentials.sh test "$(cat $cfg_file)" = "GARBAGE" test "$(cat $creds_file)" = "MOAR GARBAGE" report_success ================================================ FILE: _unit-test/error-handling-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh export REPORT_SELF_HOSTED_ISSUES=1 # This is set up in dc-detect-version.sh, but for # our purposes we don't care about proxies. dbuild="docker build" source install/error-handling.sh # mock send_envelope send_envelope() { echo "Test Sending $1" } ########################## export -f send_envelope echo "Testing initial send_event" export log_file=test_log.txt echo "Test Logs" >"$log_file" echo "Error Msg" >>"$log_file" breadcrumbs=$(generate_breadcrumb_json | sed '$d' | $jq -s -c) SEND_EVENT_RESPONSE=$( send_event \ "'foo' exited with status 1" \ "Test exited with status 1" \ "Traceback: ignore me" \ "{\"ignore\": \"me\"}" \ "$breadcrumbs" ) rm "$log_file" expected_filename='sentry-envelope-f73e4da437c42a1d28b86a81ebcff35d' test "$SEND_EVENT_RESPONSE" == "Test Sending $expected_filename" ENVELOPE_CONTENTS=$(cat "/tmp/$expected_filename") test "$ENVELOPE_CONTENTS" == "$(cat _unit-test/snapshots/$expected_filename)" echo "Pass." ########################## echo "Testing send_event duplicate" SEND_EVENT_RESPONSE=$( send_event \ "'foo' exited with status 1" \ "Test exited with status 1" \ "Traceback: ignore me" \ "{\"ignore\": \"me\"}" \ "$breadcrumbs" ) test "$SEND_EVENT_RESPONSE" == "Looks like you've already sent this error to us, we're on it :)" echo "Pass." rm "/tmp/$expected_filename" ########################## echo "Testing cleanup without minimizing downtime" export REPORT_SELF_HOSTED_ISSUES=0 export MINIMIZE_DOWNTIME='' export dc=':' echo "Test Logs" >"$log_file" CLEANUP_RESPONSE=$(cleanup ERROR) # the linenumber of this line must match just below rm "$log_file" test "$CLEANUP_RESPONSE" == 'Error in _unit-test/error-handling-test.sh:62. '\''local cmd="${BASH_COMMAND}"'\'' exited with status 0 Cleaning up...' echo "Pass." ########################## echo "Testing cleanup while minimizing downtime" export REPORT_SELF_HOSTED_ISSUES=0 export MINIMIZE_DOWNTIME=1 echo "Test Logs" >"$log_file" CLEANUP_RESPONSE=$(cleanup ERROR) # the linenumber of this line must match just below rm "$log_file" test "$CLEANUP_RESPONSE" == 'Error in _unit-test/error-handling-test.sh:76. '\''local cmd="${BASH_COMMAND}"'\'' exited with status 0 *NOT* cleaning up, to clean your environment run "docker compose stop".' echo "Pass." ########################## report_success ================================================ FILE: _unit-test/geoip-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh mmdb=geoip/GeoLite2-City.mmdb # Starts with no mmdb, ends up with empty. test ! -f $mmdb source install/geoip.sh diff -rub $mmdb $mmdb.empty # Doesn't clobber existing, though. echo GARBAGE >$mmdb source install/geoip.sh test "$(cat $mmdb)" = "GARBAGE" report_success ================================================ FILE: _unit-test/js-sdk-assets-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh source install/dc-detect-version.sh $dcb --force-rm web $dc pull nginx export SETUP_JS_SDK_ASSETS=1 source install/setup-js-sdk-assets.sh sdk_files=$($dcr --no-deps nginx ls -lah /var/www/js-sdk/) sdk_tree=$($dcr --no-deps nginx tree /var/www/js-sdk/ | tail -n 1) non_empty_file_count=$($dcr --no-deps nginx find /var/www/js-sdk/ -type f -size +1k | wc -l) # `sdk_files` should contains 7 lines, '4.*', '5.*', '6.*', `7.*`, `8.*`, `9.*`, and `10.*` echo $sdk_files total_directories=$(echo "$sdk_files" | grep -c '[4-9|10]\.[0-9]*\.[0-9]*$') echo $total_directories test "7" == "$total_directories" echo "Pass" # `sdk_tree` should output "7 directories, 29 files" echo "$sdk_tree" test "7 directories, 29 files" == "$(echo "$sdk_tree")" echo "Pass" # Files should all be >1k (ensure they are not empty) echo "Testing file sizes" test "29" == "$non_empty_file_count" echo "Pass" # Files should be owned by the root user echo "Testing file ownership" directory_owners=$(echo "$sdk_files" | awk '$3=="root" { print $0 }' | wc -l) echo "$directory_owners" test "$directory_owners" == "9" echo "Pass" report_success ================================================ FILE: _unit-test/merge-env-file-test.sh ================================================ #!/usr/bin/env bash # This is a test file for a part of `_lib.sh`, where we read `.env.custom` file if there is one. # We only want to give very minimal value to the `.env.custom` file, and expect that it would # be merged with the original `.env` file, with the `.env.custom` file taking precedence. cat <".env.custom" SENTRY_EVENT_RETENTION_DAYS=10 EOF # The `_test_setup.sh` script sources `install/_lib.sh`, so.. finger crossed this should works. source _unit-test/_test_setup.sh rm -f .env.custom echo "Expecting SENTRY_EVENT_RETENTION_DAYS to be 10, got ${SENTRY_EVENT_RETENTION_DAYS}" test "$SENTRY_EVENT_RETENTION_DAYS" == "10" echo "Pass" echo "Expecting SENTRY_BIND to be 9000, got ${SENTRY_BIND}" test "$SENTRY_BIND" == "9000" echo "Pass" echo "Expecting COMPOSE_PROJECT_NAME to be sentry-self-hosted, got ${COMPOSE_PROJECT_NAME}" test "$COMPOSE_PROJECT_NAME" == "sentry-self-hosted" echo "Pass" report_success ================================================ FILE: _unit-test/migrate-pgbouncer-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh source install/dc-detect-version.sh source install/ensure-files-from-examples.sh cp $SENTRY_CONFIG_PY /tmp/sentry_conf_py # Set the flag to apply automatic updates export APPLY_AUTOMATIC_CONFIG_UPDATES=1 # Declare expected content expected_db_config=$( cat <<'EOF' DATABASES = { "default": { "ENGINE": "sentry.db.postgres", "NAME": "postgres", "USER": "postgres", "PASSWORD": "", "HOST": "pgbouncer", "PORT": "", } } EOF ) echo "Test 1 (pre 25.9.0 release)" # Modify the `DATABASES = {` to the next `}` line, with: # DATABASES = { # "default": { # "ENGINE": "sentry.db.postgres", # "NAME": "postgres", # "USER": "postgres", # "PASSWORD": "", # "HOST": "postgres", # "PORT": "", # } # } # Create the replacement text in a temp file cat >/tmp/sentry_conf_py_db_config <<'EOF' DATABASES = { "default": { "ENGINE": "sentry.db.postgres", "NAME": "postgres", "USER": "postgres", "PASSWORD": "", "HOST": "postgres", "PORT": "", } } EOF # Replace the block sed -i '/^DATABASES = {$/,/^}$/{ /^DATABASES = {$/r /tmp/sentry_conf_py_db_config d }' $SENTRY_CONFIG_PY # Clean up rm /tmp/sentry_conf_py_db_config source install/migrate-pgbouncer.sh # Extract actual content actual_db_config=$(sed -n '/^DATABASES = {$/,/^}$/p' $SENTRY_CONFIG_PY) # Compare if [ "$actual_db_config" = "$expected_db_config" ]; then echo "DATABASES section is correct" else echo "DATABASES section does not match" echo "Expected:" echo "$expected_db_config" echo "Actual:" echo "$actual_db_config" exit 1 fi # Reset the file rm $SENTRY_CONFIG_PY cp /tmp/sentry_conf_py $SENTRY_CONFIG_PY echo "Test 2 (post 25.9.0 release)" # Modify the `DATABASES = {` to the next `}` line, with: # DATABASES = { # "default": { # "ENGINE": "sentry.db.postgres", # "NAME": "postgres", # "USER": "postgres", # "PASSWORD": "", # "HOST": "pgbouncer", # "PORT": "", # } # } # Create the replacement text in a temp file cat >/tmp/sentry_conf_py_db_config <<'EOF' DATABASES = { "default": { "ENGINE": "sentry.db.postgres", "NAME": "postgres", "USER": "postgres", "PASSWORD": "", "HOST": "pgbouncer", "PORT": "", } } EOF # Replace the block sed -i '/^DATABASES = {$/,/^}$/{ /^DATABASES = {$/r /tmp/sentry_conf_py_db_config d }' $SENTRY_CONFIG_PY # Clean up rm /tmp/sentry_conf_py_db_config source install/migrate-pgbouncer.sh # Extract actual content actual_db_config=$(sed -n '/^DATABASES = {$/,/^}$/p' $SENTRY_CONFIG_PY) # Compare if [ "$actual_db_config" = "$expected_db_config" ]; then echo "DATABASES section is correct" else echo "DATABASES section does not match" echo "Expected:" echo "$expected_db_config" echo "Actual:" echo "$actual_db_config" exit 1 fi # Reset the file rm $SENTRY_CONFIG_PY cp /tmp/sentry_conf_py $SENTRY_CONFIG_PY echo "Test 3 (custom postgres config)" # Modify the `DATABASES = {` to the next `}` line, with: # DATABASES = { # "default": { # "ENGINE": "sentry.db.postgres", # "NAME": "postgres", # "USER": "sentry", # "PASSWORD": "sentry", # "HOST": "postgres.internal", # "PORT": "5432", # } # } # Create the replacement text in a temp file cat >/tmp/sentry_conf_py_db_config <<'EOF' DATABASES = { "default": { "ENGINE": "sentry.db.postgres", "NAME": "postgres", "USER": "sentry", "PASSWORD": "sentry", "HOST": "postgres.internal", "PORT": "5432", } } EOF # Replace the block sed -i '/^DATABASES = {$/,/^}$/{ /^DATABASES = {$/r /tmp/sentry_conf_py_db_config d }' $SENTRY_CONFIG_PY # Clean up rm /tmp/sentry_conf_py_db_config source install/migrate-pgbouncer.sh # Extract actual content actual_db_config=$(sed -n '/^DATABASES = {$/,/^}$/p' $SENTRY_CONFIG_PY) # THe file should NOT be modified if [ "$actual_db_config" = "$expected_db_config" ]; then echo "DATABASES section SHOULD NOT be modified" echo "Expected:" echo "$expected_db_config" echo "Actual:" echo "$actual_db_config" exit 1 else echo "DATABASES section is correct" fi # Remove the file rm $SENTRY_CONFIG_PY /tmp/sentry_conf_py report_success ================================================ FILE: _unit-test/multiple-seaweedfs-bucket-test.sh ================================================ #!/usr/bin/env bash source _unit-test/_test_setup.sh source install/dc-detect-version.sh source install/create-docker-volumes.sh start_service_and_wait_ready seaweedfs $dcx seaweedfs apk add --no-cache s3cmd s3cmd="$dc exec seaweedfs s3cmd" # Create multiple buckets for testing buckets=(bucket1 bucket2 bucket3) for bucket in "${buckets[@]}"; do $s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' mb s3://$bucket done # Verify that all buckets were created successfully bucket_list=$($s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' ls) for bucket in "${buckets[@]}"; do if ! echo "$bucket_list" | grep -q "s3://$bucket"; then echo "Error: Bucket s3://$bucket was not created successfully." exit 1 fi done # Can find "bucket2" if ! echo "$bucket_list" | grep -q "s3://bucket2"; then echo "Error: Bucket s3://bucket2 was not found." exit 1 fi # Can't find "bucket5", should not exist if echo "$bucket_list" | grep -q "s3://bucket5"; then echo "Error: Bucket s3://bucket5 should not exist." exit 1 fi report_success ================================================ FILE: _unit-test/snapshots/sentry-envelope-f73e4da437c42a1d28b86a81ebcff35d ================================================ {"event_id":"f73e4da437c42a1d28b86a81ebcff35d","dsn":"https://19555c489ded4769978daae92f2346ca@self-hosted.getsentry.net/3"} {"type":"event"} {"level":"error","exception":{"values":[{"type":"Error","value":"Test exited with status 1","stacktrace":{"frames":[{"ignore":"me"}]}}]},"breadcrumbs":{"values":[{"message":"Test Logs","category":"log","level":"info"}]},"fingerprint":["f73e4da437c42a1d28b86a81ebcff35d"]} {"type":"attachment","length":20,"content_type":"text/plain","filename":"install_log.txt"} Test Logs Error Msg ================================================ FILE: action.yaml ================================================ name: "Sentry self-hosted end-to-end tests" inputs: project_name: required: false description: "e.g. snuba, sentry, relay, self-hosted" image_url: required: false description: "The URL to the built relay, snuba, sentry image to test against." compose_profiles: required: false description: "Docker Compose profile to use. Defaults to feature-complete." CODECOV_TOKEN: required: false description: "The Codecov token to upload coverage." runs: using: "composite" steps: - name: Validate inputs and configure test image shell: bash env: PROJECT_NAME: ${{ inputs.project_name }} IMAGE_URL: ${{ inputs.image_url }} COMPOSE_PROFILES: ${{ inputs.compose_profiles }} run: | if [[ -n "$PROJECT_NAME" && -n "$IMAGE_URL" ]]; then image_var=$(echo "${PROJECT_NAME}_IMAGE" | tr '[:lower:]' '[:upper:]') echo "${image_var}=$IMAGE_URL" >> ${{ github.action_path }}/.env elif [[ -z "$PROJECT_NAME" && -z "$IMAGE_URL" ]]; then echo "No project name and image URL set. Skipping image configuration." else echo "You must set both project_name and image_url or unset both." echo "project_name: $PROJECT_NAME, image_url: $IMAGE_URL" exit 1 fi # `COMPOSE_PROFILES` may only be `feature-complete` or `errors-only` if [[ "$COMPOSE_PROFILES" != "" && "$COMPOSE_PROFILES" != "feature-complete" && "$COMPOSE_PROFILES" != "errors-only" ]]; then echo "COMPOSE_PROFILES must be either unset, or set to either 'feature-complete' or 'errors-only'." exit 1 else echo "COMPOSE_PROFILES=$COMPOSE_PROFILES" >> ${{ github.action_path }}/.env fi - name: Cleanup runner image shell: bash run: | ### Inspired by https://github.com/endersonmenezes/free-disk-space ### sudo rm -rf /usr/local/.ghcup sudo rm -rf /home/runner/Android /usr/local/lib/android /opt/android /usr/local/android-sdk sudo rm -rf /usr/share/dotnet /usr/share/doc/dotnet-* df -h - name: Setup uv uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0 with: working-directory: ${{ github.action_path }} version: "0.9.15" # we just cache the venv-dir directly in action-setup-venv enable-cache: false - uses: getsentry/action-setup-venv@6e8aebf461914919d9de6e3082669d6bfecc400c # v3.1.0 with: python-version: "3.12" cache-dependency-path: uv.lock install-cmd: uv sync --frozen --active working-directory: ${{ github.action_path }} - name: Setup dev environment shell: bash run: | cd ${{ github.action_path }} echo "PY_COLORS=1" >> "$GITHUB_ENV" ### pytest-sentry configuration ### if [ "$GITHUB_REPOSITORY" = "getsentry/self-hosted" ]; then echo "PYTEST_SENTRY_DSN=$SELF_HOSTED_TESTING_DSN" >> $GITHUB_ENV echo "PYTEST_SENTRY_TRACES_SAMPLE_RATE=0" >> $GITHUB_ENV # This records failures on master to sentry in order to detect flakey tests, as it's # expected that people have failing tests on their PRs if [ "$GITHUB_REF" = "refs/heads/master" ]; then echo "PYTEST_SENTRY_ALWAYS_REPORT=1" >> $GITHUB_ENV fi fi - name: Compute Docker Volume Cache Keys id: cache_key shell: bash run: | source ${{ github.action_path }}/.env # See https://explainshell.com/explain?cmd=ls%20-Rv1rpq # for that long `ls` command SENTRY_MIGRATIONS_MD5=$(docker run --rm --entrypoint bash $SENTRY_IMAGE -c '{ cat migrations_lockfile.txt; grep -Poz "(?s)(?<=class Topic\\(Enum\\):\\n).+?(?=\\n\\n\\n)" src/sentry/conf/types/kafka_definition.py; }' | md5sum | cut -d ' ' -f 1) echo "SENTRY_MIGRATIONS_MD5=$SENTRY_MIGRATIONS_MD5" >> $GITHUB_OUTPUT SNUBA_MIGRATIONS_MD5=$(docker run --rm --entrypoint bash $SNUBA_IMAGE -c '{ ls -Rv1rpq snuba/snuba_migrations/**/*.py; grep -Poz "(?s)(?<=class Topic\\(Enum\\):\\n).+?(?=\\n\\n\\n)" snuba/utils/streams/topics.py; }' | md5sum | cut -d ' ' -f 1) echo "SNUBA_MIGRATIONS_MD5=$SNUBA_MIGRATIONS_MD5" >> $GITHUB_OUTPUT echo "ARCH=$(uname -m)" >> $GITHUB_OUTPUT - name: Restore Sentry Volume Cache id: restore_cache_sentry uses: BYK/docker-volume-cache-action/restore@be89365902126f508dcae387a32ec3712df6b1cd with: key: db-volumes-sentry-v3-${{ steps.cache_key.outputs.ARCH }}-${{ inputs.compose_profiles }}-${{ steps.cache_key.outputs.SENTRY_MIGRATIONS_MD5 }} restore-keys: | key: db-volumes-sentry-v3-${{ steps.cache_key.outputs.ARCH }}-${{ inputs.compose_profiles }} volumes: | sentry-postgres - name: Restore Snuba Volume Cache id: restore_cache_snuba uses: BYK/docker-volume-cache-action/restore@be89365902126f508dcae387a32ec3712df6b1cd with: key: db-volumes-snuba-v3-${{ steps.cache_key.outputs.ARCH }}-${{ inputs.compose_profiles }}-${{ steps.cache_key.outputs.SNUBA_MIGRATIONS_MD5 }} restore-keys: | key: db-volumes-snuba-v3-${{ steps.cache_key.outputs.ARCH }}-${{ inputs.compose_profiles }} volumes: | sentry-clickhouse - name: Restore Kafka Volume Cache id: restore_cache_kafka uses: BYK/docker-volume-cache-action/restore@be89365902126f508dcae387a32ec3712df6b1cd with: key: db-volumes-kafka-v2-${{ steps.cache_key.outputs.ARCH }}-${{ inputs.compose_profiles }}-${{ steps.cache_key.outputs.SENTRY_MIGRATIONS_MD5 }}-${{ steps.cache_key.outputs.SNUBA_MIGRATIONS_MD5 }} restore-keys: | db-volumes-kafka-v2-${{ steps.cache_key.outputs.ARCH }}-${{ inputs.compose_profiles }}-${{ steps.cache_key.outputs.SENTRY_MIGRATIONS_MD5 }} db-volumes-kafka-v2-${{ steps.cache_key.outputs.ARCH }}-${{ inputs.compose_profiles }} volumes: | sentry-kafka - name: Install self-hosted env: # Note that cache keys for Sentry and Snuba have their respective Kafka configs built into them # and the Kafka volume cache is comprises both keys. This way we can omit the Kafka cache hit # in here to still avoid running Sentry or Snuba migrations if only one of their Kafka config has # changed. Heats up your head a bit but if you think about it, it makes sense. SKIP_SENTRY_MIGRATIONS: ${{ steps.restore_cache_sentry.outputs.cache-hit == 'true' && '1' || '' }} SKIP_SNUBA_MIGRATIONS: ${{ steps.restore_cache_snuba.outputs.cache-hit == 'true' && '1' || '' }} shell: bash run: | cd ${{ github.action_path }} # Add some customizations to test that path cat <> sentry/enhance-image.sh #!/bin/bash touch /created-by-enhance-image apt-get update apt-get install -y gcc libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev EOT chmod 755 sentry/enhance-image.sh echo "python-ldap" > sentry/requirements.txt ./install.sh --no-report-self-hosted-issues --skip-commit-check - name: Save Sentry Volume Cache if: steps.restore_cache_sentry.outputs.cache-hit != 'true' uses: BYK/docker-volume-cache-action/save@be89365902126f508dcae387a32ec3712df6b1cd with: key: ${{ steps.restore_cache_sentry.outputs.cache-primary-key }} volumes: | sentry-postgres - name: Save Snuba Volume Cache if: steps.restore_cache_snuba.outputs.cache-hit != 'true' uses: BYK/docker-volume-cache-action/save@be89365902126f508dcae387a32ec3712df6b1cd with: key: ${{ steps.restore_cache_snuba.outputs.cache-primary-key }} volumes: | sentry-clickhouse - name: Save Kafka Volume Cache if: steps.restore_cache_kafka.outputs.cache-hit != 'true' uses: BYK/docker-volume-cache-action/save@be89365902126f508dcae387a32ec3712df6b1cd with: key: ${{ steps.restore_cache_kafka.outputs.cache-primary-key }} volumes: | sentry-kafka - name: Setup swapfile shell: bash run: | sudo swapoff -a sudo fallocate -l 16G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile sudo swapon --show free -h - name: Setup Nodejs uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: "22.x" - name: Install Nodejs dependencies shell: bash run: | cd ${{ github.action_path }}/_integration-test/nodejs npm ci - name: Integration Test shell: bash env: COMPOSE_PROFILES: ${{ inputs.compose_profiles }} run: | sudo chown root /usr/bin/rsync && sudo chmod u+s /usr/bin/rsync rsync -aW --super --numeric-ids --no-compress --mkpath \ /var/lib/docker/volumes/sentry-postgres \ /var/lib/docker/volumes/sentry-clickhouse \ /var/lib/docker/volumes/sentry-kafka \ "$RUNNER_TEMP/volumes/" cd ${{ github.action_path }} pytest -x --cov --junitxml=junit.xml _integration-test/ - name: Upload coverage to Codecov if: inputs.CODECOV_TOKEN continue-on-error: true uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: directory: ${{ github.action_path }} token: ${{ inputs.CODECOV_TOKEN }} slug: getsentry/self-hosted - name: Upload test results to Codecov if: inputs.CODECOV_TOKEN && !cancelled() continue-on-error: true uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1 with: directory: ${{ github.action_path }} token: ${{ inputs.CODECOV_TOKEN }} - name: Inspect failure if: failure() shell: bash run: | echo "::group::Inspect failure - docker compose ps" cd ${{ github.action_path }} docker compose ps echo "::endgroup::" echo "::group::Inspect failure - docker compose logs" docker compose logs echo "::endgroup::" ================================================ FILE: certificates/.gitignore ================================================ # Add all custom CAs in this folder * !.gitignore ================================================ FILE: clickhouse/Dockerfile ================================================ ARG BASE_IMAGE FROM ${BASE_IMAGE} ================================================ FILE: clickhouse/config.xml ================================================ warning true 1 0 0 1 100 ================================================ FILE: clickhouse/default-password.xml ================================================ ::/0 ================================================ FILE: codecov.yml ================================================ coverage: status: project: default: only_pulls: true patch: default: only_pulls: true ================================================ FILE: cron/Dockerfile ================================================ ARG BASE_IMAGE FROM ${BASE_IMAGE} USER 0 RUN if [ -n "${HTTP_PROXY}" ]; then echo "Acquire::http::proxy \"${HTTP_PROXY}\";" >> /etc/apt/apt.conf; fi RUN if [ -n "${HTTPS_PROXY}" ]; then echo "Acquire::https::proxy \"${HTTPS_PROXY}\";" >> /etc/apt/apt.conf; fi RUN if [ -n "${http_proxy}" ]; then echo "Acquire::http::proxy \"${http_proxy}\";" >> /etc/apt/apt.conf; fi RUN if [ -n "${https_proxy}" ]; then echo "Acquire::https::proxy \"${https_proxy}\";" >> /etc/apt/apt.conf; fi RUN apt-get update && apt-get install -y --no-install-recommends cron && \ rm -r /var/lib/apt/lists/* COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] ================================================ FILE: cron/entrypoint.sh ================================================ #!/usr/bin/env bash if [ "$(ls -A /usr/local/share/ca-certificates/)" ]; then update-ca-certificates fi # Prior art: # - https://github.com/renskiy/cron-docker-image/blob/5600db37acf841c6d7a8b4f3866741bada5b4622/debian/start-cron#L34-L36 # - https://blog.knoldus.com/running-a-cron-job-in-docker-container/ declare -p | grep -Ev 'BASHOPTS|BASH_VERSINFO|EUID|PPID|SHELLOPTS|UID' >/container.env { for cron_job in "$@"; do echo -e "SHELL=/bin/bash BASH_ENV=/container.env ${cron_job} > /proc/1/fd/1 2>/proc/1/fd/2"; done; } | sed --regexp-extended 's/\\(.)/\1/g' | crontab - crontab -l exec cron -f -l -L 15 ================================================ FILE: docker-compose.yml ================================================ x-restart-policy: &restart_policy restart: unless-stopped x-pull-policy: &pull_policy pull_policy: never x-depends_on-healthy: &depends_on-healthy condition: service_healthy x-depends_on-default: &depends_on-default condition: service_started x-healthcheck-defaults: &healthcheck_defaults # Avoid setting the interval too small, as docker uses much more CPU than one would expect. # Related issues: # https://github.com/moby/moby/issues/39102 # https://github.com/moby/moby/issues/39388 # https://github.com/getsentry/self-hosted/issues/1000 interval: "$HEALTHCHECK_INTERVAL" timeout: "$HEALTHCHECK_TIMEOUT" retries: $HEALTHCHECK_RETRIES start_period: "$HEALTHCHECK_START_PERIOD" x-file-healthcheck: &file_healthcheck_defaults test: ["CMD-SHELL", "rm /tmp/health.txt"] interval: "$HEALTHCHECK_FILE_INTERVAL" timeout: "$HEALTHCHECK_FILE_TIMEOUT" retries: $HEALTHCHECK_FILE_RETRIES start_period: "$HEALTHCHECK_FILE_START_PERIOD" x-sentry-defaults: &sentry_defaults <<: [*restart_policy, *pull_policy] image: sentry-self-hosted-local # Set the platform to build for linux/arm64 when needed on Apple silicon Macs. platform: ${DOCKER_PLATFORM:-} build: context: ./sentry args: - SENTRY_IMAGE depends_on: redis: <<: *depends_on-healthy kafka: <<: *depends_on-healthy pgbouncer: <<: *depends_on-healthy memcached: <<: *depends_on-default smtp: <<: *depends_on-default seaweedfs: <<: *depends_on-default snuba-api: <<: *depends_on-default symbolicator: <<: *depends_on-default entrypoint: "/etc/sentry/entrypoint.sh" command: ["run", "web"] environment: PYTHONUSERBASE: "/data/custom-packages" SENTRY_CONF: "/etc/sentry" SNUBA: "http://snuba-api:1218" VROOM: "http://vroom:8085" # Force everything to use the system CA bundle # This is mostly needed to support installing custom CA certs # This one is used by botocore DEFAULT_CA_BUNDLE: &ca_bundle "/etc/ssl/certs/ca-certificates.crt" # This one is used by requests REQUESTS_CA_BUNDLE: *ca_bundle # This one is used by grpc/google modules GRPC_DEFAULT_SSL_ROOTS_FILE_PATH_ENV_VAR: *ca_bundle # Leaving the value empty to just pass whatever is set # on the host system (or in the .env file) COMPOSE_PROFILES: SENTRY_EVENT_RETENTION_DAYS: SENTRY_MAIL_HOST: SENTRY_MAX_EXTERNAL_SOURCEMAP_SIZE: SENTRY_SYSTEM_SECRET_KEY: SENTRY_STATSD_ADDR: "${STATSD_ADDR:-}" volumes: - "sentry-data:/data" - "./sentry:/etc/sentry" - "./geoip:/geoip:ro" - "./certificates:/usr/local/share/ca-certificates:ro" x-snuba-defaults: &snuba_defaults <<: *restart_policy depends_on: clickhouse: <<: *depends_on-healthy kafka: <<: *depends_on-healthy redis: <<: *depends_on-healthy image: "$SNUBA_IMAGE" environment: SNUBA_SETTINGS: self_hosted CLICKHOUSE_HOST: clickhouse DEFAULT_BROKERS: "kafka:9092" REDIS_HOST: redis UWSGI_MAX_REQUESTS: "10000" UWSGI_DISABLE_LOGGING: "true" # Leaving the value empty to just pass whatever is set # on the host system (or in the .env file) SENTRY_EVENT_RETENTION_DAYS: # If you have statsd server, you can utilize that to monitor self-hosted Snuba containers. # To start, state these environment variables below on your `.env.` file and adjust the options as needed. SNUBA_STATSD_ADDR: "${STATSD_ADDR:-}" services: smtp: <<: *restart_policy image: registry.gitlab.com/egos-tech/smtp volumes: - "sentry-smtp:/var/spool/exim4" - "sentry-smtp-log:/var/log/exim4" environment: MAILNAME: ${SENTRY_MAIL_HOST:-} # Refer to https://develop.sentry.dev/self-hosted/configuration/email/#as-aws-ses-relay SES_USER: SES_PASSWORD: SES_REGION: memcached: <<: *restart_policy image: "memcached:1.6.26-alpine" command: ["-I", "${SENTRY_MAX_EXTERNAL_SOURCEMAP_SIZE:-1M}"] healthcheck: <<: *healthcheck_defaults # From: https://stackoverflow.com/a/31877626/5155484 test: echo stats | nc 127.0.0.1 11211 redis: <<: *restart_policy image: "redis:6.2.20-alpine" healthcheck: <<: *healthcheck_defaults test: redis-cli ping | grep PONG volumes: - "sentry-redis:/data" - type: bind read_only: true source: ./redis.conf target: /usr/local/etc/redis/redis.conf ulimits: nofile: soft: 10032 hard: 10032 command: ["redis-server", "/usr/local/etc/redis/redis.conf"] postgres: <<: *restart_policy # Using the same postgres version as Sentry dev for consistency purposes image: "postgres:14.19-bookworm" healthcheck: <<: *healthcheck_defaults # Using default user "postgres" from sentry/sentry.conf.example.py or value of POSTGRES_USER if provided test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"] command: ["postgres"] environment: POSTGRES_HOST_AUTH_METHOD: "trust" volumes: - "sentry-postgres:/var/lib/postgresql/data" shm_size: 256m pgbouncer: <<: *restart_policy image: "edoburu/pgbouncer:v1.24.1-p1" healthcheck: <<: *healthcheck_defaults # Using default user "postgres" from sentry/sentry.conf.example.py or value of POSTGRES_USER if provided test: ["CMD-SHELL", "psql -U postgres -p 5432 -h 127.0.0.1 -tA -c \"select 1;\" -d postgres >/dev/null"] depends_on: postgres: <<: *depends_on-healthy environment: DB_USER: ${POSTGRES_USER:-postgres} DB_HOST: postgres DB_NAME: postgres AUTH_TYPE: trust POOL_MODE: transaction ADMIN_USERS: postgres,sentry MAX_CLIENT_CONN: 10000 kafka: <<: *restart_policy image: "confluentinc/cp-kafka:7.6.6" environment: # https://docs.confluent.io/platform/current/installation/docker/config-reference.html#cp-kakfa-example KAFKA_PROCESS_ROLES: "broker,controller" KAFKA_CONTROLLER_QUORUM_VOTERS: "1001@127.0.0.1:29093" KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER" KAFKA_NODE_ID: "1001" CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk" KAFKA_LISTENERS: "PLAINTEXT://0.0.0.0:29092,INTERNAL://0.0.0.0:9093,EXTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:29093" KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://127.0.0.1:29092,INTERNAL://kafka:9093,EXTERNAL://kafka:9092" KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "PLAINTEXT:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT" KAFKA_INTER_BROKER_LISTENER_NAME: "PLAINTEXT" KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1" KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS: "1" KAFKA_LOG_RETENTION_HOURS: "24" KAFKA_MESSAGE_MAX_BYTES: "50000000" #50MB or bust KAFKA_MAX_REQUEST_SIZE: "50000000" #50MB on requests apparently too CONFLUENT_SUPPORT_METRICS_ENABLE: "false" KAFKA_LOG4J_LOGGERS: "kafka.cluster=WARN,kafka.controller=WARN,kafka.coordinator=WARN,kafka.log=WARN,kafka.server=WARN,state.change.logger=WARN" KAFKA_LOG4J_ROOT_LOGLEVEL: "WARN" KAFKA_TOOLS_LOG4J_LOGLEVEL: "WARN" ulimits: nofile: soft: 4096 hard: 4096 volumes: - "sentry-kafka:/var/lib/kafka/data" - "sentry-kafka-log:/var/lib/kafka/log" - "sentry-secrets:/etc/kafka/secrets" healthcheck: <<: *healthcheck_defaults test: ["CMD-SHELL", "nc -z localhost 9092"] interval: 10s timeout: 10s retries: 30 clickhouse: <<: [*restart_policy, *pull_policy] image: clickhouse-self-hosted-local build: context: ./clickhouse args: BASE_IMAGE: "altinity/clickhouse-server:25.3.6.10034.altinitystable" ulimits: nofile: soft: 262144 hard: 262144 volumes: - "sentry-clickhouse:/var/lib/clickhouse" - "sentry-clickhouse-log:/var/log/clickhouse-server" - type: "bind" read_only: true source: "./clickhouse/config.xml" target: "/etc/clickhouse-server/config.d/sentry.xml" - type: "bind" read_only: true source: "./clickhouse/default-password.xml" target: "/etc/clickhouse-server/users.d/default-password.xml" environment: # This limits Clickhouse's memory to 30% of the host memory # If you have high volume and your search return incomplete results # You might want to change this to a higher value (and ensure your host has enough memory) MAX_MEMORY_USAGE_RATIO: 0.3 healthcheck: test: [ "CMD-SHELL", # Manually override any http_proxy envvar that might be set, because # this wget does not support no_proxy. See: # https://github.com/getsentry/self-hosted/issues/1537 "HTTP_PROXY='' http_proxy='' wget -nv -t1 --spider 'http://localhost:8123/' || exit 1", ] interval: 10s timeout: 10s retries: 30 seaweedfs: <<: *restart_policy image: "chrislusf/seaweedfs:4.09_large_disk" entrypoint: "weed" command: >- server -dir=/data -filer=true -filer.port=8888 -filer.port.grpc=18888 -filer.defaultReplicaPlacement=000 -master=true -master.port=9333 -master.port.grpc=19333 -metricsPort=9091 -s3=true -s3.port=8333 -s3.port.grpc=18333 -volume=true -volume.dir.idx=/data/idx -volume.index=leveldbLarge -volume.max=0 -volume.preStopSeconds=8 -volume.readMode=redirect -volume.port=8080 -volume.port.grpc=18080 -ip=127.0.0.1 -ip.bind=0.0.0.0 -webdav=false environment: AWS_ACCESS_KEY_ID: sentry AWS_SECRET_ACCESS_KEY: sentry volumes: - "sentry-seaweedfs:/data" healthcheck: test: [ "CMD-SHELL", # Manually override any http_proxy envvar that might be set, because # this wget does not support no_proxy. See: # https://github.com/getsentry/self-hosted/issues/1537 "http_proxy='' wget -q -O- http://seaweedfs:8080/healthz http://seaweedfs:9333/cluster/healthz http://seaweedfs:8333/healthz || exit 1", ] interval: 30s timeout: 20s retries: 5 start_period: 60s snuba-api: <<: *snuba_defaults healthcheck: <<: *healthcheck_defaults test: - "CMD" - "/bin/bash" - "-c" # Courtesy of https://unix.stackexchange.com/a/234089/108960 - 'exec 3<>/dev/tcp/127.0.0.1/1218 && echo -e "GET /health HTTP/1.1\r\nhost: 127.0.0.1\r\n\r\n" >&3 && grep ok -s -m 1 <&3' # Kafka consumer responsible for feeding events into Clickhouse snuba-errors-consumer: <<: *snuba_defaults command: rust-consumer --storage errors --consumer-group snuba-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults # Kafka consumer responsible for feeding outcomes into Clickhouse # Use --auto-offset-reset=earliest to recover up to 7 days of TSDB data # since we did not do a proper migration snuba-outcomes-consumer: <<: *snuba_defaults command: rust-consumer --storage outcomes_raw --consumer-group snuba-consumers --auto-offset-reset=earliest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults snuba-outcomes-billing-consumer: <<: *snuba_defaults command: rust-consumer --storage outcomes_raw --consumer-group snuba-consumers --auto-offset-reset=earliest --max-batch-time-ms 750 --no-strict-offset-reset --raw-events-topic outcomes-billing --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults snuba-group-attributes-consumer: <<: *snuba_defaults command: rust-consumer --storage group_attributes --consumer-group snuba-group-attributes-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults snuba-replacer: <<: *snuba_defaults command: replacer --storage errors --auto-offset-reset=latest --no-strict-offset-reset snuba-subscription-consumer-events: <<: *snuba_defaults command: subscriptions-scheduler-executor --dataset events --entity events --auto-offset-reset=latest --no-strict-offset-reset --consumer-group=snuba-events-subscriptions-consumers --followed-consumer-group=snuba-consumers --schedule-ttl=60 --stale-threshold-seconds=900 --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults ############################################# ## Feature Complete Sentry Snuba Consumers ## ############################################# # Kafka consumer responsible for feeding transactions data into Clickhouse snuba-transactions-consumer: <<: *snuba_defaults command: rust-consumer --storage transactions --consumer-group transactions_group --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-replays-consumer: <<: *snuba_defaults command: rust-consumer --storage replays --consumer-group snuba-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-issue-occurrence-consumer: <<: *snuba_defaults command: rust-consumer --storage search_issues --consumer-group generic_events_group --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-metrics-consumer: <<: *snuba_defaults command: rust-consumer --storage metrics_raw --consumer-group snuba-metrics-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-subscription-consumer-transactions: <<: *snuba_defaults command: subscriptions-scheduler-executor --dataset transactions --entity transactions --auto-offset-reset=latest --no-strict-offset-reset --consumer-group=snuba-transactions-subscriptions-consumers --followed-consumer-group=transactions_group --schedule-ttl=60 --stale-threshold-seconds=900 --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-subscription-consumer-metrics: <<: *snuba_defaults command: subscriptions-scheduler-executor --dataset metrics --entity metrics_sets --entity metrics_counters --auto-offset-reset=latest --no-strict-offset-reset --consumer-group=snuba-metrics-subscriptions-consumers --followed-consumer-group=snuba-metrics-consumers --schedule-ttl=60 --stale-threshold-seconds=900 --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-subscription-consumer-generic-metrics-distributions: <<: *snuba_defaults command: subscriptions-scheduler-executor --dataset generic_metrics --entity=generic_metrics_distributions --auto-offset-reset=latest --no-strict-offset-reset --consumer-group=snuba-generic-metrics-distributions-subscriptions-schedulers --followed-consumer-group=snuba-gen-metrics-distributions-consumers --schedule-ttl=60 --stale-threshold-seconds=900 --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-subscription-consumer-generic-metrics-sets: <<: *snuba_defaults command: subscriptions-scheduler-executor --dataset generic_metrics --entity=generic_metrics_sets --auto-offset-reset=latest --no-strict-offset-reset --consumer-group=snuba-generic-metrics-sets-subscriptions-schedulers --followed-consumer-group=snuba-gen-metrics-sets-consumers --schedule-ttl=60 --stale-threshold-seconds=900 --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-subscription-consumer-generic-metrics-counters: <<: *snuba_defaults command: subscriptions-scheduler-executor --dataset generic_metrics --entity=generic_metrics_counters --auto-offset-reset=latest --no-strict-offset-reset --consumer-group=snuba-generic-metrics-counters-subscriptions-schedulers --followed-consumer-group=snuba-gen-metrics-counters-consumers --schedule-ttl=60 --stale-threshold-seconds=900 --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-subscription-consumer-generic-metrics-gauges: <<: *snuba_defaults command: subscriptions-scheduler-executor --dataset generic_metrics --entity=generic_metrics_gauges --auto-offset-reset=latest --no-strict-offset-reset --consumer-group=snuba-generic-metrics-gauges-subscriptions-schedulers --followed-consumer-group=snuba-gen-metrics-gauges-consumers --schedule-ttl=60 --stale-threshold-seconds=900 --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-generic-metrics-distributions-consumer: <<: *snuba_defaults command: rust-consumer --storage generic_metrics_distributions_raw --consumer-group snuba-gen-metrics-distributions-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-generic-metrics-sets-consumer: <<: *snuba_defaults command: rust-consumer --storage generic_metrics_sets_raw --consumer-group snuba-gen-metrics-sets-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-generic-metrics-counters-consumer: <<: *snuba_defaults command: rust-consumer --storage generic_metrics_counters_raw --consumer-group snuba-gen-metrics-counters-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-generic-metrics-gauges-consumer: <<: *snuba_defaults command: rust-consumer --storage generic_metrics_gauges_raw --consumer-group snuba-gen-metrics-gauges-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-profiling-profiles-consumer: <<: *snuba_defaults command: rust-consumer --storage profiles --consumer-group snuba-consumers --auto-offset-reset=latest --max-batch-time-ms 1000 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-profiling-functions-consumer: <<: *snuba_defaults command: rust-consumer --storage functions_raw --consumer-group snuba-consumers --auto-offset-reset=latest --max-batch-time-ms 1000 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-profiling-profile-chunks-consumer: <<: *snuba_defaults command: rust-consumer --storage profile_chunks --consumer-group snuba-consumers --auto-offset-reset=latest --max-batch-time-ms 1000 --no-strict-offset-reset --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-eap-items-consumer: <<: *snuba_defaults command: rust-consumer --storage eap_items --consumer-group eap_items_group --auto-offset-reset=latest --max-batch-time-ms 1000 --no-strict-offset-reset --use-rust-processor --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete snuba-subscription-consumer-eap-items: <<: *snuba_defaults command: subscriptions-scheduler-executor --dataset events_analytics_platform --entity eap_items --auto-offset-reset=latest --no-strict-offset-reset --consumer-group=snuba-eap-items-subscriptions-consumers --followed-consumer-group=eap_items_group --schedule-ttl=60 --stale-threshold-seconds=900 --health-check-file /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults symbolicator: <<: *restart_policy image: "$SYMBOLICATOR_IMAGE" environment: SYMBOLICATOR_STATSD_ADDR: ${STATSD_ADDR:-127.0.0.1:8125} command: run -c /etc/symbolicator/config.yml volumes: - "sentry-symbolicator:/data" - type: bind read_only: true source: ./symbolicator target: /etc/symbolicator healthcheck: <<: *healthcheck_defaults test: ["CMD", "/bin/symbolicator", "healthcheck", "-c", "/etc/symbolicator/config.yml"] symbolicator-cleanup: <<: *restart_policy image: "$SYMBOLICATOR_IMAGE" environment: SYMBOLICATOR_STATSD_ADDR: ${STATSD_ADDR:-127.0.0.1:8125} command: "cleanup -c /etc/symbolicator/config.yml --repeat 1h" volumes: - "sentry-symbolicator:/data" - type: bind read_only: true source: ./symbolicator target: /etc/symbolicator web: <<: *sentry_defaults ulimits: nofile: soft: 4096 hard: 4096 healthcheck: <<: *healthcheck_defaults test: - "CMD" - "/bin/bash" - "-c" # Courtesy of https://unix.stackexchange.com/a/234089/108960 - 'exec 3<>/dev/tcp/127.0.0.1/9000 && echo -e "GET /_health/ HTTP/1.1\r\nhost: 127.0.0.1\r\n\r\n" >&3 && grep ok -s -m 1 <&3' events-consumer: <<: *sentry_defaults command: run consumer ingest-events --consumer-group ingest-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults attachments-consumer: <<: *sentry_defaults command: run consumer ingest-attachments --consumer-group ingest-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults post-process-forwarder-errors: <<: *sentry_defaults command: run consumer --no-strict-offset-reset post-process-forwarder-errors --consumer-group post-process-forwarder --synchronize-commit-log-topic=snuba-commit-log --synchronize-commit-group=snuba-consumers --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults subscription-consumer-events: <<: *sentry_defaults command: run consumer events-subscription-results --consumer-group query-subscription-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults ############################################## ## Feature Complete Sentry Ingest Consumers ## ############################################## transactions-consumer: <<: *sentry_defaults command: run consumer ingest-transactions --consumer-group ingest-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete metrics-consumer: <<: *sentry_defaults command: run consumer ingest-metrics --consumer-group metrics-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete generic-metrics-consumer: <<: *sentry_defaults command: run consumer ingest-generic-metrics --consumer-group generic-metrics-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete billing-metrics-consumer: <<: *sentry_defaults command: run consumer billing-metrics-consumer --consumer-group billing-metrics-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete ingest-replay-recordings: <<: *sentry_defaults command: run consumer ingest-replay-recordings --consumer-group ingest-replay-recordings --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete ingest-occurrences: <<: *sentry_defaults command: run consumer ingest-occurrences --consumer-group ingest-occurrences --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete ingest-profiles: <<: *sentry_defaults command: run consumer ingest-profiles --consumer-group ingest-profiles --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete ingest-monitors: <<: *sentry_defaults command: run consumer ingest-monitors --consumer-group ingest-monitors --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete ingest-feedback-events: <<: *sentry_defaults command: run consumer ingest-feedback-events --consumer-group ingest-feedback --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete process-spans: <<: *sentry_defaults command: run consumer --no-strict-offset-reset process-spans --consumer-group process-spans --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete process-segments: <<: *sentry_defaults command: run consumer --no-strict-offset-reset process-segments --consumer-group process-segments --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete monitors-clock-tick: <<: *sentry_defaults command: run consumer monitors-clock-tick --consumer-group monitors-clock-tick --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete monitors-clock-tasks: <<: *sentry_defaults command: run consumer monitors-clock-tasks --consumer-group monitors-clock-tasks --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete uptime-results: <<: *sentry_defaults command: run consumer uptime-results --consumer-group uptime-results --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete post-process-forwarder-transactions: <<: *sentry_defaults command: run consumer --no-strict-offset-reset post-process-forwarder-transactions --consumer-group post-process-forwarder --synchronize-commit-log-topic=snuba-transactions-commit-log --synchronize-commit-group transactions_group --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete post-process-forwarder-issue-platform: <<: *sentry_defaults command: run consumer --no-strict-offset-reset post-process-forwarder-issue-platform --consumer-group post-process-forwarder --synchronize-commit-log-topic=snuba-generic-events-commit-log --synchronize-commit-group generic_events_group --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete subscription-consumer-transactions: <<: *sentry_defaults command: run consumer transactions-subscription-results --consumer-group query-subscription-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete subscription-consumer-eap-items: <<: *sentry_defaults command: run consumer subscription-results-eap-items --consumer-group subscription-results-eap-items --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete subscription-consumer-metrics: <<: *sentry_defaults command: run consumer metrics-subscription-results --consumer-group query-subscription-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete subscription-consumer-generic-metrics: <<: *sentry_defaults command: run consumer generic-metrics-subscription-results --consumer-group query-subscription-consumer --healthcheck-file-path /tmp/health.txt healthcheck: <<: *file_healthcheck_defaults profiles: - feature-complete sentry-cleanup: <<: *sentry_defaults image: sentry-cleanup-self-hosted-local build: context: ./cron args: BASE_IMAGE: sentry-self-hosted-local entrypoint: "/entrypoint.sh" command: '"0 0 * * * gosu sentry sentry cleanup --days $SENTRY_EVENT_RETENTION_DAYS"' nginx: <<: *restart_policy ports: - "$SENTRY_BIND:80/tcp" image: "nginx:1.29.5-alpine" volumes: - type: bind read_only: true source: ./nginx.conf target: /etc/nginx/nginx.conf - sentry-nginx-cache:/var/cache/nginx - sentry-nginx-www:/var/www healthcheck: <<: *healthcheck_defaults test: - "CMD" - "/usr/bin/curl" - http://localhost depends_on: web: <<: *depends_on-healthy restart: true relay: <<: *depends_on-healthy restart: true relay: <<: *restart_policy image: "$RELAY_IMAGE" environment: RELAY_STATSD_ADDR: ${STATSD_ADDR:-127.0.0.1:8125} volumes: - type: bind read_only: true source: ./relay target: /work/.relay - type: bind read_only: true source: ./geoip target: /geoip depends_on: kafka: <<: *depends_on-healthy redis: <<: *depends_on-healthy web: <<: *depends_on-healthy healthcheck: <<: *healthcheck_defaults test: ["CMD", "/bin/relay", "healthcheck"] taskbroker: <<: *restart_policy image: "$TASKBROKER_IMAGE" environment: TASKBROKER_KAFKA_CLUSTER: "kafka:9092" TASKBROKER_KAFKA_DEADLETTER_CLUSTER: "kafka:9092" TASKBROKER_DB_PATH: "/opt/sqlite/taskbroker-activations.sqlite" TASKBROKER_STATSD_ADDR: ${STATSD_ADDR:-127.0.0.1:8125} volumes: - sentry-taskbroker:/opt/sqlite depends_on: kafka: <<: *depends_on-healthy taskscheduler: <<: *sentry_defaults command: run taskworker-scheduler taskworker: <<: *sentry_defaults command: run taskworker --concurrency=$SENTRY_TASKWORKER_CONCURRENCY --rpc-host=taskbroker:50051 --health-check-file-path=/tmp/health.txt healthcheck: <<: *file_healthcheck_defaults vroom: <<: *restart_policy image: "$VROOM_IMAGE" environment: SENTRY_KAFKA_BROKERS_PROFILING: "kafka:9092" SENTRY_KAFKA_BROKERS_OCCURRENCES: "kafka:9092" SENTRY_BUCKET_PROFILES: "s3://profiles?region=us-east-1&endpoint=seaweedfs:8333&s3ForcePathStyle=true&disableSSL=true" AWS_ACCESS_KEY: "sentry" AWS_SECRET_KEY: "sentry" SENTRY_SNUBA_HOST: "http://snuba-api:1218" volumes: - sentry-vroom:/var/vroom/sentry-profiles healthcheck: <<: *healthcheck_defaults test: - "CMD" - "/bin/bash" - "-c" # Courtesy of https://unix.stackexchange.com/a/234089/108960 - 'exec 3<>/dev/tcp/127.0.0.1/8085 && echo -e "GET /health HTTP/1.1\r\nhost: 127.0.0.1\r\n\r\n" >&3 && grep OK -s -m 1 <&3' depends_on: kafka: <<: *depends_on-healthy profiles: - feature-complete vroom-cleanup: <<: [*restart_policy, *pull_policy] image: vroom-cleanup-self-hosted-local build: context: ./cron args: BASE_IMAGE: "$VROOM_IMAGE" entrypoint: "/entrypoint.sh" environment: # Leaving the value empty to just pass whatever is set # on the host system (or in the .env file) SENTRY_EVENT_RETENTION_DAYS: command: '"0 0 * * * find /var/vroom/sentry-profiles -type f -mtime +$SENTRY_EVENT_RETENTION_DAYS -delete"' volumes: - sentry-vroom:/var/vroom/sentry-profiles profiles: - feature-complete uptime-checker: <<: *restart_policy image: "$UPTIME_CHECKER_IMAGE" command: run environment: UPTIME_CHECKER_RESULTS_KAFKA_CLUSTER: kafka:9092 UPTIME_CHECKER_REDIS_HOST: redis://redis:6379 # Set to `true` will allow uptime checks against private IP addresses UPTIME_CHECKER_ALLOW_INTERNAL_IPS: "false" # The number of times to retry failed checks before reporting them as failed UPTIME_CHECKER_FAILURE_RETRIES: "1" # DNS name servers to use when making checks in the http checker. # Separated by commas. Leaving this unset will default to the systems dns # resolver. #UPTIME_CHECKER_HTTP_CHECKER_DNS_NAMESERVERS: "8.8.8.8,8.8.4.4" UPTIME_CHECKER_STATSD_ADDR: ${STATSD_ADDR:-127.0.0.1:8125} depends_on: kafka: <<: *depends_on-healthy redis: <<: *depends_on-healthy profiles: - feature-complete volumes: # These store application data that should persist across restarts. sentry-data: external: true sentry-postgres: external: true sentry-redis: external: true sentry-kafka: external: true sentry-clickhouse: external: true sentry-seaweedfs: external: true # The volume stores cached version of debug symbols, source maps etc. Upon # removal symbolicator will re-download them. sentry-symbolicator: # This volume stores JS SDK assets and the data inside this volume should # be cleaned periodically on upgrades. sentry-nginx-www: # This volume stores profiles and should be persisted. # Not being external will still persist data across restarts. # It won't persist if someone does a docker compose down -v. sentry-vroom: # This volume stores task data that is inflight # It should persist across restarts. If this volume is # deleted, up to ~2048 tasks will be lost. sentry-taskbroker: # These store ephemeral data that needn't persist across restarts. # That said, volumes will be persisted across restarts until they are deleted. sentry-secrets: sentry-smtp: sentry-nginx-cache: sentry-kafka-log: sentry-smtp-log: sentry-clickhouse-log: ================================================ FILE: get-compose-action/action.yaml ================================================ name: "Get Docker Compose" inputs: version: required: false default: 2.33.1 description: "Docker Compose version" runs: using: "composite" steps: - name: Get Compose shell: bash env: COMPOSE_VERSION: ${{ inputs.version }} run: | # Docker Compose v1 is installed here, remove it sudo rm -f "/usr/local/bin/docker-compose" sudo rm -f "/usr/local/lib/docker/cli-plugins/docker-compose" sudo mkdir -p "/usr/local/lib/docker/cli-plugins" sudo curl -L https://github.com/docker/compose/releases/download/v"$COMPOSE_VERSION"/docker-compose-`uname -s`-`uname -m` -o "/usr/local/lib/docker/cli-plugins/docker-compose" sudo chmod +x "/usr/local/lib/docker/cli-plugins/docker-compose" ================================================ FILE: install/_detect-container-engine.sh ================================================ echo "${_group}Detecting container engine ..." if [[ "${CONTAINER_ENGINE_PODMAN:-0}" -eq 1 ]] && command -v podman &>/dev/null; then export CONTAINER_ENGINE="podman" elif command -v docker &>/dev/null; then export CONTAINER_ENGINE="docker" else echo "FAIL: Neither podman nor docker is installed on the system." exit 1 fi echo "Detected container engine: $CONTAINER_ENGINE" echo "${_endgroup}" ================================================ FILE: install/_lib.sh ================================================ # Allow `.env` overrides using the `.env.custom` file. # We pass this to docker compose in a couple places. if [[ -f .env.custom ]]; then _ENV=.env.custom else _ENV=.env fi # Reading .env.custom has to come first. The value won't be overriden, instead # it would persist because of `export -p> >"$t"` later, which exports current # environment variables to a temporary file with a `declare -x KEY=value` format. # The new values on `.env` would be set only if they are not already set. if [[ "$_ENV" == ".env.custom" ]]; then q=$(mktemp) && export -p >"$q" && set -a && . ".env.custom" && set +a && . "$q" && rm "$q" && unset q fi # Read .env for default values with a tip o' the hat to https://stackoverflow.com/a/59831605/90297 t=$(mktemp) && export -p >"$t" && set -a && . ".env" && set +a && . "$t" && rm "$t" && unset t if [ "${GITHUB_ACTIONS:-}" = "true" ]; then _group="::group::" _endgroup="::endgroup::" else _group="▶ " _endgroup="" fi # A couple of the config files are referenced from other subscripts, so they # get vars, while multiple subscripts call ensure_file_from_example. function ensure_file_from_example { target="$1" if [[ -f "$target" ]]; then echo "$target already exists, skipped creation." else # sed from https://stackoverflow.com/a/25123013/90297 # shellcheck disable=SC2001 example="$(echo "$target" | sed 's/\.[^.]*$/.example&/')" if [[ ! -f "$example" ]]; then echo "Oops! Where did $example go? 🤨 We need it in order to create $target." exit fi echo "Creating $target ..." cp -n "$example" "$target" fi } # Check the version of $1 is greater than or equal to $2 using sort. Note: versions must be stripped of "v" function vergte() { printf "%s\n%s" "$1" "$2" | sort --version-sort --check=quiet --reverse } export SENTRY_CONFIG_PY=sentry/sentry.conf.py export SENTRY_CONFIG_YML=sentry/config.yml # Increase the default 10 second SIGTERM timeout # to ensure task queues are properly drained # between upgrades as task signatures may change across # versions export STOP_TIMEOUT=60 # seconds ================================================ FILE: install/_logging.sh ================================================ # Thanks to https://unix.stackexchange.com/a/145654/108960 log_file=sentry_install_log-$(date +'%Y-%m-%d_%H-%M-%S').txt exec &> >(tee -a "$log_file") ================================================ FILE: install/_min-requirements.sh ================================================ # Don't forget to update the README and other docs when you change these! MIN_DOCKER_VERSION='19.03.6' MIN_COMPOSE_VERSION='2.32.2' MIN_PODMAN_VERSION='4.9.3' MIN_PODMAN_COMPOSE_VERSION='1.3.0' MIN_BASH_VERSION='4.4.0' # 16 GB minimum host RAM, but there'll be some overhead outside of what # can be allotted to docker if [[ "$COMPOSE_PROFILES" == "errors-only" ]]; then MIN_RAM_HARD=7000 # MB MIN_CPU_HARD=2 else MIN_RAM_HARD=14000 # MB MIN_CPU_HARD=4 fi ================================================ FILE: install/bootstrap-s3-nodestore.sh ================================================ echo "${_group}Bootstrapping seaweedfs (node store)..." start_service_and_wait_ready seaweedfs postgres $dcx seaweedfs apk add --no-cache s3cmd $dc exec seaweedfs mkdir -p /data/idx/ s3cmd="$dc exec seaweedfs s3cmd" bucket_list=$($s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' ls) if ! echo "$bucket_list" | grep -q "s3://nodestore"; then apply_config_changes_nodestore=0 # Only touch if no existing nodestore config is found if ! grep -q "SENTRY_NODESTORE" $SENTRY_CONFIG_PY; then if [[ -z "${APPLY_AUTOMATIC_CONFIG_UPDATES:-}" ]]; then echo echo "We want to migrate Nodestore backend from Postgres to S3 which will" echo "help reducing Postgres storage issues. In order to do that, we need" echo "to modify your sentry.conf.py file contents." echo "Do you want us to do it automatically for you?" echo yn="" until [ ! -z "$yn" ]; do read -p "y or n? " yn case $yn in y | yes | 1) export apply_config_changes_nodestore=1 echo echo -n "Thank you." ;; n | no | 0) export apply_config_changes_nodestore=0 echo echo -n "Alright, you will need to update your sentry.conf.py file manually before running 'docker compose up'." ;; *) yn="" ;; esac done echo echo "To avoid this prompt in the future, use one of these flags:" echo echo " --apply-automatic-config-updates" echo " --no-apply-automatic-config-updates" echo echo "or set the APPLY_AUTOMATIC_CONFIG_UPDATES environment variable:" echo echo " APPLY_AUTOMATIC_CONFIG_UPDATES=1 to apply automatic updates" echo " APPLY_AUTOMATIC_CONFIG_UPDATES=0 to not apply automatic updates" echo sleep 5 fi if [[ "$APPLY_AUTOMATIC_CONFIG_UPDATES" == 1 || "$apply_config_changes_nodestore" == 1 ]]; then nodestore_config=$(sed -n '/SENTRY_NODESTORE/,/[}]/{p}' sentry/sentry.conf.example.py) if [[ $($dc exec postgres psql -qAt -U postgres -c "select exists (select * from nodestore_node limit 1)") = "t" ]]; then nodestore_config=$(echo -e "$nodestore_config" | sed '$s/\}/ "read_through": True,\n "delete_through": True,\n\}/') fi echo "$nodestore_config" >>$SENTRY_CONFIG_PY fi fi $dc exec seaweedfs mkdir -p /data/idx/ $s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' mb s3://nodestore else echo "Node store already exists, skipping creation..." fi if [[ -z "${APPLY_AUTOMATIC_CONFIG_UPDATES:-}" || "$APPLY_AUTOMATIC_CONFIG_UPDATES" == 1 ]]; then # XXX(aldy505): Should we refactor this? lifecycle_policy=$( cat < Sentry-Nodestore-Rule Enabled $SENTRY_EVENT_RETENTION_DAYS EOF ) echo "Making sure the bucket lifecycle policy is all set up correctly..." $dc exec seaweedfs sh -c "printf '%s' '$lifecycle_policy' > /tmp/nodestore-lifecycle-policy.xml" $s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' setlifecycle /tmp/nodestore-lifecycle-policy.xml s3://nodestore $s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' getlifecycle s3://nodestore fi echo "${_endgroup}" ================================================ FILE: install/bootstrap-s3-profiles.sh ================================================ # The purpose of this file is to have both `sentry`-based containers and `vroom` use the same bucket for profiling. # On pre-25.10.0, we have a `sentry-vroom` volume which stores the profiling data however, since this version, # the behavior changed, and `vroomrs` now ingests profiles directly. Both services must share the same bucket, # but at the time of this writing, it's not possible because the `sentry-vroom` volume has ownership set to `vroom:vroom`. # This prevents the `sentry`-based containers from performing read/write operations on that volume. # # Therefore, this script should do the following: # 1. Check if there are any files inside the `sentry-vroom` volume. # 2. If (1) finds files, copy those files into a "profiles" bucket on SeaweedFS. # 3. Point `filestore-profiles` and vroom to the SeaweedFS "profiles" bucket. # Should only run when `$COMPOSE_PROFILES` is set to `feature-complete` if [[ "$COMPOSE_PROFILES" == "feature-complete" ]]; then echo "${_group}Bootstrapping seaweedfs (profiles)..." start_service_and_wait_ready seaweedfs $dcx seaweedfs apk add --no-cache s3cmd s3cmd="$dc exec seaweedfs s3cmd" bucket_list=$($s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' ls) if ! echo "$bucket_list" | grep -q "s3://profiles"; then apply_config_changes_profiles=0 # Only touch if no existing profiles config is found if ! grep -q "filestore.profiles-backend" $SENTRY_CONFIG_YML; then if [[ -z "${APPLY_AUTOMATIC_CONFIG_UPDATES:-}" ]]; then echo echo "We are migrating the Profiles data directory from the 'sentry-vroom' volume to SeaweedFS." echo "This migration will ensure profiles ingestion works correctly with the new 'vroomrs'" echo "and allows both 'sentry' and 'vroom' to transition smoothly." echo "To complete this, your sentry/config.yml file needs to be modified." echo "Would you like us to perform this modification automatically?" echo yn="" until [ ! -z "$yn" ]; do read -p "y or n? " yn case $yn in y | yes | 1) export apply_config_changes_profiles=1 echo echo -n "Thank you." ;; n | no | 0) export apply_config_changes_profiles=0 echo echo -n "Alright, you will need to update your sentry/config.yml file manually before running 'docker compose up'." ;; *) yn="" ;; esac done echo echo "To avoid this prompt in the future, use one of these flags:" echo echo " --apply-automatic-config-updates" echo " --no-apply-automatic-config-updates" echo echo "or set the APPLY_AUTOMATIC_CONFIG_UPDATES environment variable:" echo echo " APPLY_AUTOMATIC_CONFIG_UPDATES=1 to apply automatic updates" echo " APPLY_AUTOMATIC_CONFIG_UPDATES=0 to not apply automatic updates" echo sleep 5 fi if [[ "$APPLY_AUTOMATIC_CONFIG_UPDATES" == 1 || "$apply_config_changes_profiles" == 1 ]]; then profiles_config=$(sed -n '/filestore.profiles-backend/,/s3v4"/{p}' sentry/config.example.yml) echo "$profiles_config" >>$SENTRY_CONFIG_YML fi fi $s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' mb s3://profiles # Check if there are files in the sentry-vroom volume start_service_and_wait_ready vroom vroom_files_count=$($dc exec vroom sh -c "find /var/vroom/sentry-profiles -type f | wc -l") if [[ "$vroom_files_count" -gt 0 ]]; then echo "Migrating $vroom_files_count files from 'sentry-vroom' volume to 'profiles' bucket on SeaweedFS..." # Use a temporary container to copy files from the volume to SeaweedFS $dcx -u root vroom sh -c 'mkdir -p /var/lib/apt/lists/partial && apt-get update && apt-get install -y --no-install-recommends s3cmd' $dc exec vroom sh -c 's3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=seaweedfs:8333 --host-bucket="seaweedfs:8333/%(bucket)" sync /var/vroom/sentry-profiles/ s3://profiles/' echo "Migration completed." else echo "No files found in 'sentry-vroom' volume. Skipping files migration." fi else echo "'profiles' bucket already exists on SeaweedFS. Skipping creation." fi if [[ -z "${APPLY_AUTOMATIC_CONFIG_UPDATES:-}" || "$APPLY_AUTOMATIC_CONFIG_UPDATES" == 1 ]]; then lifecycle_policy=$( cat < Sentry-Profiles-Rule Enabled $SENTRY_EVENT_RETENTION_DAYS EOF ) $dc exec seaweedfs sh -c "printf '%s' '$lifecycle_policy' > /tmp/profiles-lifecycle-policy.xml" $s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' setlifecycle /tmp/profiles-lifecycle-policy.xml s3://profiles echo "Making sure the bucket lifecycle policy is all set up correctly..." $s3cmd --access_key=sentry --secret_key=sentry --no-ssl --region=us-east-1 --host=localhost:8333 --host-bucket='localhost:8333/%(bucket)' getlifecycle s3://profiles fi echo "${_endgroup}" fi ================================================ FILE: install/bootstrap-snuba.sh ================================================ echo "${_group}Bootstrapping and migrating Snuba ..." if [[ -z "${SKIP_SNUBA_MIGRATIONS:-}" ]]; then $dcr snuba-api bootstrap --force else echo "Skipped DB migrations due to SKIP_SNUBA_MIGRATIONS=$SKIP_SNUBA_MIGRATIONS" fi echo "${_endgroup}" ================================================ FILE: install/build-docker-images.sh ================================================ echo "${_group}Building and tagging Docker images ..." echo "" # Build any service that provides the image sentry-self-hosted-local first, # as it is used as the base image for sentry-cleanup-self-hosted-local. $dcb web # Build each other service individually to localize potential failures better. for service in $($dc config --services); do $dcb "$service" done echo "" echo "Docker images built." echo "${_endgroup}" ================================================ FILE: install/check-latest-commit.sh ================================================ echo "${_group}Checking for latest commit ... " # Checks if we are on latest commit from github if it is running from master branch if [[ -d "../.git" && "${SKIP_COMMIT_CHECK:-0}" != 1 ]]; then if [[ $(git branch --show-current) == "master" ]]; then if [[ $(git rev-parse HEAD) != $(git ls-remote $(git rev-parse --abbrev-ref @{u} | sed 's/\// /g') | cut -f1) ]]; then echo "Seems like you are not using the latest commit from the self-hosted repository. Please pull the latest changes and try again, or suppress this check with --skip-commit-check." exit 1 fi fi else echo "skipped" fi echo "${_endgroup}" ================================================ FILE: install/check-memcached-backend.sh ================================================ echo "${_group}Checking memcached backend ..." if grep -q "\.PyMemcacheCache" "$SENTRY_CONFIG_PY"; then echo "PyMemcacheCache found in $SENTRY_CONFIG_PY, gonna assume you're good." else if grep -q "\.MemcachedCache" "$SENTRY_CONFIG_PY"; then echo "MemcachedCache found in $SENTRY_CONFIG_PY, you should switch to PyMemcacheCache." echo "See:" echo " https://develop.sentry.dev/self-hosted/releases/#breaking-changes" exit 1 else echo 'Your setup looks weird. Good luck.' fi fi echo "${_endgroup}" ================================================ FILE: install/check-minimum-requirements.sh ================================================ echo "${_group}Checking minimum requirements ..." source install/_min-requirements.sh DOCKER_VERSION=$($CONTAINER_ENGINE version --format '{{.Server.Version}}' || echo '') if [[ -z "$DOCKER_VERSION" ]]; then echo "FAIL: Unable to get $CONTAINER_ENGINE version, is the $CONTAINER_ENGINE daemon running?" exit 1 fi if [[ "$CONTAINER_ENGINE" == "docker" ]]; then if ! vergte ${DOCKER_VERSION//v/} $MIN_DOCKER_VERSION; then echo "FAIL: Expected minimum docker version to be $MIN_DOCKER_VERSION but found $DOCKER_VERSION" exit 1 fi if ! vergte ${COMPOSE_VERSION//v/} $MIN_COMPOSE_VERSION; then echo "FAIL: Expected minimum $dc_base version to be $MIN_COMPOSE_VERSION but found $COMPOSE_VERSION" exit 1 fi elif [[ "$CONTAINER_ENGINE" == "podman" ]]; then if ! vergte ${DOCKER_VERSION//v/} $MIN_PODMAN_VERSION; then echo "FAIL: Expected minimum podman version to be $MIN_PODMAN_VERSION but found $DOCKER_VERSION" exit 1 fi if ! vergte ${COMPOSE_VERSION//v/} $MIN_PODMAN_COMPOSE_VERSION; then echo "FAIL: Expected minimum $dc_base version to be $MIN_PODMAN_COMPOSE_VERSION but found $COMPOSE_VERSION" exit 1 fi fi echo "Found $CONTAINER_ENGINE version $DOCKER_VERSION" echo "Found $CONTAINER_ENGINE Compose version $COMPOSE_VERSION" CPU_AVAILABLE_IN_DOCKER=$($CONTAINER_ENGINE run --rm busybox nproc --all) if [[ "$CPU_AVAILABLE_IN_DOCKER" -lt "$MIN_CPU_HARD" ]]; then echo "FAIL: Required minimum CPU cores available to Docker is $MIN_CPU_HARD, found $CPU_AVAILABLE_IN_DOCKER" exit 1 fi RAM_AVAILABLE_IN_DOCKER=$($CONTAINER_ENGINE run --rm busybox free -m 2>/dev/null | awk '/Mem/ {print $2}') if [[ "$RAM_AVAILABLE_IN_DOCKER" -lt "$MIN_RAM_HARD" ]]; then echo "FAIL: Required minimum RAM available to Docker is $MIN_RAM_HARD MB, found $RAM_AVAILABLE_IN_DOCKER MB" exit 1 fi #SSE4.2 required by Clickhouse (https://clickhouse.yandex/docs/en/operations/requirements/) # On KVM, cpuinfo could falsely not report SSE 4.2 support, so skip the check. https://github.com/ClickHouse/ClickHouse/issues/20#issuecomment-226849297 # This may also happen on other virtualization software such as on VMWare ESXi hosts. IS_KVM=$($CONTAINER_ENGINE run --rm busybox grep -c 'Common KVM processor' /proc/cpuinfo || :) if [[ ! "$SKIP_SSE42_REQUIREMENTS" -eq 1 && "$IS_KVM" -eq 0 && "$DOCKER_ARCH" = "x86_64" ]]; then SUPPORTS_SSE42=$($CONTAINER_ENGINE run --rm busybox grep -c sse4_2 /proc/cpuinfo || :) if [[ "$SUPPORTS_SSE42" -eq 0 ]]; then echo "FAIL: The CPU your machine is running on does not support the SSE 4.2 instruction set, which is required for one of the services Sentry uses (Clickhouse). See https://github.com/getsentry/self-hosted/issues/340 for more info." exit 1 fi fi if ! vergte "${BASH_VERSION}" "${MIN_BASH_VERSION}"; then echo "FAIL: Expected minimum bash version to be ${MIN_BASH_VERSION} but found ${BASH_VERSION}" exit 1 fi echo "${_endgroup}" ================================================ FILE: install/create-docker-volumes.sh ================================================ echo "${_group}Creating volumes for persistent storage ..." create_volume() { create_command="$CONTAINER_ENGINE volume create" if [ "$CONTAINER_ENGINE" = "podman" ]; then create_command="$create_command --ignore $1" else create_command="$create_command --name=$1" fi $create_command } echo "Created $(create_volume sentry-clickhouse)." echo "Created $(create_volume sentry-data)." echo "Created $(create_volume sentry-kafka)." echo "Created $(create_volume sentry-postgres)." echo "Created $(create_volume sentry-redis)." echo "Created $(create_volume sentry-seaweedfs)." echo "${_endgroup}" ================================================ FILE: install/dc-detect-version.sh ================================================ if [ "${GITHUB_ACTIONS:-}" = "true" ]; then _group="::group::" _endgroup="::endgroup::" else _group="▶ " _endgroup="" fi echo "${_group}Initializing Docker|Podman Compose ..." export CONTAINER_ENGINE="docker" if [[ "${CONTAINER_ENGINE_PODMAN:-0}" -eq 1 ]]; then if command -v podman &>/dev/null; then export CONTAINER_ENGINE="podman" else echo "FAIL: Podman is not installed on the system." exit 1 fi fi # To support users that are symlinking to docker-compose dc_base="$(${CONTAINER_ENGINE} compose version --short &>/dev/null && echo "$CONTAINER_ENGINE compose" || echo '')" dc_base_standalone="$(${CONTAINER_ENGINE}-compose version --short &>/dev/null && echo "$CONTAINER_ENGINE-compose" || echo '')" COMPOSE_VERSION=$([ -n "$dc_base" ] && $dc_base version --short || echo '') STANDALONE_COMPOSE_VERSION=$([ -n "$dc_base_standalone" ] && $dc_base_standalone version --short || echo '') if [[ -z "$COMPOSE_VERSION" && -z "$STANDALONE_COMPOSE_VERSION" ]]; then echo "FAIL: Docker|Podman Compose is required to run self-hosted" exit 1 fi if [[ -z "$COMPOSE_VERSION" ]] || [[ -n "$STANDALONE_COMPOSE_VERSION" ]] && ! vergte ${COMPOSE_VERSION//v/} ${STANDALONE_COMPOSE_VERSION//v/}; then COMPOSE_VERSION="${STANDALONE_COMPOSE_VERSION}" dc_base="$dc_base_standalone" fi if [[ "$CONTAINER_ENGINE" == "podman" ]]; then NO_ANSI="--no-ansi" else NO_ANSI="--ansi never" fi if [[ "$(basename $0)" = "install.sh" ]]; then dc="$dc_base $NO_ANSI --env-file ${_ENV}" else dc="$dc_base $NO_ANSI" fi proxy_args="--build-arg HTTP_PROXY=${HTTP_PROXY:-} --build-arg HTTPS_PROXY=${HTTPS_PROXY:-} --build-arg NO_PROXY=${NO_PROXY:-} --build-arg http_proxy=${http_proxy:-} --build-arg https_proxy=${https_proxy:-} --build-arg no_proxy=${no_proxy:-}" exec_proxy_args="-e HTTP_PROXY=${HTTP_PROXY:-} -e HTTPS_PROXY=${HTTPS_PROXY:-} -e NO_PROXY=${NO_PROXY:-} -e http_proxy=${http_proxy:-} -e https_proxy=${https_proxy:-} -e no_proxy=${no_proxy:-}" if [[ "$CONTAINER_ENGINE" == "podman" ]]; then proxy_args_dc="--podman-build-args HTTP_PROXY=${HTTP_PROXY:-},HTTPS_PROXY=${HTTPS_PROXY:-},NO_PROXY=${NO_PROXY:-},http_proxy=${http_proxy:-},https_proxy=${https_proxy:-},no_proxy=${no_proxy:-}" # Disable pod creation as these are one-off commands and creating a pod # prints its pod id to stdout which is messing with the output that we # rely on various places such as configuration generation dcr="$dc --profile=feature-complete --in-pod=false run --rm" else proxy_args_dc=$proxy_args dcr="$dc run --pull=never --rm" fi dcb="$dc build $proxy_args" dbuild="$CONTAINER_ENGINE build $proxy_args" dcx="$dc exec $exec_proxy_args" echo "$dcr" # Utility function to handle --wait with docker and podman function start_service_and_wait_ready() { local options=() local services=() local found_service=0 for arg in "$@"; do if [[ $found_service -eq 0 && "$arg" == -* ]]; then options+=("$arg") else found_service=1 services+=("$arg") fi done if [ "$CONTAINER_ENGINE" = "podman" ]; then $dc up --force-recreate -d "${options[@]}" "${services[@]}" for service in "${services[@]}"; do while ! $CONTAINER_ENGINE ps --filter "health=healthy" | grep "$service"; do sleep 2 done done else $dc up --wait "${options[@]}" "${services[@]}" fi } echo "${_endgroup}" ================================================ FILE: install/detect-platform.sh ================================================ source install/_detect-container-engine.sh echo "${_group}Detecting Docker platform" # Sentry SaaS uses stock Yandex ClickHouse, but they don't provide images that # support ARM, which is relevant especially for Apple M1 laptops, Sentry's # standard developer environment. As a workaround, we use an altinity image # targeting ARM. # # See https://github.com/getsentry/self-hosted/issues/1385#issuecomment-1101824274 # # Images built on ARM also need to be tagged to use linux/arm64 on Apple # silicon Macs to work around an issue where they are built for # linux/amd64 by default due to virtualization. # See https://github.com/docker/cli/issues/3286 for the Docker bug. FORMAT="{{.Architecture}}" if [[ $CONTAINER_ENGINE == "podman" ]]; then FORMAT="{{.Host.Arch}}" fi DOCKER_ARCH_OUTPUT=$($CONTAINER_ENGINE info --format "$FORMAT" 2>&1) DOCKER_INFO_EXIT_CODE=$? if [[ $DOCKER_INFO_EXIT_CODE -ne 0 ]]; then echo "FAIL: Unable to get $CONTAINER_ENGINE architecture information." echo "$DOCKER_ARCH_OUTPUT" if [[ "$DOCKER_ARCH_OUTPUT" == *"permission denied"* ]]; then echo "" echo "You may need to add your user to the docker group:" echo " sudo usermod -aG docker \$USER" echo "Then log out and log back in, or run: newgrp docker" fi exit 1 fi export DOCKER_ARCH="$DOCKER_ARCH_OUTPUT" if [[ "$DOCKER_ARCH" = "x86_64" || "$DOCKER_ARCH" = "amd64" ]]; then export DOCKER_PLATFORM="linux/amd64" elif [[ "$DOCKER_ARCH" = "aarch64" ]]; then export DOCKER_PLATFORM="linux/arm64" else echo "FAIL: Unsupported docker architecture $DOCKER_ARCH." exit 1 fi echo "Detected Docker platform is $DOCKER_PLATFORM" echo "${_endgroup}" ================================================ FILE: install/ensure-correct-permissions-profiles-dir.sh ================================================ #!/usr/bin/env bash # TODO: Remove this after the next hard-stop # Should only run when `$COMPOSE_PROFILES` is set to `feature-complete` if [[ "$COMPOSE_PROFILES" == "feature-complete" ]]; then echo "${_group}Ensuring correct permissions on profiles directory ..." # Check if the parent directory of /var/vroom/sentry-profiles is already owned by vroom:vroom if [ "$($dcr --no-deps --entrypoint /bin/bash --user root vroom -c "stat -c '%U:%G' /var/vroom/sentry-profiles" 2>/dev/null)" = "vroom:vroom" ]; then echo "Ownership of /var/vroom/sentry-profiles is already set to vroom:vroom. Skipping chown." else $dcr --no-deps --entrypoint /bin/bash --user root vroom -c 'chown -R vroom:vroom /var/vroom/sentry-profiles && chmod -R o+rwx /var/vroom/sentry-profiles' fi echo "${_endgroup}" fi ================================================ FILE: install/ensure-files-from-examples.sh ================================================ echo "${_group}Ensuring files from examples ..." ensure_file_from_example "$SENTRY_CONFIG_PY" ensure_file_from_example "$SENTRY_CONFIG_YML" ensure_file_from_example symbolicator/config.yml echo "${_endgroup}" ================================================ FILE: install/ensure-relay-credentials.sh ================================================ echo "${_group}Ensuring Relay credentials ..." RELAY_CONFIG_YML=relay/config.yml RELAY_CREDENTIALS_JSON=relay/credentials.json ensure_file_from_example $RELAY_CONFIG_YML if [[ -f "$RELAY_CREDENTIALS_JSON" ]]; then echo "$RELAY_CREDENTIALS_JSON already exists, skipped creation." else # There are a couple gotchas here: # # 1. We need to use a tmp file because if we redirect output directly to # credentials.json, then the shell will create an empty file that relay # will then try to read from (regardless of options such as --stdout or # --overwrite) and fail because it is empty. # # 2. We pull relay:nightly before invoking `run relay credentials generate` # because an implicit pull under the run causes extra stdout that results # in a garbage credentials.json. # # 3. We need to use -T to ensure that we receive output on Docker Compose # 1.x and 2.2.3+ (funny story about that ... ;). Note that the long opt # --no-tty doesn't exist in Docker Compose 1. $dc pull relay creds="$dcr --no-deps -T relay credentials" $creds generate --stdout >"$RELAY_CREDENTIALS_JSON".tmp mv "$RELAY_CREDENTIALS_JSON".tmp "$RELAY_CREDENTIALS_JSON" if ! grep -q Credentials <($creds show); then # Let's fail early if creds failed, to make debugging easier. echo "Failed to create relay credentials in $RELAY_CREDENTIALS_JSON." echo "--- credentials.json v ---------------------------------------" cat -v "$RELAY_CREDENTIALS_JSON" || true echo "--- credentials.json ^ ---------------------------------------" exit 1 fi echo "Relay credentials written to $RELAY_CREDENTIALS_JSON." fi echo "${_endgroup}" ================================================ FILE: install/error-handling.sh ================================================ echo "${_group}Setting up error handling ..." if [ -z "${SENTRY_DSN:-}" ]; then export SENTRY_DSN='https://19555c489ded4769978daae92f2346ca@self-hosted.getsentry.net/3' fi $dbuild -t sentry-self-hosted-jq-local --platform="$DOCKER_PLATFORM" jq jq="$CONTAINER_ENGINE run --rm -i sentry-self-hosted-jq-local" sentry_cli="$CONTAINER_ENGINE run --rm -v /tmp:/work -e SENTRY_DSN=$SENTRY_DSN getsentry/sentry-cli" send_envelope() { # Send envelope $sentry_cli send-envelope "$envelope_file" } generate_breadcrumb_json() { cat $log_file | $jq -R -c 'split("\n") | {"message": (.[0]//""), "category": "log", "level": "info"}' } send_event() { # Use traceback hash as the UUID since it is 32 characters long local cmd_exit=$1 local error_msg=$2 local traceback=$3 local traceback_json=$4 local breadcrumbs=$5 local fingerprint_value=$( echo -n "$cmd_exit $error_msg $traceback" | $CONTAINER_ENGINE run -i --rm busybox md5sum | cut -d' ' -f1 ) local envelope_file="sentry-envelope-${fingerprint_value}" local envelope_file_path="/tmp/$envelope_file" # If the envelope file exists, we've already sent it if [[ -f $envelope_file_path ]]; then echo "Looks like you've already sent this error to us, we're on it :)" return fi # If we haven't sent the envelope file, make it and send to Sentry # The format is documented at https://develop.sentry.dev/sdk/envelopes/ # Grab length of log file, needed for the envelope header to send an attachment local file_length=$(wc -c <$log_file | awk '{print $1}') # Add header for initial envelope information $jq -n -c --arg event_id "$fingerprint_value" \ --arg dsn "$SENTRY_DSN" \ '$ARGS.named' >"$envelope_file_path" # Add header to specify the event type of envelope to be sent echo '{"type":"event"}' >>"$envelope_file_path" # Next we construct the meat of the event payload, which we build up # inside out using jq # See https://develop.sentry.dev/sdk/event-payloads/ # for details about the event payload # Then we need the exception payload # https://develop.sentry.dev/sdk/event-payloads/exception/ # but first we need to make the stacktrace which goes in the exception payload frames=$(echo "$traceback_json" | $jq -s -c) stacktrace=$($jq -n -c --argjson frames "$frames" '$ARGS.named') exception=$( $jq -n -c --arg "type" Error \ --arg value "$error_msg" \ --argjson stacktrace "$stacktrace" \ '$ARGS.named' ) # It'd be a bit cleaner in the Sentry UI if we passed the inputs to # fingerprint_value hash rather than the hash itself (I believe the ultimate # hash ends up simply being a hash of our hash), but we want the hash locally # so that we can avoid resending the same event (design decision to avoid # spam in the system). It was also futzy to figure out how to get the # traceback in there properly. Meh. event_body=$( $jq -n -c --arg level error \ --argjson exception "{\"values\":[$exception]}" \ --argjson breadcrumbs "{\"values\": $breadcrumbs}" \ --argjson fingerprint "[\"$fingerprint_value\"]" \ '$ARGS.named' ) echo "$event_body" >>$envelope_file_path # Add attachment to the event attachment=$( $jq -n -c --arg "type" attachment \ --arg length "$file_length" \ --arg content_type "text/plain" \ --arg filename install_log.txt \ '{"type": $type,"length": $length|tonumber,"content_type": $content_type,"filename": $filename}' ) echo "$attachment" >>$envelope_file_path cat $log_file >>$envelope_file_path # Send envelope send_envelope $envelope_file } if [[ -z "${REPORT_SELF_HOSTED_ISSUES:-}" ]]; then echo echo "Hey, so ... we would love to automatically find out about issues with your" echo "Sentry instance so that we can improve the product. Turns out there is an app" echo "for that, called Sentry. Would you be willing to let us automatically send data" echo "about your instance upstream to Sentry for development and debugging purposes?" echo echo " y / yes / 1" echo " n / no / 0" echo echo "(Btw, we send this to our own self-hosted Sentry instance, not to Sentry SaaS," echo "so that we can be in this together.)" echo echo "Here's the info we may collect:" echo echo " - OS username" echo " - IP address" echo " - install log" echo " - runtime errors" echo " - performance data" echo echo "Thirty (30) day retention. No marketing. Privacy policy at sentry.io/privacy." echo yn="" until [ ! -z "$yn" ]; do read -p "y or n? " yn case $yn in y | yes | 1) export REPORT_SELF_HOSTED_ISSUES=1 echo echo -n "Thank you." ;; n | no | 0) export REPORT_SELF_HOSTED_ISSUES=0 echo echo -n "Understood." ;; *) yn="" ;; esac done echo " To avoid this prompt in the future, use one of these flags:" echo echo " --report-self-hosted-issues" echo " --no-report-self-hosted-issues" echo echo "or set the REPORT_SELF_HOSTED_ISSUES environment variable:" echo echo " REPORT_SELF_HOSTED_ISSUES=1 to send data" echo " REPORT_SELF_HOSTED_ISSUES=0 to not send data" echo sleep 5 fi # Make sure we can use sentry-cli if we need it. if [ "$REPORT_SELF_HOSTED_ISSUES" == 1 ]; then if ! $CONTAINER_ENGINE pull getsentry/sentry-cli:latest; then echo "Failed to pull sentry-cli, won't report to Sentry after all." export REPORT_SELF_HOSTED_ISSUES=0 fi fi # Courtesy of https://stackoverflow.com/a/2183063/90297 trap_with_arg() { func="$1" shift for sig; do trap "$func $sig" "$sig" done } DID_CLEAN_UP=0 # the cleanup function will be the exit point cleanup() { local retcode=$? local cmd="${BASH_COMMAND}" if [[ "$DID_CLEAN_UP" -eq 1 ]]; then return 0 fi DID_CLEAN_UP=1 if [[ "$1" != "EXIT" ]]; then set +o xtrace # Save the error message that comes from the last line of the log file error_msg=$(tail -n 1 "$log_file") # Create the breadcrumb payload now before stacktrace is printed # https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ # Use sed to remove the last line, that is reported through the error message breadcrumbs=$(generate_breadcrumb_json | sed '$d' | $jq -s -c) printf -v err '%s' "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}." printf -v cmd_exit '%s' "'$cmd' exited with status $retcode" printf '%s\n%s\n' "$err" "$cmd_exit" local stack_depth=${#FUNCNAME[@]} local traceback="" local traceback_json="" if [ $stack_depth -gt 2 ]; then for ((i = $(($stack_depth - 1)), j = 1; i > 0; i--, j++)); do local indent="$(yes a | head -$j | tr -d '\n')" local src=${BASH_SOURCE[$i]} local lineno=${BASH_LINENO[$i - 1]} local funcname=${FUNCNAME[$i]} JSON=$( $jq -n -c --arg filename "$src" \ --arg "function" "$funcname" \ --arg lineno "$lineno" \ '{"filename": $filename, "function": $function, "lineno": $lineno|tonumber}' ) # If we're in the stacktrace of the file we failed on, we can add a context line with the command run that failed if [[ $i -eq 1 ]]; then JSON=$( $jq -n -c --arg cmd "$cmd" \ --argjson json "$JSON" \ '$json + {"context_line": $cmd}' ) fi printf -v traceback_json '%s\n' "$traceback_json$JSON" printf -v traceback '%s\n' "$traceback${indent//a/-}> $src:$funcname:$lineno" done fi echo "$traceback" # Only send event when report issues flag is set and if trap signal is not INT (ctrl+c) if [[ "$REPORT_SELF_HOSTED_ISSUES" == 1 && "$1" != "INT" ]]; then send_event "$cmd_exit" "$error_msg" "$traceback" "$traceback_json" "$breadcrumbs" fi if [[ -n "$MINIMIZE_DOWNTIME" ]]; then echo "*NOT* cleaning up, to clean your environment run \"docker compose stop\"." else echo "Cleaning up..." fi fi if [[ -z "$MINIMIZE_DOWNTIME" ]]; then $dc stop -t $STOP_TIMEOUT &>/dev/null fi } echo "${_endgroup}" ================================================ FILE: install/generate-secret-key.sh ================================================ echo "${_group}Generating secret key ..." if grep -xq "system.secret-key: '!!changeme!!'" $SENTRY_CONFIG_YML; then # This is to escape the secret key to be used in sed below # Note the need to set LC_ALL=C due to BSD tr and sed always trying to decode # whatever is passed to them. Kudos to https://stackoverflow.com/a/23584470/90297 SECRET_KEY=$( export LC_ALL=C head /dev/urandom | tr -dc "a-z0-9@#%^&*(-_=+)" | head -c 50 | sed -e 's/[\/&]/\\&/g' ) sed -i -e 's/^system.secret-key:.*$/system.secret-key: '"'$SECRET_KEY'"'/' $SENTRY_CONFIG_YML echo "Secret key written to $SENTRY_CONFIG_YML" fi echo "${_endgroup}" ================================================ FILE: install/geoip.sh ================================================ echo "${_group}Setting up GeoIP integration ..." # If `$CONTAINER_ENGINE` is not set, we assume that we are running this script independently # to update the geoip database as written on the documentation. # Therefore we need to `source _detect-container-engine.sh` to detect the container engine. script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) if [[ -z "$CONTAINER_ENGINE" ]]; then if [[ -f "$script_dir/_detect-container-engine.sh" ]]; then source $script_dir/_detect-container-engine.sh else echo "Error: Cannot find _detect-container-engine.sh. Defaulting to docker." export CONTAINER_ENGINE="docker" fi fi install_geoip() { local mmdb=geoip/GeoLite2-City.mmdb local conf=geoip/GeoIP.conf local result='Done' echo "Setting up IP address geolocation ..." if [[ ! -f "$mmdb" ]]; then echo -n "Installing (empty) IP address geolocation database ... " cp "$mmdb.empty" "$mmdb" echo "done." else echo "IP address geolocation database already exists." fi if [[ ! -f "$conf" ]]; then echo "IP address geolocation is not configured for updates." echo "See https://develop.sentry.dev/self-hosted/geolocation/ for instructions." result='Error' else echo "IP address geolocation is configured for updates." echo "Updating IP address geolocation database ... " if ! $CONTAINER_ENGINE run --rm -v "./geoip:/sentry" --entrypoint '/usr/bin/geoipupdate' "ghcr.io/maxmind/geoipupdate:v6.1.0" "-d" "/sentry" "-f" "/sentry/GeoIP.conf"; then result='Error' fi echo "$result updating IP address geolocation database." fi echo "$result setting up IP address geolocation." } install_geoip echo "${_endgroup}" ================================================ FILE: install/migrate-pgbouncer.sh ================================================ echo "${_group}Migrating Postgres config to PGBouncer..." # If users has this EXACT configuration on their `sentry.conf.py` file: # ```python # DATABASES = { # "default": { # "ENGINE": "sentry.db.postgres", # "NAME": "postgres", # "USER": "postgres", # "PASSWORD": "", # "HOST": "postgres", # "PORT": "", # } # } # ``` # We need to migrate it to this configuration: # ```python # DATABASES = { # "default": { # "ENGINE": "sentry.db.postgres", # "NAME": "postgres", # "USER": "postgres", # "PASSWORD": "", # "HOST": "pgbouncer", # "PORT": "", # } # } # ``` if sed -n '/^DATABASES = {$/,/^}$/p' "$SENTRY_CONFIG_PY" | grep -q '"HOST": "postgres"'; then apply_config_changes_pgbouncer=0 if [[ -z "${APPLY_AUTOMATIC_CONFIG_UPDATES:-}" ]]; then echo echo "We added PGBouncer to the default Compose stack, and to use that" echo "you will need to modify your sentry.conf.py file contents." echo "Do you want us to make this change automatically for you?" echo yn="" until [ ! -z "$yn" ]; do read -p "y or n? " yn case $yn in y | yes | 1) export apply_config_changes_pgbouncer=1 echo echo -n "Thank you." ;; n | no | 0) export apply_config_changes_pgbouncer=0 echo echo -n "Alright, you will need to update your sentry.conf.py file manually before running 'docker compose up' or remove the $(pgbouncer) service if you don't want to use that." ;; *) yn="" ;; esac done echo echo "To avoid this prompt in the future, use one of these flags:" echo echo " --apply-automatic-config-updates" echo " --no-apply-automatic-config-updates" echo echo "or set the APPLY_AUTOMATIC_CONFIG_UPDATES environment variable:" echo echo " APPLY_AUTOMATIC_CONFIG_UPDATES=1 to apply automatic updates" echo " APPLY_AUTOMATIC_CONFIG_UPDATES=0 to not apply automatic updates" echo sleep 5 fi if [[ "$APPLY_AUTOMATIC_CONFIG_UPDATES" == 1 || "$apply_config_changes_pgbouncer" == 1 ]]; then echo "Migrating $SENTRY_CONFIG_PY to use PGBouncer" sed -i 's/"HOST": "postgres"/"HOST": "pgbouncer"/' "$SENTRY_CONFIG_PY" echo "Migrated $SENTRY_CONFIG_PY to use PGBouncer" fi elif sed -n '/^DATABASES = {$/,/^}$/p' "$SENTRY_CONFIG_PY" | grep -q '"HOST": "pgbouncer"'; then echo "Found pgbouncer in $SENTRY_CONFIG_PY, I'm assuming you're good! :)" else echo "⚠️ You don't have standard configuration for Postgres in $SENTRY_CONFIG_PY, skipping pgbouncer migration. I'm assuming you know what you're doing." echo " For more information about PGBouncer, refer to https://github.com/getsentry/self-hosted/pull/3884" fi echo "${_endgroup}" ================================================ FILE: install/parse-cli.sh ================================================ echo "${_group}Parsing command line ..." show_help() { cat <&/dev/null } remove_volume() { remove_command="$CONTAINER_ENGINE volume remove" $remove_command $1 } if exists_volume sentry-symbolicator; then echo "Removed $(remove_volume sentry-symbolicator)." fi echo "${_endgroup}" ================================================ FILE: install/update-docker-images.sh ================================================ echo "${_group}Fetching and updating $CONTAINER_ENGINE images ..." if [ "$CONTAINER_ENGINE" = "podman" ]; then # podman compose doesn't have the --ignore-pull-failures option, so can just # run the command normally $dc --profile feature-complete pull || true else # We tag locally built images with a '-self-hosted-local' suffix. `docker # compose pull` tries to pull these too and shows a 404 error on the console # which is confusing and unnecessary. To overcome this, we add the # stderr>stdout redirection below and pass it through grep, ignoring all lines # having this '-onpremise-local' suffix. $dc pull --ignore-pull-failures 2>&1 | grep -v -- -self-hosted-local || true fi # We may not have the set image on the repo (local images) so allow fails $CONTAINER_ENGINE pull ${SENTRY_IMAGE} || true echo "${_endgroup}" ================================================ FILE: install/upgrade-clickhouse.sh ================================================ echo "${_group}Upgrading Clickhouse ..." # First check to see if user is upgrading by checking for existing clickhouse volume if [ "$CONTAINER_ENGINE" = "podman" ]; then ps_command="$dc ps" build_arg="--podman-build-args" else # docker compose needs to be run with the -a flag to show all containers ps_command="$dc ps -a" build_arg="--build-arg" fi if $ps_command | grep -q clickhouse; then # Start clickhouse if it is not already running start_service_and_wait_ready clickhouse # In order to get to 25.3, we need to first upgrade go from 21.8 -> 22.8 -> 23.3 -> 23.8 -> 24.8 -> 25.3 version=$($dc exec clickhouse clickhouse-client -q 'SELECT version()') if [[ "$version" == "21.8.13.1.altinitystable" || "$version" == "21.8.12.29.altinitydev.arm" ]]; then echo "Detected clickhouse version $version" $dc down clickhouse echo "Upgrading clickhouse to 22.8" $dcb $build_arg BASE_IMAGE=altinity/clickhouse-server:22.8.15.25.altinitystable clickhouse start_service_and_wait_ready clickhouse $dc down clickhouse echo "Upgrading clickhouse to 23.3" $dcb $build_arg BASE_IMAGE=altinity/clickhouse-server:23.3.19.33.altinitystable clickhouse start_service_and_wait_ready clickhouse $dc down clickhouse echo "Upgrading clickhouse to 23.8" $dcb $build_arg BASE_IMAGE=altinity/clickhouse-server:23.8.11.29.altinitystable clickhouse start_service_and_wait_ready clickhouse $dc down clickhouse echo "Upgrading clickhouse to 24.8" $dcb $build_arg BASE_IMAGE=altinity/clickhouse-server:24.8.14.10459.altinitystable clickhouse start_service_and_wait_ready clickhouse $dc down clickhouse echo "Upgrading clickhouse to 25.3" $dcb $build_arg BASE_IMAGE=altinity/clickhouse-server:25.3.6.10034.altinitystable clickhouse start_service_and_wait_ready clickhouse elif [[ "$version" == "23.8.11.29.altinitystable" ]]; then echo "Detected clickhouse version $version" $dc down clickhouse echo "Upgrading clickhouse to 24.8" $dcb $build_arg BASE_IMAGE=altinity/clickhouse-server:24.8.14.10459.altinitystable clickhouse start_service_and_wait_ready clickhouse $dc down clickhouse echo "Upgrading clickhouse to 25.3" $dcb $build_arg BASE_IMAGE=altinity/clickhouse-server:25.3.6.10034.altinitystable clickhouse start_service_and_wait_ready clickhouse else echo "Detected clickhouse version $version. Skipping upgrades!" fi fi echo "${_endgroup}" ================================================ FILE: install/upgrade-postgres.sh ================================================ echo "${_group}Ensuring proper PostgreSQL version ..." if [[ -n "$($CONTAINER_ENGINE volume ls -q --filter name=sentry-postgres)" && "$($CONTAINER_ENGINE run --rm -v sentry-postgres:/db busybox cat /db/PG_VERSION 2>/dev/null)" == "9.6" ]]; then $CONTAINER_ENGINE volume rm sentry-postgres-new || true # If this is Postgres 9.6 data, start upgrading it to 14.0 in a new volume $CONTAINER_ENGINE run --rm \ -v sentry-postgres:/var/lib/postgresql/9.6/data \ -v sentry-postgres-new:/var/lib/postgresql/14/data \ tianon/postgres-upgrade:9.6-to-14 # Get rid of the old volume as we'll rename the new one to that $CONTAINER_ENGINE volume rm sentry-postgres $CONTAINER_ENGINE volume create --name sentry-postgres # There's no rename volume in Docker so copy the contents from old to new name # Also append the `host all all all trust` line as `tianon/postgres-upgrade:9.6-to-14` # doesn't do that automatically. $CONTAINER_ENGINE run --rm -v sentry-postgres-new:/from -v sentry-postgres:/to alpine ash -c \ "cd /from ; cp -av . /to ; echo 'host all all all trust' >> /to/pg_hba.conf" # Finally, remove the new old volume as we are all in sentry-postgres now. $CONTAINER_ENGINE volume rm sentry-postgres-new echo "Re-indexing due to glibc change, this may take a while..." echo "Starting up new PostgreSQL version" start_service_and_wait_ready postgres # Wait for postgres RETRIES=5 until $dc exec postgres psql -U postgres -c "select 1" >/dev/null 2>&1 || [ $RETRIES -eq 0 ]; do echo "Waiting for postgres server, $((RETRIES--)) remaining attempts..." sleep 1 done # VOLUME_NAME is the same as container name # Reindex all databases and their system catalogs which are not templates DBS=$($dc exec postgres psql -qAt -U postgres -c "SELECT datname FROM pg_database WHERE datistemplate = false;") for db in ${DBS}; do echo "Re-indexing database: ${db}" $dc exec postgres psql -qAt -U postgres -d ${db} -c "reindex system ${db}" $dc exec postgres psql -qAt -U postgres -d ${db} -c "reindex database ${db};" done $dc stop postgres fi echo "${_endgroup}" ================================================ FILE: install/wrap-up.sh ================================================ if [[ "$MINIMIZE_DOWNTIME" ]]; then echo "${_group}Waiting for Sentry to start ..." # Start the whole setup, except nginx and relay. start_service_and_wait_ready --remove-orphans $($dc config --services | grep -v -E '^(nginx|relay)$') $dc restart relay $dc exec -T nginx nginx -s reload $CONTAINER_ENGINE run --rm --network="${COMPOSE_PROJECT_NAME}_default" alpine ash \ -c 'while [[ "$(wget -T 1 -q -O- http://web:9000/_health/)" != "ok" ]]; do sleep 0.5; done' # Make sure everything is up. This should only touch relay and nginx start_service_and_wait_ready $($dc config --services) echo "${_endgroup}" else echo "" echo "-----------------------------------------------------------------" echo "" echo "You're all done! Run the following command to get Sentry running:" echo "" if [[ "${_ENV}" =~ ".env.custom" ]]; then echo " $dc_base --env-file .env --env-file ${_ENV} up --wait" else if [[ "$CONTAINER_ENGINE" == "podman" ]]; then if [[ "$COMPOSE_PROFILES" == "feature-complete" ]]; then echo " $dc_base --profile=feature-complete up --force-recreate -d" else echo " $dc_base up --force-recreate -d" fi else echo " $dc_base up --wait" fi fi echo "" echo "-----------------------------------------------------------------" echo "" fi ================================================ FILE: install.sh ================================================ #!/usr/bin/env bash set -eEuo pipefail test "${DEBUG:-}" && set -x # Override any user-supplied umask that could cause problems, see #1222 umask 002 # Pre-pre-flight? 🤷 if [[ -n "${MSYSTEM:-}" ]]; then echo "Seems like you are using an MSYS2-based system (such as Git Bash) which is not supported. Please use WSL instead." exit 1 fi source install/_logging.sh source install/_lib.sh # Pre-flight. No impact yet. source install/parse-cli.sh source install/detect-platform.sh source install/dc-detect-version.sh source install/error-handling.sh # We set the trap at the top level so that we get better tracebacks. trap_with_arg cleanup ERR INT TERM EXIT source install/check-latest-commit.sh source install/check-minimum-requirements.sh # Let's go! Start impacting things. # Upgrading clickhouse needs to come first before turning things off, since we need the old clickhouse image # in order to determine whether or not the clickhouse version needs to be upgraded. source install/upgrade-clickhouse.sh source install/update-docker-images.sh source install/turn-things-off.sh source install/create-docker-volumes.sh source install/ensure-files-from-examples.sh source install/check-memcached-backend.sh source install/ensure-relay-credentials.sh source install/generate-secret-key.sh source install/build-docker-images.sh source install/bootstrap-s3-nodestore.sh source install/bootstrap-snuba.sh source install/upgrade-postgres.sh source install/ensure-correct-permissions-profiles-dir.sh source install/bootstrap-s3-profiles.sh source install/set-up-and-migrate-database.sh source install/migrate-pgbouncer.sh source install/geoip.sh source install/setup-js-sdk-assets.sh source install/wrap-up.sh ================================================ FILE: jq/Dockerfile ================================================ FROM debian:bookworm-slim LABEL MAINTAINER="oss@sentry.io" RUN set -x \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends jq \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* ENTRYPOINT ["jq"] ================================================ FILE: nginx.conf ================================================ user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; reset_timedout_connection on; keepalive_timeout 75s; gzip off; server_tokens off; server_names_hash_bucket_size 64; types_hash_max_size 2048; types_hash_bucket_size 64; client_body_buffer_size 64k; client_max_body_size 100m; proxy_http_version 1.1; proxy_redirect off; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; # Docker default address pools # https://github.com/moby/libnetwork/blob/3797618f9a38372e8107d8c06f6ae199e1133ae8/ipamutils/utils.go#L10-L22 set_real_ip_from 172.17.0.0/16; set_real_ip_from 172.18.0.0/16; set_real_ip_from 172.19.0.0/16; set_real_ip_from 172.20.0.0/14; set_real_ip_from 172.24.0.0/14; set_real_ip_from 172.28.0.0/14; set_real_ip_from 192.168.0.0/16; set_real_ip_from 10.0.0.0/8; real_ip_header X-Forwarded-For; real_ip_recursive on; # Remove the Connection header if the client sends it, # it could be "close" to close a keepalive connection proxy_set_header Connection ''; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Request-Id $request_id; proxy_read_timeout 30s; proxy_send_timeout 5s; upstream relay { server relay:3000; keepalive 2; } upstream sentry { server web:9000; keepalive 2; } server { listen 80; location /api/store/ { proxy_pass http://relay; } location ~ ^/api/[1-9]\d*/ { proxy_pass http://relay; } location ^~ /api/0/relays/ { proxy_pass http://relay; } location ^~ /js-sdk/ { root /var/www/; # This value is set to mimic the behavior of the upstream Sentry CDN. For security reasons, # it is recommended to change this to your Sentry URL (in most cases same as system.url-prefix). add_header Access-Control-Allow-Origin *; } location / { proxy_pass http://sentry; } location /_assets/ { proxy_pass http://sentry/_static/dist/sentry/; proxy_hide_header Content-Disposition; } location /_static/ { proxy_pass http://sentry; proxy_hide_header Content-Disposition; } } } ================================================ FILE: optional-modifications/README.md ================================================ # Optional Modifications While the default self-hosted Sentry installation is often sufficient, there are instances where leveraging existing infrastructure becomes a practical necessity, particularly for users with limited resources. This is where **patches**, or what can be understood as a **plugin system**, come into play. A patch system comprises a collection of patch files (refer to man patch(1) for detailed information) designed to modify an existing Sentry configuration. This allows for targeted adjustments to achieve specific operational goals, optimizing Sentry's functionality within your current environment. This approach provides a flexible alternative to a full, customized re-installation, enabling users to adapt Sentry to their specific needs with greater efficiency. We also actively encourage the community to contribute! If you've developed a patch that enhances your self-hosted Sentry experience, consider submitting a pull request. Your contributions can be invaluable to other users facing similar challenges, fostering a collaborative environment where shared solutions benefit everyone. > [!WARNING] > Beware that this is very experimental and might not work as expected. > > **Use it at your own risk!** ## How to use patches The patches are designed mostly to help modify the existing configuration files. You will need to run the `install.sh` script afterwards. They should be run from the root directory. For example, the `external-kafka` patches should be run as: ```bash patch -p0 < optional-modifications/patches/external-kafka/.env.patch patch -p0 < optional-modifications/patches/external-kafka/config.example.yml.patch patch -p0 < optional-modifications/patches/external-kafka/sentry.conf.example.py.patch patch -p0 < optional-modifications/patches/external-kafka/docker-compose.yml.patch ``` The `-p0` flag is important to ensure the patch applies to the correct absolute file path. Some patches might require additional steps to be taken, like providing credentials or additional TLS certificates. Make sure to see your changed files before running the `install.sh` script. ## How to create patches 1. Copy the original file to a temporary file name. For example, if you want to create a `clustered-redis` patch, you might want to copy `docker-compose.yml` to `docker-compose.clustered-redis.yml`. 2. Make your changes on the `docker-compose.clustered-redis.yml` file. 3. Run the following command to create the patch: ```bash diff -Naru docker-compose.yml docker-compose.clustered-redis.yml > docker-compose.yml.patch ``` Or the template command: ```bash diff -Naru [original file] [patched file] > [destination file].patch ``` 4. Create a new directory in the `optional-modifications/patches` folder with the name of the patch. For example, `optional-modifications/patches/clustered-redis`. 5. Move the patched files (like `docker-compose.yml.patch` earlier) into the new directory. ## Official support While Sentry employees aren't able to offer dedicated support for these patches, they can provide valuable information to help move things forward. Ultimately, we really encourage the community to take the wheel, maintaining and fostering these patches themselves. If you have questions, Sentry employees will be there to help guide you. See the [support policy for self-hosted Sentry](https://develop.sentry.dev/self-hosted/support/) for more information. ================================================ FILE: optional-modifications/patches/external-kafka/config.example.yml.patch ================================================ --- relay/config.example.yml 2025-05-15 08:27:40.426876887 +0700 +++ relay/config.example.external-kafka.yml 2025-05-15 08:34:21.113311217 +0700 @@ -7,8 +7,15 @@ processing: enabled: true kafka_config: - - {name: "bootstrap.servers", value: "kafka:9092"} + - {name: "bootstrap.servers", value: "kafka-node1:9092,kafka-node2:9092,kafka-node3:9092"} - {name: "message.max.bytes", value: 50000000} # 50MB + - {name: "security.protocol", value: "PLAINTEXT"} + - {name: "sasl.mechanism", value: "PLAIN"} # Remove or comment this line if SASL is not used. + - {name: "sasl.username", value: "username"} # Remove or comment this line if SASL is not used. + - {name: "sasl.password", value: "password"} # Remove or comment this line if SASL is not used. + - {name: "ssl.ca.location", value: "/kafka-certificates/ca.pem"} # Remove or comment this line if SSL is not used. + - {name: "ssl.certificate.location", value: "/kafka-certificates/client.pem"} # Remove or comment this line if SSL is not used. + - {name: "ssl.key.location", value: "/kafka-certificates/client.key"} # Remove or comment this line if SSL is not used. redis: redis://redis:6379 geoip_path: "/geoip/GeoLite2-City.mmdb" ================================================ FILE: optional-modifications/patches/external-kafka/docker-compose.yml.patch ================================================ --- docker-compose.yml 2025-07-08 10:22:36.600616503 +0700 +++ docker-compose.external-kafka.yml 2025-07-08 10:36:44.069900011 +0700 @@ -26,8 +26,6 @@ depends_on: redis: <<: *depends_on-healthy - kafka: - <<: *depends_on-healthy postgres: <<: *depends_on-healthy memcached: @@ -59,6 +57,14 @@ SENTRY_EVENT_RETENTION_DAYS: SENTRY_MAIL_HOST: SENTRY_MAX_EXTERNAL_SOURCEMAP_SIZE: + KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} + KAFKA_SECURITY_PROTOCOL: ${KAFKA_SECURITY_PROTOCOL:-PLAINTEXT} + KAFKA_SSL_CA_LOCATION: ${KAFKA_SSL_CA_LOCATION:-} + KAFKA_SSL_CERTIFICATE_LOCATION: ${KAFKA_SSL_CERTIFICATE_LOCATION:-} + KAFKA_SSL_KEY_LOCATION: ${KAFKA_SSL_KEY_LOCATION:-} + KAFKA_SASL_MECHANISM: ${KAFKA_SASL_MECHANISM:-} + KAFKA_SASL_USERNAME: ${KAFKA_SASL_USERNAME:-} + KAFKA_SASL_PASSWORD: ${KAFKA_SASL_PASSWORD:-} volumes: - "sentry-data:/data" - "./sentry:/etc/sentry" @@ -69,15 +75,20 @@ depends_on: clickhouse: <<: *depends_on-healthy - kafka: - <<: *depends_on-healthy redis: <<: *depends_on-healthy image: "$SNUBA_IMAGE" environment: SNUBA_SETTINGS: self_hosted CLICKHOUSE_HOST: clickhouse - DEFAULT_BROKERS: "kafka:9092" + DEFAULT_BROKERS: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} + KAFKA_SECURITY_PROTOCOL: ${KAFKA_SECURITY_PROTOCOL:-PLAINTEXT} + KAFKA_SSL_CA_PATH: ${KAFKA_SSL_CA_LOCATION:-} + KAFKA_SSL_CERT_PATH: ${KAFKA_SSL_CERTIFICATE_LOCATION:-} + KAFKA_SSL_KEY_PATH: ${KAFKA_SSL_KEY_LOCATION:-} + KAFKA_SASL_MECHANISM: ${KAFKA_SASL_MECHANISM:-} + KAFKA_SASL_USERNAME: ${KAFKA_SASL_USERNAME:-} + KAFKA_SASL_PASSWORD: ${KAFKA_SASL_PASSWORD:-} REDIS_HOST: redis UWSGI_MAX_REQUESTS: "10000" UWSGI_DISABLE_LOGGING: "true" @@ -136,43 +147,7 @@ POSTGRES_HOST_AUTH_METHOD: "trust" volumes: - "sentry-postgres:/var/lib/postgresql/data" - kafka: - <<: *restart_policy - image: "confluentinc/cp-kafka:7.6.1" - environment: - # https://docs.confluent.io/platform/current/installation/docker/config-reference.html#cp-kakfa-example - KAFKA_PROCESS_ROLES: "broker,controller" - KAFKA_CONTROLLER_QUORUM_VOTERS: "1001@127.0.0.1:29093" - KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER" - KAFKA_NODE_ID: "1001" - CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk" - KAFKA_LISTENERS: "PLAINTEXT://0.0.0.0:29092,INTERNAL://0.0.0.0:9093,EXTERNAL://0.0.0.0:9092,CONTROLLER://0.0.0.0:29093" - KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://127.0.0.1:29092,INTERNAL://kafka:9093,EXTERNAL://kafka:9092" - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "PLAINTEXT:PLAINTEXT,INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT" - KAFKA_INTER_BROKER_LISTENER_NAME: "PLAINTEXT" - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1" - KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS: "1" - KAFKA_LOG_RETENTION_HOURS: "24" - KAFKA_MESSAGE_MAX_BYTES: "50000000" #50MB or bust - KAFKA_MAX_REQUEST_SIZE: "50000000" #50MB on requests apparently too - CONFLUENT_SUPPORT_METRICS_ENABLE: "false" - KAFKA_LOG4J_LOGGERS: "kafka.cluster=WARN,kafka.controller=WARN,kafka.coordinator=WARN,kafka.log=WARN,kafka.server=WARN,state.change.logger=WARN" - KAFKA_LOG4J_ROOT_LOGLEVEL: "WARN" - KAFKA_TOOLS_LOG4J_LOGLEVEL: "WARN" - ulimits: - nofile: - soft: 4096 - hard: 4096 - volumes: - - "sentry-kafka:/var/lib/kafka/data" - - "sentry-kafka-log:/var/lib/kafka/log" - - "sentry-secrets:/etc/kafka/secrets" - healthcheck: - <<: *healthcheck_defaults - test: ["CMD-SHELL", "nc -z localhost 9092"] - interval: 10s - timeout: 10s - retries: 30 + kafka: !reset null clickhouse: <<: *restart_policy image: clickhouse-self-hosted-local @@ -509,9 +484,8 @@ read_only: true source: ./geoip target: /geoip + - ./certificates/kafka:/kafka-certificates:ro depends_on: - kafka: - <<: *depends_on-healthy redis: <<: *depends_on-healthy web: @@ -520,8 +494,22 @@ <<: *restart_policy image: "$TASKBROKER_IMAGE" environment: - TASKBROKER_KAFKA_CLUSTER: "kafka:9092" - TASKBROKER_KAFKA_DEADLETTER_CLUSTER: "kafka:9092" + TASKBROKER_KAFKA_CLUSTER: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} + TASKBROKER_KAFKA_SECURITY_PROTOCOL: ${KAFKA_SECURITY_PROTOCOL:-PLAINTEXT} + TASKBROKER_KAFKA_SSL_CA_LOCATION: ${KAFKA_SSL_CA_LOCATION:-} + TASKBROKER_KAFKA_SSL_CERTIFICATE_LOCATION: ${KAFKA_SSL_CERTIFICATE_LOCATION:-} + TASKBROKER_KAFKA_SSL_KEY_LOCATION: ${KAFKA_SSL_KEY_LOCATION:-} + TASKBROKER_KAFKA_SASL_MECHANISM: ${KAFKA_SASL_MECHANISM:-} + TASKBROKER_KAFKA_SASL_USERNAME: ${KAFKA_SASL_USERNAME:-} + TASKBROKER_KAFKA_SASL_PASSWORD: ${KAFKA_SASL_PASSWORD:-} + TASKBROKER_KAFKA_DEADLETTER_CLUSTER: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} + TASKBROKER_KAFKA_DEADLETTER_SECURITY_PROTOCOL: ${KAFKA_SECURITY_PROTOCOL:-PLAINTEXT} + TASKBROKER_KAFKA_DEADLETTER_SSL_CA_LOCATION: ${KAFKA_SSL_CA_LOCATION:-} + TASKBROKER_KAFKA_DEADLETTER_SSL_CERTIFICATE_LOCATION: ${KAFKA_SSL_CERTIFICATE_LOCATION:-} + TASKBROKER_KAFKA_DEADLETTER_SSL_KEY_LOCATION: ${KAFKA_SSL_KEY_LOCATION:-} + TASKBROKER_KAFKA_DEADLETTER_SASL_MECHANISM: ${KAFKA_SASL_MECHANISM:-} + TASKBROKER_KAFKA_DEADLETTER_SASL_USERNAME: ${KAFKA_SASL_USERNAME:-} + TASKBROKER_KAFKA_DEADLETTER_SASL_PASSWORD: ${KAFKA_SASL_PASSWORD:-} TASKBROKER_DB_PATH: "/opt/sqlite/taskbroker-activations.sqlite" volumes: - sentry-taskbroker:/opt/sqlite @@ -538,15 +526,21 @@ <<: *restart_policy image: "$VROOM_IMAGE" environment: - SENTRY_KAFKA_BROKERS_PROFILING: "kafka:9092" - SENTRY_KAFKA_BROKERS_OCCURRENCES: "kafka:9092" + SENTRY_KAFKA_BROKERS_PROFILING: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} + SENTRY_KAFKA_BROKERS_OCCURRENCES: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} + SENTRY_KAFKA_BROKERS_SPANS: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} + SENTRY_KAFKA_SECURITY_PROTOCOL: ${KAFKA_SECURITY_PROTOCOL:-PLAINTEXT} + SENTRY_KAFKA_SSL_CA_PATH: ${KAFKA_SSL_CA_LOCATION:-} + SENTRY_KAFKA_SSL_CERT_PATH: ${KAFKA_SSL_CERTIFICATE_LOCATION:-} + SENTRY_KAFKA_SSL_KEY_PATH: ${KAFKA_SSL_KEY_LOCATION:-} + SENTRY_KAFKA_SASL_MECHANISM: ${KAFKA_SASL_MECHANISM:-} + SENTRY_KAFKA_SASL_USERNAME: ${KAFKA_SASL_USERNAME:-} + SENTRY_KAFKA_SASL_PASSWORD: ${KAFKA_SASL_PASSWORD:-} SENTRY_BUCKET_PROFILES: file:///var/vroom/sentry-profiles SENTRY_SNUBA_HOST: "http://snuba-api:1218" volumes: - sentry-vroom:/var/vroom/sentry-profiles - depends_on: - kafka: - <<: *depends_on-healthy + - ./certificates/kafka:/kafka-certificates:ro profiles: - feature-complete vroom-cleanup: @@ -571,7 +565,14 @@ image: "$UPTIME_CHECKER_IMAGE" command: run environment: - UPTIME_CHECKER_RESULTS_KAFKA_CLUSTER: kafka:9092 + UPTIME_CHECKER_RESULTS_KAFKA_CLUSTER: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} + UPTIME_CHECKER_KAFKA_SECURITY_PROTOCOL: ${KAFKA_SECURITY_PROTOCOL:-PLAINTEXT} + UPTIME_CHECKER_KAFKA_SSL_CA_LOCATION: ${KAFKA_SSL_CA_LOCATION:-} + UPTIME_CHECKER_KAFKA_SSL_CERT_LOCATION: ${KAFKA_SSL_CERTIFICATE_LOCATION:-} + UPTIME_CHECKER_KAFKA_SSL_KEY_LOCATION: ${KAFKA_SSL_KEY_LOCATION:-} + UPTIME_CHECKER_KAFKA_SASL_MECHANISM: ${KAFKA_SASL_MECHANISM:-} + UPTIME_CHECKER_KAFKA_SASL_USERNAME: ${KAFKA_SASL_USERNAME:-} + UPTIME_CHECKER_KAFKA_SASL_PASSWORD: ${KAFKA_SASL_PASSWORD:-} UPTIME_CHECKER_REDIS_HOST: redis://redis:6379 # Set to `true` will allow uptime checks against private IP addresses UPTIME_CHECKER_ALLOW_INTERNAL_IPS: "false" @@ -582,8 +583,6 @@ # resolver. #UPTIME_CHECKER_HTTP_CHECKER_DNS_NAMESERVERS: "8.8.8.8,8.8.4.4" depends_on: - kafka: - <<: *depends_on-healthy redis: <<: *depends_on-healthy profiles: @@ -597,8 +596,6 @@ external: true sentry-redis: external: true - sentry-kafka: - external: true sentry-clickhouse: external: true sentry-symbolicator: ================================================ FILE: optional-modifications/patches/external-kafka/sentry.conf.example.py.patch ================================================ --- sentry/sentry.conf.example.py 2025-05-15 08:27:40.427876868 +0700 +++ sentry/sentry.conf.example.external-kafka.py 2025-05-15 08:32:44.845127931 +0700 @@ -132,9 +132,17 @@ SENTRY_CACHE = "sentry.cache.redis.RedisCache" DEFAULT_KAFKA_OPTIONS = { - "bootstrap.servers": "kafka:9092", + "bootstrap.servers": env("KAFKA_BOOTSTRAP_SERVERS", "kafka:9092"), "message.max.bytes": 50000000, "socket.timeout.ms": 1000, + "security.protocol": env("KAFKA_SECURITY_PROTOCOL", "PLAINTEXT"), # Valid options are PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL + # If you don't use any of these options below, you can remove them or set them to `None`. + "sasl.mechanism": env("KAFKA_SASL_MECHANISM", None), # Valid options are PLAIN, SCRAM-SHA-256, SCRAM-SHA-512. Other mechanism might be unavailable. + "sasl.username": env("KAFKA_SASL_USERNAME", None), + "sasl.password": env("KAFKA_SASL_PASSWORD", None), + "ssl.ca.location": env("KAFKA_SSL_CA_LOCATION", None), # Remove this line if SSL is not used. + "ssl.certificate.location": env("KAFKA_SSL_CERTIFICATE_LOCATION", None), # Remove this line if SSL is not used. + "ssl.key.location": env("KAFKA_SSL_KEY_LOCATION", None), # Remove this line if SSL is not used. } SENTRY_EVENTSTREAM = "sentry.eventstream.kafka.KafkaEventStream" ================================================ FILE: pyproject.toml ================================================ [project] name = "sentry-self-hosted" version = "0.1.0" description = "Sentry, feature-complete and packaged up for low-volume deployments and proofs-of-concept." readme = "README.md" requires-python = ">=3.11" dependencies = [] [dependency-groups] dev = [ "beautifulsoup4>=4.7.1", "cryptography>=43.0.3", "httpx>=0.25.2", "pytest>=8.0.0", "pytest-cov>=4.1.0", "pytest-rerunfailures>=11.0", "pytest-sentry>=0.1.11", "sentry-sdk>=2.4.0,<3.0.0", ] ================================================ FILE: redis.conf ================================================ # redis.conf # The 'maxmemory' directive controls the maximum amount of memory Redis is allowed to use. # Setting 'maxmemory 0' means there is no limit on memory usage, allowing Redis to use as much # memory as the operating system allows. This is suitable for environments where memory # constraints are not a concern. # # Alternatively, you can specify a limit, such as 'maxmemory 15gb', to restrict Redis to # using a maximum of 15 gigabytes of memory. # # Example: # maxmemory 0 # Unlimited memory usage # maxmemory 15gb # Limit memory usage to 15 GB maxmemory 0 # This setting determines how Redis evicts keys when it reaches the memory limit. # `allkeys-lru` evicts the least recently used keys from all keys stored in Redis, # allowing frequently accessed data to remain in memory while older data is removed. # That said we use `volatile-lru` as Redis is used both as a cache and processing # queue in self-hosted Sentry. # > The volatile-lru and volatile-random policies are mainly useful when you want to # > use a single Redis instance for both caching and for a set of persistent keys. # > However, you should consider running two separate Redis instances in a case like # > this, if possible. maxmemory-policy volatile-lru ================================================ FILE: relay/config.example.yml ================================================ relay: upstream: "http://web:9000/" host: 0.0.0.0 port: 3000 logging: level: WARN processing: enabled: true kafka_config: - {name: "bootstrap.servers", value: "kafka:9092"} - {name: "message.max.bytes", value: 50000000} # 50MB redis: redis://redis:6379 geoip_path: "/geoip/GeoLite2-City.mmdb" # In some cases, relay might fail to find out the actual machine memory # therefore it makes the healthcheck fail and events can't be submitted. # See https://github.com/getsentry/self-hosted/issues/3330 for more details. # As a workaround, uncomment the following `health` and `spool` sections: # # health: # max_memory_percent: 1.0 # spool: # envelopes: # path: "/tmp/relay-spool-envelopes" # max_backpressure_memory_percent: 1.0 # If you have statsd server, you can utilize that to monitor self-hosted Relay. # To start, uncomment the following `metrics` section and adjust the options as needed. # metrics: statsd: "${RELAY_STATSD_ADDR}" prefix: "sentry.relay" # Adjust this to your needs, default is "sentry.relay" # sample_rate: 1.0 # Adjust this to your needs, default is 1.0 # # `periodic_secs` is the interval for periodic metrics emitted from Relay. # # Setting it to `0` seconds disables the periodic metrics. # periodic_secs: 5 ================================================ FILE: scripts/_lib.sh ================================================ #!/usr/bin/env bash set -eEuo pipefail if [ -n "${DEBUG:-}" ]; then set -x fi function confirm() { read -r -p "$1 [y/n] " confirmation if [ "$confirmation" != "y" ]; then echo "Canceled. 😅" exit fi } # The purpose of this script is to make it easy to reset a local self-hosted # install to a clean state, optionally targeting a particular version. function reset() { # If we have a version given, validate it. # ---------------------------------------- # Note that arbitrary git refs won't work, because the *_IMAGE variables in # .env will almost certainly point to :latest. Tagged releases are generally # the only refs where these component versions are pinned, so enforce that # we're targeting a valid tag here. Do this early in order to fail fast. if [ -n "$version" ]; then set +e if ! git rev-parse --verify --quiet "refs/tags/$version" >/dev/null; then echo "Bad version: $version" exit fi set -e fi # Make sure they mean it. if [ "${FORCE_CLEAN:-}" == "1" ]; then echo "☠️ Seeing FORCE=1, forcing cleanup." echo else confirm "☠️ Warning! 😳 This is highly destructive! 😱 Are you sure you wish to proceed?" echo "Okay ... good luck! 😰" fi # assert that commands are defined : "${dc:?}" "${cmd:?}" # Hit the reset button. $dc down --volumes --remove-orphans --rmi local # Remove any remaining (likely external) volumes with name matching 'sentry-.*'. for volume in $(docker volume list --format '{{ .Name }}' | grep '^sentry-'); do docker volume remove "$volume" >/dev/null && echo "Removed volume: $volume" || echo "Skipped volume: $volume" done # If we have a version given, switch to it. if [ -n "$version" ]; then git checkout "$version" fi } function backup() { local type type=${1:-"global"} touch "${PWD}/sentry/backup.json" chmod 666 "${PWD}/sentry/backup.json" $dc run -v "${PWD}/sentry:/sentry-data/backup" --rm -T -e SENTRY_LOG_LEVEL=CRITICAL web export "$type" /sentry-data/backup/backup.json } function restore() { local type type="${1:-global}" $dc run --rm -T web import "$type" /etc/sentry/backup.json } # Needed variables to source error-handling script export STOP_TIMEOUT=60 # Save logs in order to send envelope to Sentry log_file="sentry_${cmd%% *}_log-$(date +%Y-%m-%d_%H-%M-%S).txt" exec &> >(tee -a "$log_file") version="" # Source files needed to set up error-handling source install/_lib.sh source install/dc-detect-version.sh source install/detect-platform.sh source install/error-handling.sh trap_with_arg cleanup ERR INT TERM EXIT ================================================ FILE: scripts/backup.sh ================================================ #!/usr/bin/env bash MINIMIZE_DOWNTIME="${MINIMIZE_DOWNTIME:-}" REPORT_SELF_HOSTED_ISSUES="${REPORT_SELF_HOSTED_ISSUES:-}" while (($#)); do case "$1" in --report-self-hosted-issues) REPORT_SELF_HOSTED_ISSUES=1 ;; --no-report-self-hosted-issues) REPORT_SELF_HOSTED_ISSUES=0 ;; --minimize-downtime) MINIMIZE_DOWNTIME=1 ;; *) version=$1 ;; esac shift done cmd="backup $1" source scripts/_lib.sh $cmd ================================================ FILE: scripts/bump-version.sh ================================================ #!/usr/bin/env bash set -eu # Craft passes versions via env vars (preferred) with positional args as fallback # for manual invocation (e.g. post-release.sh calls this with positional args). OLD_VERSION="${CRAFT_OLD_VERSION:-${1:-}}" NEW_VERSION="${CRAFT_NEW_VERSION:-${2:-}}" sed -i -e "s/^\(SENTRY\|SNUBA\|RELAY\|SYMBOLICATOR\|TASKBROKER\|VROOM\|UPTIME_CHECKER\)_IMAGE=\([^:]\+\):.\+\$/\1_IMAGE=\2:$NEW_VERSION/" .env sed -i -e "s/^# Self-Hosted Sentry.*/# Self-Hosted Sentry $NEW_VERSION/" README.md [ -z "$OLD_VERSION" ] || echo "Previous version: $OLD_VERSION" echo "New version: $NEW_VERSION" ================================================ FILE: scripts/post-release.sh ================================================ #!/usr/bin/env bash set -eu # Bring master back to nightlies after merge from release branch git checkout master && git pull --rebase ./scripts/bump-version.sh '' 'nightly' git diff --quiet || git commit -anm $'build: Set master version to nightly\n\n#skip-changelog' && git pull --rebase && git push ================================================ FILE: scripts/reset.sh ================================================ #!/usr/bin/env bash MINIMIZE_DOWNTIME="${MINIMIZE_DOWNTIME:-}" REPORT_SELF_HOSTED_ISSUES="${REPORT_SELF_HOSTED_ISSUES:-}" while (($#)); do case "$1" in --report-self-hosted-issues) REPORT_SELF_HOSTED_ISSUES=1 ;; --no-report-self-hosted-issues) REPORT_SELF_HOSTED_ISSUES=0 ;; --minimize-downtime) MINIMIZE_DOWNTIME=1 ;; *) version=$1 ;; esac shift done cmd=reset source scripts/_lib.sh $cmd ================================================ FILE: scripts/restore.sh ================================================ #!/usr/bin/env bash MINIMIZE_DOWNTIME="${MINIMIZE_DOWNTIME:-}" REPORT_SELF_HOSTED_ISSUES="${REPORT_SELF_HOSTED_ISSUES:-}" while (($#)); do case "$1" in --report-self-hosted-issues) REPORT_SELF_HOSTED_ISSUES=1 ;; --no-report-self-hosted-issues) REPORT_SELF_HOSTED_ISSUES=0 ;; --minimize-downtime) MINIMIZE_DOWNTIME=1 ;; *) version=$1 ;; esac shift done cmd="restore $1" source scripts/_lib.sh $cmd ================================================ FILE: sentry/Dockerfile ================================================ ARG SENTRY_IMAGE FROM ${SENTRY_IMAGE} RUN pip install https://github.com/getsentry/sentry-nodestore-s3/archive/main.zip COPY . /usr/src/sentry RUN if [ -s /usr/src/sentry/enhance-image.sh ]; then \ /usr/src/sentry/enhance-image.sh; \ fi RUN if [ -s /usr/src/sentry/requirements.txt ]; then \ echo "sentry/requirements.txt is deprecated, use sentry/enhance-image.sh - see https://develop.sentry.dev/self-hosted/#enhance-sentry-image"; \ pip install -r /usr/src/sentry/requirements.txt; \ fi ================================================ FILE: sentry/config.example.yml ================================================ # While a lot of configuration in Sentry can be changed via the UI, for all # new-style config (as of 8.0) you can also declare values here in this file # to enforce defaults or to ensure they cannot be changed via the UI. For more # information see the Sentry documentation. ############### # Mail Server # ############### # mail.backend: 'smtp' # Use dummy if you want to disable email entirely mail.host: 'smtp' # mail.port: 25 # mail.username: '' # mail.password: '' # NOTE: `mail.use-tls` and `mail.use-ssl` are mutually exclusive and should not # appear at the same time. Only uncomment one of them. # mail.use-tls: false # mail.use-ssl: false # NOTE: The following 2 configs (mail.from and mail.list-namespace) are set # through SENTRY_MAIL_HOST in sentry.conf.py so remove those first if # you want your values in this file to be effective! # The email address to send on behalf of # mail.from: 'root@localhost' or ... # mail.from: 'System Administrator ' # The mailing list namespace for emails sent by this Sentry server. # This should be a domain you own (often the same domain as the domain # part of the `mail.from` configuration parameter value) or `localhost`. # mail.list-namespace: 'localhost' # If you'd like to configure email replies, enable this. # mail.enable-replies: true # When email-replies are enabled, this value is used in the Reply-To header # mail.reply-hostname: '' # If you're using mailgun for inbound mail, set your API key and configure a # route to forward to /api/hooks/mailgun/inbound/ # Also don't forget to set `mail.enable-replies: true` above. # mail.mailgun-api-key: '' ################### # System Settings # ################### # This is the main URL prefix where Sentry can be accessed. # Sentry will use this to create links to its different parts of the web UI. # This is most helpful if you are using an external reverse proxy. # system.url-prefix: https://example.sentry.com # Most of the time, this should NOT be changed. It's used for communication # between containers. `web` is the container's name, and `9000` is the # default port opened by the Sentry backend (this is NOT the public port). # # If you want to change the publicly exposed domain or port, you should change # `system.url-prefix` above instead, along with `SENTRY_BIND` in `.env` file. # Also see https://develop.sentry.dev/self-hosted/#productionalizing. system.internal-url-prefix: 'http://web:9000' # If this file ever becomes compromised, it's important to generate a new key. # Changing this value will result in all current sessions being invalidated. # A new key can be generated with `$ sentry config generate-secret-key` # # If you are using SENTRY_SYSTEM_SECRET_KEY that is being set on your `.env` or `.env.custom` file, # you should remove this line below as it won't be used anyway. system.secret-key: '!!changeme!!' # The ``redis.clusters`` setting is used, unsurprisingly, to configure Redis # clusters. These clusters can be then referred to by name when configuring # backends such as the cache, digests, or TSDB backend. # redis.clusters: # default: # hosts: # 0: # host: 127.0.0.1 # port: 6379 ################ # File storage # ################ # Uploaded media uses these `filestore` settings. The available # backends are either `filesystem` or `s3`. filestore.backend: 'filesystem' filestore.options: location: '/data/files' dsym.cache-path: '/data/dsym-cache' releasefile.cache-path: '/data/releasefile-cache' # filestore.backend: 's3' # filestore.options: # access_key: 'AKIXXXXXX' # secret_key: 'XXXXXXX' # bucket_name: 's3-bucket-name' filestore.profiles-backend: 's3' filestore.profiles-options: bucket_acl: "private" default_acl: "private" access_key: "sentry" secret_key: "sentry" bucket_name: "profiles" region_name: "us-east-1" endpoint_url: "http://seaweedfs:8333" addressing_style: "path" signature_version: "s3v4" symbolicator.enabled: true symbolicator.options: url: "http://symbolicator:3021" transaction-events.force-disable-internal-project: true ###################### # GitHub Integration # ###################### # Refer to https://develop.sentry.dev/integrations/github/ for setup instructions. # github-login.extended-permissions: ['repo'] # github-app.id: GITHUB_APP_ID # github-app.name: 'GITHUB_APP_NAME' # github-app.webhook-secret: 'GITHUB_WEBHOOK_SECRET' # Use only if configured in GitHub # github-app.client-id: 'GITHUB_CLIENT_ID' # github-app.client-secret: 'GITHUB_CLIENT_SECRET' # github-app.private-key: | # -----BEGIN RSA PRIVATE KEY----- # privatekeyprivatekeyprivatekeyprivatekey # privatekeyprivatekeyprivatekeyprivatekey # privatekeyprivatekeyprivatekeyprivatekey # privatekeyprivatekeyprivatekeyprivatekey # privatekeyprivatekeyprivatekeyprivatekey # -----END RSA PRIVATE KEY----- ##################### # Slack Integration # ##################### # Refer to https://develop.sentry.dev/integrations/slack/ for setup instructions. # slack.client-id: <'client id'> # slack.client-secret: # slack.signing-secret: ## If legacy-app is True use verification-token instead of signing-secret # slack.verification-token: ####################### # Discord Integration # ####################### # Refer to https://develop.sentry.dev/integrations/discord/ # discord.application-id: "" # discord.public-key: "" # discord.client-secret: "" # discord.bot-token: "" ############### # Google Auth # ############### # Refer to https://develop.sentry.dev/self-hosted/sso/#google-auth # auth-google.client-id: "" # auth-google.client-secret: "" ================================================ FILE: sentry/enhance-image.example.sh ================================================ #!/bin/bash set -euo pipefail # Enhance the base $SENTRY_IMAGE with additional dependencies, plugins - see https://develop.sentry.dev/self-hosted/#enhance-sentry-image # For example: # apt-get update # apt-get install -y gcc libsasl2-dev libldap2-dev libssl-dev # pip install python-ldap ================================================ FILE: sentry/entrypoint.sh ================================================ #!/bin/bash set -e if [ "$(ls -A /usr/local/share/ca-certificates/)" ]; then update-ca-certificates fi if [ -e /etc/sentry/requirements.txt ]; then echo "sentry/requirements.txt is deprecated, use sentry/enhance-image.sh - see https://develop.sentry.dev/self-hosted/#enhance-sentry-image" fi source /docker-entrypoint.sh ================================================ FILE: sentry/requirements.example.txt ================================================ # sentry/requirements.txt is deprecated, use sentry/enhance-image.sh - see https://develop.sentry.dev/self-hosted/#enhance-sentry-image ================================================ FILE: sentry/sentry.conf.example.py ================================================ # This file is just Python, with a touch of Django which means # you can inherit and tweak settings to your hearts content. from sentry.conf.server import * # NOQA BYTE_MULTIPLIER = 1024 UNITS = ("K", "M", "G") def unit_text_to_bytes(text): unit = text[-1].upper() power = UNITS.index(unit) + 1 return float(text[:-1]) * (BYTE_MULTIPLIER**power) # Generously adapted from pynetlinux: https://github.com/rlisagor/pynetlinux/blob/e3f16978855c6649685f0c43d4c3fcf768427ae5/pynetlinux/ifconfig.py#L197-L223 def get_internal_network(): import ctypes import fcntl import math import socket import struct iface = b"eth0" sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ifreq = struct.pack(b"16sH14s", iface, socket.AF_INET, b"\x00" * 14) try: ip = struct.unpack( b"!I", struct.unpack(b"16sH2x4s8x", fcntl.ioctl(sockfd, 0x8915, ifreq))[2] )[0] netmask = socket.ntohl( struct.unpack(b"16sH2xI8x", fcntl.ioctl(sockfd, 0x891B, ifreq))[2] ) except IOError: return () base = socket.inet_ntoa(struct.pack(b"!I", ip & netmask)) netmask_bits = 32 - int(round(math.log(ctypes.c_uint32(~netmask).value + 1, 2), 1)) return "{0:s}/{1:d}".format(base, netmask_bits) INTERNAL_SYSTEM_IPS = (get_internal_network(),) DATABASES = { "default": { "ENGINE": "sentry.db.postgres", "NAME": "postgres", "USER": "postgres", "PASSWORD": "", "HOST": "pgbouncer", "PORT": "", } } # If you're expecting any kind of real traffic on Sentry, we highly recommend # configuring the CACHES and Redis settings ########### # General # ########### # Instruct Sentry that this install intends to be run by a single organization # and thus various UI optimizations should be enabled. SENTRY_SINGLE_ORGANIZATION = True # Sentry event retention days specifies how long events are retained in the database. # This should be set on your `.env` or `.env.custom` file, instead of modifying # the value here. # NOTE: The longer the days, the more disk space is required. SENTRY_OPTIONS["system.event-retention-days"] = int( env("SENTRY_EVENT_RETENTION_DAYS", "90") ) # The secret key is being used for various cryptographic operations, such as # generating a CSRF token, session token, and registering Relay instances. # The secret key value should be set on your `.env` or `.env.custom` file # instead of modifying the value here. # # If the key ever becomes compromised, it's important to generate a new key. # Changing this value will result in all current sessions being invalidated. # A new key can be generated with `$ sentry config generate-secret-key` if env("SENTRY_SYSTEM_SECRET_KEY"): SENTRY_OPTIONS["system.secret-key"] = env("SENTRY_SYSTEM_SECRET_KEY", "") # Self-hosted Sentry infamously has a lot of Docker containers required to make # all the features work. Oftentimes, users don't use the full feature set that # requires all the containers. This is a way to enable only the error monitoring # feature which also reduces the amount of containers required to run Sentry. # # To make Sentry work with all features, set `COMPOSE_PROFILES` to `feature-complete` # in your `.env` file. To enable only the error monitoring feature, set # `COMPOSE_PROFILES` to `errors-only`. # # See https://develop.sentry.dev/self-hosted/optional-features/errors-only/ SENTRY_SELF_HOSTED_ERRORS_ONLY = env("COMPOSE_PROFILES") != "feature-complete" # When running in an air-gapped environment, set this to True to entirely disable # external network calls and features that require Internet connectivity. # # Setting the value to False while running in an air-gapped environment will # cause some containers to raise exceptions. One known example is fetching # AI model prices from various public APIs. SENTRY_AIR_GAP = False # As of 25.9.0 (September 2025 release), Sentry enforces tighter restrictions # of allowed IP addresses for outgoing requests. This is to prevent # accidentally leaking sensitive information to third parties. # # By default, Sentry will not allow requests to private IP addresses. # You can override this by configuring the allowed IP addresses here. # Below is the default value, which is a list of IP addresses that are # considered "private" and "reserved". # # SENTRY_DISALLOWED_IPS: tuple[str, ...] = ( # # https://en.wikipedia.org/wiki/Reserved_IP_addresses#IPv4 # "0.0.0.0/8", # "10.0.0.0/8", # "100.64.0.0/10", # "127.0.0.0/8", # "169.254.0.0/16", # "172.16.0.0/12", # "192.0.0.0/29", # "192.0.2.0/24", # "192.88.99.0/24", # "192.168.0.0/16", # "198.18.0.0/15", # "198.51.100.0/24", # "224.0.0.0/4", # "240.0.0.0/4", # "255.255.255.255/32", # # https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses # # Subnets match the IPv4 subnets above # "::ffff:0:0/104", # "::ffff:a00:0/104", # "::ffff:6440:0/106", # "::ffff:7f00:0/104", # "::ffff:a9fe:0/112", # "::ffff:ac10:0/108", # "::ffff:c000:0/125", # "::ffff:c000:200/120", # "::ffff:c058:6300/120", # "::ffff:c0a8:0/112", # "::ffff:c612:0/111", # "::ffff:c633:6400/120", # "::ffff:e000:0/100", # "::ffff:f000:0/100", # "::ffff:ffff:ffff/128", # # https://en.wikipedia.org/wiki/Reserved_IP_addresses#IPv6 # "::1/128", # "::ffff:0:0/96", # "64:ff9b::/96", # "64:ff9b:1::/48", # "100::/64", # "2001:0000::/32", # "2001:20::/28", # "2001:db8::/32", # "2002::/16", # "fc00::/7", # "fe80::/10", # "ff00::/8", # ) ################ # Node Storage # ################ # Sentry uses an abstraction layer called "node storage" to store raw events. # Previously, it used PostgreSQL as the backend, but this didn't scale for # high-throughput environments. Read more about this in the documentation: # https://develop.sentry.dev/backend/application-domains/nodestore/ # # Through this setting, you can use the provided blob storage or # your own S3-compatible API from your infrastructure. # Other backend implementations for node storage developed by the community # are available in public GitHub repositories. SENTRY_NODESTORE = "sentry_nodestore_s3.S3PassthroughDjangoNodeStorage" SENTRY_NODESTORE_OPTIONS = { "compression": True, "endpoint_url": "http://seaweedfs:8333", "bucket_path": "nodestore", "bucket_name": "nodestore", "region_name": "us-east-1", "aws_access_key_id": "sentry", "aws_secret_access_key": "sentry", } ######### # Redis # ######### # Generic Redis configuration used as defaults for various things including: # Buffers, Quotas, TSDB SENTRY_OPTIONS["redis.clusters"] = { "default": { "hosts": {0: {"host": "redis", "password": "", "port": "6379", "db": "0"}} } } ######### # Cache # ######### # Sentry currently utilizes two separate mechanisms. While CACHES is not a # requirement, it will optimize several high throughput patterns. CACHES = { "default": { "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache", "LOCATION": ["memcached:11211"], "TIMEOUT": 3600, "OPTIONS": {"ignore_exc": True}, } } # A primary cache is required for things such as processing events SENTRY_CACHE = "sentry.cache.redis.RedisCache" DEFAULT_KAFKA_OPTIONS = { "bootstrap.servers": "kafka:9092", "message.max.bytes": 50000000, "socket.timeout.ms": 1000, } SENTRY_EVENTSTREAM = "sentry.eventstream.kafka.KafkaEventStream" SENTRY_EVENTSTREAM_OPTIONS = {"producer_configuration": DEFAULT_KAFKA_OPTIONS} KAFKA_CLUSTERS["default"] = DEFAULT_KAFKA_OPTIONS ############### # Rate Limits # ############### # Rate limits apply to notification handlers and are enforced per-project # automatically. SENTRY_RATELIMITER = "sentry.ratelimits.redis.RedisRateLimiter" ################## # Update Buffers # ################## # Buffers (combined with queueing) act as an intermediate layer between the # database and the storage API. They will greatly improve efficiency on large # numbers of the same events being sent to the API in a short amount of time. # (read: if you send any kind of real data to Sentry, you should enable buffers) SENTRY_BUFFER = "sentry.buffer.redis.RedisBuffer" ########## # Quotas # ########## # Quotas allow you to rate limit individual projects or the Sentry install as # a whole. SENTRY_QUOTAS = "sentry.quotas.redis.RedisQuota" ######## # TSDB # ######## # The TSDB is used for building charts as well as making things like per-rate # alerts possible. SENTRY_TSDB = "sentry.tsdb.redissnuba.RedisSnubaTSDB" ######### # SNUBA # ######### SENTRY_SEARCH = "sentry.search.snuba.EventsDatasetSnubaSearchBackend" SENTRY_SEARCH_OPTIONS = {} SENTRY_TAGSTORE_OPTIONS = {} ########### # Digests # ########### # The digest backend powers notification summaries. SENTRY_DIGESTS = "sentry.digests.backends.redis.RedisBackend" ############## # Web Server # ############## SENTRY_WEB_HOST = "0.0.0.0" SENTRY_WEB_PORT = 9000 SENTRY_WEB_OPTIONS = { "http": "%s:%s" % (SENTRY_WEB_HOST, SENTRY_WEB_PORT), "protocol": "uwsgi", # This is needed in order to prevent https://github.com/getsentry/sentry/blob/c6f9660e37fcd9c1bbda8ff4af1dcfd0442f5155/src/sentry/services/http.py#L70 "uwsgi-socket": None, "so-keepalive": True, # Keep this between 15s-75s as that's what Relay supports "http-keepalive": 15, "http-chunked-input": True, # the number of web workers "workers": 3, "threads": 4, "memory-report": False, # The `harakiri` option terminates requests that take longer than the # defined amount of time (in seconds) which can help avoid stuck workers # caused by GIL issues or deadlocks. # Ensure nginx `proxy_read_timeout` configuration (default: 30) # on your `nginx.conf` file to be at least 5 seconds longer than this. # "harakiri": 25, # Some stuff so uwsgi will cycle workers sensibly "max-requests": 100000, "max-requests-delta": 500, "max-worker-lifetime": 86400, # Duplicate options from sentry default just so we don't get # bit by sentry changing a default value that we depend on. "thunder-lock": True, "log-x-forwarded-for": False, "buffer-size": 32768, "limit-post": 209715200, "disable-logging": True, "reload-on-rss": 600, "ignore-sigpipe": True, "ignore-write-errors": True, "disable-write-exception": True, } ########### # SSL/TLS # ########### # If you're using a reverse SSL proxy, you should enable the X-Forwarded-Proto # header and enable the settings below # SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # USE_X_FORWARDED_HOST = True # SESSION_COOKIE_SECURE = True # CSRF_COOKIE_SECURE = True # SOCIAL_AUTH_REDIRECT_IS_HTTPS = True # End of SSL/TLS settings ######## # Mail # ######## SENTRY_OPTIONS["mail.list-namespace"] = env("SENTRY_MAIL_HOST", "localhost") SENTRY_OPTIONS["mail.from"] = f"sentry@{SENTRY_OPTIONS['mail.list-namespace']}" ############ # Features # ############ # Sentry uses feature flags to enable certain features. Some features may # require additional configuration or containers. To learn more about how # Sentry uses feature flags, see https://develop.sentry.dev/backend/application-domains/feature-flags/ # # The features listed here are stable and generally available on SaaS. # To enable preview features, see https://develop.sentry.dev/self-hosted/configuration/#enabling-preview-features SENTRY_FEATURES["projects:sample-events"] = False SENTRY_FEATURES.update( { feature: True for feature in ( "organizations:discover", "organizations:global-views", "organizations:issue-views", "organizations:incidents", "organizations:integrations-issue-basic", "organizations:integrations-issue-sync", "organizations:invite-members", "organizations:sso-basic", "organizations:sso-saml2", "organizations:advanced-search", "organizations:issue-platform", "organizations:monitors", "organizations:dashboards-mep", "organizations:mep-rollout-flag", "organizations:dashboards-rh-widget", "organizations:dynamic-sampling", "projects:custom-inbound-filters", "projects:data-forwarding", "projects:discard-groups", "projects:plugins", "projects:rate-limits", "projects:servicehooks", ) # Performance/Tracing/Spans related flags + ( "organizations:performance-view", "organizations:span-stats", "organizations:visibility-explore-view", "organizations:visibility-explore-range-high", "organizations:transaction-metrics-extraction", "organizations:indexed-spans-extraction", "organizations:insights-entry-points", "organizations:insights-initial-modules", "organizations:insights-addon-modules", "organizations:insights-modules-use-eap", "organizations:standalone-span-ingestion", "organizations:starfish-mobile-appstart", "organizations:on-demand-metrics-extraction", "projects:span-metrics-extraction", "projects:span-metrics-extraction-addons", ) # Session Replay related flags + ( "organizations:session-replay", ) # User Feedback related flags + ( "organizations:user-feedback-ui", ) # Profiling related flags + ( "organizations:profiling", "organizations:profiling-view", ) # Continuous Profiling related flags + ( "organizations:continuous-profiling", "organizations:continuous-profiling-stats", ) # Uptime Monitoring related flags + ( "organizations:uptime", "organizations:uptime-create-issues", ) # Logs related flags + ( "organizations:ourlogs-enabled", "organizations:ourlogs-ingestion", "organizations:ourlogs-stats", "organizations:ourlogs-replay-ui", ) } ) ####################### # MaxMind Integration # ####################### GEOIP_PATH_MMDB = "/geoip/GeoLite2-City.mmdb" ######################### # Bitbucket Integration # ######################### # BITBUCKET_CONSUMER_KEY = 'YOUR_BITBUCKET_CONSUMER_KEY' # BITBUCKET_CONSUMER_SECRET = 'YOUR_BITBUCKET_CONSUMER_SECRET' ############################################## # Content Security Policy settings ############################################## # CSP_REPORT_URI = "https://{your-sentry-installation}/api/{csp-project}/security/?sentry_key={sentry-key}" CSP_REPORT_ONLY = True # optional extra permissions # https://django-csp.readthedocs.io/en/latest/configuration.html # CSP_SCRIPT_SRC += ["example.com"] ############################ # Sentry Endpoint Settings # ############################ # If your Sentry installation has different hostnames for ingestion and web UI, # in which your web UI is accessible via private corporate network, yet your # ingestion hostname is accessible from the public internet, you can uncomment # this following options in order to have the ingestion hostname rendered # correctly on the SDK configuration UI. # # SENTRY_ENDPOINT = "https://sentry.ingest.example.com" ################# # CSRF Settings # ################# # Since version 24.1.0, Sentry migrated to Django 4 which contains stricter CSRF protection. # If you are accessing Sentry from multiple domains behind a reverse proxy, you should set # this to match your IPs/domains. Ports should be included if you are using custom ports. # https://docs.djangoproject.com/en/4.2/ref/settings/#std-setting-CSRF_TRUSTED_ORIGINS # CSRF_TRUSTED_ORIGINS = ["https://example.com", "http://127.0.0.1:9000"] ################# # JS SDK Loader # ################# # Configure Sentry JS SDK bundle URL template for Loader Scripts. # Learn more about the Loader Scripts: https://docs.sentry.io/platforms/javascript/install/loader/ # If you wish to host your own JS SDK bundles, set `SETUP_JS_SDK_ASSETS` environment variable to `1` # on your `.env` or `.env.custom` file. Then, replace the value below with your own public URL. # For example: "https://sentry.example.com/js-sdk/%s/bundle%s.min.js" # # By default, the previous JS SDK assets version will be pruned during upgrades, if you wish # to keep the old assets, set `SETUP_JS_SDK_KEEP_OLD_ASSETS` environment variable to any value on # your `.env` or `.env.custom` file. The files should only be a few KBs, and this might be useful # if you're using it directly like a CDN instead of using the loader script. JS_SDK_LOADER_DEFAULT_SDK_URL = "https://browser.sentry-cdn.com/%s/bundle%s.min.js" ##################### # Insights Settings # ##################### # Since version 24.3.0, Insights features are available on self-hosted. For Requests module, # there are scrubbing logic done on Relay to prevent high cardinality of stored HTTP hosts. # However in self-hosted scenario, the amount of stored HTTP hosts might be consistent, # and you may have allow list of hosts that you want to keep. Uncomment the following line # to allow specific hosts. It might be IP addresses or domain names (without `http://` or `https://`). # SENTRY_OPTIONS["relay.span-normalization.allowed_hosts"] = ["example.com", "192.168.10.1"] ############## # Monitoring # ############## # By default, Sentry uses dummy statsd monitoring backend that is a no-op. # If you have a statsd server, you can utilize that to monitor self-hosted # Sentry for "sentry"-related containers. # # To start, uncomment the following line and adjust the options as needed. SENTRY_STATSD_ADDR = env("SENTRY_STATSD_ADDR") if SENTRY_STATSD_ADDR: host, _, port = SENTRY_STATSD_ADDR.partition(":") port = int(port or 8125) SENTRY_METRICS_BACKEND = 'sentry.metrics.statsd.StatsdMetricsBackend' SENTRY_METRICS_OPTIONS: dict[str, Any] = { 'host': host, 'port': port, } # SENTRY_METRICS_SAMPLE_RATE = 1.0 # Adjust this to your needs, default is 1.0 # SENTRY_METRICS_PREFIX = "sentry." # Adjust this to your needs, default is "sentry." ================================================ FILE: sentry-admin.sh ================================================ #!/usr/bin/env bash # Set the script directory as working directory. cd $(dirname $0) # Detect docker and platform state. source install/_lib.sh source install/dc-detect-version.sh source install/detect-platform.sh # Define the Docker volume mapping. VOLUME_MAPPING="${SENTRY_DOCKER_IO_DIR:-$HOME/.sentry/sentry-admin}:/sentry-admin" # Custom help text paragraphs HELP_TEXT_SUFFIX=" All file paths are relative to the 'web' docker container, not the host environment. To pass files to/from the host system for commands that require it ('execfile', 'export', 'import', etc), you may specify a 'SENTRY_DOCKER_IO_DIR' environment variable to mount a volume for file IO operations into the host filesystem. The default value of 'SENTRY_DOCKER_IO_DIR' points to '~/.sentry/sentry-admin' on the host filesystem. Commands that write files should write them to the '/sentry-admin' in the 'web' container (ex: './sentry-admin.sh export global /sentry-admin/my-export.json'). " # Actual invocation that runs the command in the container. invocation() { start_service_and_wait_ready postgres start_service_and_wait_ready pgbouncer start_service_and_wait_ready redis --wait $dcr --no-deps -v "$VOLUME_MAPPING" -T -e SENTRY_LOG_LEVEL=CRITICAL web "$@" 2>&1 } # Function to modify lines starting with `Usage: sentry` to say `Usage: ./sentry-admin.sh` instead. rename_sentry_bin_in_help_output() { local output="$1" local help_prefix="$2" local usage_seen=false output=$(invocation "$@") echo -e "\n\n" while IFS= read -r line; do if [[ $line == "Usage: sentry"* ]] && [ "$usage_seen" = false ]; then echo -e "\n\n" echo "${line/sentry/./sentry-admin.sh}" echo "$help_prefix" usage_seen=true else if [[ $line == "Options:"* ]] && [ -n "$1" ]; then echo "$help_prefix" fi echo "$line" fi done <<<"$output" } # Check for the user passing ONLY the '--help' argument - we'll add a special prefix to the output. if { [ "$1" = "help" ] || [ "$1" = "--help" ]; } && [ "$#" -eq 1 ]; then rename_sentry_bin_in_help_output "$(invocation "$@")" "$HELP_TEXT_SUFFIX" exit 0 fi # Check for '--help' in other contexts. for arg in "$@"; do if [ "$arg" = "--help" ]; then rename_sentry_bin_in_help_output "$(invocation "$@")" exit 0 fi done # Help has not been requested - go ahead and execute the command. echo -e "\n\n" invocation "$@" ================================================ FILE: symbolicator/config.example.yml ================================================ # See: https://getsentry.github.io/symbolicator/#configuration cache_dir: "/data" bind: "0.0.0.0:3021" logging: level: "warn" sentry_dsn: null # TODO: Automatically fill this with the internal project DSN # If you have statsd server, you can utilize that to monitor self-hosted Symbolicator. metrics: statsd: "${SYMBOLICATOR_STATSD_ADDR}" # It is recommended to use IP address instead of domain name prefix: "sentry.symbolicator" # Adjust this to your needs, default is "symbolicator" ================================================ FILE: unit-test.sh ================================================ #!/usr/bin/env bash # The test should not be run by regular users. This should only be run in CI or by developers. if [[ "$CI" != "true" ]]; then echo "This script is intended to be run in CI or by developers only." exit 1 fi export REPORT_SELF_HOSTED_ISSUES=0 # will be over-ridden in the relevant test FORCE_CLEAN=1 "./scripts/reset.sh" fail=0 for test_file in _unit-test/*-test.sh; do if [ -n "$1" ] && [ "$1" != "$test_file" ]; then echo "🙊 Skipping $test_file ..." continue fi echo "🙈 Running $test_file ..." $test_file exit_code=$? if [ $exit_code != 0 ]; then echo fail 👎 with exit code $exit_code fail=1 fi done exit $fail ================================================ FILE: workstation/200_download-self-hosted.sh ================================================ #!/bin/bash set -eo pipefail # Create getsentry folder and enter. mkdir /home/user/getsentry cd /home/user/getsentry # Pull down sentry and self-hosted. git clone https://github.com/getsentry/sentry.git git clone https://github.com/getsentry/self-hosted.git cd self-hosted ================================================ FILE: workstation/201_install-self-hosted.sh ================================================ #!/bin/bash # # Install self-hosted. Assumed `200_download-self-hosted.sh` has already run. ./install.sh --skip-commit-check --skip-user-creation --skip-sse42-requirements --no-report-self-hosted-issues # Apply CSRF override to the newly installed sentry settings. echo "CSRF_TRUSTED_ORIGINS = [\"https://9000-$WEB_HOST\"]" >>/home/user/getsentry/self-hosted/sentry/sentry.conf.py ================================================ FILE: workstation/299_setup-completed.sh ================================================ #!/bin/bash # # Add a dot file to the home directory indicating that the setup has been completed successfully. # The host-side of the connection will look for this file when polling for completion to indicate to # the user that the workstation is ready for use. # # Works under the assumption that this is the last setup script to run! echo "ready_at: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" >/home/user/.sentry.workstation.remote ================================================ FILE: workstation/README.md ================================================ # Remote Self-Hosted Development on Google Cloud Workstation This document specifies how to set up remote workstation development for `self-hosted` using Google Cloud. While this feature is primarily intended for Sentry developers seeking to develop or test changes to `self-hosted`, in theory anyone with a Google Cloud account and the willingness to incur the associated costs could replicate the setup described here. The goal of remote workstations is to provide turn-key instances for developing on `self-hosted`, in either postinstall (the `/.install.sh` script has already run) or preinstall (it has not) modes. By using Ubuntu as a base image, we are able to provide a fresh development environment that is very similar to the Linux-based x86 instances that self-hosted is intended to be deployed to. Specifically, the goals of this effort are: - Create and manage turn-key virtual machines for development in either preinstall or postinstall mode quickly and with minimal manual user input. - Simulate real `self-hosted` deployment environments as faithfully as possible. - Create a smooth developer experience when using VSCode and GitHub. The last point is worth emphasizing: this tool is specifically optimized to work well with VSCode remote server (for the actual development) and GitHub (for pushing changes). Supporting any other workflows is an explicit ***non-goal*** of this setup. The instructions here are for how to setup workstations as an administrator (that is, the person in charge of managing and paying for the entire fleet of workstations). End users are expected to create, connect to, manage, and shut down workstations as needed via the the `sentry` developer CLI using the `sentry workstations ...` set of commands. For most use cases outside of Sentry-the-company, the administrator and end user will be the same individual: they'll configure their Google Cloud projects and billing, and then use them via `sentry workstations ...` commands on their local machine. ## Configuring Google Cloud You'll need to use two Google Cloud services to enable remote `self-hosted` deployment: Google Cloud Workstations to run the actual virtual machines, and the Artifact Registry to store the base images described in the adjacent Dockerfiles. The rest of this document will assume that you are configuring these services to be used from the west coast of the United States (ie: `us-west1`), but a similar set of processes could be applied for any region supported by Google Cloud. ### Creating an Artifact Registry You can create an artifact registry using the Google Cloud Platform UI [here](https://console.cloud.google.com/artifacts): ![Create an Artifact Registry](./docs/img/create_artifcat_registry.png) The dialog should be straightforward. We'll name our new repository `sentry-workstation-us` and put it in `us-west1`, but you could change these to whatever options suit your liking. Leave the remaining configurations as they are: ![Create a Repository](./docs/img/create_repository.png) ### Setting up Cloud Workstations To use Google Cloud Workstations, you'll need to make at least one workstation cluster, and at least one configuration therein. Navigate to the services [control panel](https://console.cloud.google.com/workstations/overview). From here, you'll need to make one cluster for each region you plan to support. We'll make one for `us-west1` in this tutorial, naming it `us-west` for clarity: ![Create a Workstation cluster](./docs/img/create_cluster.png) Now, create a new configuration for that cluster. There are a few choices to make here: - Do you want it to be a preinstall (ie, `./install.sh` has not run) or postinstall (it has) instance? - Do you want to use a small, standard or large resource allocation? - How aggressively do you want to auto-sleep and auto-shutdown instances? More aggressive setups will save money, but be more annoying for end users. For this example, we'll make a `postinstall` instance with a `standard` resource allocation, but you can of course change these as you wish. On the first panel, name the instance (we recommend using the convention `[INSTALL_KIND]-[SIZE]-[CLUSTER_NAME]`, so this one `postinstall-standard-us-west`) and assign it to the existing cluster: ![Create a config](./docs/img/create_config_1.png) Next, pick a resource and cost saving configuration that makes sense for you. In our experience, an E2 instance is plenty for most day-to-day development work. ![Create a config](./docs/img/create_config_2.png) On the third panel, select `Custom container image`, then choose one of your `postinstall` images (see below for how to generate these). Assign the default Compute Engine service account to it, then choose to `Create a new empty persistent disk` for it. A balanced 10GB disk should be plenty for shorter development stints: ![Create a config](./docs/img/create_config_3.png) On the last screen, set the appropriate IAM policy to allow access to the new machine for your users. You should be ready to go! ## Creating and uploading an image artifact Each Cloud Workstation configuration you create will need to use a Docker image, the `Dockerfile`s and scripts for which are found in this directory. There are two kinds of images: `preinstall` (ie, `./install.sh` has not run) and `postinstall` (it has). To proceed, you'll need to install the `gcloud` and `docker` CLI, then login to both and set your project as the default: ```shell $> export GCP_PROJECT_ID=my-gcp-project # Obviously, your project is likely to have another name. $> gcloud auth application-default login $> gcloud config set project $GCP_PROJECT_ID $> gcloud auth print-access-token | docker login -u oauth2accesstoken --password-stdin https://us-docker.pkg.dev ``` Next, you'll set some useful variables for this session (note: the code below assumes we are pushing to the `sentry-workstation-us` repository defined above): ```shell $> export GROUP=sentry-workstation # Pick whatever name you like here. $> export REGION=us # Name your regions as you see fit - these are not tied to GCP definitions. $> export PHASE=pre # Use `pre` for preinstall, `post` for postinstall. $> export REPO=${GROUP}-${REGION} $> export IMAGE_TAG=${GROUP}/${PHASE}install:latest $> export IMAGE_URL=us-docker.pkg.dev/${GCP_PROJECT_ID}/${REPO}/${GROUP}/${PHASE}install:latest ``` Now, build the docker image of your choosing: ```shell $> docker build -t ${IMAGE_TAG} -f ./${PHASE}install/Dockerfile . $> docker image ls | grep "${GROUP}/${PHASE}install" ``` Finally, upload it to the Google Cloud Artifact Registry repository of interest: ```shell $> docker tag ${IMAGE_TAG} ${IMAGE_URL} $> docker push ${IMAGE_URL} ``` ## Creating and connecting to a workstation Once the Google Cloud services are configured and the docker images uploaded per the instructions above, end users should be able to list the configurations available to them using `sentry workstations config`: ```shell $> sentry workstations configs --project=$GCP_PROJECT_ID NAME CLUSTER REGION MACHINE TYPE postinstall-standard-us-west us-west us-west1 e2-standard-4 postinstall-large-us-west us-west us-west1 e2-standard-8 preinstall-standard-us-west us-west us-west1 e2-standard-4 preinstall-large-us-west us-west us-west1 e2-standard-8 ``` They will then be able to create a new workstation using the `sentry workstations create` command, connect to an existing one using `sentry workstations connect`, and use similar `sentry workstations ...` commands to disconnect from and destroy workstations, as well as to check the status of their active connections. The `create` and `connect` commands will provide further instructions in-band on how to connect a their local VSCode to the remote server, use SSH to connect to the terminal directly, add a GitHub access token, or access their running `self-hosted` instance via a web browser. ================================================ FILE: workstation/commands.sh ================================================ sudo wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor >packages.microsoft.gpg sudo install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg sudo sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list' sudo rm -f packages.microsoft.gpg ================================================ FILE: workstation/postinstall/Dockerfile ================================================ # Use a predefined image with no out-of-the-box IDE set up. FROM us-west1-docker.pkg.dev/cloud-workstations-images/predefined/base:latest # Modifications to the `/home` directory must take place outside of the Dockerfile. Add a startup # script to handle the cloning of the `sentry` and `self-hosted` repositories. COPY 200_download-self-hosted.sh /etc/workstation-startup.d/ COPY 201_install-self-hosted.sh /etc/workstation-startup.d/ COPY 299_setup-completed.sh /etc/workstation-startup.d/ RUN chmod -R +x /etc/workstation-startup.d # Avoid prompts from apt by setting it to non-interactive. ENV DEBIAN_FRONTEND=noninteractive # Install VSCode and the GitHub CLI. RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > vscode.gpg \ && install -D -o root -g root -m 644 vscode.gpg /etc/apt/keyrings/vscode.gpg \ && sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/vscode.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list' \ && rm -f vscode.gpg \ && apt-get update \ && apt-get install -y code \ && apt-get install -y curl \ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/gh.gpg \ && chmod go+r /usr/share/keyrings/gh.gpg \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gh.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \ && rm -f gh.gpg \ && apt-get update \ && apt-get install -y gh \ && rm -rf /var/lib/apt/lists/* # Reset the DEBIAN_FRONTEND environment variable. ENV DEBIAN_FRONTEND= ================================================ FILE: workstation/preinstall/Dockerfile ================================================ # Use a predefined image with no out-of-the-box IDE set up. FROM us-west1-docker.pkg.dev/cloud-workstations-images/predefined/base:latest # Modifications to the `/home` directory must take place outside of the Dockerfile. Add a startup # script to handle the cloning of the `sentry` and `self-hosted` repositories. COPY 200_download-self-hosted.sh /etc/workstation-startup.d/ COPY 299_setup-completed.sh /etc/workstation-startup.d/ RUN chmod -R +x /etc/workstation-startup.d # Avoid prompts from apt by setting it to non-interactive. ENV DEBIAN_FRONTEND=noninteractive # Install VSCode and the GitHub CLI. RUN wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > vscode.gpg \ && install -D -o root -g root -m 644 vscode.gpg /etc/apt/keyrings/vscode.gpg \ && sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/vscode.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list' \ && rm -f vscode.gpg \ && apt-get update \ && apt-get install -y code \ && apt-get install -y curl \ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/gh.gpg \ && chmod go+r /usr/share/keyrings/gh.gpg \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gh.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \ && rm -f gh.gpg \ && apt-get update \ && apt-get install -y gh \ && rm -rf /var/lib/apt/lists/* # Reset the DEBIAN_FRONTEND environment variable. ENV DEBIAN_FRONTEND=