diff --git a/.env b/.env index 8e54a3df2c9..dfe279267f8 100644 --- a/.env +++ b/.env @@ -1,19 +1,28 @@ COMPOSE_PROJECT_NAME=sentry-self-hosted +# Set COMPOSE_PROFILES to "feature-complete" to enable all features +# To enable errors monitoring only, set COMPOSE_PROFILES=errors-only +# See https://develop.sentry.dev/self-hosted/experimental/errors-only/ +COMPOSE_PROFILES=feature-complete SENTRY_EVENT_RETENTION_DAYS=90 # You can either use a port number or an IP:PORT combo for SENTRY_BIND # See https://docs.docker.com/compose/compose-file/#ports for more SENTRY_BIND=9000 # Set SENTRY_MAIL_HOST to a valid FQDN (host/domain name) to be able to send emails! # SENTRY_MAIL_HOST=example.com -SENTRY_IMAGE=getsentry/sentry:nightly -SNUBA_IMAGE=getsentry/snuba:nightly -RELAY_IMAGE=getsentry/relay:nightly -SYMBOLICATOR_IMAGE=getsentry/symbolicator:nightly -VROOM_IMAGE=getsentry/vroom:nightly -WAL2JSON_VERSION=latest +SENTRY_IMAGE=ghcr.io/getsentry/sentry:nightly +SNUBA_IMAGE=ghcr.io/getsentry/snuba:nightly +RELAY_IMAGE=ghcr.io/getsentry/relay:nightly +SYMBOLICATOR_IMAGE=ghcr.io/getsentry/symbolicator:nightly +TASKBROKER_IMAGE=ghcr.io/getsentry/taskbroker:nightly +VROOM_IMAGE=ghcr.io/getsentry/vroom:nightly +UPTIME_CHECKER_IMAGE=ghcr.io/getsentry/uptime-checker:nightly HEALTHCHECK_INTERVAL=30s HEALTHCHECK_TIMEOUT=1m30s HEALTHCHECK_RETRIES=10 -# Caution: Raising max connections of postgres increases CPU and RAM usage -# see https://github.com/getsentry/self-hosted/pull/2740 for more information -POSTGRES_MAX_CONNECTIONS=100 +HEALTHCHECK_START_PERIOD=10s +HEALTHCHECK_FILE_INTERVAL=60s +HEALTHCHECK_FILE_TIMEOUT=10s +HEALTHCHECK_FILE_RETRIES=3 +HEALTHCHECK_FILE_START_PERIOD=600s +# Set SETUP_JS_SDK_ASSETS to 1 to enable the setup of JS SDK assets +# SETUP_JS_SDK_ASSETS=1 diff --git a/.github/ISSUE_TEMPLATE/problem-report.yml b/.github/ISSUE_TEMPLATE/problem-report.yml index 53ac4ec2acb..1be5d3e4796 100644 --- a/.github/ISSUE_TEMPLATE/problem-report.yml +++ b/.github/ISSUE_TEMPLATE/problem-report.yml @@ -23,7 +23,7 @@ body: id: docker_version attributes: label: Docker Version - placeholder: 20.10.16 ← should look like this + 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) @@ -39,6 +39,16 @@ body: 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: textarea id: repro attributes: @@ -54,6 +64,8 @@ body: id: expected attributes: label: Expected Result + description: | + What did you expect to happen? validations: required: true - type: textarea @@ -70,7 +82,7 @@ body: - logs output validations: required: true - - type: textarea + - type: input id: event_id attributes: label: Event ID @@ -82,5 +94,7 @@ body: 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 diff --git a/.github/ISSUE_TEMPLATE/release.yml b/.github/ISSUE_TEMPLATE/release.yml index c8285b5409e..545e733befe 100644 --- a/.github/ISSUE_TEMPLATE/release.yml +++ b/.github/ISSUE_TEMPLATE/release.yml @@ -10,13 +10,12 @@ body: [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)_). - - [ ] [`develop`](https://github.com/getsentry/develop/actions/workflows/prepare-release.yml) - [ ] [`relay`](https://github.com/getsentry/relay/actions/workflows/release_binary.yml) - [ ] [`sentry`](https://github.com/getsentry/sentry/actions/workflows/release.yml) - - [ ] [`sentry-docs`](https://github.com/getsentry/sentry-docs/actions/workflows/prepare-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) + - [ ] [`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. @@ -25,7 +24,6 @@ body: - [ ] 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 [quarterly ticket](https://github.com/getsentry/team-ospo/issues). - [ ] 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_ diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f83c1c8e04b..31589ecff7a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,7 +6,7 @@ updates: interval: daily open-pull-requests-limit: 0 # only security updates reviewers: - - "@getsentry/open-source" + - "@getsentry/dev-infra" - "@getsentry/security" - package-ecosystem: "github-actions" @@ -15,5 +15,5 @@ updates: # Check for updates to GitHub Actions every week interval: "weekly" reviewers: - - "@getsentry/open-source" + - "@getsentry/dev-infra" - "@getsentry/security" diff --git a/.github/workflows/enforce-license-compliance.yml b/.github/workflows/enforce-license-compliance.yml index 46d724d32c4..43ce1996390 100644 --- a/.github/workflows/enforce-license-compliance.yml +++ b/.github/workflows/enforce-license-compliance.yml @@ -6,8 +6,12 @@ on: 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' diff --git a/.github/workflows/fast-revert.yml b/.github/workflows/fast-revert.yml index d3699da4609..6b88ed831a5 100644 --- a/.github/workflows/fast-revert.yml +++ b/.github/workflows/fast-revert.yml @@ -19,7 +19,7 @@ jobs: if: | github.event_name == 'workflow_dispatch' || github.event.label.name == 'Trigger: Revert' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: token: ${{ secrets.BUMP_SENTRY_TOKEN }} - uses: getsentry/action-fast-revert@v2.0.1 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index f8169727aff..35e1183b6e3 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -5,12 +5,15 @@ on: push: branches: [master] +permissions: + contents: read + jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: python-version: 3.x - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4f6156bc1c..d4bacd7aed1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,21 +13,29 @@ on: # 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: - - uses: actions/checkout@v4 + - name: Get auth token + id: token + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 with: - token: ${{ secrets.GH_RELEASE_PAT }} + app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }} + private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }} + - uses: actions/checkout@v5 + with: + token: ${{ steps.token.outputs.token }} fetch-depth: 0 - name: Prepare release id: prepare-release uses: getsentry/action-prepare-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GH_RELEASE_PAT }} + GITHUB_TOKEN: ${{ steps.token.outputs.token }} with: version: ${{ github.event.inputs.version }} force: ${{ github.event.inputs.force }} @@ -40,11 +48,10 @@ jobs: name: Create release on self-hosted dogfood instance needs: release steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: - token: ${{ secrets.GH_RELEASE_PAT }} fetch-depth: 0 - - uses: getsentry/action-release@v1 + - uses: getsentry/action-release@v3 env: SENTRY_ORG: self-hosted SENTRY_PROJECT: installer diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 00000000000..692da9f4a8f --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,46 @@ +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@v5 + 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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b264e451441..49298277c4f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,3 +1,4 @@ + name: Test on: # Run CI on all pushes to the master and release/** branches, and on all new @@ -10,127 +11,66 @@ on: 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: - e2e-test: + unit-test: if: github.repository_owner == 'getsentry' - runs-on: ubuntu-22.04 - name: "Sentry self-hosted end-to-end tests" + 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@v4 - with: - path: self-hosted + uses: actions/checkout@v5 - name: Get Compose - run: | - # Always remove `docker compose` support as that's the newer version - # and comes installed by default nowadays. - sudo rm -f "/usr/local/lib/docker/cli-plugins/docker-compose" - # 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/v2.7.0/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" - - - name: End to end tests - uses: getsentry/action-self-hosted-e2e-tests@main - with: - project_name: self-hosted - - unit-test: - if: github.repository_owner == 'getsentry' - runs-on: ubuntu-22.04 - name: "unit tests" - steps: - - name: Checkout - uses: actions/checkout@v4 + uses: ./get-compose-action - name: Unit Tests run: ./unit-test.sh integration-test: if: github.repository_owner == 'getsentry' - runs-on: ubuntu-22.04 - name: integration test ${{ matrix.compose_version }} - customizations ${{ matrix.customizations }} + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - customizations: ["disabled", "enabled"] - compose_version: ["v2.0.1", "v2.26.0"] - include: - - compose_version: "v2.0.1" - compose_path: "/usr/local/lib/docker/cli-plugins" - - compose_version: "v2.26.0" - compose_path: "/usr/local/lib/docker/cli-plugins" + 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: - COMPOSE_PROJECT_NAME: self-hosted-${{ strategy.job-index }} 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@v4 - - - name: Setup dev environment - run: | - pip install -r requirements-dev.txt - 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: Get Compose - run: | - # Always remove `docker compose` support as that's the newer version - # and comes installed by default nowadays. - sudo rm -f "/usr/local/lib/docker/cli-plugins/docker-compose" - # Docker Compose v1 is installed here, remove it - sudo rm -f "/usr/local/bin/docker-compose" - sudo rm -f "${{ matrix.compose_path }}/docker-compose" - sudo mkdir -p "${{ matrix.compose_path }}" - sudo curl -L https://github.com/docker/compose/releases/download/${{ matrix.compose_version }}/docker-compose-`uname -s`-`uname -m` -o "${{ matrix.compose_path }}/docker-compose" - sudo chmod +x "${{ matrix.compose_path }}/docker-compose" - - - name: Install self-hosted - uses: nick-fields/retry@v3 - with: - timeout_minutes: 10 - max_attempts: 3 - command: ./install.sh + uses: actions/checkout@v5 - - name: Integration Test + - name: Install Podman + if: matrix.container_engine == 'podman' run: | - if [ "${{ matrix.compose_version }}" = "v2.0.1" ]; then - pytest --reruns 3 --cov --junitxml=junit.xml _integration-test/ --customizations=${{ matrix.customizations }} - else - pytest --cov --junitxml=junit.xml _integration-test/ --customizations=${{ matrix.customizations }} - fi - - - name: Inspect failure - if: failure() - run: | - docker compose ps - docker compose logs - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: getsentry/self-hosted + 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: Upload test results to Codecov - if: ${{ !cancelled() }} - uses: codecov/test-results-action@v1 + - name: Use action from local checkout + uses: './' with: - token: ${{ secrets.CODECOV_TOKEN }} + compose_profiles: ${{ matrix.compose_profiles }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 7311a09ae9c..26d3ff590b1 100644 --- a/.gitignore +++ b/.gitignore @@ -89,6 +89,7 @@ data/ sentry/sentry.conf.py sentry/config.yml sentry/*.bak +sentry/backup.json sentry/enhance-image.sh sentry/requirements.txt relay/credentials.json @@ -98,9 +99,6 @@ geoip/GeoIP.conf geoip/*.mmdb geoip/.geoipupdate.lock -# wal2json download -postgres/wal2json - # integration testing _integration-test/custom-ca-roots/nginx/* sentry/test-custom-ca-roots.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1d995372865..9d544410bab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: '\.patch$' repos: - repo: local hooks: @@ -11,7 +12,6 @@ repos: 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: diff --git a/CHANGELOG.md b/CHANGELOG.md index d3bcd69c13d..811ebf38e5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,356 @@ # Changelog +## 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49120afa854..3c3e987b2a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,13 @@ ## Testing -Validate changes to the setup by running the integration test: +### Running Tests with Pytest -```shell -./integration-test.sh +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. diff --git a/README.md b/README.md index 13f4f2b8934..a8f1fef5b1a 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,5 @@ -# Self-Hosted Sentry nightly +# Self-Hosted Sentry -Official bootstrap for running your own [Sentry](https://sentry.io/) with [Docker](https://www.docker.com/). +[Sentry](https://sentry.io/), feature-complete and packaged up for low-volume deployments and proofs-of-concept. -## Requirements - -* Docker 19.03.6+ -* Compose 2.0.1+ -* 4 CPU Cores -* 16 GB RAM -* 20 GB Free Disk Space - -## Setup - -### Installation - -To get started with all the defaults, simply clone the repo and run `./install.sh` in your local check-out. Please also read the section below about monitoring. Sentry uses Python 3 by default since December 4th, 2020 and Sentry 21.1.0 is the last version to support Python 2. - -During the install, a prompt will ask if you want to create a user account. If you require that the install goes on without creating a user, run `./install.sh --skip-user-creation`. - -Thinking of not managing this yourself? Check out the [SaaS migration docs](https://docs.sentry.io/product/sentry-basics/migration/) or [contact us](https://sentry.io/from/self-hosted) for help. - -Please visit [our documentation](https://develop.sentry.dev/self-hosted/) for everything else. - -### Customize DotEnv (.env) file - -Environment specific configurations can be done in the `.env.custom` file. It will be located in the root directory of the Sentry installation, and if it exists then `.env` will be ignored entirely. - -By default, there exists no `.env.custom` file. In this case, you can manually add this file by copying the `.env` file to a new `.env.custom` file and adjust your settings in the `.env.custom` file. - -Please keep in mind to check the `.env` file for changes, when you perform an upgrade of Sentry, so that you can adjust your `.env.custom` accordingly, if required, as `.env` is ignored entirely if `.env.custom` is present. - -### Enhance Sentry image - -To install plugins and their dependencies or make other modifications to the Sentry base image, -copy `sentry/enhance-image.example.sh` to `sentry/enhance-image.sh` and add necessary steps there. -For example, you can use `apt-get` to install dependencies and use `pip` to install plugins. - -After making modifications to `sentry/enhance-image.sh`, run `./install.sh` again to apply them. - -## Tips & Tricks - -### Event Retention - -Sentry comes with a cleanup cron job that prunes events older than `90 days` by default. If you want to change that, you can change the `SENTRY_EVENT_RETENTION_DAYS` environment variable in `.env` or simply override it in your environment. If you do not want the cleanup cron, you can remove the `sentry-cleanup` service from the `docker-compose.yml`file. - -### Installing a specific SHA - -If you want to install a specific release of Sentry, use the tags/releases on this repo. - -We continuously push the Docker image for each commit made into [Sentry](https://github.com/getsentry/sentry), and other services such as [Snuba](https://github.com/getsentry/snuba) or [Symbolicator](https://github.com/getsentry/symbolicator) to [our Docker Hub](https://hub.docker.com/u/getsentry) and tag the latest version on master as `:nightly`. This is also usually what we have on sentry.io and what the install script uses. You can use a custom Sentry image, such as a modified version that you have built on your own, or simply a specific commit hash by setting the `SENTRY_IMAGE` environment variable to that image name before running `./install.sh`: - -```shell -SENTRY_IMAGE=getsentry/sentry:83b1380 ./install.sh -``` - -Note that this may not work for all commit SHAs as this repository evolves with Sentry and its satellite projects. It is highly recommended to check out a version of this repository that is close to the timestamp of the Sentry commit you are installing. - -### Using Linux - -If you are using Linux and you need to use `sudo` when running `./install.sh`, make sure to place the environment variable *after* `sudo`: - -```shell -sudo SENTRY_IMAGE=us.gcr.io/sentryio/sentry:83b1380 ./install.sh -``` - -Where you replace `83b1380` with the sha you want to use. - -### Self-Hosted Monitoring - -We'd love to catch errors in self-hosted so you don't run into them, and so we can fix them faster! When you run `./install.sh`, you will be prompted to select whether to opt in or out of our monitoring. If you opt into our monitoring, we will send information to our own self-hosted Sentry instance for development and debugging purposes. We may collect: - -- OS username -- IP address -- install log -- runtime errors in Sentry -- performance data - -Thirty (30) day retention. No marketing. Privacy policy at sentry.io/privacy. - -Starting with the 22.10.0 release in October, we will require those running the Sentry installer to choose to opt in or out. If you are running the installer under automation, you may want to set `REPORT_SELF_HOSTED_ISSUES` or pass `--(no-)report-self-hosted-issues` to the installer accordingly. +Documentation [here](https://develop.sentry.dev/self-hosted/). diff --git a/_integration-test/conftest.py b/_integration-test/conftest.py index 9d2a0fff236..e73a7f286c5 100644 --- a/_integration-test/conftest.py +++ b/_integration-test/conftest.py @@ -1,58 +1,22 @@ import os +from os.path import join import subprocess -import time -import httpx 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" -TIMEOUT_SECONDS = 60 - - -def pytest_addoption(parser): - parser.addoption("--customizations", default="disabled") @pytest.fixture(scope="session", autouse=True) def configure_self_hosted_environment(request): subprocess.run( - ["docker", "compose", "--ansi", "never", "up", "-d"], + ["docker", "compose", "--ansi", "never", "up", "--wait"], check=True, capture_output=True, ) - for i in range(TIMEOUT_SECONDS): - try: - response = httpx.get(SENTRY_TEST_HOST, follow_redirects=True) - except httpx.RequestError: - time.sleep(1) - else: - if response.status_code == 200: - break - else: - raise AssertionError("timeout waiting for self-hosted to come up") - - if request.config.getoption("--customizations") == "enabled": - os.environ["TEST_CUSTOMIZATIONS"] = "enabled" - script_content = """\ -#!/bin/bash -touch /created-by-enhance-image -apt-get update -apt-get install -y gcc libsasl2-dev python-dev libldap2-dev libssl-dev -""" - - with open("sentry/enhance-image.sh", "w") as script_file: - script_file.write(script_content) - # Set executable permissions for the shell script - os.chmod("sentry/enhance-image.sh", 0o755) - - # Write content to the requirements.txt file - with open("sentry/requirements.txt", "w") as req_file: - req_file.write("python-ldap\n") - os.environ["MINIMIZE_DOWNTIME"] = "1" - subprocess.run(["./install.sh"], check=True) # Create test user subprocess.run( [ diff --git a/_integration-test/custom-ca-roots/test.py b/_integration-test/custom-ca-roots/custom-ca-roots-test.py similarity index 100% rename from _integration-test/custom-ca-roots/test.py rename to _integration-test/custom-ca-roots/custom-ca-roots-test.py diff --git a/_integration-test/test_run.py b/_integration-test/test_01_basics.py similarity index 82% rename from _integration-test/test_run.py rename to _integration-test/test_01_basics.py index 84bc027c2b3..a552988baa1 100644 --- a/_integration-test/test_run.py +++ b/_integration-test/test_01_basics.py @@ -11,6 +11,7 @@ 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 @@ -22,7 +23,7 @@ SENTRY_TEST_HOST = os.getenv("SENTRY_TEST_HOST", "http://localhost:9000/") TEST_USER = "test@example.com" TEST_PASS = "test123TEST" -TIMEOUT_SECONDS = 60 +TIMEOUT_SECONDS = 120 def poll_for_response( @@ -80,6 +81,14 @@ def test_initial_redirect(): 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") @@ -115,8 +124,10 @@ def test_login(client_login): def test_receive_event(client_login): event_id = None client, _ = client_login - with sentry_sdk.init(dsn=get_sentry_dsn(client)): - event_id = sentry_sdk.capture_exception(Exception("a failure")) + + 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 @@ -169,8 +180,10 @@ def test_custom_certificate_authorities(): .issuer_name(ca_name) .public_key(ca_key.public_key()) .serial_number(x509.random_serial_number()) - .not_valid_before(datetime.datetime.utcnow()) - .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=1)) + .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( @@ -313,12 +326,20 @@ def test_custom_certificate_authorities(): 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/test.py", + "_integration-test/custom-ca-roots/custom-ca-roots-test.py", "sentry/test-custom-ca-roots.py", ) subprocess.run( - ["docker", "compose", "--ansi", "never", "up", "-d", "fixture-custom-ca-roots"], + [ + "docker", + "compose", + "--ansi", + "never", + "up", + "--wait", + "fixture-custom-ca-roots", + ], check=True, ) subprocess.run( @@ -359,32 +380,63 @@ def test_custom_certificate_authorities(): 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 - with sentry_sdk.init( + 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) + 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() - 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=spansIndexed&field=id&project=1&statsPeriod=1h", + 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_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 = [ [ @@ -440,7 +492,4 @@ def test_customizations(): ] for command in commands: result = subprocess.run(command, check=False) - if os.getenv("TEST_CUSTOMIZATIONS", "disabled") == "enabled": - assert result.returncode == 0 - else: - assert result.returncode != 0 + assert result.returncode == 0 diff --git a/_integration-test/test_backup.py b/_integration-test/test_02_backup.py similarity index 52% rename from _integration-test/test_backup.py rename to _integration-test/test_02_backup.py index 41c099741a2..ad42824524e 100644 --- a/_integration-test/test_backup.py +++ b/_integration-test/test_02_backup.py @@ -1,4 +1,5 @@ import os +from os.path import join import subprocess @@ -19,8 +20,8 @@ def test_sentry_admin(setup_backup_restore_env_variables): assert "Usage: ./sentry-admin.sh permissions" in output -def test_backup(setup_backup_restore_env_variables): - # Docker was giving me permissioning issues when trying to create this file and write to it even after giving read + write access +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") @@ -40,26 +41,31 @@ def test_backup(setup_backup_restore_env_variables): assert os.path.getsize(file_path) > 0 -def test_import(setup_backup_restore_env_variables): +def test_02_import(setup_backup_restore_env_variables): # Bring postgres down and recreate the docker volume - subprocess.run( - ["docker", "compose", "--ansi", "never", "stop", "postgres"], check=True - ) - subprocess.run( - ["docker", "compose", "--ansi", "never", "rm", "-f", "-v", "postgres"], - check=True, - ) - subprocess.run(["docker", "volume", "rm", "sentry-postgres"], check=True) - subprocess.run(["docker", "volume", "create", "--name=sentry-postgres"], check=True) - subprocess.run( - ["docker", "compose", "--ansi", "never", "run", "web", "upgrade", "--noinput"], - check=True, - ) - subprocess.run( - ["docker", "compose", "--ansi", "never", "up", "-d"], - check=True, - capture_output=True, - ) + 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( [ @@ -71,3 +77,4 @@ def test_import(setup_backup_restore_env_variables): ], check=True, ) + # TODO: Check something actually restored here like the test user we have from earlier diff --git a/_unit-test/bootstrap-s3-seaweed-test.sh b/_unit-test/bootstrap-s3-seaweed-test.sh new file mode 100755 index 00000000000..2bd052f04c8 --- /dev/null +++ b/_unit-test/bootstrap-s3-seaweed-test.sh @@ -0,0 +1,16 @@ +#!/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 diff --git a/_unit-test/create-docker-volumes-test.sh b/_unit-test/create-docker-volumes-test.sh index 6b1176c5c73..5de88e420f7 100755 --- a/_unit-test/create-docker-volumes-test.sh +++ b/_unit-test/create-docker-volumes-test.sh @@ -14,8 +14,7 @@ sentry-data sentry-kafka sentry-postgres sentry-redis -sentry-symbolicator -sentry-zookeeper" +sentry-seaweedfs" before=$(get_volumes) diff --git a/_unit-test/js-sdk-assets-test.sh b/_unit-test/js-sdk-assets-test.sh new file mode 100755 index 00000000000..c46c7a51e5f --- /dev/null +++ b/_unit-test/js-sdk-assets-test.sh @@ -0,0 +1,40 @@ +#!/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 5 lines, '4.*', '5.*', '6.*', `7.*` and `8.*` +echo $sdk_files +total_directories=$(echo "$sdk_files" | grep -c '[45678]\.[0-9]*\.[0-9]*$') +echo $total_directories +test "5" == "$total_directories" +echo "Pass" + +# `sdk_tree` should output "6 directories, 23 files" +echo "$sdk_tree" +test "6 directories, 23 files" == "$(echo "$sdk_tree")" +echo "Pass" + +# Files should all be >1k (ensure they are not empty) +echo "Testing file sizes" +test "23" == "$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" == "8" +echo "Pass" + +report_success diff --git a/_unit-test/merge-env-file-test.sh b/_unit-test/merge-env-file-test.sh new file mode 100755 index 00000000000..156110d0060 --- /dev/null +++ b/_unit-test/merge-env-file-test.sh @@ -0,0 +1,25 @@ +#!/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 diff --git a/_unit-test/migrate-pgbouncer-test.sh b/_unit-test/migrate-pgbouncer-test.sh new file mode 100755 index 00000000000..b78ee633c04 --- /dev/null +++ b/_unit-test/migrate-pgbouncer-test.sh @@ -0,0 +1,197 @@ +#!/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 diff --git a/action.yaml b/action.yaml new file mode 100644 index 00000000000..7f7a98f84fc --- /dev/null +++ b/action.yaml @@ -0,0 +1,215 @@ +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 + + - name: Setup dev environment + shell: bash + run: | + cd ${{ github.action_path }} + pip install -r requirements-dev.txt + 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: Get Compose + uses: getsentry/self-hosted/get-compose-action@master + + - 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: 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@v5 + 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@v1 + 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::" diff --git a/clickhouse/config.xml b/clickhouse/config.xml index d26bfbb3850..28ec384cb62 100644 --- a/clickhouse/config.xml +++ b/clickhouse/config.xml @@ -1,10 +1,6 @@ - - - - + + warning true @@ -20,6 +16,8 @@ + 1 + 0 diff --git a/clickhouse/default-password.xml b/clickhouse/default-password.xml new file mode 100644 index 00000000000..13588039cba --- /dev/null +++ b/clickhouse/default-password.xml @@ -0,0 +1,10 @@ + + + + + + ::/0 + + + + diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000000..ed9aed0e588 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +coverage: + status: + project: + default: + only_pulls: true + patch: + default: + only_pulls: true diff --git a/cron/Dockerfile b/cron/Dockerfile index c14304eb89a..26c46a6f68f 100644 --- a/cron/Dockerfile +++ b/cron/Dockerfile @@ -1,6 +1,8 @@ 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 && \ diff --git a/docker-compose.yml b/docker-compose.yml index 0736bdb63c0..e7400ae61a3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,7 @@ 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 @@ -13,9 +15,15 @@ x-healthcheck-defaults: &healthcheck_defaults interval: "$HEALTHCHECK_INTERVAL" timeout: "$HEALTHCHECK_TIMEOUT" retries: $HEALTHCHECK_RETRIES - start_period: 10s + 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 + <<: [*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:-} @@ -28,32 +36,18 @@ x-sentry-defaults: &sentry_defaults <<: *depends_on-healthy kafka: <<: *depends_on-healthy - postgres: + pgbouncer: <<: *depends_on-healthy memcached: <<: *depends_on-default smtp: <<: *depends_on-default - snuba-api: - <<: *depends_on-default - snuba-errors-consumer: - <<: *depends_on-default - snuba-outcomes-consumer: - <<: *depends_on-default - snuba-outcomes-billing-consumer: - <<: *depends_on-default - snuba-transactions-consumer: + seaweedfs: <<: *depends_on-default - snuba-subscription-consumer-events: - <<: *depends_on-default - snuba-subscription-consumer-transactions: - <<: *depends_on-default - snuba-replacer: + snuba-api: <<: *depends_on-default symbolicator: <<: *depends_on-default - vroom: - <<: *depends_on-default entrypoint: "/etc/sentry/entrypoint.sh" command: ["run", "web"] environment: @@ -71,10 +65,10 @@ x-sentry-defaults: &sentry_defaults 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: - OPENAI_API_KEY: volumes: - "sentry-data:/data" - "./sentry:/etc/sentry" @@ -100,15 +94,19 @@ x-snuba-defaults: &snuba_defaults # 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_HOST: # Example value: "100.100.123.123". Must be an IP address, not domain name + SNUBA_STATSD_PORT: # Example value: 8125 services: smtp: <<: *restart_policy - platform: linux/amd64 - image: tianon/exim4 - hostname: "${SENTRY_MAIL_HOST:-}" + image: registry.gitlab.com/egos-tech/smtp volumes: - "sentry-smtp:/var/spool/exim4" - "sentry-smtp-log:/var/log/exim4" + environment: + - MAILNAME=${SENTRY_MAIL_HOST:-} memcached: <<: *restart_policy image: "memcached:1.6.26-alpine" @@ -119,82 +117,75 @@ services: test: echo stats | nc 127.0.0.1 11211 redis: <<: *restart_policy - image: "redis:6.2.14-alpine" + image: "redis:6.2.20-alpine" healthcheck: <<: *healthcheck_defaults - test: redis-cli ping + 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.11-alpine" + 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", - "-c", - "wal_level=logical", - "-c", - "max_replication_slots=1", - "-c", - "max_wal_senders=1", - "-c", - "max_connections=${POSTGRES_MAX_CONNECTIONS:-100}", - ] + ["postgres"] environment: POSTGRES_HOST_AUTH_METHOD: "trust" - entrypoint: /opt/sentry/postgres-entrypoint.sh volumes: - "sentry-postgres:/var/lib/postgresql/data" - - type: bind - read_only: true - source: ./postgres/ - target: /opt/sentry/ - zookeeper: + pgbouncer: <<: *restart_policy - image: "confluentinc/cp-zookeeper:5.5.7" - environment: - ZOOKEEPER_CLIENT_PORT: "2181" - CONFLUENT_SUPPORT_METRICS_ENABLE: "false" - ZOOKEEPER_LOG4J_ROOT_LOGLEVEL: "WARN" - ZOOKEEPER_TOOLS_LOG4J_LOGLEVEL: "WARN" - KAFKA_OPTS: "-Dzookeeper.4lw.commands.whitelist=ruok" - ulimits: - nofile: - soft: 4096 - hard: 4096 - volumes: - - "sentry-zookeeper:/var/lib/zookeeper/data" - - "sentry-zookeeper-log:/var/lib/zookeeper/log" - - "sentry-secrets:/etc/zookeeper/secrets" + image: "edoburu/pgbouncer:v1.24.1-p1" healthcheck: <<: *healthcheck_defaults - test: - ["CMD-SHELL", 'echo "ruok" | nc -w 2 localhost 2181 | grep imok'] - kafka: - <<: *restart_policy + # 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: - zookeeper: + postgres: <<: *depends_on-healthy - image: "confluentinc/cp-kafka:5.5.7" environment: - KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" - KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka:9092" + 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,kafka.zookeeper=WARN,state.change.logger=WARN" + 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: @@ -212,12 +203,12 @@ services: timeout: 10s retries: 30 clickhouse: - <<: *restart_policy + <<: [*restart_policy, *pull_policy] image: clickhouse-self-hosted-local build: context: ./clickhouse args: - BASE_IMAGE: "${CLICKHOUSE_IMAGE:-}" + BASE_IMAGE: "altinity/clickhouse-server:25.3.6.10034.altinitystable" ulimits: nofile: soft: 262144 @@ -225,10 +216,14 @@ services: volumes: - "sentry-clickhouse:/var/lib/clickhouse" - "sentry-clickhouse-log:/var/log/clickhouse-server" - - type: bind + - type: "bind" read_only: true - source: ./clickhouse/config.xml - target: /etc/clickhouse-server/config.d/sentry.xml + 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 @@ -240,100 +235,264 @@ services: # 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 -nv -t1 --spider 'http://localhost:8123/' || exit 1", + "HTTP_PROXY='' http_proxy='' wget -nv -t1 --spider 'http://localhost:8123/' || exit 1", ] interval: 10s timeout: 10s retries: 30 - geoipupdate: - image: "ghcr.io/maxmind/geoipupdate:v6.1.0" - # Override the entrypoint in order to avoid using envvars for config. - # Futz with settings so we can keep mmdb and conf in same dir on host - # (image looks for them in separate dirs by default). - entrypoint: ["/usr/bin/geoipupdate", "-d", "/sentry", "-f", "/sentry/GeoIP.conf"] + seaweedfs: + <<: *restart_policy + image: "chrislusf/seaweedfs:3.96_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: - - "./geoip:/sentry" + - "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 --no-skip-write + 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 --no-skip-write + 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 --no-skip-write --raw-events-topic outcomes-billing + 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 --no-skip-write + 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 --no-skip-write + 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 --no-skip-write + 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 --no-skip-write - snuba-group-attributes-consumer: + 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: rust-consumer --storage group_attributes --consumer-group snuba-group-attributes-consumers --auto-offset-reset=latest --max-batch-time-ms 750 --no-strict-offset-reset --no-skip-write + 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 --no-skip-write + 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 --no-skip-write + 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 --no-skip-write - snuba-replacer: + 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: replacer --storage errors --auto-offset-reset=latest --no-strict-offset-reset - snuba-subscription-consumer-events: + 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: 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 - snuba-subscription-consumer-transactions: + 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: 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 - snuba-subscription-consumer-metrics: + 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: 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 - snuba-profiling-profiles-consumer: + 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 profiles --consumer-group snuba-consumers --auto-offset-reset=latest --max-batch-time-ms 1000 --no-strict-offset-reset --no-skip-write - snuba-profiling-functions-consumer: + 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: rust-consumer --storage functions_raw --consumer-group snuba-consumers --auto-offset-reset=latest --max-batch-time-ms 1000 --no-strict-offset-reset --no-skip-write - snuba-spans-consumer: + 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 + snuba-uptime-results-consumer: <<: *snuba_defaults - command: rust-consumer --storage spans --consumer-group snuba-spans-consumers --auto-offset-reset=latest --max-batch-time-ms 1000 --no-strict-offset-reset --no-skip-write + command: rust-consumer --storage uptime_monitor_checks --consumer-group snuba-uptime-results --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 symbolicator: <<: *restart_policy image: "$SYMBOLICATOR_IMAGE" + command: run -c /etc/symbolicator/config.yml volumes: - "sentry-symbolicator:/data" - type: bind read_only: true source: ./symbolicator target: /etc/symbolicator - command: run -c /etc/symbolicator/config.yml + healthcheck: + <<: *healthcheck_defaults + test: ["CMD", "/bin/symbolicator", "healthcheck", "-c", "/etc/symbolicator/config.yml"] symbolicator-cleanup: <<: *restart_policy - image: symbolicator-cleanup-self-hosted-local - build: - context: ./cron - args: - BASE_IMAGE: "$SYMBOLICATOR_IMAGE" - command: '"55 23 * * * gosu symbolicator symbolicator cleanup"' + image: "$SYMBOLICATOR_IMAGE" + 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: @@ -348,63 +507,169 @@ services: - "-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' - cron: - <<: *sentry_defaults - command: run cron - worker: - <<: *sentry_defaults - command: run worker events-consumer: <<: *sentry_defaults - command: run consumer ingest-events --consumer-group ingest-consumer + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 --no-strict-offset-reset ingest-profiles --consumer-group ingest-profiles + 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 --no-strict-offset-reset ingest-monitors --consumer-group ingest-monitors - post-process-forwarder-errors: + 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 post-process-forwarder-errors --consumer-group post-process-forwarder --synchronize-commit-log-topic=snuba-commit-log --synchronize-commit-group=snuba-consumers + 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 post-process-forwarder-transactions --consumer-group post-process-forwarder --synchronize-commit-log-topic=snuba-transactions-commit-log --synchronize-commit-group transactions_group + 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 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 - subscription-consumer-events: - <<: *sentry_defaults - command: run consumer events-subscription-results --consumer-group query-subscription-consumer + 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 + 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 + 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 + 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 @@ -418,16 +683,27 @@ services: <<: *restart_policy ports: - "$SENTRY_BIND:80/tcp" - image: "nginx:1.25.4-alpine" + image: "nginx:1.29.1-alpine" volumes: - type: bind read_only: true - source: ./nginx - target: /etc/nginx + 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 - - relay + web: + <<: *depends_on-healthy + restart: true + relay: + <<: *depends_on-healthy + restart: true relay: <<: *restart_policy image: "$RELAY_IMAGE" @@ -447,21 +723,54 @@ services: <<: *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" + 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=4 --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: file://localhost//var/lib/sentry-profiles + SENTRY_BUCKET_PROFILES: file:///var/vroom/sentry-profiles SENTRY_SNUBA_HOST: "http://snuba-api:1218/" volumes: - - sentry-vroom:/var/lib/sentry-profiles + - 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 + <<: [*restart_policy, *pull_policy] image: vroom-cleanup-self-hosted-local build: context: ./cron @@ -472,9 +781,33 @@ services: # 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/lib/sentry-profiles -type f -mtime +$SENTRY_EVENT_RETENTION_DAYS -delete"' + command: '"0 0 * * * find /var/vroom/sentry-profiles -type f -mtime +$SENTRY_EVENT_RETENTION_DAYS -delete"' volumes: - - sentry-vroom:/var/lib/sentry-profiles + - 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" + depends_on: + kafka: + <<: *depends_on-healthy + redis: + <<: *depends_on-healthy + profiles: + - feature-complete volumes: # These store application data that should persist across restarts. @@ -484,24 +817,31 @@ volumes: external: true sentry-redis: external: true - sentry-zookeeper: - external: true sentry-kafka: external: true sentry-clickhouse: external: true - sentry-symbolicator: + 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-zookeeper-log: sentry-kafka-log: sentry-smtp-log: sentry-clickhouse-log: diff --git a/get-compose-action/action.yaml b/get-compose-action/action.yaml new file mode 100644 index 00000000000..7fac08d3489 --- /dev/null +++ b/get-compose-action/action.yaml @@ -0,0 +1,19 @@ +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 + 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${{ inputs.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" diff --git a/install.sh b/install.sh index 78a96fece61..c6b3a62a178 100755 --- a/install.sh +++ b/install.sh @@ -1,12 +1,17 @@ #!/usr/bin/env bash -set -eE +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 +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. @@ -20,6 +25,9 @@ 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/turn-things-off.sh source install/create-docker-volumes.sh source install/ensure-files-from-examples.sh @@ -28,10 +36,11 @@ source install/ensure-relay-credentials.sh source install/generate-secret-key.sh source install/update-docker-images.sh source install/build-docker-images.sh -source install/install-wal2json.sh +source install/bootstrap-s3-nodestore.sh source install/bootstrap-snuba.sh -source install/create-kafka-topics.sh source install/upgrade-postgres.sh +source install/ensure-correct-permissions-profiles-dir.sh source install/set-up-and-migrate-database.sh source install/geoip.sh +source install/setup-js-sdk-assets.sh source install/wrap-up.sh diff --git a/install/_detect-container-engine.sh b/install/_detect-container-engine.sh new file mode 100644 index 00000000000..7fc23de2e2e --- /dev/null +++ b/install/_detect-container-engine.sh @@ -0,0 +1,12 @@ +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}" diff --git a/install/_lib.sh b/install/_lib.sh index 3a76d8624f0..29097049576 100644 --- a/install/_lib.sh +++ b/install/_lib.sh @@ -1,13 +1,3 @@ -set -euo pipefail -test "${DEBUG:-}" && set -x - -# Override any user-supplied umask that could cause problems, see #1222 -umask 002 - -# 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") - # Allow `.env` overrides using the `.env.custom` file. # We pass this to docker compose in a couple places. if [[ -f .env.custom ]]; then @@ -16,8 +6,16 @@ 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 +t=$(mktemp) && export -p >"$t" && set -a && . ".env" && set +a && . "$t" && rm "$t" && unset t if [ "${GITHUB_ACTIONS:-}" = "true" ]; then _group="::group::" @@ -35,6 +33,7 @@ function ensure_file_from_example { 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." @@ -45,11 +44,16 @@ function ensure_file_from_example { fi } -SENTRY_CONFIG_PY=sentry/sentry.conf.py -SENTRY_CONFIG_YML=sentry/config.yml +# 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 celery queues are properly drained +# to ensure task queues are properly drained # between upgrades as task signatures may change across # versions -STOP_TIMEOUT=60 # seconds +export STOP_TIMEOUT=60 # seconds diff --git a/install/_logging.sh b/install/_logging.sh new file mode 100644 index 00000000000..ea529dade98 --- /dev/null +++ b/install/_logging.sh @@ -0,0 +1,3 @@ +# 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") diff --git a/install/_min-requirements.sh b/install/_min-requirements.sh index e756c765d80..2ffded50700 100644 --- a/install/_min-requirements.sh +++ b/install/_min-requirements.sh @@ -1,7 +1,18 @@ -# Don't forget to update the README and othes docs when you change these! +# Don't forget to update the README and other docs when you change these! MIN_DOCKER_VERSION='19.03.6' -MIN_COMPOSE_VERSION='2.0.1' -MIN_RAM_HARD=3800 # MB -MIN_RAM_SOFT=7800 # MB -MIN_CPU_HARD=2 -MIN_CPU_SOFT=4 +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 diff --git a/install/bootstrap-s3-nodestore.sh b/install/bootstrap-s3-nodestore.sh new file mode 100644 index 00000000000..c2cf91669f5 --- /dev/null +++ b/install/bootstrap-s3-nodestore.sh @@ -0,0 +1,91 @@ +echo "${_group}Bootstrapping seaweedfs (node store)..." + +$dc up --wait seaweedfs postgres +$dc exec -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:-}" 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" | tail -1 | awk '{print $3}') != '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 + + # XXX(aldy505): Should we refactor this? + lifecycle_policy=$( + cat < + + + Sentry-Nodestore-Rule + Enabled + + + $SENTRY_EVENT_RETENTION_DAYS + + + +EOF + ) + $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 + + 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://nodestore +else + echo "Node store already exists, skipping..." +fi + +echo "${_endgroup}" diff --git a/install/bootstrap-snuba.sh b/install/bootstrap-snuba.sh index 2952ed0b33c..496becd6c57 100644 --- a/install/bootstrap-snuba.sh +++ b/install/bootstrap-snuba.sh @@ -1,6 +1,9 @@ echo "${_group}Bootstrapping and migrating Snuba ..." -$dcr snuba-api bootstrap --no-migrate --force -$dcr snuba-api migrations migrate --force +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}" diff --git a/install/build-docker-images.sh b/install/build-docker-images.sh index c793b9ea9c8..dce4c43ca4b 100644 --- a/install/build-docker-images.sh +++ b/install/build-docker-images.sh @@ -3,10 +3,10 @@ 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 --force-rm web +$dcb web # Build each other service individually to localize potential failures better. for service in $($dc config --services); do - $dcb --force-rm "$service" + $dcb "$service" done echo "" echo "Docker images built." diff --git a/install/check-minimum-requirements.sh b/install/check-minimum-requirements.sh index 01488e8d176..91e5116b6d3 100644 --- a/install/check-minimum-requirements.sh +++ b/install/check-minimum-requirements.sh @@ -2,62 +2,61 @@ echo "${_group}Checking minimum requirements ..." source install/_min-requirements.sh -# 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 - echo $? -} - -DOCKER_VERSION=$(docker version --format '{{.Server.Version}}' || echo '') +DOCKER_VERSION=$($CONTAINER_ENGINE version --format '{{.Server.Version}}' || echo '') if [[ -z "$DOCKER_VERSION" ]]; then - echo "FAIL: Unable to get docker version, is the docker daemon running?" - exit 1 -fi - -if [[ "$(vergte ${DOCKER_VERSION//v/} $MIN_DOCKER_VERSION)" -eq 1 ]]; then - echo "FAIL: Expected minimum docker version to be $MIN_DOCKER_VERSION but found $DOCKER_VERSION" - exit 1 -fi -echo "Found Docker version $DOCKER_VERSION" - -COMPOSE_VERSION=$($dc_base version --short || echo '') -if [[ -z "$COMPOSE_VERSION" ]]; then - echo "FAIL: Docker compose is required to run self-hosted" + echo "FAIL: Unable to get $CONTAINER_ENGINE version, is the $CONTAINER_ENGINE daemon running?" exit 1 fi -if [[ "$(vergte ${COMPOSE_VERSION//v/} $MIN_COMPOSE_VERSION)" -eq 1 ]]; then - echo "FAIL: Expected minimum $dc_base version to be $MIN_COMPOSE_VERSION but found $COMPOSE_VERSION" - exit 1 +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 Docker Compose version $COMPOSE_VERSION" +echo "Found $CONTAINER_ENGINE version $DOCKER_VERSION" +echo "Found $CONTAINER_ENGINE Compose version $COMPOSE_VERSION" -CPU_AVAILABLE_IN_DOCKER=$(docker run --rm busybox nproc --all) +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 -elif [[ "$CPU_AVAILABLE_IN_DOCKER" -lt "$MIN_CPU_SOFT" ]]; then - echo "WARN: Recommended minimum CPU cores available to Docker is $MIN_CPU_SOFT, found $CPU_AVAILABLE_IN_DOCKER" fi -RAM_AVAILABLE_IN_DOCKER=$(docker run --rm busybox free -m 2>/dev/null | awk '/Mem/ {print $2}') +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 -elif [[ "$RAM_AVAILABLE_IN_DOCKER" -lt "$MIN_RAM_SOFT" ]]; then - echo "WARN: Recommended minimum RAM available to Docker is $MIN_RAM_SOFT MB, found $RAM_AVAILABLE_IN_DOCKER MB" 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=$(docker run --rm busybox grep -c 'Common KVM processor' /proc/cpuinfo || :) +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=$(docker run --rm busybox grep -c sse4_2 /proc/cpuinfo || :) + 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}" diff --git a/install/create-docker-volumes.sh b/install/create-docker-volumes.sh index ca3ef0b23ed..9241caa0e1b 100644 --- a/install/create-docker-volumes.sh +++ b/install/create-docker-volumes.sh @@ -1,11 +1,21 @@ echo "${_group}Creating volumes for persistent storage ..." -echo "Created $(docker volume create --name=sentry-clickhouse)." -echo "Created $(docker volume create --name=sentry-data)." -echo "Created $(docker volume create --name=sentry-kafka)." -echo "Created $(docker volume create --name=sentry-postgres)." -echo "Created $(docker volume create --name=sentry-redis)." -echo "Created $(docker volume create --name=sentry-symbolicator)." -echo "Created $(docker volume create --name=sentry-zookeeper)." +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}" diff --git a/install/create-kafka-topics.sh b/install/create-kafka-topics.sh deleted file mode 100644 index 63e0cffa016..00000000000 --- a/install/create-kafka-topics.sh +++ /dev/null @@ -1,25 +0,0 @@ -echo "${_group}Creating additional Kafka topics ..." - -$dc up -d --no-build --no-recreate kafka - -while [ true ]; do - kafka_healthy=$($dc ps kafka | grep 'healthy') - if [ ! -z "$kafka_healthy" ]; then - break - fi - - echo "Kafka container is not healthy, waiting for 30 seconds. If this took too long, abort the installation process, and check your Kafka configuration" - sleep 30s -done - -# XXX(BYK): We cannot use auto.create.topics as Confluence and Apache hates it now (and makes it very hard to enable) -EXISTING_KAFKA_TOPICS=$($dc exec -T kafka kafka-topics --list --bootstrap-server kafka:9092 2>/dev/null) -NEEDED_KAFKA_TOPICS="ingest-attachments ingest-transactions ingest-events ingest-replay-recordings profiles ingest-occurrences ingest-metrics ingest-performance-metrics ingest-monitors" -for topic in $NEEDED_KAFKA_TOPICS; do - if ! echo "$EXISTING_KAFKA_TOPICS" | grep -qE "(^| )$topic( |$)"; then - $dc exec kafka kafka-topics --create --topic $topic --bootstrap-server kafka:9092 - echo "" - fi -done - -echo "${_endgroup}" diff --git a/install/dc-detect-version.sh b/install/dc-detect-version.sh index 64e814e1ecd..f7a4cbdda97 100644 --- a/install/dc-detect-version.sh +++ b/install/dc-detect-version.sh @@ -6,18 +6,86 @@ else _endgroup="" fi -echo "${_group}Initializing Docker Compose ..." +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="$(docker compose version &>/dev/null && echo 'docker compose' || echo '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 --ansi never --env-file ${_ENV}" + 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:-}" +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 - dc="$dc_base --ansi never" + proxy_args_dc=$proxy_args + dcr="$dc run --pull=never --rm" fi -proxy_args="--build-arg http_proxy=${http_proxy:-} --build-arg https_proxy=${https_proxy:-} --build-arg no_proxy=${no_proxy:-}" -dcr="$dc run --rm" dcb="$dc build $proxy_args" -dbuild="docker build $proxy_args" +dbuild="$CONTAINER_ENGINE build $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}" diff --git a/install/detect-platform.sh b/install/detect-platform.sh index 32ef5ea1cdf..9009f79b63d 100644 --- a/install/detect-platform.sh +++ b/install/detect-platform.sh @@ -1,3 +1,5 @@ +source install/_detect-container-engine.sh + echo "${_group}Detecting Docker platform" # Sentry SaaS uses stock Yandex ClickHouse, but they don't provide images that @@ -12,18 +14,16 @@ echo "${_group}Detecting Docker platform" # linux/amd64 by default due to virtualization. # See https://github.com/docker/cli/issues/3286 for the Docker bug. -if ! command -v docker &>/dev/null; then - echo "FAIL: Could not find a \`docker\` binary on this system. Are you sure it's installed?" - exit 1 +FORMAT="{{.Architecture}}" +if [[ $CONTAINER_ENGINE == "podman" ]]; then + FORMAT="{{.Host.Arch}}" fi -export DOCKER_ARCH=$(docker info --format '{{.Architecture}}') -if [[ "$DOCKER_ARCH" = "x86_64" ]]; then +export DOCKER_ARCH=$($CONTAINER_ENGINE info --format "$FORMAT") +if [[ "$DOCKER_ARCH" = "x86_64" || "$DOCKER_ARCH" = "amd64" ]]; then export DOCKER_PLATFORM="linux/amd64" - export CLICKHOUSE_IMAGE="altinity/clickhouse-server:21.8.13.1.altinitystable" elif [[ "$DOCKER_ARCH" = "aarch64" ]]; then export DOCKER_PLATFORM="linux/arm64" - export CLICKHOUSE_IMAGE="altinity/clickhouse-server:21.8.12.29.altinitydev.arm" else echo "FAIL: Unsupported docker architecture $DOCKER_ARCH." exit 1 diff --git a/install/ensure-correct-permissions-profiles-dir.sh b/install/ensure-correct-permissions-profiles-dir.sh new file mode 100755 index 00000000000..2fd95b885e6 --- /dev/null +++ b/install/ensure-correct-permissions-profiles-dir.sh @@ -0,0 +1,17 @@ +#!/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 diff --git a/install/error-handling.sh b/install/error-handling.sh index cbd0676858a..09aa1c2fe13 100644 --- a/install/error-handling.sh +++ b/install/error-handling.sh @@ -6,8 +6,8 @@ fi $dbuild -t sentry-self-hosted-jq-local --platform="$DOCKER_PLATFORM" jq -jq="docker run --rm -i sentry-self-hosted-jq-local" -sentry_cli="docker run --rm -v /tmp:/work -e SENTRY_DSN=$SENTRY_DSN getsentry/sentry-cli" +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 @@ -27,7 +27,7 @@ send_event() { local breadcrumbs=$5 local fingerprint_value=$( echo -n "$cmd_exit $error_msg $traceback" | - docker run -i --rm busybox md5sum | + $CONTAINER_ENGINE run -i --rm busybox md5sum | cut -d' ' -f1 ) local envelope_file="sentry-envelope-${fingerprint_value}" @@ -151,7 +151,7 @@ fi # Make sure we can use sentry-cli if we need it. if [ "$REPORT_SELF_HOSTED_ISSUES" == 1 ]; then - if ! docker pull getsentry/sentry-cli:latest; 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 diff --git a/install/geoip.sh b/install/geoip.sh index 577b6ba5ca5..9cea3082409 100644 --- a/install/geoip.sh +++ b/install/geoip.sh @@ -1,5 +1,18 @@ 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 @@ -21,7 +34,7 @@ install_geoip() { else echo "IP address geolocation is configured for updates." echo "Updating IP address geolocation database ... " - if ! $dcr geoipupdate; then + 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." diff --git a/install/install-wal2json.sh b/install/install-wal2json.sh deleted file mode 100644 index 9df0df7603e..00000000000 --- a/install/install-wal2json.sh +++ /dev/null @@ -1,45 +0,0 @@ -echo "${_group}Downloading and installing wal2json ..." - -WAL2JSON_DIR=postgres/wal2json -FILE_TO_USE="$WAL2JSON_DIR/wal2json.so" -ARCH=$(uname -m) -FILE_NAME="wal2json-Linux-$ARCH-glibc.so" - -docker_curl() { - # The environment variables can be specified in lower case or upper case. - # The lower case version has precedence. http_proxy is an exception as it is only available in lower case. - docker run --rm -e http_proxy -e https_proxy -e HTTPS_PROXY -e no_proxy -e NO_PROXY curlimages/curl:7.77.0 \ - --connect-timeout 5 \ - --max-time 10 \ - --retry 5 \ - --retry-max-time 60 \ - "$@" -} - -if [[ $WAL2JSON_VERSION == "latest" ]]; then - # Hard-code this. Super-hacky. We were curling the GitHub API here but - # hitting rate limits in CI. This library hasn't seen a new release for a - # year and a half at time of writing. - # - # If you're reading this do us a favor and go check: - # - # https://github.com/getsentry/wal2json/releases - # - # If there's a new release can you update this please? If not maybe subscribe - # for notifications on the repo with "Watch > Custom > Releases". Together we - # can make a difference. - VERSION=0.0.2 -else - VERSION=$WAL2JSON_VERSION -fi - -mkdir -p "$WAL2JSON_DIR" -if [ ! -f "$WAL2JSON_DIR/$VERSION/$FILE_NAME" ]; then - mkdir -p "$WAL2JSON_DIR/$VERSION" - docker_curl -L \ - "https://github.com/getsentry/wal2json/releases/download/$VERSION/$FILE_NAME" \ - >"$WAL2JSON_DIR/$VERSION/$FILE_NAME" -fi -cp "$WAL2JSON_DIR/$VERSION/$FILE_NAME" "$FILE_TO_USE" - -echo "${_endgroup}" diff --git a/install/migrate-pgbouncer.sh b/install/migrate-pgbouncer.sh new file mode 100644 index 00000000000..8c698c72d92 --- /dev/null +++ b/install/migrate-pgbouncer.sh @@ -0,0 +1,82 @@ +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}" diff --git a/install/parse-cli.sh b/install/parse-cli.sh index 0390b54f9e9..aec899c6398 100644 --- a/install/parse-cli.sh +++ b/install/parse-cli.sh @@ -4,7 +4,7 @@ show_help() { cat </dev/null 2>&1 || [ $RETRIES -eq 0 ]; do - echo "Waiting for postgres server, $((RETRIES--)) remaining attempts..." - sleep 1 -done +if [[ -z "${SKIP_SENTRY_MIGRATIONS:-}" ]]; then + # Fixes https://github.com/getsentry/self-hosted/issues/2758, where a migration fails due to indexing issue + start_service_and_wait_ready postgres + start_service_and_wait_ready pgbouncer -# Using django ORM to provide broader support for users with external databases -$dcr web shell -c " -from django.db import connection + os=$($dc exec postgres cat /etc/os-release | grep 'ID=debian') + if [[ -z $os ]]; then + echo "Postgres image debian check failed, exiting..." + exit 1 + fi -with connection.cursor() as cursor: - cursor.execute('ALTER TABLE IF EXISTS sentry_groupedmessage DROP CONSTRAINT IF EXISTS sentry_groupedmessage_project_id_id_515aaa7e_uniq;') - cursor.execute('DROP INDEX IF EXISTS sentry_groupedmessage_project_id_id_515aaa7e_uniq;') -" - -if [[ -n "${CI:-}" || "${SKIP_USER_CREATION:-0}" == 1 ]]; then - $dcr web upgrade --noinput - echo "" - echo "Did not prompt for user creation. Run the following command to create one" - echo "yourself (recommended):" - echo "" - echo " $dc_base run --rm web createuser" - echo "" + if [[ -n "${CI:-}" || "${SKIP_USER_CREATION:-0}" == 1 ]]; then + $dcr web upgrade --noinput --create-kafka-topics + echo "" + echo "Did not prompt for user creation. Run the following command to create one" + echo "yourself (recommended):" + echo "" + echo " $dc_base run --rm web createuser" + echo "" + else + $dcr web upgrade --create-kafka-topics + fi else - $dcr web upgrade + echo "Skipped DB migrations due to SKIP_SENTRY_MIGRATIONS=$SKIP_SENTRY_MIGRATIONS" fi - echo "${_endgroup}" diff --git a/install/setup-js-sdk-assets.sh b/install/setup-js-sdk-assets.sh new file mode 100644 index 00000000000..cfd53cfbb03 --- /dev/null +++ b/install/setup-js-sdk-assets.sh @@ -0,0 +1,46 @@ +# This will only run if the SETUP_JS_SDK_ASSETS environment variable is set to 1. +# Think of this as some kind of a feature flag. +if [[ "${SETUP_JS_SDK_ASSETS:-}" == "1" ]]; then + echo "${_group}Setting up JS SDK assets" + + # If the `sentry-nginx-www` volume exists, we need to prune the contents. + # We don't want to fill the volume with old JS SDK assets. + # If people want to keep the old assets, they can set the environment variable + # `SETUP_JS_SDK_KEEP_OLD_ASSETS` to any value. + if [[ -z "${SETUP_JS_SDK_KEEP_OLD_ASSETS:-}" ]]; then + echo "Cleaning up old JS SDK assets..." + $dcr --no-deps nginx rm -rf /var/www/js-sdk/* + fi + + $dbuild -t sentry-self-hosted-jq-local --platform="$DOCKER_PLATFORM" jq + + jq="$CONTAINER_ENGINE run --rm -i sentry-self-hosted-jq-local" + + loader_registry=$($dcr --no-deps --rm -T web cat /usr/src/sentry/src/sentry/loader/_registry.json) + # The `loader_registry` should start with "Updating certificates...", we want to delete that and the subsequent ca-certificates related lines. + # We want to remove everything before the first '{'. + loader_registry=$(echo "$loader_registry" | sed '0,/{/s/[^{]*//') + + # Sentry backend provides SDK versions from v4.x up to v8.x. + latest_js_v4=$(echo "$loader_registry" | $jq -r '.versions | reverse | map(select(.|any(.; startswith("4.")))) | .[0]') + latest_js_v5=$(echo "$loader_registry" | $jq -r '.versions | reverse | map(select(.|any(.; startswith("5.")))) | .[0]') + latest_js_v6=$(echo "$loader_registry" | $jq -r '.versions | reverse | map(select(.|any(.; startswith("6.")))) | .[0]') + latest_js_v7=$(echo "$loader_registry" | $jq -r '.versions | reverse | map(select(.|any(.; startswith("7.")))) | .[0]') + latest_js_v8=$(echo "$loader_registry" | $jq -r '.versions | reverse | map(select(.|any(.; startswith("8.")))) | .[0]') + latest_js_v9=$(echo "$loader_registry" | $jq -r '.versions | reverse | map(select(.|any(.; startswith("9.")))) | .[0]') + + echo "Found JS SDKs: v${latest_js_v4}, v${latest_js_v5}, v${latest_js_v6}, v${latest_js_v7}, v${latest_js_v8}, v${latest_js_v9}" + + versions="{$latest_js_v4,$latest_js_v5,$latest_js_v6,$latest_js_v7,$latest_js_v8,$latest_js_v9}" + variants="{bundle,bundle.tracing,bundle.tracing.replay,bundle.replay,bundle.tracing.replay.feedback,bundle.feedback}" + + # Download those versions & variants using curl + $dcr --no-deps nginx curl -w '%{response_code} %{url}\n' --no-progress-meter --compressed --retry 3 --create-dirs -fLo "/var/www/js-sdk/#1/#2.min.js" "https://browser.sentry-cdn.com/$%7Bversions%7D/$%7Bvariants%7D.min.js" || true + + # Make sure permissions are correct + # See https://github.com/getsentry/self-hosted/issues/3614 for reported issue + $dcr --no-deps nginx find /var/www/js-sdk -type d -exec chmod 755 {} \; + $dcr --no-deps nginx find /var/www/js-sdk -type f -exec chmod 644 {} \; + + echo "${_endgroup}" +fi diff --git a/install/turn-things-off.sh b/install/turn-things-off.sh index 270dc4e78c2..fc0ddf38d48 100644 --- a/install/turn-things-off.sh +++ b/install/turn-things-off.sh @@ -5,7 +5,28 @@ if [[ -n "$MINIMIZE_DOWNTIME" ]]; then $dc rm -fsv $($dc config --services | grep -v -E '^(nginx|relay)$') else # Clean up old stuff and ensure nothing is working while we install/update - $dc down -t $STOP_TIMEOUT --rmi local --remove-orphans + if [ "$CONTAINER_ENGINE" = "podman" ]; then + $dc down -t $STOP_TIMEOUT --remove-orphans + dangling_images=$($CONTAINER_ENGINE images --quiet --filter dangling=true) + if [ -n "$dangling_images" ]; then + # Remove dangling images + $CONTAINER_ENGINE rmi -f $dangling_images + fi + else + $dc down -t $STOP_TIMEOUT --rmi local --remove-orphans + fi +fi + +exists_volume() { + $CONTAINER_ENGINE volume inspect $1 >&/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}" diff --git a/install/update-docker-images.sh b/install/update-docker-images.sh index a23558559f7..fd16070d068 100644 --- a/install/update-docker-images.sh +++ b/install/update-docker-images.sh @@ -1,14 +1,20 @@ -echo "${_group}Fetching and updating Docker images ..." +echo "${_group}Fetching and updating $CONTAINER_ENGINE images ..." -# 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. +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 -q --ignore-pull-failures 2>&1 | grep -v -- -self-hosted-local || true + $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 -docker pull ${SENTRY_IMAGE} || true +$CONTAINER_ENGINE pull ${SENTRY_IMAGE} || true echo "${_endgroup}" diff --git a/install/upgrade-clickhouse.sh b/install/upgrade-clickhouse.sh new file mode 100644 index 00000000000..7ac7e826e61 --- /dev/null +++ b/install/upgrade-clickhouse.sh @@ -0,0 +1,62 @@ +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}" diff --git a/install/upgrade-postgres.sh b/install/upgrade-postgres.sh index 86b76646f33..6785ae879ea 100644 --- a/install/upgrade-postgres.sh +++ b/install/upgrade-postgres.sh @@ -1,26 +1,26 @@ echo "${_group}Ensuring proper PostgreSQL version ..." -if [[ -n "$(docker volume ls -q --filter name=sentry-postgres)" && "$(docker run --rm -v sentry-postgres:/db busybox cat /db/PG_VERSION 2>/dev/null)" == "9.6" ]]; then - docker volume rm sentry-postgres-new || true +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 - docker run --rm \ + $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 - docker volume rm sentry-postgres - docker volume create --name sentry-postgres + $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. - docker run --rm -v sentry-postgres-new:/from -v sentry-postgres:/to alpine ash -c \ + $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. - docker volume rm sentry-postgres-new + $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" - $dc up -d postgres + start_service_and_wait_ready postgres # Wait for postgres RETRIES=5 diff --git a/install/wrap-up.sh b/install/wrap-up.sh index a811f81a618..52e31137d84 100644 --- a/install/wrap-up.sh +++ b/install/wrap-up.sh @@ -2,15 +2,15 @@ if [[ "$MINIMIZE_DOWNTIME" ]]; then echo "${_group}Waiting for Sentry to start ..." # Start the whole setup, except nginx and relay. - $dc up -d --remove-orphans $($dc config --services | grep -v -E '^(nginx|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 - docker run --rm --network="${COMPOSE_PROJECT_NAME}_default" alpine ash \ + $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 - $dc up -d + start_service_and_wait_ready $($dc config --services) echo "${_endgroup}" else @@ -20,9 +20,17 @@ else 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} up -d" + echo " $dc_base --env-file .env --env-file ${_ENV} up --wait" else - echo " $dc_base up -d" + 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 "-----------------------------------------------------------------" diff --git a/nginx/nginx.conf b/nginx.conf similarity index 60% rename from nginx/nginx.conf rename to nginx.conf index 3f1e6d847fd..95e96226c1c 100644 --- a/nginx/nginx.conf +++ b/nginx.conf @@ -11,6 +11,7 @@ events { http { + include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' @@ -40,14 +41,25 @@ http { proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; - proxy_next_upstream error timeout invalid_header http_502 http_503 non_idempotent; - proxy_next_upstream_tries 2; + + # 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 $proxy_add_x_forwarded_for; + 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; @@ -72,9 +84,22 @@ http { 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; diff --git a/optional-modifications/README.md b/optional-modifications/README.md new file mode 100644 index 00000000000..054942138de --- /dev/null +++ b/optional-modifications/README.md @@ -0,0 +1,50 @@ +# 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. diff --git a/optional-modifications/patches/external-kafka/.env.patch b/optional-modifications/patches/external-kafka/.env.patch new file mode 100644 index 00000000000..36e45f7785f --- /dev/null +++ b/optional-modifications/patches/external-kafka/.env.patch @@ -0,0 +1,22 @@ +--- .env 2025-02-04 07:31:54.868049984 +0700 ++++ .env.external-kafka 2025-05-15 08:33:15.442361105 +0700 +@@ -22,3 +22,19 @@ + POSTGRES_MAX_CONNECTIONS=100 + # Set SETUP_JS_SDK_ASSETS to 1 to enable the setup of JS SDK assets + # SETUP_JS_SDK_ASSETS=1 ++ ++################################################################################ ++## Additional External Kafka options ++################################################################################ ++KAFKA_BOOTSTRAP_SERVERS=kafka-node1:9092,kafka-node2:9092,kafka-node3:9092 ++# Valid options are PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL ++KAFKA_SECURITY_PROTOCOL=PLAINTEXT ++# Valid options are PLAIN, SCRAM-SHA-256, SCRAM-SHA-512. Other mechanism might be unavailable. ++# KAFKA_SASL_MECHANISM=PLAIN ++# KAFKA_SASL_USERNAME=username ++# KAFKA_SASL_PASSWORD=password ++# Put your certificates on the \`certificates/kafka\` directory. ++# The certificates will be mounted as read-only volumes. ++# KAFKA_SSL_CA_LOCATION=/kafka-certificates/ca.pem ++# KAFKA_SSL_CERTIFICATE_LOCATION=/kafka-certificates/client.pem ++# KAFKA_SSL_KEY_LOCATION=/kafka-certificates/client.key diff --git a/optional-modifications/patches/external-kafka/config.example.yml.patch b/optional-modifications/patches/external-kafka/config.example.yml.patch new file mode 100644 index 00000000000..a0c1aab04b8 --- /dev/null +++ b/optional-modifications/patches/external-kafka/config.example.yml.patch @@ -0,0 +1,19 @@ +--- 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" + diff --git a/optional-modifications/patches/external-kafka/docker-compose.yml.patch b/optional-modifications/patches/external-kafka/docker-compose.yml.patch new file mode 100644 index 00000000000..7f2561cce67 --- /dev/null +++ b/optional-modifications/patches/external-kafka/docker-compose.yml.patch @@ -0,0 +1,192 @@ +--- 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: diff --git a/optional-modifications/patches/external-kafka/sentry.conf.example.py.patch b/optional-modifications/patches/external-kafka/sentry.conf.example.py.patch new file mode 100644 index 00000000000..abc755c00a0 --- /dev/null +++ b/optional-modifications/patches/external-kafka/sentry.conf.example.py.patch @@ -0,0 +1,21 @@ +--- 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" diff --git a/postgres/init_hba.sh b/postgres/init_hba.sh deleted file mode 100755 index 9952ab14864..00000000000 --- a/postgres/init_hba.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Initializes the pg_hba file with access permissions to the replication -# slots. - -set -e - -{ echo "host replication all all trust"; } >>"$PGDATA/pg_hba.conf" diff --git a/postgres/postgres-entrypoint.sh b/postgres/postgres-entrypoint.sh deleted file mode 100755 index 68a469f7cae..00000000000 --- a/postgres/postgres-entrypoint.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -# This script replaces the default docker entrypoint for postgres in the -# development environment. -# Its job is to ensure postgres is properly configured to support the -# Change Data Capture pipeline (by setting access permissions and installing -# the replication plugin we use for CDC). Unfortunately the default -# Postgres image does not allow this level of configurability so we need -# to do it this way in order not to have to publish and maintain our own -# Postgres image. -# -# This then, at the end, transfers control to the default entrypoint. - -set -e - -prep_init_db() { - cp /opt/sentry/init_hba.sh /docker-entrypoint-initdb.d/init_hba.sh -} - -cdc_setup_hba_conf() { - # Ensure pg-hba is properly configured to allow connections - # to the replication slots. - - PG_HBA="$PGDATA/pg_hba.conf" - if [ ! -f "$PG_HBA" ]; then - echo "DB not initialized. Postgres will take care of pg_hba" - elif [ "$(grep -c -E "^host\s+replication" "$PGDATA"/pg_hba.conf)" != 0 ]; then - echo "Replication config already present in pg_hba. Not changing anything." - else - # Execute the same script we run on DB initialization - /opt/sentry/init_hba.sh - fi -} - -bind_wal2json() { - # Copy the file in the right place - cp /opt/sentry/wal2json/wal2json.so $(pg_config --pkglibdir)/wal2json.so -} - -echo "Setting up Change Data Capture" - -prep_init_db -if [ "$1" = 'postgres' ]; then - cdc_setup_hba_conf - bind_wal2json -fi -exec /usr/local/bin/docker-entrypoint.sh "$@" diff --git a/redis.conf b/redis.conf new file mode 100644 index 00000000000..090b148b15e --- /dev/null +++ b/redis.conf @@ -0,0 +1,27 @@ +# 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 diff --git a/relay/config.example.yml b/relay/config.example.yml index 52e6630671f..06c73079564 100644 --- a/relay/config.example.yml +++ b/relay/config.example.yml @@ -11,3 +11,26 @@ processing: - {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: "100.100.123.123:8125" # It is recommended to use IP address instead of domain name +# 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 diff --git a/requirements-dev.txt b/requirements-dev.txt index 7ef178da75a..97d735ef800 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,7 +1,8 @@ -sentry-sdk>=1.39.2 +sentry-sdk>=2.4.0,<3.0.0 pytest>=8.0.0 pytest-cov>=4.1.0 pytest-rerunfailures>=11.0 pytest-sentry>=0.1.11 httpx>=0.25.2 beautifulsoup4>=4.7.1 +cryptography>=43.0.3 diff --git a/scripts/_lib.sh b/scripts/_lib.sh index 7ec51eecac1..a381b6e585d 100755 --- a/scripts/_lib.sh +++ b/scripts/_lib.sh @@ -7,7 +7,7 @@ if [ -n "${DEBUG:-}" ]; then fi function confirm() { - read -p "$1 [y/n] " confirmation + read -r -p "$1 [y/n] " confirmation if [ "$confirmation" != "y" ]; then echo "Canceled. 😅" exit @@ -26,8 +26,7 @@ function reset() { # we're targeting a valid tag here. Do this early in order to fail fast. if [ -n "$version" ]; then set +e - git rev-parse --verify --quiet "refs/tags/$version" >/dev/null - if [ $? -gt 0 ]; then + if ! git rev-parse --verify --quiet "refs/tags/$version" >/dev/null; then echo "Bad version: $version" exit fi @@ -43,12 +42,15 @@ function reset() { 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 && + docker volume remove "$volume" >/dev/null && echo "Removed volume: $volume" || echo "Skipped volume: $volume" done @@ -60,36 +62,31 @@ function reset() { } 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 + 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() { - type=${1:-"global"} - $dc run --rm -T web import $type /etc/sentry/backup.json + local type + + type="${1:-global}" + $dc run --rm -T web import "$type" /etc/sentry/backup.json } # Needed variables to source error-handling script -MINIMIZE_DOWNTIME="${MINIMIZE_DOWNTIME:-}" -STOP_TIMEOUT=60 +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 +log_file="sentry_${cmd%% *}_log-$(date +%Y-%m-%d_%H-%M-%S).txt" exec &> >(tee -a "$log_file") version="" -while (($#)); do - case "$1" in - --report-self-hosted-issues) REPORT_SELF_HOSTED_ISSUES=1 ;; - --no-report-self-hosted-issues) REPORT_SELF_HOSTED_ISSUES=0 ;; - *) version=$1 ;; - esac - shift -done - # 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 diff --git a/scripts/backup.sh b/scripts/backup.sh index c056b7078bc..c92ee4b9ace 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -1,4 +1,17 @@ #!/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 diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh index e59a1d1eb27..64ff9732bf9 100755 --- a/scripts/bump-version.sh +++ b/scripts/bump-version.sh @@ -1,14 +1,11 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu OLD_VERSION="$1" NEW_VERSION="$2" -WAL2JSON_VERSION=${WAL2JSON_VERSION:-$(curl -s "https://api.github.com/repos/getsentry/wal2json/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")')} - -sed -i -e "s/^WAL2JSON_VERSION=\([^:]\+\):.\+\$/WAL2JSON_VERSION=\1:$WAL2JSON_VERSION/" .env -sed -i -e "s/^\(SENTRY\|SNUBA\|RELAY\|SYMBOLICATOR\|VROOM\)_IMAGE=\([^:]\+\):.\+\$/\1_IMAGE=\2:$NEW_VERSION/" .env +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" -echo "New wal2json version: $WAL2JSON_VERSION" diff --git a/scripts/post-release.sh b/scripts/post-release.sh index 820d8ef6d69..f2e6a238d16 100755 --- a/scripts/post-release.sh +++ b/scripts/post-release.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu # Bring master back to nightlies after merge from release branch diff --git a/scripts/reset.sh b/scripts/reset.sh index f519435420f..75c244efedc 100755 --- a/scripts/reset.sh +++ b/scripts/reset.sh @@ -1,4 +1,17 @@ #!/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 diff --git a/scripts/restore.sh b/scripts/restore.sh index ededd3114f3..57cab1bb60c 100755 --- a/scripts/restore.sh +++ b/scripts/restore.sh @@ -1,4 +1,18 @@ #!/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 diff --git a/sentry-admin.sh b/sentry-admin.sh index 85705516d27..ef5608488f9 100755 --- a/sentry-admin.sh +++ b/sentry-admin.sh @@ -1,6 +1,10 @@ -#!/bin/bash +#!/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 @@ -19,8 +23,10 @@ on the host filesystem. Commands that write files should write them to the '/sen # Actual invocation that runs the command in the container. invocation() { - output=$($dc run -v "$VOLUME_MAPPING" --rm -T -e SENTRY_LOG_LEVEL=CRITICAL web "$@" 2>&1) - echo "$output" + 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. diff --git a/sentry/Dockerfile b/sentry/Dockerfile index 5afe9c2dd86..40398a773fb 100644 --- a/sentry/Dockerfile +++ b/sentry/Dockerfile @@ -1,13 +1,15 @@ ARG SENTRY_IMAGE FROM ${SENTRY_IMAGE} +RUN pip install https://github.com/stayallive/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 + fi RUN if [ -s /usr/src/sentry/requirements.txt ]; then \ - echo "sentry/requirements.txt is deprecated, use sentry/enhance-image.sh - see https://github.com/getsentry/self-hosted#enhance-sentry-image"; \ + 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 + fi diff --git a/sentry/config.example.yml b/sentry/config.example.yml index 10f5c8c16f7..fefe9511b81 100644 --- a/sentry/config.example.yml +++ b/sentry/config.example.yml @@ -12,6 +12,8 @@ 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 @@ -19,7 +21,6 @@ mail.host: 'smtp' # 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 ' @@ -44,9 +45,26 @@ mail.host: 'smtp' # 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 @@ -78,9 +96,6 @@ releasefile.cache-path: '/data/releasefile-cache' # secret_key: 'XXXXXXX' # bucket_name: 's3-bucket-name' -# The URL prefix in which Sentry is accessible -# system.url-prefix: https://example.sentry.com -system.internal-url-prefix: 'http://web:9000/' symbolicator.enabled: true symbolicator.options: url: "http://symbolicator:3021/" @@ -117,7 +132,7 @@ transaction-events.force-disable-internal-project: true # slack.client-id: <'client id'> # slack.client-secret: # slack.signing-secret: -## If legacy-app is True use verfication-token instead of signing-secret +## If legacy-app is True use verification-token instead of signing-secret # slack.verification-token: @@ -131,3 +146,12 @@ transaction-events.force-disable-internal-project: true # 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: "" diff --git a/sentry/enhance-image.example.sh b/sentry/enhance-image.example.sh index e5b31826462..de17136d9c9 100755 --- a/sentry/enhance-image.example.sh +++ b/sentry/enhance-image.example.sh @@ -1,7 +1,8 @@ #!/bin/bash +set -euo pipefail -# Enhance the base $SENTRY_IMAGE with additional dependencies, plugins - see https://github.com/getsentry/self-hosted#enhance-sentry-image +# 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 python-dev libldap2-dev libssl-dev +# apt-get install -y gcc libsasl2-dev libldap2-dev libssl-dev # pip install python-ldap diff --git a/sentry/entrypoint.sh b/sentry/entrypoint.sh index 552de05b69a..7be738fdd7e 100755 --- a/sentry/entrypoint.sh +++ b/sentry/entrypoint.sh @@ -6,7 +6,7 @@ if [ "$(ls -A /usr/local/share/ca-certificates/)" ]; then fi if [ -e /etc/sentry/requirements.txt ]; then - echo "sentry/requirements.txt is deprecated, use sentry/enhance-image.sh - see https://github.com/getsentry/self-hosted#enhance-sentry-image" + 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 diff --git a/sentry/requirements.example.txt b/sentry/requirements.example.txt index e7b63dc9a67..393a2f52fc3 100644 --- a/sentry/requirements.example.txt +++ b/sentry/requirements.example.txt @@ -1 +1 @@ -# sentry/requirements.txt is deprecated, use sentry/enhance-image.sh - see https://github.com/getsentry/self-hosted#enhance-sentry-image +# sentry/requirements.txt is deprecated, use sentry/enhance-image.sh - see https://develop.sentry.dev/self-hosted/#enhance-sentry-image diff --git a/sentry/sentry.conf.example.py b/sentry/sentry.conf.example.py index 5247cca12db..38147f7bb02 100644 --- a/sentry/sentry.conf.example.py +++ b/sentry/sentry.conf.example.py @@ -48,15 +48,11 @@ def get_internal_network(): "NAME": "postgres", "USER": "postgres", "PASSWORD": "", - "HOST": "postgres", + "HOST": "pgbouncer", "PORT": "", } } -# You should not change this setting after your database has been created -# unless you have altered all schemas first -SENTRY_USE_BIG_INTS = True - # If you're expecting any kind of real traffic on Sentry, we highly recommend # configuring the CACHES and Redis settings @@ -68,10 +64,70 @@ def get_internal_network(): # 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/experimental/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 + +################ +# 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 # ######### @@ -85,25 +141,6 @@ def get_internal_network(): } } -######### -# Queue # -######### - -# See https://develop.sentry.dev/services/queue/ for more -# information on configuring your queue broker and workers. Sentry relies -# on a Python framework called Celery to manage queues. - -rabbitmq_host = None -if rabbitmq_host: - BROKER_URL = "amqp://{username}:{password}@{host}/{vhost}".format( - username="guest", password="guest", host=rabbitmq_host, vhost="/" - ) -else: - BROKER_URL = "redis://:{password}@{host}:{port}/{db}".format( - **SENTRY_OPTIONS["redis.clusters"]["default"]["hosts"][0] - ) - - ######### # Cache # ######### @@ -188,15 +225,6 @@ def get_internal_network(): SENTRY_DIGESTS = "sentry.digests.backends.redis.RedisBackend" -################### -# Metrics Backend # -################### - -SENTRY_RELEASE_HEALTH = "sentry.release_health.metrics.MetricsReleaseHealthBackend" -SENTRY_RELEASE_MONITOR = ( - "sentry.release_health.release_monitor.metrics.MetricReleaseMonitorBackend" -) - ############## # Web Server # ############## @@ -216,6 +244,12 @@ def get_internal_network(): "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, @@ -259,33 +293,34 @@ def get_internal_network(): # 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:events", "organizations:global-views", + "organizations:issue-views", "organizations:incidents", "organizations:integrations-issue-basic", "organizations:integrations-issue-sync", "organizations:invite-members", - "organizations:metric-alert-builder-aggregate", "organizations:sso-basic", - "organizations:sso-rippling", "organizations:sso-saml2", - "organizations:performance-view", "organizations:advanced-search", - "organizations:session-replay", "organizations:issue-platform", - "organizations:profiling", "organizations:monitors", "organizations:dashboards-mep", "organizations:mep-rollout-flag", "organizations:dashboards-rh-widget", - "organizations:metrics-extraction", - "organizations:transaction-metrics-extraction", + "organizations:dynamic-sampling", "projects:custom-inbound-filters", "projects:data-forwarding", "projects:discard-groups", @@ -293,22 +328,53 @@ def get_internal_network(): "projects:rate-limits", "projects:servicehooks", ) + # Performance/Tracing/Spans related flags + ( - "projects:span-metrics-extraction", - "organizations:starfish-browser-resource-module-image-view", - "organizations:starfish-browser-resource-module-ui", - "organizations:starfish-browser-webvitals", - "organizations:starfish-browser-webvitals-pageoverview-v2", - "organizations:starfish-browser-webvitals-use-backend-scores", - "organizations:performance-calculate-score-relay", - "organizations:starfish-browser-webvitals-replace-fid-with-inp", - "organizations:deprecate-fid-from-performance-score", - "organizations:performance-database-view", - "organizations:performance-screens-view", - "organizations:mobile-ttid-ttfd-contribution", - "organizations:starfish-mobile-appstart", + "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", - ) # starfish related flags + "organizations:starfish-mobile-appstart", + "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", + ) } ) @@ -325,20 +391,6 @@ def get_internal_network(): # BITBUCKET_CONSUMER_KEY = 'YOUR_BITBUCKET_CONSUMER_KEY' # BITBUCKET_CONSUMER_SECRET = 'YOUR_BITBUCKET_CONSUMER_SECRET' -############################################## -# Suggested Fix Feature / OpenAI Integration # -############################################## - -# See https://docs.sentry.io/product/issues/issue-details/ai-suggested-solution/ -# for more information about the feature. Make sure the OpenAI's privacy policy is -# aligned with your company. - -# Set the OPENAI_API_KEY on the .env or .env.custom file with a valid -# OpenAI API key to turn on the feature. -OPENAI_API_KEY = env("OPENAI_API_KEY", "") - -SENTRY_FEATURES["organizations:open-ai-suggestion"] = bool(OPENAI_API_KEY) - ############################################## # Content Security Policy settings ############################################## @@ -350,6 +402,18 @@ def get_internal_network(): # 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 # ################# @@ -360,3 +424,49 @@ def get_internal_network(): # 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_METRICS_BACKEND = 'sentry.metrics.statsd.StatsdMetricsBackend' +# SENTRY_METRICS_OPTIONS: dict[str, Any] = { +# 'host': '100.100.123.123', # It is recommended to use IP address instead of domain name +# 'port': 8125, +# } +# 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." diff --git a/symbolicator/config.example.yml b/symbolicator/config.example.yml index 62cf9b83b70..de716de8c4c 100644 --- a/symbolicator/config.example.yml +++ b/symbolicator/config.example.yml @@ -6,3 +6,10 @@ logging: metrics: statsd: null 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. +# To start, uncomment the following line and adjust the options as needed. +# +# metrics: +# statsd: "100.100.123.123:8125" # It is recommended to use IP address instead of domain name +# prefix: "sentry.symbolicator" # Adjust this to your needs, default is "symbolicator" diff --git a/unit-test.sh b/unit-test.sh index 6cb81fa8d20..8dbc0d727bd 100755 --- a/unit-test.sh +++ b/unit-test.sh @@ -5,14 +5,15 @@ 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 [ "$1" -a "$1" != "$test_file" ]; then + if [ -n "$1" ] && [ "$1" != "$test_file" ]; then echo "🙊 Skipping $test_file ..." continue fi echo "🙈 Running $test_file ..." $test_file - if [ $? != 0 ]; then - echo fail 👎 + exit_code=$? + if [ $exit_code != 0 ]; then + echo fail 👎 with exit code $exit_code fail=1 fi done diff --git a/workstation/200_download-self-hosted.sh b/workstation/200_download-self-hosted.sh index a07735d7003..406a52c184d 100644 --- a/workstation/200_download-self-hosted.sh +++ b/workstation/200_download-self-hosted.sh @@ -1,5 +1,5 @@ #!/bin/bash -# +set -eo pipefail # Create getsentry folder and enter. mkdir /home/user/getsentry