Repository: roadrunner-server/roadrunner Branch: master Commit: e41e6474026f Files: 146 Total size: 642.9 KB Directory structure: gitextract_hav1tonz/ ├── .claude/ │ └── settings.json ├── .dockerignore ├── .editorconfig ├── .githooks/ │ └── pre-commit ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── chore.yml │ │ ├── config.yml │ │ └── feature-request.yml │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── codeql-analysis.yml │ ├── dependency-review.yml │ ├── e2e.yml │ ├── release.yml │ ├── release_dep.yml │ ├── release_dep_aarch64.yml │ ├── release_grpc.yml │ ├── semgrep.yml │ └── tests.yml ├── .gitignore ├── .golangci.yml ├── .rr.yaml ├── .vscode/ │ └── launch.json ├── CHANGELOG.md ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── benchmarks/ │ └── simple.js ├── cmd/ │ └── rr/ │ ├── command_test.go │ ├── doc.go │ └── main.go ├── codecov.yml ├── composer.json ├── container/ │ ├── config.go │ ├── config_test.go │ ├── container_test.go │ ├── doc.go │ ├── plugins.go │ ├── plugins_test.go │ └── test/ │ ├── endure_ok.yaml │ ├── endure_ok_debug.yaml │ ├── endure_ok_error.yaml │ ├── endure_ok_foobar.yaml │ ├── endure_ok_info.yaml │ ├── endure_ok_warn.yaml │ └── without_endure_ok.yaml ├── download-latest.sh ├── githooks-installer.sh ├── go.mod ├── go.sum ├── go.work ├── go.work.sum ├── internal/ │ ├── cli/ │ │ ├── doc.go │ │ ├── jobs/ │ │ │ ├── command.go │ │ │ ├── command_test.go │ │ │ ├── doc.go │ │ │ ├── render.go │ │ │ └── subcommands.go │ │ ├── reset/ │ │ │ ├── command.go │ │ │ ├── command_test.go │ │ │ └── doc.go │ │ ├── root.go │ │ ├── root_test.go │ │ ├── serve/ │ │ │ ├── command.go │ │ │ ├── command_test.go │ │ │ ├── command_windows.go │ │ │ └── doc.go │ │ ├── stop/ │ │ │ ├── command.go │ │ │ ├── command_test.go │ │ │ └── doc.go │ │ └── workers/ │ │ ├── command.go │ │ ├── command_test.go │ │ ├── doc.go │ │ └── render.go │ ├── debug/ │ │ ├── doc.go │ │ ├── server.go │ │ └── server_test.go │ ├── meta/ │ │ ├── doc.go │ │ ├── meta.go │ │ └── meta_test.go │ ├── rpc/ │ │ ├── client.go │ │ ├── client_test.go │ │ ├── doc.go │ │ ├── includes.go │ │ └── test/ │ │ ├── config_rpc_conn_err.yaml │ │ ├── config_rpc_empty.yaml │ │ ├── config_rpc_ok.yaml │ │ ├── config_rpc_ok_env.yaml │ │ ├── config_rpc_wrong.yaml │ │ └── include1/ │ │ ├── .rr-include.yaml │ │ └── .rr.yaml │ └── sdnotify/ │ ├── doc.go │ └── sdnotify.go ├── lib/ │ ├── doc.go │ ├── roadrunner.go │ └── roadrunner_test.go ├── schemas/ │ ├── config/ │ │ ├── 1.0.schema.json │ │ ├── 2.0.schema.json │ │ └── 3.0.schema.json │ ├── package.json │ ├── readme.md │ └── test.js └── tests/ ├── configs/ │ ├── .rr-grpc-otel.yaml │ ├── .rr-grpc.yaml │ ├── .rr-http-middleware.yaml │ ├── .rr-http-otel.yaml │ ├── .rr-http-static.yaml │ ├── .rr-jobs-memory-otel.yaml │ └── .rr-jobs-memory.yaml ├── doc.go ├── e2e_grpc_test.go ├── e2e_http_test.go ├── e2e_jobs_test.go ├── go.mod ├── go.sum ├── helpers/ │ ├── doc.go │ └── helpers.go ├── mock/ │ ├── doc.go │ ├── logger.go │ └── observer.go ├── php_test_files/ │ ├── .gitignore │ ├── composer.json │ ├── grpc/ │ │ ├── src/ │ │ │ ├── EchoService.php │ │ │ ├── GPBMetadata/ │ │ │ │ └── Service.php │ │ │ ├── Health/ │ │ │ │ ├── HealthCheckRequest.php │ │ │ │ ├── HealthCheckResponse/ │ │ │ │ │ └── ServingStatus.php │ │ │ │ ├── HealthCheckResponse.php │ │ │ │ └── HealthInterface.php │ │ │ ├── HealthService.php │ │ │ └── Service/ │ │ │ ├── EchoInterface.php │ │ │ └── Message.php │ │ └── worker-grpc.php │ ├── http/ │ │ ├── client.php │ │ └── echo.php │ └── jobs/ │ └── jobs_ok.php ├── proto/ │ └── service/ │ ├── service.pb.go │ ├── service.proto │ └── service_grpc.pb.go └── testdata/ └── sample.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .claude/settings.json ================================================ { "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "golangci-lint run --timeout 120s 2>&1 || true", "timeout": 180, "statusMessage": "Running golangci-lint..." } ] } ] } } ================================================ FILE: .dockerignore ================================================ .dockerignore .git .gitignore .editorconfig .github /src /tests /bin composer.json vendor_php Makefile CHANGELOG.md LICENSE .golangci* .rr-sample-*.yaml go.work go.work.sum ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = tab indent_size = 4 trim_trailing_whitespace = true [*.{yml,yaml,sh,conf}] indent_size = 2 [{Makefile,go.mod,*.go}] indent_style = tab ================================================ FILE: .githooks/pre-commit ================================================ #!/bin/bash set -e -o pipefail # https://github.com/koalaman/shellcheck/wiki/SC2039#redirect-both-stdout-and-stderr if ! command -v golangci-lint 2>&1 /dev/null; then echo "golangci-lint is not installed" exit 1 fi exec golangci-lint --build-tags=race run "$@" ================================================ FILE: .github/CODEOWNERS ================================================ # Primary owners @wolfy-j @rustatian ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: roadrunner-server ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: Bug Report description: 🐛 File a bug report title: "[🐛 BUG]: " labels: ["B-bug", "F-need-verification"] assignees: - rustatian body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: checkboxes id: search-done attributes: label: No duplicates 🥲. options: - label: I have searched for a similar issue in our bug tracker and didn't find any solutions. required: true - type: textarea id: what-happened attributes: label: What happened? description: Also tell us, what did you expect to happen? placeholder: Tell us what you see! value: "A bug happened!" validations: required: true - type: textarea id: version attributes: label: Version (rr --version) description: What version of our software are you running? placeholder: 2.6.0 validations: required: true - type: textarea id: repro attributes: label: How to reproduce the issue? description: .rr.yaml and steps on how to reproduce the issue? validations: required: true - type: textarea id: logs attributes: label: Relevant log output description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. render: shell ================================================ FILE: .github/ISSUE_TEMPLATE/chore.yml ================================================ name: Chore description: 🧹 Enhancement or chore of the existing code title: "[🧹 CHORE]: " labels: ["C-enhancement"] assignees: - rustatian body: - type: markdown attributes: value: | Thanks for taking the time to fill out this report! - type: checkboxes id: search-done attributes: label: No duplicates 🥲. options: - label: I have searched for a similar issue. required: true - type: textarea id: what-happened attributes: label: What should be improved or cleaned up? description: Also tell us, what did you expect to happen? placeholder: Tell us what you see! validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: ❓ Start a discussion or ask a question. url: https://github.com/roadrunner-server/roadrunner/discussions about: Please ask and answer questions here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yml ================================================ name: Feature request description: 💡 Suggest an idea for this project title: "[💡 FEATURE REQUEST]: " labels: ["C-feature-request"] assignees: - rustatian body: - type: markdown attributes: value: | Thanks for taking the time to share your idea! - type: dropdown id: plugin attributes: label: Plugin description: What plugin is affected? options: - GRPC - HTTP - HTTP Middleware (any) - Jobs - Jobs driver - TCP - File server - Config - KV - KV driver - Logger - Metrics - Temporal - Service - Server - Status - Centrifuge - type: textarea id: idea attributes: label: I have an idea! description: Clear and concise description of your idea. placeholder: Tell us what you see! value: "I have an idea, listen to me!!" validations: required: true ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: gomod # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: daily reviewers: - "rustatian" assignees: - "rustatian" - package-ecosystem: "github-actions" directory: "/" schedule: interval: daily reviewers: - "rustatian" assignees: - "rustatian" - package-ecosystem: "docker" directory: "/" schedule: interval: daily reviewers: - "rustatian" assignees: - "rustatian" ================================================ FILE: .github/pull_request_template.md ================================================ # Reason for This PR `[Author TODO: add issue # or explain reasoning.]` ## Description of Changes `[Author TODO: add description of changes.]` ## License Acceptance By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license. ## PR Checklist `[Author TODO: Meet these criteria.]` `[Reviewer TODO: Verify that these criteria are met. Request changes if not]` - [ ] All commits in this PR are signed (`git commit -s`). - [ ] The reason for this PR is clearly provided (issue no. or explanation). - [ ] The description of changes is clear and encompassing. - [ ] Any required documentation changes (code and docs) are included in this PR. - [ ] Any user-facing changes are mentioned in `CHANGELOG.md`. - [ ] All added/changed functionality is tested. ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, or to provide custom queries or build logic. name: "CodeQL" on: push: branches: [master, stable] pull_request: branches: [master, stable] schedule: - cron: '0 15 * * 6' jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: language: ['go'] # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] steps: - name: Checkout repository uses: actions/checkout@v6 with: # We must fetch at least the immediate parents so that if this is a pull request then we can checkout the head fetch-depth: 2 # Initializes the Golang environment for the CodeQL tools. # https://github.com/github/codeql-action/issues/1842#issuecomment-1704398087 - name: Install Go uses: actions/setup-go@v6 with: go-version-file: go.mod - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/dependency-review.yml ================================================ # Dependency Review Action # # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. # # Source repository: https://github.com/actions/dependency-review-action # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement name: 'Dependency Review' on: [pull_request] permissions: contents: read jobs: dependency-review: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' uses: actions/checkout@v6 - name: 'Dependency Review' uses: actions/dependency-review-action@v4 ================================================ FILE: .github/workflows/e2e.yml ================================================ name: e2e_tests on: push: branches: - master - stable pull_request: branches: - master - stable jobs: e2e_test: name: E2E tests (Go ${{ matrix.go }}, PHP ${{ matrix.php }}, OS ${{ matrix.os }}) runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: matrix: php: ["8.5"] go: [stable] os: ["ubuntu-latest"] steps: - name: Set up Go ${{ matrix.go }} uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - name: Set up PHP ${{ matrix.php }} uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: sockets - name: Check out code uses: actions/checkout@v6 - name: Get Composer Cache Directory id: composer-cache run: | cd tests/php_test_files echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Init Composer Cache uses: actions/cache@v5 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install Composer dependencies run: cd tests/php_test_files && composer update --prefer-dist --no-progress --ansi - name: Init Go modules Cache uses: actions/cache@v5 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-go- - name: Install Go dependencies run: cd tests && go mod download - name: Run e2e tests run: | cd tests go test -timeout 15m -v -race -failfast ./... ================================================ FILE: .github/workflows/release.yml ================================================ name: release on: release: # Docs: types: - released - prereleased jobs: build: name: Build for ${{ matrix.os }} (${{ matrix.arch }}, ${{ matrix.compiler }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: os: [windows, darwin] # linux, darwin, windows compiler: [gcc] # gcc, musl-gcc archiver: [zip] # tar, zip arch: [amd64] # amd64, 386 include: - os: linux compiler: gcc archiver: tar arch: amd64 # ----- - os: linux compiler: gcc archiver: tar arch: arm64 # ----- - os: darwin compiler: gcc archiver: tar arch: arm64 # ----- - os: freebsd compiler: gcc archiver: tar arch: amd64 # ----- - os: '' compiler: musl-gcc # more info: archiver: tar arch: amd64 steps: - name: Set up Go uses: actions/setup-go@v6 with: go-version: stable - name: Check out code uses: actions/checkout@v6 - name: Install musl if: matrix.compiler == 'musl-gcc' run: sudo apt-get install -y musl-tools - name: Download dependencies run: go mod download # `-x` means "verbose" mode - name: Generate builder values id: values run: | echo "version=$(echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//')" >> $GITHUB_OUTPUT echo "timestamp=$(echo $(date +%FT%T%z))" >> $GITHUB_OUTPUT echo "binary-name=$(echo $(echo rr`[ ${{ matrix.os }} = 'windows' ] && echo '.exe'`))" >> $GITHUB_OUTPUT if [ ${{ matrix.os }} == "windows" ]; then echo "sign-cert-name=rr.exe.asc" >> $GITHUB_OUTPUT else echo "sign-cert-name=rr.asc" >> $GITHUB_OUTPUT fi - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} passphrase: ${{ secrets.GPG_PASS }} git_user_signingkey: true git_commit_gpgsign: false - name: Compile binary file env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} CC: ${{ matrix.compiler }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} GPG_PASS: ${{secrets.GPG_PASS}} CGO_ENABLED: 0 LDFLAGS: >- -s -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.version=${{ steps.values.outputs.version }} -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.buildTime=${{ steps.values.outputs.timestamp }} run: | go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/rr stat "./${{ steps.values.outputs.binary-name }}" gpg --detach-sign --armor "./${{ steps.values.outputs.binary-name }}" - name: Generate distributive directory name id: dist-dir run: > echo "name=$(echo roadrunner-${{ steps.values.outputs.version }}-$( [ ${{ matrix.os }} != '' ] && echo '${{ matrix.os }}' || echo 'unknown' )$( [ ${{ matrix.compiler }} = 'musl-gcc' ] && echo '-musl' ))-${{ matrix.arch }}" >> $GITHUB_OUTPUT - name: Generate distributive archive name id: dist-arch run: > echo "name=$(echo ${{ steps.dist-dir.outputs.name }}.$( case ${{ matrix.archiver }} in zip) echo 'zip';; tar) echo 'tar.gz';; *) exit 10; esac ))" >> $GITHUB_OUTPUT - name: Create distributive run: | mkdir ${{ steps.dist-dir.outputs.name }} mv "./${{ steps.values.outputs.binary-name }}" "./${{ steps.values.outputs.sign-cert-name }}" ./${{ steps.dist-dir.outputs.name }}/ cp ./README.md ./CHANGELOG.md ./LICENSE ./${{ steps.dist-dir.outputs.name }} - name: Pack distributive using tar if: matrix.archiver == 'tar' run: tar -zcf "${{ steps.dist-arch.outputs.name }}" "${{ steps.dist-dir.outputs.name }}" - name: Pack distributive using zip if: matrix.archiver == 'zip' run: zip -r -q "${{ steps.dist-arch.outputs.name }}" "${{ steps.dist-dir.outputs.name }}" - name: Upload artifact uses: actions/upload-artifact@v7 with: name: ${{ steps.dist-dir.outputs.name }} path: ${{ steps.dist-arch.outputs.name }} if-no-files-found: error retention-days: 30 - name: Upload binaries to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.dist-arch.outputs.name }} asset_name: ${{ steps.dist-arch.outputs.name }} tag: ${{ github.ref }} docker: name: Build docker image runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v6 - name: Set up QEMU uses: docker/setup-qemu-action@v4 # Action page: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 # Action page: - name: Login to Docker Hub uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_LOGIN }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GitHub Container Registry uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ secrets.GHCR_LOGIN }} password: ${{ secrets.GHCR_PASSWORD }} - name: Generate builder values id: values run: | echo "version_full=$(echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//')" >> $GITHUB_OUTPUT echo "timestamp=$(echo $(date +%FT%T%z))" >> $GITHUB_OUTPUT - name: Build image uses: docker/build-push-action@v7 # Action page: with: context: . file: Dockerfile push: true platforms: linux/amd64,linux/arm64 build-args: | APP_VERSION=${{ steps.values.outputs.version_full}} BUILD_TIME=${{ steps.values.outputs.timestamp }} tags: | spiralscout/roadrunner:${{ steps.values.outputs.version_full}} spiralscout/roadrunner:latest spiralscout/roadrunner:2025 spiralscout/roadrunner:2025.1 ghcr.io/roadrunner-server/roadrunner:${{ steps.values.outputs.version_full}} ghcr.io/roadrunner-server/roadrunner:latest ghcr.io/roadrunner-server/roadrunner:2025 ghcr.io/roadrunner-server/roadrunner:2025.1 ================================================ FILE: .github/workflows/release_dep.yml ================================================ name: release_dep on: release: # Docs: types: - released - prereleased jobs: build: name: Build for ${{ matrix.os }} (${{ matrix.arch }}, ${{ matrix.compiler }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: os: [linux] compiler: [gcc] arch: [amd64] steps: - name: Check out code uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version: stable - name: Download dependencies run: go mod download - name: Generate builder values id: values run: | echo "version=$(echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//')" >> $GITHUB_OUTPUT echo "timestamp=$(echo $(date +%FT%T%z))" >> $GITHUB_OUTPUT echo "binary-name=rr" >> $GITHUB_OUTPUT echo "sign-cert-name=rr.asc" >> $GITHUB_OUTPUT - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} passphrase: ${{ secrets.GPG_PASS }} git_user_signingkey: true git_commit_gpgsign: false - name: Compile binary file env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} CC: ${{ matrix.compiler }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} GPG_PASS: ${{secrets.GPG_PASS}} CGO_ENABLED: 0 LDFLAGS: >- -s -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.version=${{ steps.values.outputs.version }} -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.buildTime=${{ steps.values.outputs.timestamp }} run: | go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/rr stat "./${{ steps.values.outputs.binary-name }}" gpg --detach-sign --armor "./${{ steps.values.outputs.binary-name }}" - name: Create DEB dirs run: | mkdir -p dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN mkdir -p dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/usr/bin ls -la dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64 - name: Create DEB control file run: touch dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control - name: Build Debian package run: | echo "Package: roadrunner-${{ steps.values.outputs.version }}-linux-amd64.deb" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control echo "Version: ${{ steps.values.outputs.version }}" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control echo "Section: custom" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control echo "Priority: optional" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control echo "Architecture: amd64" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control echo "Maintainer: roadrunner.dev" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control echo "Description: High-performance PHP application server, load-balancer, process manager written in Go and powered with plugins." >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control cat dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/DEBIAN/control cp "./${{ steps.values.outputs.binary-name }}" dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64/usr/bin/ dpkg --build dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64 - name: Upload dep to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-amd64.deb asset_name: roadrunner-${{ steps.values.outputs.version }}-linux-amd64.deb tag: ${{ github.ref }} ================================================ FILE: .github/workflows/release_dep_aarch64.yml ================================================ name: release_dep_arm64 on: release: # Docs: types: - released - prereleased jobs: build: name: Build for ${{ matrix.os }} (${{ matrix.arch }}, ${{ matrix.compiler }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: os: [linux] compiler: [gcc] arch: [arm64] steps: - name: Check out code uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version: stable - name: Download dependencies run: go mod download - name: Generate builder values id: values run: | echo "version=$(echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//')" >> $GITHUB_OUTPUT echo "timestamp=$(echo $(date +%FT%T%z))" >> $GITHUB_OUTPUT echo "binary-name=rr" >> $GITHUB_OUTPUT echo "sign-cert-name=rr.asc" >> $GITHUB_OUTPUT - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} passphrase: ${{ secrets.GPG_PASS }} git_user_signingkey: true git_commit_gpgsign: false - name: Compile binary file env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} CC: ${{ matrix.compiler }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} GPG_PASS: ${{secrets.GPG_PASS}} CGO_ENABLED: 0 LDFLAGS: >- -s -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.version=${{ steps.values.outputs.version }} -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.buildTime=${{ steps.values.outputs.timestamp }} run: | go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/rr stat "./${{ steps.values.outputs.binary-name }}" gpg --detach-sign --armor "./${{ steps.values.outputs.binary-name }}" - name: Create DEB dirs run: | mkdir -p dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN mkdir -p dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/usr/bin ls -la dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64 - name: Create DEB control file run: touch dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control - name: Build Debian package run: | echo "Package: roadrunner-${{ steps.values.outputs.version }}-linux-arm64.deb" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control echo "Version: ${{ steps.values.outputs.version }}" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control echo "Section: custom" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control echo "Priority: optional" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control echo "Architecture: arm64" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control echo "Maintainer: roadrunner.dev" >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control echo "Description: High-performance PHP application server, load-balancer, process manager written in Go and powered with plugins." >> dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control cat dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/DEBIAN/control cp "./${{ steps.values.outputs.binary-name }}" dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64/usr/bin/ dpkg --build dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64 - name: Upload dep to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: dist/ubuntu/roadrunner-${{ steps.values.outputs.version }}-linux-arm64.deb asset_name: roadrunner-${{ steps.values.outputs.version }}-linux-arm64.deb tag: ${{ github.ref }} ================================================ FILE: .github/workflows/release_grpc.yml ================================================ name: release_grpc on: release: # Docs: types: - prereleased - released jobs: build: name: Build for ${{ matrix.os }} (${{ matrix.arch }}, ${{ matrix.compiler }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: os: [windows, darwin] # linux, darwin, windows compiler: [gcc] # gcc, musl-gcc archiver: [zip] # tar, zip arch: [amd64] # amd64, 386 include: - os: linux compiler: gcc archiver: tar arch: amd64 #---------- - os: linux compiler: gcc archiver: tar arch: arm64 #---------- - os: freebsd compiler: gcc archiver: tar arch: amd64 #---------- - os: darwin compiler: gcc archiver: tar arch: arm64 #---------- - os: '' compiler: musl-gcc # more info: archiver: tar arch: amd64 steps: - name: Set up Go uses: actions/setup-go@v6 with: go-version: stable - name: Check out code uses: actions/checkout@v6 with: repository: 'roadrunner-server/grpc' - name: Install musl if: matrix.compiler == 'musl-gcc' run: sudo apt-get install -y musl-tools - name: Download dependencies run: cd protoc_plugins && go mod download - name: Generate builder values id: values run: | echo "version=$(echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//')" >> $GITHUB_OUTPUT echo "timestamp=$(echo $(date +%FT%T%z))" >> $GITHUB_OUTPUT echo "binary-name=$(echo $(echo protoc-gen-php-grpc`[ ${{ matrix.os }} = 'windows' ] && echo '.exe'`))" >> $GITHUB_OUTPUT if [ ${{ matrix.os }} == "windows" ]; then echo "sign-cert-name=protoc-gen-php-grpc.exe.asc" >> $GITHUB_OUTPUT else echo "sign-cert-name=protoc-gen-php-grpc.asc" >> $GITHUB_OUTPUT fi - name: Import GPG key uses: crazy-max/ghaction-import-gpg@v7 with: gpg_private_key: ${{ secrets.GPG_SIGNING_KEY }} passphrase: ${{ secrets.GPG_PASS }} git_user_signingkey: true git_commit_gpgsign: false - name: Compile binary file env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} CC: ${{ matrix.compiler }} CGO_ENABLED: 0 LDFLAGS: >- -s run: | cd protoc_plugins && go build -trimpath -ldflags "$LDFLAGS" -o "../${{ steps.values.outputs.binary-name }}" protoc-gen-php-grpc/main.go stat "../${{ steps.values.outputs.binary-name }}" gpg --detach-sign --armor "../${{ steps.values.outputs.binary-name }}" - name: Generate distributive directory name id: dist-dir run: > echo "name=$(echo protoc-gen-php-grpc-${{ steps.values.outputs.version }}-$( [ ${{ matrix.os }} != '' ] && echo '${{ matrix.os }}' || echo 'unknown' )$( [ ${{ matrix.compiler }} = 'musl-gcc' ] && echo '-musl' ))-${{ matrix.arch }}" >> $GITHUB_OUTPUT - name: Generate distributive archive name id: dist-arch run: > echo "name=$(echo ${{ steps.dist-dir.outputs.name }}.$( case ${{ matrix.archiver }} in zip) echo 'zip';; tar) echo 'tar.gz';; *) exit 10; esac ))" >> $GITHUB_OUTPUT - name: Create distributive run: | mkdir ${{ steps.dist-dir.outputs.name }} mv "./${{ steps.values.outputs.binary-name }}" "./${{ steps.values.outputs.sign-cert-name }}" ./${{ steps.dist-dir.outputs.name }}/ - name: Pack distributive using tar if: matrix.archiver == 'tar' run: tar -zcf "${{ steps.dist-arch.outputs.name }}" "${{ steps.dist-dir.outputs.name }}" - name: Pack distributive using zip if: matrix.archiver == 'zip' run: zip -r -q "${{ steps.dist-arch.outputs.name }}" "${{ steps.dist-dir.outputs.name }}" - name: Upload artifact uses: actions/upload-artifact@v7 with: name: ${{ steps.dist-dir.outputs.name }} path: ${{ steps.dist-arch.outputs.name }} if-no-files-found: error retention-days: 30 - name: Upload binaries to release uses: svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: ${{ steps.dist-arch.outputs.name }} asset_name: ${{ steps.dist-arch.outputs.name }} tag: ${{ github.ref }} ================================================ FILE: .github/workflows/semgrep.yml ================================================ name: semgrep on: pull_request: {} push: branches: - master - stable paths: - .github/workflows/semgrep.yml jobs: semgrep: name: semgrep/ci runs-on: ubuntu-latest env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} container: image: returntocorp/semgrep steps: - uses: actions/checkout@v6 - run: semgrep ci ================================================ FILE: .github/workflows/tests.yml ================================================ name: rr_cli_tests on: push: branches: - master - stable pull_request: jobs: golangci-lint: name: Golang-CI (lint) runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 # action page: with: go-version: stable - name: Run linter uses: golangci/golangci-lint-action@v9 with: only-new-issues: false # show only new issues if it's a pull request args: -v --build-tags=race --timeout=10m go-test: name: Unit tests runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v6 with: go-version: stable - name: Check out code uses: actions/checkout@v6 with: fetch-depth: 2 # Fixes codecov error 'Issue detecting commit SHA' - name: Init Go modules Cache # Docs: uses: actions/cache@v5 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-go- - name: Install Go dependencies run: go mod download - name: Run Unit tests run: go test -race -covermode=atomic -coverprofile /tmp/coverage.txt ./... - name: Upload Coverage report to CodeCov continue-on-error: true uses: codecov/codecov-action@v5.5.2 # https://github.com/codecov/codecov-action with: files: /tmp/coverage.txt build: name: Build for ${{ matrix.os }} runs-on: ubuntu-latest needs: [go-test] strategy: fail-fast: false matrix: os: [linux, darwin, windows, freebsd] steps: - name: Set up Go uses: actions/setup-go@v6 # action page: with: go-version: stable - name: Check out code uses: actions/checkout@v6 - name: Init Go modules Cache # Docs: uses: actions/cache@v5 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-go- - name: Install Go dependencies run: go mod download && go mod verify - name: Generate version value id: values # for PR this value will be `merge@__hash__`, SO: run: | echo "version=$(echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//')" >> $GITHUB_OUTPUT echo "timestamp=$(echo $(date +%FT%T%z))" >> $GITHUB_OUTPUT - name: Compile binary file env: GOOS: ${{ matrix.os }} GOARCH: amd64 CGO_ENABLED: 0 LDFLAGS: -s -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.version=${{ steps.values.outputs.version }} -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.buildTime=${{ steps.values.outputs.timestamp }} run: go build -trimpath -ldflags "$LDFLAGS" -o ./rr ./cmd/rr - name: Try to execute if: matrix.os == 'linux' run: ./rr -v - name: Upload artifact uses: actions/upload-artifact@v7 with: name: rr-${{ matrix.os }} path: ./rr if-no-files-found: error retention-days: 10 docker-image: name: Build docker image runs-on: ubuntu-latest needs: [go-test] steps: - name: Check out code uses: actions/checkout@v6 - name: Build image run: docker build -t rr:local -f ./Dockerfile . - name: Try to execute run: docker run --rm rr:local -v - uses: aquasecurity/trivy-action@0.35.0 # action page: with: image-ref: rr:local format: "table" severity: HIGH,CRITICAL exit-code: 1 ================================================ FILE: .gitignore ================================================ # Created by .ignore support plugin (hsz.mobi) ### Go template # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ .idea coverage /rr rr.exe .rr-sample-* .pid .DS_Store **/.DS_Store **/node_modules **/vendor/ **/composer.lock ================================================ FILE: .golangci.yml ================================================ version: "2" run: allow-parallel-runners: true output: formats: text: path: stdout linters: default: none enable: - asciicheck - bodyclose - copyloopvar - dogsled - dupl - errcheck - errorlint - exhaustive - gochecknoglobals - goconst - gocritic - goprintffuncname - gosec - govet - ineffassign - misspell - nakedret - noctx - nolintlint - prealloc - revive - staticcheck - tparallel - unconvert - unparam - unused - whitespace settings: dupl: threshold: 100 goconst: min-len: 2 min-occurrences: 3 godot: scope: declarations capital: true lll: line-length: 120 misspell: locale: US nolintlint: require-specific: true prealloc: simple: true range-loops: true for-loops: true revive: enable-default-rules: true rules: - name: var-naming disabled: true wsl: allow-assign-and-anything: true exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling rules: - linters: - dupl - funlen - gocognit - scopelint path: _test\.go paths: - internal/debug/server_test.go - .github - .git - third_party$ - builtin$ - examples$ formatters: enable: - gofmt - goimports exclusions: generated: lax paths: - internal/debug/server_test.go - .github - .git - third_party$ - builtin$ - examples$ ================================================ FILE: .rr.yaml ================================================ ###################################################################################### # THIS IS SAMPLE OF THE CONFIGURATION # # IT'S NOT A DEFAULT CONFIGURATION, IT'S JUST A REFERENCE TO ALL OPTIONS AND PLUGINS # # MORE DOCS CAN BE FOUND HERE: # ###################################################################################### # Production usage guide: https://roadrunner.dev/docs/app-server-production/2.x/en # Hint: RR will replace any config options using reference to environment variables, # eg.: `option_key: ${ENVIRONMENT_VARIABLE_NAME}`. # Important: TCP port numbers for each plugin (rpc, http, etc) must be unique! # RR configuration version version: '3' # Remote Procedures Calling (docs: https://roadrunner.dev/docs/plugins-rpc/2.x/en) # Is used for connecting to RoadRunner server from your PHP workers. rpc: # TCP address:port for listening. # # Default: "tcp://127.0.0.1:6001" listen: tcp://127.0.0.1:6001 # Application server settings (docs: https://roadrunner.dev/docs/php-worker) server: # Execute command before the main server's command. on_init: # Command to execute before the main server's command # # This option is required if using on_init command: "any php or script here" # Script execute timeout # # Default: 60s [60m, 60h], if used w/o units its means - NANOSECONDS. exec_timeout: 20s # Exit on init error # Default: false exit_on_error: false # Environment variables for the worker processes. # # Default: env: SOME_KEY: "SOME_VALUE" SOME_KEY2: "SOME_VALUE2" # Username (not UID) of the user from whom the on_init command is executed. An empty value means to use the RR process user. # # Default: "" # user: "" # Worker starting command, with any required arguments. # # This option is required. command: "php psr-worker.php" # Username (not UID) for the worker processes. An empty value means to use the RR process user. # # Default: "" # user: "" # Group name (not GID) for the worker processes. An empty value means to use the RR process group. # # Default: "" # group: "" # Environment variables for the worker processes. # # Default: env: SOME_KEY: "SOME_VALUE" SOME_KEY2: "SOME_VALUE2" # Worker relay can be: "pipes", TCP (eg.: tcp://127.0.0.1:6002), or socket (eg.: unix:///var/run/rr.sock). # # Default: "pipes" relay: pipes # Logging settings (docs: https://roadrunner.dev/docs/plugins-logger/2.x/en) logs: # Logging mode can be "development", "production" or "raw". Do not forget to change this value for production environment. # # Development mode (which makes DPanicLevel logs panic), uses a console encoder, writes to standard error, and # disables sampling. Stacktraces are automatically included on logs of WarnLevel and above. # # Default: "development" mode: development # Logging level can be "panic", "error", "warn", "info", "debug". # # Default: "debug" level: debug # Encoding format can be "console" or "json" (last is preferred for production usage). # # Default: "console" encoding: console # Log line ending # # Default: "\n" line_ending: "\n" # Output can be file (eg.: "/var/log/rr_errors.log"), "stderr" or "stdout". # # Default: "stderr" output: ["stderr"] # Errors only output can be file (eg.: "/var/log/rr_errors.log"), "stderr" or "stdout". # # Default: ["stderr"] err_output: ["stderr"] # File logger options # # Default: null file_logger_options: # Path to the file # # Default: It uses -lumberjack.log name in the os tempdir if empty. log_output: "/tmp/my.log" # Max file size in MB # # Default: 100 max_size: 100 # max_age is the maximum number of days to retain old log files based on the timestamp encoded in their filename. # # Default: 1 (day) max_age: 1 # max_backups is the maximum number of old log files to retain. # # Default: retain all (if set to 0) max_backups: 5 # Compress determines if the rotated log files should be compressed using gzip. # # Default: false compress: false # You can configure each plugin log messages individually (key is plugin name, and value is logging options in same # format as above). # # Default: channels: http: mode: development level: panic encoding: console output: ["stdout"] err_output: ["stderr"] server: mode: production level: info encoding: json output: ["stdout"] err_output: ["stdout"] rpc: mode: raw level: debug encoding: console output: ["stderr"] err_output: ["stdout"] # Workflow and activity mesh service. # # Drop this section for temporal feature disabling. temporal: # Address of temporal server. # # Default: "127.0.0.1:7233" address: 127.0.0.1:7233 # Sticky cache size. Sticky workflow execution is the affinity # between workflow tasks of a specific workflow execution to a specific worker. The benefit of sticky execution is that # the workflow does not have to reconstruct state by replaying history from the beginning. The cache is shared between # workers running within same process. This must be called before any worker is started. If not called, the default # size of 10K (which may change) will be used. # # Default: 10_000 cache_size: 10000 # Namespace name for this client to work with # # Default: default namespace: default # Temporal metrics # # Optional section metrics: # Metrics driver to use # Optional, default: prometheus. Available values: prometheus, statsd driver: prometheus # ---- Prometheus prometheus: # Server metrics address # Required for the production. Default: 127.0.0.1:9091, for the metrics 127.0.0.1:9091/metrics address: 127.0.0.1:9091 # Metrics type type: "summary" # Temporal metrics prefix # Default: (empty) prefix: "foobar" # ---- Statsd (uncomment) # Statsd host and port #statsd: # Optional # default: 127.0.0.1:8125 # host_port: "127.0.0.1:8125" # # Prefix for the metrics # Optional, default: empty # prefix: "samples" # # Flush interval is the maximum interval for sending packets. # Optional, default: 1s # flush_interval: 1s # # Flush bytes specifies the maximum udp packet size you wish to send. # If FlushBytes is unspecified, it defaults to 1432 bytes, which is # considered safe for local traffic # Optional, default: 1432 # flush_bytes: 1432 # # Tags passed to the statsd on init # Optional, default: empty #tags: # foo: bar # Temporal TLS configuration # # This section is optional tls: # Path to the key file # # This option is required key: "/ssl/key.pem" # Path to the certificate # # This option is required cert: "/ssl/cert.crt" # Path to the CA certificate, defines the set of root certificate authorities that servers use if required to verify a client certificate. Used with the `client_auth_type` option. # # This option is optional root_ca: "/ssl/ca.crt" # Client auth type. # # This option is optional. Default value: no_client_certs. Possible values: request_client_cert, require_any_client_cert, verify_client_cert_if_given, require_and_verify_client_cert, no_client_certs client_auth_type: no_client_certs # ServerName is used to verify the hostname on the returned # certificates unless InsecureSkipVerify is given. It is also included # in the client's handshake to support virtual hosting unless it is # an IP address. # # Default: hostname server_name: "tls-sample" # Activities pool settings. # # Equal to the regular pool options activities: # Debug mode for the pool. In this mode, pool will not pre-allocate the worker. Worker (only 1, num_workers ignored) will be allocated right after the request arrived. # # Default: false debug: false # Override server's command # # Default: empty command: "php my-super-app.php" # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. # # Default: 0 num_workers: 0 # Maximal count of worker executions. Zero (or nothing) means no limit. # # Default: 0 max_jobs: 0 # [2023.3.10] # Maximum size of the internal requests queue. After reaching the limit, all additional requests would be rejected with error. # # Default: 0 (no limit) max_queue_size: 0 # Timeout for worker allocation. Zero means 60s. # # Default: 60s allocate_timeout: 60s # Timeout for the reset timeout. Zero means 60s. # # Default: 60s reset_timeout: 60s # Timeout for worker destroying before process killing. Zero means 60s. # # Default: 60s destroy_timeout: 60s # Supervisor is used to control http workers (previous name was "limit", docs: # https://roadrunner.dev/docs/php-limit). "Soft" limits will not interrupt current request processing. "Hard" # limit on the contrary - interrupts the execution of the request. supervisor: # How often to check the state of the workers. # # Default: 5s watch_tick: 5s # Maximum time worker is allowed to live (soft limit). Zero means no limit. # # Default: 0s ttl: 0s # How long worker can spend in IDLE mode after first using (soft limit). Zero means no limit. # # Default: 0s idle_ttl: 10s # Maximal worker memory usage in megabytes (soft limit). Zero means no limit. # # Default: 0 max_worker_memory: 128 # Maximal job lifetime (hard limit). Zero means no limit. # # Default: 0s exec_ttl: 60s # KV plugin settings. Available drivers: boltdb, redis, memcached, memory. # # Any number of sections can be defined here. kv: # User defined name of the section # # Default: none boltdb-south: # Driver which should be used for the storage # # This option is required. driver: boltdb # Local configuration section # # This option is required to use local section, otherwise (boltdb-south) global configuration will be used. config: # File name for the DB # # Default: "rr.db" file: "rr.db" # Access permission for the DB file. # # Default: "0755" permissions: 0755 # TTL keys check interval in seconds. It's safe to use 1 second here, but can be a little costly to performance. # # Default: "60" seconds interval: 40 # User defined name of the section (us-cental-kv used as example) # # Default: none us-central-kv: # Driver which should be used for the storage # # Default: none driver: memcached # Local configuration section # # This option is required to use local section, otherwise (us-central-kv) global configuration will be used. config: # Driver specific section. Addresses of the memcached node(s). # # Default: [ "localhost:11211" ] addr: [ "localhost:11211" ] # User defined name of the section # # Default: none fast-kv-fr: # Driver which should be used for the storage. # # Default: none driver: redis # Redis specific section. If one address provided - single node client will be used. # # # UniversalClient is an abstract client which - based on the provided options - # can connect to either clusters, or sentinel-backed failover instances # or simple single-instance servers. This can be useful for testing # cluster-specific applications locally. # if the number of addrs is 1 and master_name is empty, a single-node redis Client will be returned # if the number of addrs is two or more, a ClusterClient will be returned # Local configuration section # # This option is required to use local section, otherwise (fast-kv-fr) global configuration will be used. config: addrs: - "localhost:6379" # if a MasterName is passed a sentinel-backed FailoverClient will be returned master_name: "" username: "" password: "" db: 0 sentinel_password: "" route_by_latency: false route_randomly: false dial_timeout: 0s # accepted values [1s, 5m, 3h] max_retries: 1 min_retry_backoff: 0s # accepted values [1s, 5m, 3h] max_retry_backoff: 0s # accepted values [1s, 5m, 3h] pool_size: 0 min_idle_conns: 0 max_conn_age: 0s # accepted values [1s, 5m, 3h] read_timeout: 0s # accepted values [1s, 5m, 3h] write_timeout: 0s # accepted values [1s, 5m, 3h] pool_timeout: 0s # accepted values [1s, 5m, 3h] idle_timeout: 0s # accepted values [1s, 5m, 3h] idle_check_freq: 0s # accepted values [1s, 5m, 3h] read_only: false # User defined name of the section # # Default: none local-memory: # In memory driver specific section # # Default: none driver: memory config: { } # Service plugin settings service: # User defined service name # # Default: none, required some_service_1: # Command to execute. Can be any command here which can be executed. # # Default: none, required. command: php tests/plugins/service/test_files/loop.php # Env variables for the process # # Default: empty env: foo: "BAR" foo2: "BAR2" # Number of copies (processes) to start per command. # # Default: 1 process_num: 1 # Timeout for the process stop operation # # Default: 5 seconds timeout_stop_sec: 5 # Allowed time before stop. # # Default: 0 (infinity), can be 1s, 2m, 2h (seconds, minutes, hours) and complex 2h2m1s. exec_timeout: 0s # Show the name of the service in logs (e.g. service.some_service_1) # # Default: false service_name_in_log: false # Remain process after exit. In other words, restart process after exit with any exit code. # # Default: "false" remain_after_exit: true # Number of seconds to wait before process restart. # # Default: 30 restart_sec: 1 # User defined service name # # Default: none, required some_service_2: # Command to execute. Can be any command here which can be executed. # # Default: none, required. command: "binary" # Timeout for the process stop operation # # Default: 5 seconds timeout_stop_sec: 5 # Env variables for the process # # Default: empty env: foo: "BAR" foo2: "BAR2" # Number of copies (processes) to start per command. # # Default: 1 process_num: 1 # Show the name of the service in logs (e.g. service.some_service_1) # # Default: false service_name_in_log: false # Allowed time before stop. # # Default: 0 (infinity), can be 1s, 2m, 2h (seconds, minutes, hours) and complex 2h2m1s. exec_timeout: 0s # Remain process after exit. In other words, restart process after exit with any exit code. # # Default: "false" remain_after_exit: true # Number of seconds to wait before process restart. # # Default: 30 restart_sec: 1 otel: # OpenTelemetry Resources # # https://github.com/open-telemetry/opentelemetry-specification/blob/v1.25.0/specification/resource/semantic_conventions/README.md resource: # User's service name # # Default: RoadRunner service_name: "rr_test" # User's service version # # Default: RoadRunner service_version: "1.0.0" # User's service namespace # # Default: RoadRunner service_namespace: "RR-Shop" # User's service instance id # # Default: Generated UUID service_instance_id: "UUID" # Use insecure endpoint (http) or insecure gRPC # # Default: false insecure: true # Use gzip to compress the spans # # Default: false compress: false # Client to send the spans # # Default: http. Possible values: `http`, `grpc` client: http # Provides functionality to emit telemetry to consumers # # Default: otlp. Possible values: otlp (used for new_relic, datadog), zipkin stderr or stdout exporter: otlp # Used for the http client to override the default URL # # Default: empty # custom_url: "" # Consumer's endpoint # # Default: 127.0.0.1:4318 endpoint: "127.0.0.1:4318" # HTTP plugin settings. http: # Host and port to listen on (eg.: `127.0.0.1:8080`). # # This option is required. address: 127.0.0.1:8080 # override http error code for the internal RR errors # # Default: 500 internal_error_code: 505 # HTTP access logs # # Default: false access_logs: false # Maximal incoming request size in megabytes. Zero means no limit. # # Default: 0 max_request_size: 256 # Send raw body (unescaped) to the PHP worker for the application/x-www-form-urlencoded content type # # Optional, default: false raw_body: false # Middlewares for the http plugin, order is important. Allowed values is: "headers", "gzip", "static", "sendfile", [SINCE 2.6] -> "new_relic", [SINCE 2.6] -> "http_metrics", [SINCE 2.7] -> "cache" # # Default value: [] middleware: [ "headers", "gzip" ] # Allow incoming requests only from the following subnets (https://en.wikipedia.org/wiki/Reserved_IP_addresses). # # Default: ["10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10"] trusted_subnets: [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10", ] # File uploading settings. uploads: # Directory for file uploads. Empty value means to use $TEMP based on your OS. # # Default: "" dir: "/tmp" # Deny files with the following extensions to upload. # # Default: [".php", ".exe", ".bat"] forbid: [ ".php", ".exe", ".bat", ".sh" ] # [SINCE 2.6] Allow files with the following extensions to upload # # Default: empty allow: [ ".html", ".aaa" ] # Settings for "headers" middleware (docs: https://roadrunner.dev/docs/middleware-headers/2.x/en). headers: # Allows to control CORS headers. Additional headers "Vary: Origin", "Vary: Access-Control-Request-Method", # "Vary: Access-Control-Request-Headers" will be added to the server responses. Drop this section for this # feature disabling. cors: # Controls "Access-Control-Allow-Origin" header value (docs: https://mzl.la/2OgD4Qf). # # Default: empty allowed_origin: "*" # Controls "Access-Control-Allow-Origin" header value (docs: https://mzl.la/2OgD4Qf). # # Default: empty. If set, overrides allowed_origin. allowed_origin_regex: "^https://foo" # Controls "Access-Control-Allow-Headers" header value (docs: https://mzl.la/2OzDVvk). # # Default: "" allowed_headers: "*" # Controls "Access-Control-Allow-Methods" header value (docs: https://mzl.la/3lbwyXf). # # Default: "" allowed_methods: "GET,POST,PUT,DELETE" # Controls "Access-Control-Allow-Credentials" header value (docs: https://mzl.la/3ekJGaY). # # Default: false allow_credentials: true # Controls "Access-Control-Expose-Headers" header value (docs: https://mzl.la/3qAqgkF). # # Default: "" exposed_headers: "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma" # Controls "Access-Control-Max-Age" header value in seconds (docs: https://mzl.la/2PCSdvt). # # Default: 0 max_age: 600 # Automatically add headers to every request passed to PHP. # # Default: request: input: "custom-header" # Automatically add headers to every response. # # Default: response: X-Powered-By: "RoadRunner" # Settings for "static" middleware (docs: https://roadrunner.dev/docs/middleware-static/2.x/en). static: # Path to the directory to serve # # Default: "." (current) dir: "." # File patterns to forbid # # Default: empty forbid: [ ] # Etag calculation (base on the body CRC32) # # Default: false calculate_etag: false # Weak etag calculation (based only on the content-length CRC32) # # Default: false weak: false # Patterns to allow # # Default: empty allow: [ ".txt", ".php" ] # Request headers # # Default: empty request: input: "custom-header" # Response headers # # Default: empty response: output: "output-header" # Workers pool settings. pool: # Debug mode for the pool. In this mode, pool will not pre-allocate the worker. Worker (only 1, num_workers ignored) will be allocated right after the request arrived. # # Default: false debug: false # Override server's command # # Default: empty command: "php my-super-app.php" # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. # # Default: 0 num_workers: 0 # Maximal count of worker executions. Zero (or nothing) means no limit. # # Default: 0 max_jobs: 0 # [2023.3.10] # Maximum size of the internal requests queue. After reaching the limit, all additional requests would be rejected with error. # # Default: 0 (no limit) max_queue_size: 0 # Timeout for worker allocation. Zero means 60s. # # Default: 60s allocate_timeout: 60s # Timeout for the reset timeout. Zero means 60s. # # Default: 60s reset_timeout: 60s # Timeout for the stream cancellation. Zero means 60s. # # Default: 60s stream_timeout: 60s # Timeout for worker destroying before process killing. Zero means 60s. # # Default: 60s destroy_timeout: 60s # Dynamic allocator settings. # # Default: empty dynamic_allocator: max_workers: 25 spawn_rate: 10 idle_timeout: 10s # Supervisor is used to control http workers (previous name was "limit", video: https://www.youtube.com/watch?v=NdrlZhyFqyQ). # "Soft" limits will not interrupt current request processing. "Hard" # limit on the contrary - interrupts the execution of the request. supervisor: # How often to check the state of the workers. # # Default: 5s watch_tick: 5s # Maximum time worker is allowed to live (soft limit). Zero means no limit. # # Default: 0s ttl: 0s # How long worker can spend in IDLE mode after first using (soft limit). Zero means no limit. # # Default: 0s idle_ttl: 10s # Maximal worker memory usage in megabytes (soft limit). Zero means no limit. # # Default: 0 max_worker_memory: 128 # Maximal job lifetime (hard limit). Zero means no limit. # # Default: 0s exec_ttl: 60s # SSL (Secure Sockets Layer) (TLS) settings (docs: https://roadrunner.dev/docs/app-server-https/2.x/en). ssl: # Host and port to listen on (eg.: `127.0.0.1:443`). # # Default: ":443" address: "127.0.0.1:443" # Use ACME certificates provider (Let's encrypt) # Must not be specified if key + cert is used. acme: # Directory to use as a certificate/pk, account info storage # # Optional. Default: rr_cache cache_dir: "rr_le_certs" # User email # # Used to create LE account. Mandatory. Error on empty. email: your-email-here@email # Alternate port for the http challenge. Challenge traffic should be redirected to this port if overridden. # # Optional. Default: 80 alt_http_port: 80 # Alternate port for the tls-alpn-01 challenge. Challenge traffic should be redirected to this port if overridden. # # Optional. Default: 443. alt_tlsalpn_port: 443 # Challenge types # # Optional. Default: http-01. Possible values: http-01, tlsalpn-01 challenge_type: http-01 # Use production or staging endpoint. NOTE, try to use staging endpoint to make sure, that everything works correctly. # # Optional, but for production should be set to true. Default: false use_production_endpoint: true # List of your domains to obtain certificates # # Mandatory. Error on empty. domains: [ "your-cool-domain.here", "your-second-domain.here" ] # Automatic redirect from http:// to https:// schema. # # Default: false redirect: true # Path to the cert file. This option is required for SSL. Must not be specified if ACME is used. # # This option is required. # cert: "/ssl/cert.crt" # Path to the cert key file. Must not be specified if ACME is used. # # This option is required. # key: "/ssl/key.pem" # Path to the root certificate authority file. # # This option is optional (required for the mTLS). Must not be specified if ACME is used. # root_ca: "/ssl/ca.crt" # Client auth type (mTLS). Must not be specified if ACME is used. # # This option is optional. Default value: no_client_certs. Possible values: request_client_cert, require_any_client_cert, verify_client_cert_if_given, require_and_verify_client_cert, no_client_certs # client_auth_type: no_client_certs # FastCGI frontend support. fcgi: # FastCGI connection DSN. Supported TCP and Unix sockets. An empty value disables this. # # Default: "" address: tcp://0.0.0.0:7921 # HTTP/2 settings. http2: # HTTP/2 over non-encrypted TCP connection using H2C. # # Default: false h2c: false # Maximal concurrent streams count. # # Default: 128 max_concurrent_streams: 128 # HTTP/3 settings (experimental). Enable QUIC + HTTP/3 on a separate (optional) address. # Provide key+cert here if ACME is not configured (same as for the TLS section above). http3: # Host and port to listen on for HTTP/3. # # Default: disabled (section absent) address: 127.0.0.1:8443 # Path to the certificate (must be provided together with the key if section enabled). # cert: "/ssl/cert.crt" # Path to the private key. # key: "/ssl/key.pem" # Redis section. redis: # UniversalClient is an abstract client which - based on the provided options - # can connect to either clusters, or sentinel-backed failover instances # or simple single-instance servers. This can be useful for testing # cluster-specific applications locally. # if the number of addrs is 1 and master_name is empty, a single-node redis Client will be returned # if the number of addrs is two or more, a ClusterClient will be returned addrs: - "localhost:6379" # if a MasterName is passed a sentinel-backed FailoverClient will be returned master_name: "" username: "" password: "" db: 0 sentinel_password: "" route_by_latency: false route_randomly: false dial_timeout: 0s # accepted values [1s, 5m, 3h] max_retries: 1 min_retry_backoff: 0s # accepted values [1s, 5m, 3h] max_retry_backoff: 0s # accepted values [1s, 5m, 3h] pool_size: 0 min_idle_conns: 0 max_conn_age: 0s # accepted values [1s, 5m, 3h] read_timeout: 0s # accepted values [1s, 5m, 3h] write_timeout: 0s # accepted values [1s, 5m, 3h] pool_timeout: 0s # accepted values [1s, 5m, 3h] idle_timeout: 0s # accepted values [1s, 5m, 3h] idle_check_freq: 0s # accepted values [1s, 5m, 3h] read_only: false # Optional TLS configuration for Redis (leave section absent to disable TLS). # tls: # # Path to the CA certificate (required if section enabled). # root_ca: "/ssl/ca.crt" # # (Optional) Client certificate + key if mTLS required. # # cert: "/ssl/cert.crt" # # key: "/ssl/key.pem" # # client_auth_type: no_client_certs # Application metrics in Prometheus format (docs: https://roadrunner.dev/docs/plugins-metrics/2.x/en). Drop this section # for this feature disabling. metrics: # Prometheus client address (path /metrics added automatically). # # Default: "127.0.0.1:2112" address: "127.0.0.1:2112" # Application-specific metrics (published using an RPC connection to the server). collect: app_metric_summary: type: summary help: "Custom summary application metric" labels: [ "type" ] # Objectives defines the quantile rank estimates with their respective absolute error (for summary only). objectives: 0.1: 2.3 1.0: 1.4 app_metric_histogram: type: histogram help: "Custom histogram application metric" labels: [ "type" ] buckets: [ 0.1, 0.2, 0.3, 1.0 ] # Health check endpoint (docs: https://roadrunner.dev/docs/app-server-health/2.x/en). If response code is 200 - it means at # least one worker ready to serve requests. 500 - there are no workers ready to service requests. # Drop this section for this feature disabling. status: # Host and port to listen on (eg.: `127.0.0.1:2114`). Use the following URL: http://127.0.0.1:2114/health?plugin=http # Multiple plugins must be separated using "&" - http://127.0.0.1:2114/health?plugin=http&plugin=rpc where "http" and # "rpc" are active (connected) plugins. # # This option is required. address: 127.0.0.1:2114 # Response status code if a requested plugin not ready to handle requests # Valid for both /health and /ready endpoints # # Default: 503 unavailable_status_code: 503 # Maximum duration (in seconds) to wait for a complete response from the queried plugin(s). # If the plugin does not respond within this time RR returns `unavailable_status_code`. # # Default: 60 check_timeout: 60 # Automatically detect PHP file changes and reload connected services # (docs: https://roadrunner.dev/docs/plugins-reload/2.x/en). Drop this section for this feature disabling. reload: # Sync interval. # # Default: "1s" interval: 1s # Global patterns to sync. # # Default: [".php"] patterns: [ ".php" ] # List of included for sync services (this is a map, where key name is a plugin name). # # Default: services: http: # Directories to sync. If recursive is set to true, recursive sync will be applied only to the directories in # "dirs" section. Dot (.) means "current working directory". # # Default: [] dirs: [ "." ] # Recursive search for file patterns to add. # # Default: false recursive: true # Ignored folders. # # Default: [] ignore: [ "vendor" ] # Service specific file pattens to sync. # # Default: [] patterns: [ ".php", ".go", ".md" ] # NATS jobs driver # # Default: nats://127.0.0.1:4222 nats: addr: "demo.nats.io" # AMQP jobs driver # # This option is required to use AMQP driver amqp: # AMQP Uri to connect to the rabbitmq server https://www.rabbitmq.com/uri-spec.html # # This option is required for the production. Default: amqp(s)://guest:guest@127.0.0.1:5672 addr: amqp://guest:guest@127.0.0.1:5672/ # AMQPS TLS configuration # # This section is optional tls: # Path to the key file # # This option is required key: "/ssl/key.pem" # Path to the certificate # # This option is required cert: "/ssl/cert.crt" # Path to the CA certificate, defines the set of root certificate authorities that servers use if required to verify a client certificate. Used with the `client_auth_type` option. # # This option is optional root_ca: "/ssl/ca.crt" # Client auth type (mTLS, peer verification). # # This option is optional. Default value: no_client_certs. Possible values: request_client_cert, require_any_client_cert, verify_client_cert_if_given, require_and_verify_client_cert, no_client_certs client_auth_type: no_client_certs # Beanstalk jobs driver # # This option is required to use Beanstalk driver beanstalk: # Beanstalk address # # This option is required for the production. Default: tcp://127.0.0.1:11300 addr: tcp://127.0.0.1:11300 # Beanstalk connect timeout. # # Default: 30s timeout: 10s # SQS jobs driver (https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html) # # This option is required to use SQS driver sqs: # AccessKey ID # # This option is required for the production. Default: empty key: api-key # Secret access key # # This option is required for the production. Default: empty secret: api-secret # AWS region # # This option is required for the production. Default: empty region: us-west-1 # AWS session token # # This option is required for the production. Default: empty session_token: test # AWS SQS endpoint to connect # # This option is required for the production. Default: http://127.0.0.1:9324 endpoint: http://127.0.0.1:9324 # Kafka jobs driver # # This option is required to use Kafka driver, kafka: # Kafka brokers addresses # # Required to use Kafka driver brokers: [ "127.0.0.1:9092", "127.0.0.1:9002" ] # SSL/TLS configuration # # Optional, default: empty tls: # Secure connection timeout # # Examples: "2s", "5m" # Optional, default: "10s" timeout: "10s" # Path to the key file # # This option is required key: "/ssl/key.pem" # Path to the certificate # # This option is required cert: "/ssl/cert.crt" # Path to the CA certificate, defines the set of root certificate authorities that servers use if required to verify a client certificate. Used with the `client_auth_type` option. # # This option is optional root_ca: "/ssl/ca.crt" # Client auth type. # # This option is optional. Default value: no_client_certs. Possible values: request_client_cert, require_any_client_cert, verify_client_cert_if_given, require_and_verify_client_cert, no_client_certs client_auth_type: no_client_certs # SASL authentication options to use for all connections. Depending on the auth type, plain/SCRAM or aws_msk_plain sections should be removed. # # Optional, default: empty sasl: # ----------- 1. PLAIN and SCRAM auth section --------------- # Mechanism used for the authentication # # Required for the section. Might be: 'aws_msk_iam', 'plain', 'SCRAM-SHA-256', 'SCRAM-SHA-512' mechanism: plain # Username to use for authentication. # # Required for the plain auth mechanism. username: foo # Password to use for authentication. # # Required for the plain auth mechanism. password: bar # Nonce. # # Optional for the SHA auth types. Empty by default. nonce: "foo" # If true, suffixes the "tokenauth=true" extra attribute to the initial authentication message. # Set this to true if the user and pass are from a delegation token. # Optional for the SHA auth types. Empty by default. is_token: false # Zid is an optional authorization ID to use in authenticating. # # Optional, default: empty. zid: "foo" # -------------- 2. AWS_MSK_IAM auth section ------------------ # AWS Access key ID. # # Required access_key: foo # AWS Secret Access Key. # # secret_key: bar # SessionToken, if non-empty, is a session / security token to use for authentication. # See the following link for more details: # # https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html session_token: bar # UserAgent is the user agent to for the client to use when connecting # to Kafka, overriding the default "franz-go//". # Setting a UserAgent allows authorizing based on the aws:UserAgent # condition key; see the following link for more details: # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-useragent user_agent: baz jobs: # Number of threads which will try to obtain the job from the priority queue # # Default: number of the workers + 1 num_pollers: 32 # Push operation timeout in seconds # # Default: 60 timeout: 60 # Size of the internal priority queue # # Default: 1_000_000 pipeline_size: 100000 # worker pool configuration pool: # Debug mode for the pool. In this mode, pool will not pre-allocate the worker. Worker (only 1, num_workers ignored) will be allocated right after the request arrived. # # Default: false debug: false # Override server's command # # Default: empty command: "php my-super-app.php" # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. # # Default: 0 num_workers: 0 # Maximal count of worker executions. Zero (or nothing) means no limit. # # Default: 0 max_jobs: 0 # [2023.3.10] # Maximum size of the internal requests queue. After reaching the limit, all additional requests would be rejected with error. # # Default: 0 (no limit) max_queue_size: 0 # Timeout for worker allocation. Zero means 60s. # # Default: 60s allocate_timeout: 60s # Timeout for the reset timeout. Zero means 60s. # # Default: 60s reset_timeout: 60s # Timeout for the stream cancellation. Zero means 60s. # # Default: 60s stream_timeout: 60s # Timeout for worker destroying before process killing. Zero means 60s. # # Default: 60s destroy_timeout: 60s dynamic_allocator: max_workers: 25 spawn_rate: 10 idle_timeout: 10s # List of broker pipelines associated with the drivers. # # This option is not required since you can declare pipelines in the runtime. Pipeline driver should exist. pipelines: # Pipeline name # # This option is required when defining pipelines via configuration. test-local: # Driver associated with the pipeline # # This option is required. Possible values: amqp, memory, sqs, beanstalk, boltdb driver: memory # Driver's configuration # # Should not be empty config: # Pipeline priority # # If the job has priority set to 0, it will inherit the pipeline's priority. Default: 10. priority: 10 # Number of job to prefetch from the driver until ACK/NACK. # # Default: 100_000. prefetch: 10000 # Pipeline name # # This option is required when defining pipelines via configuration. test-local-1: # Driver associated with the pipeline # # This option is required. Possible values: amqp, memory, sqs, beanstalk, boltdb driver: boltdb # Driver's configuration # # Should not be empty config: # Number of job to prefetch from the driver. # # Default: 100_000. prefetch: 10000 # Pipeline priority # # If the job has priority set to 0, it will inherit the pipeline's priority. Default: 10. priority: 10 # BoldDB file to create or DB to use # # Default: "rr.db" file: "path/to/rr.db" # Permissions for the boltdb database file # # This option is optional. Default: 0755 permissions: 0755 test-local-2: # Driver name # # This option is required. driver: amqp # Driver's configuration # # Should not be empty config: # QoS - prefetch. # # Default: 10 prefetch: 10 # Pipeline priority # # If the job has priority set to 0, it will inherit the pipeline's priority. Default: 10. priority: 1 # Durable queue # # Default: false durable: false # Durable exchange (rabbitmq option: https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchanges) # # Default: false exchange_durable: false # Auto-delete (exchange is deleted when last queue is unbound from it): https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchanges # # Default: false exchange_auto_delete: false # Auto-delete (queue that has had at least one consumer is deleted when last consumer unsubscribes) (rabbitmq option: https://www.rabbitmq.com/queues.html#properties) # # Default: false queue_auto_delete: false # Delete queue when stopping the pipeline # # Default: false delete_queue_on_stop: false # Queue name # # Default: default queue: test-1-queue # Exchange name # # Default: amqp.default exchange: default # Redial timeout (in seconds). How long to try to reconnect to the AMQP server. # # Default: 60 redial_timeout: 60 # Exchange type # # Default: direct. exchange_type: direct # Routing key for the queue # # Default: empty. routing_key: test # Declare a queue exclusive at the exchange # # Default: false exclusive: false # When multiple is true, this delivery and all prior unacknowledged deliveries # on the same channel will be acknowledged. This is useful for batch processing # of deliveries # # Default: false multiple_ack: false # The consumer_id is identified by a string that is unique and scoped for all consumers on this channel. # # Default: "roadrunner" + uuid. consumer_id: "roadrunner-uuid" # Use rabbitmq mechanism to requeue the job on fail # # Default: false requeue_on_fail: false # Queue headers # # Default: null queue_headers: x-queue-mode: lazy test-local-3: # Driver name # # This option is required. driver: beanstalk # Driver's configuration # # Should not be empty config: # Pipeline priority # # Default priority: 11 # Beanstalk internal tube priority # # Default: 1 tube_priority: 1 # Tube name # # Default: default tube: default-1 # If no job is available before this timeout has passed, Reserve returns a ConnError recording ErrTimeout. # # Default: 5s reserve_timeout: 10s test-local-4: # Driver name # # This option is required. driver: sqs # Driver's configuration # # Should not be empty config: # Pipeline priority # # If the job has priority set to 0, it will inherit the pipeline's priority. Default: 10. priority: 10 # Number of jobs to prefetch from the SQS until ACK/NACK. # # Default: 10 prefetch: 10 # Get queue URL only # # Default: false skip_queue_declaration: false # The duration (in seconds) that the received messages are hidden from subsequent # retrieve requests after being retrieved by a ReceiveMessage request # # Default: 0 visibility_timeout: 0 # The duration (in seconds) for which the call waits for a message to arrive # in the queue before returning. If a message is available, the call returns # sooner than WaitTimeSeconds. If no messages are available and the wait time # expires, the call returns successfully with an empty list of messages. # # Default: 0 wait_time_seconds: 0 # Queue name. # # Default: default queue: default # Message group ID: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html#SQS-SendMessage-request-MessageGroupId # # Default: empty, should be set if FIFO queue is used message_group_id: "test" # List of the AWS SQS attributes https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SetQueueAttributes.html. attributes: DelaySeconds: 0 MaximumMessageSize: 262144 MessageRetentionPeriod: 345600 ReceiveMessageWaitTimeSeconds: 0 VisibilityTimeout: 30 # Tags don't have any semantic meaning. Amazon SQS interprets tags as character # strings. tags: test: "tag" test-local-5: # Driver name # # This option is required driver: nats # Driver's configuration # # Should not be empty config: # Pipeline priority # # If the job has priority set to 0, it will inherit the pipeline's priority. Default: 10. priority: 2 # NATS prefetch # # Messages to read into the channel prefetch: 100 # NATS subject # # Default: default subject: default # NATS stream # # Default: default-stream stream: foo # The consumer will only start receiving messages that were created after the consumer was created # # Default: false (deliver all messages from the stream beginning) deliver_new: true # Consumer rate-limiter in bytes https://docs.nats.io/jetstream/concepts/consumers#ratelimit # # Default: 1000 rate_limit: 100 # Delete the stream when after pipeline was stopped # # Default: false delete_stream_on_stop: false # Delete message from the stream after successful acknowledge # # Default: false delete_after_ack: false test-local-6: # Driver name # # This option is required driver: kafka # Driver's configuration # # Should not be empty config: # Pipeline priority # # If the job has priority set to 0, it will inherit the pipeline's priority. Default: 10. priority: 1 # Auto create topic for the consumer/producer # # Optional, default: false auto_create_topics_enable: false # Kafka producer options # # Optional, required only if Push/PushBatch is used. producer_options: # disable_idempotent disables idempotent produce requests, opting out of # Kafka server-side deduplication in the face of reissued requests due to # transient network problems. # Idempotent production is strictly a win, but does require the IDEMPOTENT_WRITE permission on CLUSTER # (pre Kafka 3.0), and not all clients can have that permission. # # Optional, defaut: false disable_idempotent: false # required_acks sets the required acks for produced records. # # Optional, default: AllISRAcks. Possible values: NoAck, LeaderAck, AllISRAck required_acks: AllISRAck # max_message_bytes upper bounds the size of a record batch, overriding the default 1,000,012 bytes. # This mirrors Kafka's max.message.bytes. # # Optional, default: 1000012 max_message_bytes: 1000012 # request_timeout sets how long Kafka broker's are allowed to respond produce requests, overriding the default 10s. # If a broker exceeds this duration, it will reply with a request timeout error. # # Optional, default: 10s. Possible values: 10s, 10m. request_timeout: 10s # delivery_timeout sets a rough time of how long a record can sit around in a batch before timing out, # overriding the unlimited default. If idempotency is enabled (as it is by default), this option is only # enforced if it is safe to do so without creating invalid sequence numbers. # # Optional, default: delivery.timeout.ms Kafka option. Possible values: 10s, 10m. delivery_timeout: 100s # transaction_timeout sets the allowed for a transaction, overriding the default 40s. It is a good idea to # keep this less than a group's session timeout. # # Optional, default 40s. Possible values: 10s, 10m. transaction_timeout: 100s # compression_codec sets the compression codec to use for producing records. # # Optional, default is chosen in the order preferred based on broker support. Possible values: gzip, snappy, lz4, zstd. compression_codec: gzip # Partitioning strategy to use. Possible values: Manual, Uniform, RoundRobin, LeastBackup, Sticky. # Default (if omitted): Uniform partitioning_strategy: Uniform # Kafka Consumer options. Needed to consume messages from the Kafka cluster. # # Optional, needed only if `consume` is used. consumer_options: # topics: adds topics to use for consuming # # Default: empty (will produce an error), possible to use regexp if `consume_regexp` is set to true. topics: [ "foo", "bar", "^[a-zA-Z0-9._-]+$" ] # consume_regexp sets the client to parse all topics passed to `topics` as regular expressions. # When consuming via regex, every metadata request loads *all* topics, so that all topics can be passed to # any regular expressions. Every topic is evaluated only once ever across all regular expressions; either it # permanently is known to match, or is permanently known to not match. # # Optional, default: false. consume_regexp: true # max_fetch_message_size sets the maximum amount of bytes a broker will try to send during a fetch, overriding the default 50MiB. # Note that brokers may not obey this limit if it has records larger than this limit. # Also note that this client sends a fetch to each broker concurrently, meaning the client will # buffer up to worth of memory. This corresponds to the Java fetch.max.bytes setting. # # Optional, default 50000 max_fetch_message_size: 50000 # min_fetch_message_size sets the minimum amount of bytes a broker will try to send during a fetch, # overriding the default 1 byte. With the default of 1, data is sent as soon as it is available. # This corresponds to the Java fetch.min.bytes setting. # # Optional, default: 1. min_fetch_message_size: 1 # consume_partitions sets partitions to consume from directly and the offsets to start consuming those partitions from. # This option is basically a way to explicitly consume from subsets of partitions in topics, or to consume at exact offsets. # # NOTE: This option is not compatible with group consuming and regex consuming. # # Optional, default: null consume_partitions: # Topic for the consume_partitions # # Required at least one topic. foo: # Partition for the topic. # # Required at least one partition. 0: # Partition offset. # # Required if all options is used. No default, error on empty. # Possible values: AtEnd, At, AfterMilli, AtStart, Relative, WithEpoch type: AtStart # Value for the: At, AfterMilli, Relative and WithEpoch offsets. # # Optional, default: 0. value: 1 # consumer_offset sets the offset to start consuming from, or if OffsetOutOfRange is seen while fetching, # to restart consuming from. # # Optional, default: AtStart consumer_offset: # Partition offset. # # Optional, default: AtStart. Possible values: AtEnd, At, AfterMilli, AtStart, Relative, WithEpoch type: AtStart # Value for the: At, AfterMilli, Relative and WithEpoch offsets. # # Optional, default: 0. value: 1 # group_options sets the consumer group for the client to join and consume in. # This option is required if using any other group options. # # Default: empty. group_options: # group_id sets the group to consume. # # Required if using group consumer. group_id: foo # block_rebalance_on_poll switches the client to block rebalances whenever you poll. # # Optional, default: false. block_rebalance_on_poll: true # InstanceID switches the group member from dynamic to static membership. # Optional. # instance_id: rr-instance-1 # SASL options specific for this pipeline (optional). If omitted, global driver SASL settings are used. sasl_options: mechanism: plain username: foo password: bar # list of pipelines to be consumed by the server automatically at the start, keep empty if you want to start consuming manually consume: [ "test-local", "test-local-1", "test-local-2", "test-local-3", "test-local-4", "test-local-5", "test-local-6", ] grpc: # GRPC address to listen # # This option is required listen: "tcp://127.0.0.1:9001" # Proto file to use, multiply files supported [SINCE 2.6]. As of v2023.1.4, wilcards are allowed in the proto field. # # This option is required proto: - "*.proto" # wilcard - "first.proto" - "second.proto" # GRPC TLS configuration # # This section is optional tls: # Path to the key file # # This option is required key: "/ssl/key.pem" # Path to the certificate # # This option is required cert: "/ssl/cert.crt" # Path to the CA certificate, defines the set of root certificate authorities that servers use if required to verify a client certificate. Used with the `client_auth_type` option. # # This option is optional root_ca: "/ssl/ca.crt" # Client auth type. # # This option is optional. Default value: no_client_certs. Possible values: request_client_cert, require_any_client_cert, verify_client_cert_if_given, require_and_verify_client_cert, no_client_certs client_auth_type: no_client_certs # Maximum send message size # # This option is optional. Default value: 50 (MB) max_send_msg_size: 50 # Maximum receive message size # # This option is optional. Default value: 50 (MB) max_recv_msg_size: 50 # MaxConnectionIdle is a duration for the amount of time after which an # idle connection would be closed by sending a GoAway. Idleness duration is # defined since the most recent time the number of outstanding RPCs became # zero or the connection establishment. # # This option is optional. Default value: infinity. # Can be 1s, 2m, 2h (seconds, minutes, hours) and complex 2h2m1s. max_connection_idle: 0s # MaxConnectionAge is a duration for the maximum amount of time a # connection may exist before it will be closed by sending a GoAway. A # random jitter of +/-10% will be added to MaxConnectionAge to spread out # connection storms. # # This option is optional. Default value: infinity. # Can be 1s, 2m, 2h (seconds, minutes, hours) and complex 2h2m1s. max_connection_age: 0s # MaxConnectionAgeGrace is an additive period after MaxConnectionAge after # which the connection will be forcibly closed. # Can be 1s, 2m, 2h (seconds, minutes, hours) and complex 2h2m1s. max_connection_age_grace: 8h # MaxConnectionAgeGrace is an additive period after MaxConnectionAge after # which the connection will be forcibly closed. # # This option is optional: Default value: 10 max_concurrent_streams: 10 # After a duration of this time if the server doesn't see any activity it # pings the client to see if the transport is still alive. # If set below 1s, a minimum value of 1s will be used instead. # # This option is optional. Default value: 2h # Can be 1s, 2m, 2h (seconds, minutes, hours) and complex 2h2m1s. ping_time: 1s # After having pinged for keepalive check, the server waits for a duration # of Timeout and if no activity is seen even after that the connection is # closed. # # This option is optional. Default value: 20s # Can be 1s, 2m, 2h (seconds, minutes, hours) and complex 2h2m1s. timeout: 200s # Usual workers pool configuration pool: # Debug mode for the pool. In this mode, pool will not pre-allocate the worker. Worker (only 1, num_workers ignored) will be allocated right after the request arrived. # # Default: false debug: false # Override server's command # # Default: empty command: "php my-super-app.php" # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. # # Default: 0 num_workers: 0 # Maximal count of worker executions. Zero (or nothing) means no limit. # # Default: 0 max_jobs: 0 # [2023.3.10] # Maximum size of the internal requests queue. After reaching the limit, all additional requests would be rejected with error. # # Default: 0 (no limit) max_queue_size: 0 # Timeout for worker allocation. Zero means 60s. # # Default: 60s allocate_timeout: 60s # Timeout for the reset timeout. Zero means 60s. # # Default: 60s reset_timeout: 60s # Timeout for the stream cancellation. Zero means 60s. # # Default: 60s stream_timeout: 60s # Timeout for worker destroying before process killing. Zero means 60s. # # Default: 60s destroy_timeout: 60s # [SINCE 2.6] TCP plugin tcp: # The list of TCP servers to start. There are can be any number of servers you want to allocate. servers: # Server name. Can be any string. Will be sent to the worker in the context. # # Default: none. server1: # Address to listen. # # Error if empty. addr: 127.0.0.1:7778 # Data packets delimiter. Every send should end either with EOF or with the delimiter. # # Default: CRLF (\r\n) delimiter: "\r\n" server2: addr: 127.0.0.1:8811 server3: addr: 127.0.0.1:8812 delimiter: "\r\n" # Chunks that RR uses to read the data. In MB. If you expect big payloads on a TCP server, to reduce `read` syscalls, would be a good practice to use a fairly big enough buffer. # # Default: 1MB read_buf_size: 10 # The worker pool to use for the TCP service. pool: # Debug mode for the pool. In this mode, pool will not pre-allocate the worker. Worker (only 1, num_workers ignored) will be allocated right after the request arrived. # # Default: false debug: false # Override server's command # # Default: empty command: "php my-super-app.php" # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. # # Default: 0 num_workers: 0 # Maximal count of worker executions. Zero (or nothing) means no limit. # # Default: 0 max_jobs: 0 # [2023.3.10] # Maximum size of the internal requests queue. After reaching the limit, all additional requests would be rejected with error. # # Default: 0 (no limit) max_queue_size: 0 # Timeout for worker allocation. Zero means 60s. # # Default: 60s allocate_timeout: 60s # Timeout for the reset timeout. Zero means 60s. # # Default: 60s reset_timeout: 60s # Timeout for the stream cancellation. Zero means 60s. # # Default: 60s stream_timeout: 60s # Timeout for worker destroying before process killing. Zero means 60s. # # Default: 60s destroy_timeout: 60s # [SINCE 2.6] Fileserver to serve static files. fileserver: # File server address # # Error on empty address: 127.0.0.1:10101 # Etag calculation. Request body CRC32. # # Default: false calculate_etag: true # Weak etag calculation # # Default: false weak: false # Enable body streaming for the files more than 4KB # # Default: false stream_request_body: true serve: # HTTP prefix # # Error on empty - prefix: "/foo" # Directory to serve # # Default: "." root: "../../../tests" # When set to true, the server tries minimizing CPU usage by caching compressed files # # Default: false compress: false # Expiration duration for inactive file handlers. Units: seconds. # # Default: 10, use a negative value to disable it. cache_duration: 10 # The value for the Cache-Control HTTP-header. Units: seconds # # Default: 10 seconds max_age: 10 # Enable range requests # https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests # # Default: false bytes_range: true - prefix: "/foo/bar" root: "../../../tests" compress: false cache_duration: 10 max_age: 10 bytes_range: true # Centrifugo server plugin # # Docs: https://centrifugal.dev/ centrifuge: # Centrifugo server proxy address (docs: https://centrifugal.dev/docs/server/proxy#grpc-proxy) # # Optional, default: tcp://127.0.0.1:30000 proxy_address: "tcp://127.0.0.1:30000" # gRPC server API address (docs: https://centrifugal.dev/docs/server/server_api#grpc-api) # # Optional, default: tcp://127.0.0.1:10000. Centrifugo: `grpc_api` should be set to true and `grpc_port` should match this value. grpc_api_address: tcp://127.0.0.1:10000 # Use gRPC gzip compressor # # Optional, default: false use_compressor: true # Your application version # # Optional, default: v1.0.0 version: "v1.0.0" # Your application name # # Optional, default: roadrunner name: "roadrunner" # Workers pool settings. pool: # Debug mode for the pool. In this mode, pool will not pre-allocate the worker. Worker (only 1, num_workers ignored) will be allocated right after the request arrived. # # Default: false debug: false # Override server's command # # Default: empty command: "php my-super-app.php" # How many worker processes will be started. Zero (or nothing) means the number of logical CPUs. # # Default: 0 num_workers: 0 # Maximal count of worker executions. Zero (or nothing) means no limit. # # Default: 0 max_jobs: 0 # [2023.3.10] # Maximum size of the internal requests queue. After reaching the limit, all additional requests would be rejected with error. # # Default: 0 (no limit) max_queue_size: 0 # Timeout for worker allocation. Zero means 60s. # # Default: 60s allocate_timeout: 60s # Timeout for the reset timeout. Zero means 60s. # # Default: 60s reset_timeout: 60s # Timeout for the stream cancellation. Zero means 60s. # # Default: 60s stream_timeout: 60s # Timeout for worker destroying before process killing. Zero means 60s. # # Default: 60s destroy_timeout: 60s # Supervisor is used to control http workers (previous name was "limit", video: https://www.youtube.com/watch?v=NdrlZhyFqyQ). # "Soft" limits will not interrupt current request processing. "Hard" # limit on the contrary - interrupts the execution of the request. supervisor: # How often to check the state of the workers. # # Default: 5s watch_tick: 5s # Maximum time worker is allowed to live (soft limit). Zero means no limit. # # Default: 0s ttl: 0s # How long worker can spend in IDLE mode after first using (soft limit). Zero means no limit. # # Default: 0s idle_ttl: 10s # Maximal worker memory usage in megabytes (soft limit). Zero means no limit. # # Default: 0 max_worker_memory: 128 # Maximal job lifetime (hard limit). Zero means no limit. # # Default: 0s exec_ttl: 60s # TLS configuration # # Optional, default: null tls: # TLS key # # Required key: "/ssl/key.pem" # TLS certificate # # Required cert: "/ssl/cert.crt" ## RoadRunner internal container configuration (docs: https://github.com/spiral/endure). endure: # How long to wait for stopping. # # Default: 30s grace_period: 30s # Print graph in the graphviz format to the stdout (paste here to visualize https://dreampuf.github.io) # # Default: false print_graph: false # Logging level. Possible values: "debug", "info", "warn", "error", "panic", "fatal". # # Default: "error" log_level: error ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Start RR with AMQP", "type": "go", "request": "launch", "mode": "auto", "program": "${workspaceFolder}/cmd/rr/main.go", "args": [ "serve", "-c", "../../.rr-sample-bench-jobs.yaml" ], }, { "name": "Start RR with HTTP", "type": "go", "request": "launch", "mode": "auto", "program": "${workspaceFolder}/cmd/rr/main.go", "args": [ "serve", "-c", "../../.rr-sample-bench-http.yaml" ], }, { "name": "RR workers", "type": "go", "request": "launch", "mode": "auto", "program": "${workspaceFolder}/cmd/rr/main.go", "args": [ "workers", "-c", "../../.rr-sample-bench-http.yaml" ], }, ] } ================================================ FILE: CHANGELOG.md ================================================ # CHANGELOG releases: [docs](https://docs.roadrunner.dev/docs/releases) ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview RoadRunner is a high-performance PHP application server and process manager written in Go. It supports running as a service with extensive plugin functionality for HTTP/2/3, gRPC, queues (RabbitMQ, Kafka, SQS, NATS), KV stores, WebSockets, Temporal workflows, and more. ## Development Commands ### Build ```bash make build # Or manually: CGO_ENABLED=0 go build -trimpath -ldflags "-s" -o rr cmd/rr/main.go ``` ### Test ```bash make test # Or manually: go test -v -race ./... ``` ### Debug ```bash make debug # Uses delve to debug with sample config ``` ### Run RoadRunner ```bash ./rr serve -c .rr.yaml ``` ### Other Commands ```bash ./rr workers # Show worker status ./rr workers -i # Interactive worker information ./rr reset # Reset workers ./rr jobs # Jobs management commands ./rr stop # Stop RoadRunner server ``` ### Run Single Test ```bash go test -v -race -run TestName ./path/to/package ``` ## Architecture ### Plugin System RoadRunner uses the **Endure** dependency injection container. All plugins are registered in `container/plugins.go:Plugins()`. The plugin architecture follows these principles: 1. **Plugin Registration**: Plugins are listed in `container/plugins.go` and automatically wired by Endure 2. **Plugin Dependencies**: Plugins declare dependencies via struct fields with interface types 3. **Initialization Order**: Endure resolves the dependency graph and initializes plugins in correct order ### Key Components - **`cmd/rr/main.go`**: Entry point that delegates to CLI commands - **`internal/cli/`**: CLI command implementations (serve, workers, reset, jobs, stop) - **`container/`**: Plugin registration and Endure container configuration - **Plugin packages**: External packages under `github.com/roadrunner-server/*` (imported in go.mod) ### Configuration - Primary config: `.rr.yaml` (extensive sample provided) - Version 3 config format required (`version: '3'`) - Environment variable substitution supported: `${ENVIRONMENT_VARIABLE_NAME}` - Sample configs: `.rr-sample-*.yaml` for different use cases (HTTP, gRPC, Temporal, Kafka, etc.) ### Core Plugins **Server Management:** - `server`: Worker pool management (NewWorker, NewWorkerPool) - `rpc`: RPC server for PHP-to-Go communication (default: tcp://127.0.0.1:6001) - `logger`: Logging infrastructure - `informer`: Worker status reporting - `resetter`: Worker reset functionality **Protocol Servers:** - `http`: HTTP/1/2/3 and FastCGI server with middleware support - `grpc`: gRPC server - `tcp`: Raw TCP connection handling **Jobs/Queue Drivers:** - `jobs`: Core jobs plugin - `amqp`, `sqs`, `nats`, `kafka`, `beanstalk`: Queue backends - `gps`: Google Pub/Sub **KV Stores:** - `kv`: Core KV plugin - `memory`, `boltdb`, `redis`, `memcached`: Storage backends **HTTP Middleware:** - `static`, `headers`, `gzip`, `prometheus`, `send`, `proxy_ip_parser`, `otel`, `fileserver` **Other:** - `temporal`: Temporal.io workflow engine integration - `centrifuge`: WebSocket/Broadcast via Centrifugo - `lock`: Distributed locks - `metrics`: Prometheus metrics - `service`: Systemd-like service manager ### Worker Communication RoadRunner communicates with PHP workers via: - **Goridge protocol**: Binary protocol over pipes, TCP, or Unix sockets - **RPC**: For management operations (reset, stats, etc.) - Workers are PHP processes that implement the RoadRunner worker protocol ### Testing - Tests use standard Go testing with `-race` flag - Test files follow `*_test.go` convention - Sample configs in `.rr-sample-*.yaml` are used for integration tests - Test directories: `container/test`, `internal/rpc/test` ## Important Notes - Go version: 1.25+ required (see go.mod) - Module path: `github.com/roadrunner-server/roadrunner/v2025` - Some versions are explicitly excluded in go.mod (e.g., go-redis v9.15.0, viper v1.18.x) - Debug mode available via `--debug` flag (starts debug server on :6061) - Config overrides supported via `-o dot.notation=value` flag - Working directory can be set with `-w` flag - `.env` file support via `--dotenv` flag or `DOTENV_PATH` environment variable ## Adding New Plugins 1. Import the plugin package in `container/plugins.go` 2. Add plugin instance to the `Plugins()` slice 3. Plugin must implement appropriate RoadRunner plugin interfaces 4. Endure will handle dependency injection and lifecycle management ## Configuration Patterns - Each plugin has its own configuration section (named after plugin) - Pools configuration is consistent across plugins (num_workers, max_jobs, timeouts, supervisor) - TLS configuration follows similar pattern across plugins - Most plugins support graceful shutdown via timeouts ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at wolfy-j@spiralscout.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Welcome to RoadRunner docs contributing guide Thank you for investing your time in contributing to our project! Any contribution you make will be reflected on [RR](https://github.com/roadrunner-server/roadrunner#contributors) :sparkles:. Read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectable. In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR. ### Issues #### Create a new issue If you spot a problem with the RR, [search if an issue already exists](https://github.com/roadrunner-server/roadrunner/issues). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/roadrunner-server/roadrunner/issues/new/choose). #### Solve an issue Scan through our [existing issues](https://github.com/roadrunner-server/roadrunner/issues) to find one that interests you. You can narrow down the search using `labels` as filters. If you find an issue to work on, you are welcome to open a PR with a fix. ### Pull Request When you're finished with the changes, create a pull request, also known as a PR. - Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request. - Don't forget to update the docs if you are solving one. - Make sure, that all checkboxes in the PR template are solved. Once you submit your PR, a RR team member will review your proposal. We may ask questions or request for additional information. - As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations). - If you run into any merge issues, checkout this [git tutorial](https://github.com/skills/resolve-merge-conflicts) to help you resolve merge conflicts and other issues. ### Your PR is merged! Congratulations :tada::tada: The RoadRunner team thanks you :sparkles:. Once your PR is merged, your contributions will be publicly visible on the [RR page](https://github.com/roadrunner-server/roadrunner#contributors). Now that you are part of the RoadRunner server community, see how else you can [contribute to the RR](https://github.com/roadrunner-server/roadrunner/issues). ================================================ FILE: Dockerfile ================================================ # Image page: FROM --platform=${TARGETPLATFORM:-linux/amd64} golang:1.26-alpine AS builder # app version and build date must be passed during image building (version without any prefix). # e.g.: `docker build --build-arg "APP_VERSION=1.2.3" --build-arg "BUILD_TIME=$(date +%FT%T%z)" .` ARG APP_VERSION="undefined" ARG BUILD_TIME="undefined" WORKDIR /src # Copy module files first for layer caching COPY go.mod go.sum ./ RUN go mod download # Copy source and build COPY . . # arguments to pass on each go tool link invocation ENV LDFLAGS="-s \ -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.version=$APP_VERSION \ -X github.com/roadrunner-server/roadrunner/v2025/internal/meta.buildTime=$BUILD_TIME" # compile and verify binary RUN CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./rr ./cmd/rr && ./rr -v # ---- Final stage ---- FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3 RUN apk upgrade --no-cache && apk add --no-cache ca-certificates # use same build arguments for image labels ARG APP_VERSION="undefined" ARG BUILD_TIME="undefined" # https://github.com/opencontainers/image-spec/blob/main/annotations.md LABEL org.opencontainers.image.title="roadrunner" LABEL org.opencontainers.image.description="High-performance PHP application server and process manager written in Go and powered with plugins" LABEL org.opencontainers.image.url="https://roadrunner.dev" LABEL org.opencontainers.image.source="https://github.com/roadrunner-server/roadrunner" LABEL org.opencontainers.image.vendor="SpiralScout" LABEL org.opencontainers.image.version="$APP_VERSION" LABEL org.opencontainers.image.created="$BUILD_TIME" LABEL org.opencontainers.image.licenses="MIT" # Non-root user RUN addgroup -S rr && adduser -S -G rr rr # copy required files from builder image COPY --from=builder /src/rr /usr/bin/rr COPY --from=builder /src/.rr.yaml /etc/rr.yaml USER rr # use roadrunner binary as image entrypoint ENTRYPOINT ["/usr/bin/rr"] CMD ["serve", "-c", "/etc/rr.yaml"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Spiral Scout Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ test: go test -v -race ./... build: CGO_ENABLED=0 go build -trimpath -ldflags "-s" -o rr cmd/rr/main.go debug: dlv debug cmd/rr/main.go -- serve -c .rr-sample-bench-http.yaml ================================================ FILE: README.md ================================================

All releases

RoadRunner is an open-source (MIT licensed) high-performance PHP application server, process manager written in Go and powered with plugins ❤️. It supports running as a service with the ability to extend its functionality on a per-project basis with plugins. # Features **RoadRunner** features a range of plugins, including `HTTP(S)/2/3` and `fCGI` servers that are compatible with PSR-7/PSR-17 standards. This is just one of its many capabilities. It serves as an effective alternative to the traditional Nginx+FPM setup, providing improved performance and more flexibility. Its extensive plugin options go far beyond just `HTTP(S)/2/3` and `fCGI` servers, offering a broad range of functionalities: - Queue drivers: RabbitMQ, Kafka, SQS, Beanstalk, NATS, In-Memory. - KV drivers: Redis, Memcached, BoltDB, In-Memory. - OpenTelemetry protocol support (`gRPC`, `http`, `jaeger`). - [Workflow engine](https://github.com/temporalio/sdk-php) via [Temporal](https://temporal.io). - `gRPC` server. For increased speed, the `protobuf` extension can be used. - `HTTP(S)/2/3` and `fCGI` servers features **automatic TLS management**, **103 Early Hints** support and middleware like: Static, Headers, gzip, prometheus (metrics), send (x-sendfile), OTEL, proxy_ip_parser, etc. - Embedded distribute lock plugin which manages access to shared resources. - Metrics server (you might easily expose your own). - WebSockets and Broadcast via [Centrifugo](https://centrifugal.dev) server. - Systemd-like services manager with auto-restarts, execution time limiter, etc. - Production-ready. - And more 😉 # Join our discord server: [Link](https://discord.gg/TFeEmCs)

Official Website | Documentation | Forum | Release schedule | Ask RoadRunner Guru

# Installation The easiest way to get the latest RoadRunner version is to use one of the pre-built release binaries, which are available for OSX, Linux, FreeBSD, and Windows. Instructions for using these binaries are on the GitHub [releases page](https://github.com/roadrunner-server/roadrunner/releases). ## Docker: To get the roadrunner binary file you can use our docker image: `ghcr.io/roadrunner-server/roadrunner:2025.X.X` (more information about image and tags can be found [here](https://github.com/roadrunner-server/roadrunner/pkgs/container/roadrunner)). ```dockerfile FROM ghcr.io/roadrunner-server/roadrunner:2025.X.X AS roadrunner FROM php:8.3-cli COPY --from=roadrunner /usr/bin/rr /usr/local/bin/rr # USE THE RR ``` Configuration located in the `.rr.yaml` file ([full sample](https://github.com/roadrunner-server/roadrunner/blob/master/.rr.yaml)): ## Installation via Composer You can also install RoadRunner automatically using the command shipped with the composer package, run: ```bash composer require spiral/roadrunner-cli ./vendor/bin/rr get-binary ``` Server binary will be available at the root of your project. > **Note** > > PHP's extensions `php-curl` and `php-zip` are required to download RoadRunner automatically. > PHP's extensions `php-sockets` need to be installed to run roadrunner. > Check with `php --modules` your installed extensions. ## Installation option for the Debian-derivatives (Ubuntu, Mint, MX, etc) ```bash wget https://github.com/roadrunner-server/roadrunner/releases/download/v2025.X.X/roadrunner-2025.X.X-linux-amd64.deb sudo dpkg -i roadrunner-2025.X.X-linux-amd64.deb ``` ## Download the latest release via curl: ```bash curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/roadrunner-server/roadrunner/master/download-latest.sh | sh ``` ## MacOS using [Homebrew](https://brew.sh/): ```bash brew install roadrunner ``` ## Windows using [Chocolatey](https://community.chocolatey.org/): ```bash choco install roadrunner ``` --- Configuration can be located in `.rr.yaml` file ([full sample](https://github.com/roadrunner-server/roadrunner/blob/master/.rr.yaml)): ```yaml version: '3' rpc: listen: tcp://127.0.0.1:6001 server: command: "php worker.php" http: address: "0.0.0.0:8080" logs: level: error ``` > Read more in [Documentation](https://docs.roadrunner.dev). Example Worker: -------- ```php waitRequest()) { try { $rsp = new Psr7\Response(); $rsp->getBody()->write('Hello world!'); $worker->respond($rsp); } catch (\Throwable $e) { $worker->getWorker()->error((string)$e); } } ``` > [!IMPORTANT] > If you see the `EOF` error, check that you have installed the PHP packages from [this step](https://github.com/roadrunner-server/roadrunner#installation-via-composer). > If this does not help, try to execute the command `php worker.php` directly and check the output. --- ### Available Plugins: [link](https://docs.roadrunner.dev) Run: ---- To run application server: ``` $ ./rr serve -c .rr.yaml ``` License: -------- The MIT License (MIT). Please see [`LICENSE`](./LICENSE) for more information. Maintained by [Spiral Scout](https://spiralscout.com). ## Contributors Thanks to all the people who already contributed! ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions | Version | Supported | |----------|-----------| | 2.x | No | | 2023.x.x | No | | 2024.x.x | Yes | | 2025.x.x | Yes | ================================================ FILE: benchmarks/simple.js ================================================ import http from 'k6/http'; import { sleep } from 'k6'; export const options = { // A number specifying the number of VUs to run concurrently. vus: 1000, // A string specifying the total duration of the test run. duration: '30s', // The following section contains configuration options for execution of this // test script in Grafana Cloud. // // See https://grafana.com/docs/grafana-cloud/k6/get-started/run-cloud-tests-from-the-cli/ // to learn about authoring and running k6 test scripts in Grafana k6 Cloud. // // ext: { // loadimpact: { // // The ID of the project to which the test is assigned in the k6 Cloud UI. // // By default tests are executed in default project. // projectID: "", // // The name of the test in the k6 Cloud UI. // // Test runs with the same name will be grouped. // name: "script.js" // } // }, // Uncomment this section to enable the use of Browser API in your tests. // // See https://grafana.com/docs/k6/latest/using-k6-browser/running-browser-tests/ to learn more // about using Browser API in your test scripts. // // scenarios: { // // The scenario name appears in the result summary, tags, and so on. // // You can give the scenario any name, as long as each name in the script is unique. // ui: { // // Executor is a mandatory parameter for browser-based tests. // // Shared iterations in this case tells k6 to reuse VUs to execute iterations. // // // // See https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/ for other executor types. // executor: 'shared-iterations', // options: { // browser: { // // This is a mandatory parameter that instructs k6 to launch and // // connect to a chromium-based browser, and use it to run UI-based // // tests. // type: 'chromium', // }, // }, // }, // } }; // The function that defines VU logic. // // See https://grafana.com/docs/k6/latest/examples/get-started-with-k6/ to learn more // about authoring k6 scripts. // let params = { timeout: '120s' }; export default function() { http.get('http://127.0.0.1:15389', params); } ================================================ FILE: cmd/rr/command_test.go ================================================ package main import ( "bytes" "io" "os" "testing" "time" "github.com/stretchr/testify/assert" ) func Test_Main(t *testing.T) { os.Args = []string{"rr", "--help"} exitFn = func(code int) { assert.Equal(t, 0, code) } r, w, _ := os.Pipe() os.Stdout = w main() _ = w.Close() buf := new(bytes.Buffer) _ = r.SetReadDeadline(time.Now().Add(time.Second)) _, _ = io.Copy(buf, r) assert.Contains(t, buf.String(), "Usage:") assert.Contains(t, buf.String(), "Available Commands:") assert.Contains(t, buf.String(), "Flags:") } func Test_MainWithoutCommands(t *testing.T) { os.Args = []string{"rr"} exitFn = func(code int) { assert.Equal(t, 0, code) } r, w, _ := os.Pipe() os.Stdout = w main() buf := new(bytes.Buffer) _ = r.SetReadDeadline(time.Now().Add(time.Second)) _, _ = io.Copy(buf, r) assert.Contains(t, buf.String(), "Usage:") assert.Contains(t, buf.String(), "Available Commands:") assert.Contains(t, buf.String(), "Flags:") } func Test_MainUnknownSubcommand(t *testing.T) { os.Args = []string{"", "foobar"} exitFn = func(code int) { assert.Equal(t, 1, code) } r, w, _ := os.Pipe() os.Stderr = w main() _ = w.Close() buf := new(bytes.Buffer) _ = r.SetReadDeadline(time.Now().Add(time.Second)) _, _ = io.Copy(buf, r) assert.Contains(t, buf.String(), "unknown command") assert.Contains(t, buf.String(), "foobar") } ================================================ FILE: cmd/rr/doc.go ================================================ // Package main is the entry point for the RoadRunner CLI application. package main ================================================ FILE: cmd/rr/main.go ================================================ package main import ( "os" "path/filepath" "github.com/fatih/color" "github.com/roadrunner-server/roadrunner/v2025/internal/cli" ) // exitFn is a function for an application exiting. var exitFn = os.Exit //nolint:gochecknoglobals // main CLI application entrypoint. func main() { exitFn(run()) } // run this CLI application. func run() int { cmd := cli.NewCommand(filepath.Base(os.Args[0])) if err := cmd.Execute(); err != nil { _, _ = color.New(color.FgHiRed, color.Bold).Fprintln(os.Stderr, err.Error()) return 1 } return 0 } ================================================ FILE: codecov.yml ================================================ coverage: status: project: default: target: auto threshold: 0% informational: true patch: default: target: auto threshold: 0% informational: true ================================================ FILE: composer.json ================================================ { "name": "spiral/roadrunner", "type": "metapackage", "description": "RoadRunner: High-performance PHP application server and process manager written in Go and powered with plugins", "license": "MIT", "homepage": "https://roadrunner.dev/", "support": { "docs": "https://docs.roadrunner.dev/", "issues": "https://github.com/roadrunner-server/roadrunner/issues", "chat": "https://discord.gg/V6EK4he" }, "authors": [ { "name": "Anton Titov / Wolfy-J", "email": "wolfy.jd@gmail.com" }, { "name": "Valery Piashchynski", "homepage": "https://github.com/rustatian" }, { "name": "RoadRunner Community", "homepage": "https://github.com/roadrunner-server/roadrunner/graphs/contributors" } ], "funding": [ { "type": "github", "url": "https://github.com/sponsors/roadrunner-server" } ] } ================================================ FILE: container/config.go ================================================ package container import ( "fmt" "log/slog" "time" "github.com/spf13/viper" ) // Config defines endure container configuration. type Config struct { GracePeriod time.Duration `mapstructure:"grace_period"` LogLevel string `mapstructure:"log_level"` WatchdogSec int `mapstructure:"watchdog_sec"` PrintGraph bool `mapstructure:"print_graph"` } const ( // endure config key endureKey = "endure" // overall grace period, after which container will be stopped forcefully defaultGracePeriod = time.Second * 30 ) // NewConfig creates endure container configuration. func NewConfig(cfgFile string) (*Config, error) { v := viper.New() v.SetConfigFile(cfgFile) err := v.ReadInConfig() if err != nil { return nil, err } cfg := &Config{ GracePeriod: defaultGracePeriod, LogLevel: "error", PrintGraph: false, } if !v.IsSet(endureKey) { return cfg, nil } err = v.UnmarshalKey(endureKey, cfg) if err != nil { return nil, err } return cfg, nil } func ParseLogLevel(s string) (slog.Leveler, error) { switch s { case "debug": return slog.LevelDebug, nil case "info": return slog.LevelInfo, nil case "warn", "warning": return slog.LevelWarn, nil case "error": return slog.LevelError, nil default: return slog.LevelError, fmt.Errorf(`unknown log level "%s" (allowed: debug, info, warn, error)`, s) } } ================================================ FILE: container/config_test.go ================================================ package container_test import ( "log/slog" "testing" "time" "github.com/roadrunner-server/config/v5" "github.com/roadrunner-server/roadrunner/v2025/container" "github.com/stretchr/testify/assert" ) func TestNewConfig_SuccessfulReading(t *testing.T) { c, err := container.NewConfig("test/endure_ok.yaml") assert.NoError(t, err) assert.NotNil(t, c) ll, err := container.ParseLogLevel(c.LogLevel) assert.NoError(t, err) assert.Equal(t, time.Second*10, c.GracePeriod) assert.True(t, c.PrintGraph) assert.Equal(t, slog.LevelWarn, ll.Level()) } func TestNewConfig_WithoutEndureKey(t *testing.T) { cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte{}} assert.NoError(t, cfgPlugin.Init()) c, err := container.NewConfig("test/without_endure_ok.yaml") assert.NoError(t, err) assert.NotNil(t, c) ll, err := container.ParseLogLevel(c.LogLevel) assert.NoError(t, err) assert.Equal(t, time.Second*30, c.GracePeriod) assert.False(t, c.PrintGraph) assert.Equal(t, slog.LevelError, ll.Level()) } func TestNewConfig_LoggingLevels(t *testing.T) { for _, tt := range []struct { path string giveLevel string wantLevel slog.Leveler wantError bool }{ {path: "test/endure_ok_debug.yaml", giveLevel: "debug", wantLevel: slog.LevelDebug}, {path: "test/endure_ok_info.yaml", giveLevel: "info", wantLevel: slog.LevelInfo}, {path: "test/endure_ok_warn.yaml", giveLevel: "warn", wantLevel: slog.LevelWarn}, {path: "test/endure_ok_error.yaml", giveLevel: "error", wantLevel: slog.LevelError}, {path: "test/endure_ok_foobar.yaml", giveLevel: "foobar", wantError: true}, } { t.Run(tt.giveLevel, func(t *testing.T) { cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte("endure:\n log_level: " + tt.giveLevel)} assert.NoError(t, cfgPlugin.Init()) c, err := container.NewConfig(tt.path) assert.NotNil(t, c) ll, err2 := container.ParseLogLevel(c.LogLevel) if tt.wantError { assert.Error(t, err2) assert.Contains(t, err2.Error(), "unknown log level") } else { assert.NoError(t, err) assert.NoError(t, err2) assert.Equal(t, tt.wantLevel, ll.Level()) } }) } } ================================================ FILE: container/container_test.go ================================================ package container_test import ( "log/slog" "testing" "time" "github.com/roadrunner-server/endure/v2" "github.com/stretchr/testify/assert" ) func TestNewContainer(t *testing.T) { // there is no legal way to test container options c := endure.New(slog.LevelDebug, endure.Visualize(), endure.GracefulShutdownTimeout(time.Second)) c2 := endure.New(slog.LevelDebug, endure.Visualize(), endure.GracefulShutdownTimeout(time.Second)) assert.NotNil(t, c) assert.NotNil(t, c2) } ================================================ FILE: container/doc.go ================================================ // Package container provides Endure dependency injection container configuration // and plugin registration for the RoadRunner application server. package container ================================================ FILE: container/plugins.go ================================================ package container import ( "github.com/roadrunner-server/amqp/v5" appLogger "github.com/roadrunner-server/app-logger/v5" "github.com/roadrunner-server/beanstalk/v5" "github.com/roadrunner-server/boltdb/v5" "github.com/roadrunner-server/centrifuge/v5" "github.com/roadrunner-server/fileserver/v5" gps "github.com/roadrunner-server/google-pub-sub/v5" grpcPlugin "github.com/roadrunner-server/grpc/v5" "github.com/roadrunner-server/gzip/v5" "github.com/roadrunner-server/headers/v5" httpPlugin "github.com/roadrunner-server/http/v5" "github.com/roadrunner-server/informer/v5" "github.com/roadrunner-server/jobs/v5" "github.com/roadrunner-server/kafka/v5" "github.com/roadrunner-server/kv/v5" "github.com/roadrunner-server/lock/v5" "github.com/roadrunner-server/logger/v5" "github.com/roadrunner-server/memcached/v5" "github.com/roadrunner-server/memory/v5" "github.com/roadrunner-server/metrics/v5" "github.com/roadrunner-server/nats/v5" rrOtel "github.com/roadrunner-server/otel/v5" "github.com/roadrunner-server/prometheus/v5" proxyIP "github.com/roadrunner-server/proxy_ip_parser/v5" "github.com/roadrunner-server/redis/v5" "github.com/roadrunner-server/resetter/v5" rpcPlugin "github.com/roadrunner-server/rpc/v5" "github.com/roadrunner-server/send/v5" "github.com/roadrunner-server/server/v5" "github.com/roadrunner-server/service/v5" "github.com/roadrunner-server/sqs/v5" "github.com/roadrunner-server/static/v5" "github.com/roadrunner-server/status/v5" "github.com/roadrunner-server/tcp/v5" rrt "github.com/temporalio/roadrunner-temporal/v5" ) // Plugins return active plugins for the endured container. Feel free to add or remove any plugins. func Plugins() []any { //nolint:funlen return []any{ // bundled // informer plugin (./rr workers, ./rr workers -i) &informer.Plugin{}, // resetter plugin (./rr reset) &resetter.Plugin{}, // mutexes(locks) &lock.Plugin{}, // logger plugin &logger.Plugin{}, // psr-3 logger extension &appLogger.Plugin{}, // metrics plugin &metrics.Plugin{}, // rpc plugin (workers, reset) &rpcPlugin.Plugin{}, // server plugin (NewWorker, NewWorkerPool) &server.Plugin{}, // service plugin &service.Plugin{}, // centrifuge ¢rifuge.Plugin{}, // // ========= JOBS bundle &jobs.Plugin{}, &amqp.Plugin{}, &sqs.Plugin{}, &nats.Plugin{}, &kafka.Plugin{}, &beanstalk.Plugin{}, &gps.Plugin{}, // ========= // // http server plugin with middleware &httpPlugin.Plugin{}, &static.Plugin{}, &headers.Plugin{}, &status.Plugin{}, &gzip.Plugin{}, &prometheus.Plugin{}, &send.Plugin{}, &proxyIP.Plugin{}, &rrOtel.Plugin{}, &fileserver.Plugin{}, // =================== // gRPC &grpcPlugin.Plugin{}, // =================== // KV + Jobs &memory.Plugin{}, // KV + Jobs &boltdb.Plugin{}, // ============== KV &kv.Plugin{}, &memcached.Plugin{}, &redis.Plugin{}, // ============== // raw TCP connections handling &tcp.Plugin{}, // temporal plugin &rrt.Plugin{}, } } ================================================ FILE: container/plugins_test.go ================================================ package container import ( "reflect" "testing" ) func TestPlugins(t *testing.T) { for _, p := range Plugins() { if p == nil { t.Error("plugin cannot be nil") } if pk := reflect.TypeOf(p).Kind(); pk != reflect.Ptr && pk != reflect.Struct { t.Errorf("plugin %v must be a structure or pointer to the structure", p) } } } ================================================ FILE: container/test/endure_ok.yaml ================================================ endure: grace_period: 10s print_graph: true retry_on_fail: true log_level: warn ================================================ FILE: container/test/endure_ok_debug.yaml ================================================ endure: grace_period: 10s print_graph: true retry_on_fail: true log_level: debug ================================================ FILE: container/test/endure_ok_error.yaml ================================================ endure: grace_period: 10s print_graph: true retry_on_fail: true log_level: error ================================================ FILE: container/test/endure_ok_foobar.yaml ================================================ endure: grace_period: 10s print_graph: true retry_on_fail: true log_level: foobar ================================================ FILE: container/test/endure_ok_info.yaml ================================================ endure: grace_period: 10s print_graph: true retry_on_fail: true log_level: info ================================================ FILE: container/test/endure_ok_warn.yaml ================================================ endure: grace_period: 10s print_graph: true retry_on_fail: true log_level: warn ================================================ FILE: container/test/without_endure_ok.yaml ================================================ ================================================ FILE: download-latest.sh ================================================ #!/bin/sh # This script can optionally use a GitHub token to increase your request limit (for example, if using this script in a CI). # To use a GitHub token, pass it through the GITHUB_PAT environment variable. # GLOBALS # Colors RED='\033[31m' GREEN='\033[32m' DEFAULT='\033[0m' # Project name PNAME='roadrunner' # GitHub API address GITHUB_API='https://api.github.com/repos/roadrunner-server/roadrunner/releases' # GitHub Release address GITHUB_REL='https://github.com/roadrunner-server/roadrunner/releases/download' # FUNCTIONS # Gets the version of the latest stable version of RoadRunner by setting the $latest variable. # Returns 0 in case of success, 1 otherwise. get_latest() { # temp_file is needed because the grep would start before the download is over temp_file=$(mktemp -q /tmp/$PNAME.XXXXXXXXX) latest_release="$GITHUB_API/latest" if ! temp_file=$(mktemp -q /tmp/$PNAME.XXXXXXXXX); then echo "$0: Can't create temp file." fetch_release_failure_usage exit 1 fi if [ -z "$GITHUB_PAT" ]; then curl -s "$latest_release" >"$temp_file" || return 1 else curl -H "Authorization: token $GITHUB_PAT" -s "$latest_release" >"$temp_file" || return 1 fi latest="$(grep <"$temp_file" '"tag_name":' | cut -d ':' -f2 | tr -d '"' | tr -d ',' | tr -d ' ' | tr -d 'v')" latestV="$(grep <"$temp_file" '"tag_name":' | cut -d ':' -f2 | tr -d '"' | tr -d ',' | tr -d ' ')" rm -f "$temp_file" return 0 } # 0 -> not alpine # 1 -> alpine isAlpine() { # shellcheck disable=SC2143 if [ "$(grep <"/etc/os-release" "NAME=" | grep -ic "Alpine")" ]; then return 1 fi return 0 } # Gets the OS by setting the $os variable. # Returns 0 in case of success, 1 otherwise. get_os() { os_name=$(uname -s) case "$os_name" in # --- 'Darwin') os='darwin' ;; # --- 'Linux') os='linux' if isAlpine; then os="unknown-musl" fi ;; # --- 'MINGW'*) os='windows' ;; # --- *) return 1 ;; esac return 0 } # Gets the architecture by setting the $arch variable. # Returns 0 in case of success, 1 otherwise. get_arch() { architecture=$(uname -m) # case 1 case "$architecture" in 'x86_64' | 'amd64') arch='amd64' ;; # case 2 'arm64' | 'aarch64') arch='arm64' ;; # all other *) return 1 ;; esac return 0 } get_compress() { os_name=$(uname -s) case "$os_name" in 'Darwin') compress='tar.gz' ;; 'Linux') compress='tar.gz' if isAlpine; then compress="zip" fi ;; 'MINGW'*) compress='zip' ;; *) return 1 ;; esac return 0 } not_available_failure_usage() { printf "$RED%s\n$DEFAULT" 'ERROR: RoadRunner binary is not available for your OS distribution or your architecture yet.' echo '' echo 'However, you can easily compile the binary from the source files.' echo 'Follow the steps at the page ("Source" tab): TODO' } fetch_release_failure_usage() { echo '' printf "$RED%s\n$DEFAULT" 'ERROR: Impossible to get the latest stable version of RoadRunner.' echo 'Please let us know about this issue: https://github.com/roadrunner-server/roadrunner/issues/new/choose' echo '' echo 'In the meantime, you can manually download the appropriate binary from the GitHub release assets here: https://github.com/roadrunner-server/roadrunner/releases/latest' } fill_release_variables() { # Fill $latest variable. if ! get_latest; then fetch_release_failure_usage exit 1 fi if [ "$latest" = '' ]; then fetch_release_failure_usage exit 1 fi # Fill $os variable. if ! get_os; then not_available_failure_usage exit 1 fi # Fill $arch variable. if ! get_arch; then not_available_failure_usage exit 1 fi # Fill $compress variable if ! get_compress; then not_available_failure_usage exit 1 fi } download_binary() { fill_release_variables echo "Downloading RoadRunner binary $latest for $os, architecture $arch..." release_file="$PNAME-$latest-$os-$arch.$compress" if ! curl --fail -OL "$GITHUB_REL/$latestV/$release_file"; then fetch_release_failure_usage exit 1 fi printf "$GREEN%s\n$DEFAULT" "RoadRunner $latest archive successfully downloaded as $release_file" } download_binary ================================================ FILE: githooks-installer.sh ================================================ #!/bin/sh set -e cp ./.githooks/pre-commit .git/hooks/pre-commit echo "DONE" ================================================ FILE: go.mod ================================================ module github.com/roadrunner-server/roadrunner/v2025 go 1.26.1 require ( github.com/buger/goterm v1.0.4 github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.18.0 github.com/joho/godotenv v1.5.1 github.com/olekukonko/tablewriter v1.1.4 github.com/roadrunner-server/amqp/v5 v5.2.3 github.com/roadrunner-server/api/v4 v4.23.0 github.com/roadrunner-server/app-logger/v5 v5.1.9 github.com/roadrunner-server/beanstalk/v5 v5.1.9 github.com/roadrunner-server/boltdb/v5 v5.1.9 github.com/roadrunner-server/centrifuge/v5 v5.1.9 github.com/roadrunner-server/config/v5 v5.1.9 github.com/roadrunner-server/endure/v2 v2.6.2 github.com/roadrunner-server/errors v1.4.1 github.com/roadrunner-server/fileserver/v5 v5.1.9 github.com/roadrunner-server/google-pub-sub/v5 v5.1.9 github.com/roadrunner-server/goridge/v3 v3.8.3 github.com/roadrunner-server/grpc/v5 v5.3.0 github.com/roadrunner-server/gzip/v5 v5.3.0 github.com/roadrunner-server/headers/v5 v5.2.0 github.com/roadrunner-server/http/v5 v5.3.0 github.com/roadrunner-server/informer/v5 v5.1.9 github.com/roadrunner-server/jobs/v5 v5.1.9 github.com/roadrunner-server/kafka/v5 v5.2.5 github.com/roadrunner-server/kv/v5 v5.2.9 github.com/roadrunner-server/lock/v5 v5.1.9 github.com/roadrunner-server/logger/v5 v5.1.9 github.com/roadrunner-server/memcached/v5 v5.1.9 github.com/roadrunner-server/memory/v5 v5.2.9 github.com/roadrunner-server/metrics/v5 v5.2.0 github.com/roadrunner-server/nats/v5 v5.1.9 github.com/roadrunner-server/otel/v5 v5.5.0 github.com/roadrunner-server/pool v1.1.3 github.com/roadrunner-server/prometheus/v5 v5.2.0 github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9 github.com/roadrunner-server/redis/v5 v5.1.10 github.com/roadrunner-server/resetter/v5 v5.1.9 github.com/roadrunner-server/rpc/v5 v5.1.9 github.com/roadrunner-server/send/v5 v5.2.0 github.com/roadrunner-server/server/v5 v5.2.10 github.com/roadrunner-server/service/v5 v5.1.9 github.com/roadrunner-server/sqs/v5 v5.1.9 github.com/roadrunner-server/static/v5 v5.2.0 github.com/roadrunner-server/status/v5 v5.1.9 github.com/roadrunner-server/tcp/v5 v5.1.9 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/temporalio/roadrunner-temporal/v5 v5.10.0 ) exclude ( github.com/olekukonko/tablewriter v1.1.1 github.com/redis/go-redis/v9 v9.15.0 github.com/redis/go-redis/v9 v9.15.1 github.com/spf13/viper v1.18.0 github.com/spf13/viper v1.18.1 go.temporal.io/api v1.26.1 ) require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/iam v1.5.3 // indirect cloud.google.com/go/pubsub/v2 v2.4.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/aws/aws-sdk-go-v2 v1.41.4 // indirect github.com/aws/aws-sdk-go-v2/config v1.32.12 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect github.com/aws/aws-sdk-go-v2/service/sqs v1.42.24 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect github.com/aws/smithy-go v1.24.2 // indirect github.com/beanstalkd/go-beanstalk v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cactus/go-statsd-client/v5 v5.1.0 // indirect github.com/caddyserver/certmagic v0.25.2 // indirect github.com/caddyserver/zerossl v0.1.5 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/proto v1.14.3 // indirect github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/gofiber/fiber/v2 v2.52.12 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.7.0-rc.1 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect github.com/googleapis/gax-go/v2 v2.18.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/libdns/libdns v1.1.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.21 // indirect github.com/mholt/acmez v1.2.0 // indirect github.com/mholt/acmez/v3 v3.1.6 // indirect github.com/miekg/dns v1.1.72 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nats-io/nats.go v1.49.0 // indirect github.com/nats-io/nkeys v0.4.15 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/nexus-rpc/sdk-go v0.6.0 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.2.0 // indirect github.com/olekukonko/ll v0.1.7 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pierrec/lz4/v4 v4.1.26 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.20.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/rabbitmq/amqp091-go v1.10.0 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.18.0 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.18.0 // indirect github.com/redis/go-redis/extra/redisprometheus/v9 v9.18.0 // indirect github.com/redis/go-redis/v9 v9.18.0 // indirect github.com/roadrunner-server/context v1.3.0 // indirect github.com/roadrunner-server/events v1.0.1 // indirect github.com/roadrunner-server/priority_queue v1.0.6 // indirect github.com/roadrunner-server/tcplisten v1.5.2 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/numcpus v0.11.0 // indirect github.com/twmb/franz-go v1.20.7 // indirect github.com/twmb/franz-go/pkg/kmsg v1.12.0 // indirect github.com/twmb/murmur3 v1.1.8 // indirect github.com/uber-go/tally/v4 v4.1.17 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.69.0 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/assert v1.3.1 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.etcd.io/bbolt v1.4.3 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache v0.43.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 // indirect go.opentelemetry.io/otel v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/exporters/zipkin v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/otel/sdk v1.42.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.temporal.io/api v1.62.4 // indirect go.temporal.io/sdk v1.41.0 // indirect go.temporal.io/sdk/contrib/opentelemetry v0.7.0 // indirect go.temporal.io/sdk/contrib/tally v0.2.0 // indirect go.temporal.io/server v1.30.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.49.0 // indirect golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.43.0 // indirect google.golang.org/api v0.272.0 // indirect google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect google.golang.org/grpc v1.79.2 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= cloud.google.com/go/pubsub/v2 v2.4.0 h1:oMKNiBQpXImRWnHYla9uSU66ZzByZwBSCJOEs/pTKVg= cloud.google.com/go/pubsub/v2 v2.4.0/go.mod h1:2lS/XQKq5qtOMs6kHBK+WX1ytUC36kLl2ig3zqsGUx8= code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.24 h1:JP2wjWGmUp8lTCZb13Dv0Eciyc1jbO8pd0HZVMHFlrc= github.com/aws/aws-sdk-go-v2/service/sqs v1.42.24/go.mod h1:Ql9ziDutk8ERAN9HMaYANCW3lop451ppebkxEJMLCTM= github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beanstalkd/go-beanstalk v0.2.0 h1:6UOJugnu47uNB2jJO/lxyDgeD1Yds7owYi1USELqexA= github.com/beanstalkd/go-beanstalk v0.2.0/go.mod h1:/G8YTyChOtpOArwLTQPY1CHB+i212+av35bkPXXj56Y= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI= github.com/cactus/go-statsd-client/v5 v5.1.0 h1:sbbdfIl9PgisjEoXzvXI1lwUKWElngsjJKaZeC021P4= github.com/cactus/go-statsd-client/v5 v5.1.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc= github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q= github.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw= github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.18.0 h1:jxP5Uuo3bxm3M6gGtV94P4lliVetoCB4Wk2x8QA86LI= github.com/googleapis/gax-go/v2 v2.18.0/go.mod h1:uSzZN4a356eRG985CzJ3WfbFSpqkLTjsnhWGJR6EwrE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ= github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE= github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw= github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4= github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo= github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= github.com/olekukonko/errors v1.2.0 h1:10Zcn4GeV59t/EGqJc8fUjtFT/FuUh5bTMzZ1XwmCRo= github.com/olekukonko/errors v1.2.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/ll v0.1.7 h1:WyK1YZwOTUKHEXZz3VydBDT5t3zDqa9yI8iJg5PHon4= github.com/olekukonko/ll v0.1.7/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw= github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= github.com/redis/go-redis/extra/rediscmd/v9 v9.18.0 h1:QY4nmPHLFAJjtT5O4OMUEOxP8WVaRNOFpcbmxT2NLZU= github.com/redis/go-redis/extra/rediscmd/v9 v9.18.0/go.mod h1:WH8cY/0fT41Bsf341qzo8v4nx0GCE8FykAA23IVbVmo= github.com/redis/go-redis/extra/redisotel/v9 v9.18.0 h1:2dKdoEYBJ0CZCLPiCdvvc7luz3DPwY6hKdzjL6m1eHE= github.com/redis/go-redis/extra/redisotel/v9 v9.18.0/go.mod h1:WzkrVG9ro9BwCQD0eJOWn6AGL4Z1CleGflM45w1hu10= github.com/redis/go-redis/extra/redisprometheus/v9 v9.18.0 h1:i4WuWCYs4kYB/UKZvRUUlQ8214KIpE+0svtzTwBmdUY= github.com/redis/go-redis/extra/redisprometheus/v9 v9.18.0/go.mod h1:Ji1RzjPy388jVCLG0uYRyVnp4PD1dzGRiXvax9yXKPA= github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= github.com/roadrunner-server/amqp/v5 v5.2.3 h1:joOO8csUbv9IN2Q2E+dqlaMOkwi7fjmul9wNeG0AHHc= github.com/roadrunner-server/amqp/v5 v5.2.3/go.mod h1:hjp/CCrpkCVS/k19f+IsNQ8CCUDBjhpn0An4tKKcTGs= github.com/roadrunner-server/api/v4 v4.23.0 h1:lrVXgP4ozD/H5DrIdT181ldVhD1R9QT5qsi8qWUTDF4= github.com/roadrunner-server/api/v4 v4.23.0/go.mod h1:AlHuVVOklb7XF33Cf7IfmwOn3j4gGg37on9Xi6j08Bg= github.com/roadrunner-server/app-logger/v5 v5.1.9 h1:CNSBqdVli60d0n8LneqVyea52z1/esLpv5A0J6K1U8E= github.com/roadrunner-server/app-logger/v5 v5.1.9/go.mod h1:F80BR3sa9RkhraBwdhkS+ZSjWPzqzhRmqohJih2MVWU= github.com/roadrunner-server/beanstalk/v5 v5.1.9 h1:PHY/K1oQVxFBswTu0B3KIj8xy4nEYbkumcrmvhv6orI= github.com/roadrunner-server/beanstalk/v5 v5.1.9/go.mod h1:w0ghg+tAnR/eowGPej35GMkyYnCa82L9qy504DltK+w= github.com/roadrunner-server/boltdb/v5 v5.1.9 h1:UWpxBrY0SECbNl3jG7ZmNXSMGe5OQaKMKZvHYHkz+VE= github.com/roadrunner-server/boltdb/v5 v5.1.9/go.mod h1:THo9ebdAAaWffKDzmPKVLm4e6oncIcoqlgQ2RdI/fE8= github.com/roadrunner-server/centrifuge/v5 v5.1.9 h1:rkXRR6JFAUTHMQhqS0RiLciQDhF55VwAoN5SK7JaBqY= github.com/roadrunner-server/centrifuge/v5 v5.1.9/go.mod h1:N015rsGdTD7dDIkNeYidwBXQoseizHltv3q94tRzIic= github.com/roadrunner-server/config/v5 v5.1.9 h1:ReWwts/prEvuC4yVJ0BRDmY5sxw/1c+hGTSdJ71hIQU= github.com/roadrunner-server/config/v5 v5.1.9/go.mod h1:R6YyTWahW61tWHOI2BfdkQU/0Zc/2d6/JbJ/KEvq8F8= github.com/roadrunner-server/context v1.3.0 h1:iyTXVORhPU2/26z7kdzEaggwG5P8yhIKUDLiePjylFQ= github.com/roadrunner-server/context v1.3.0/go.mod h1:KPAzAlnErXekQazW9t4h55U1S42Q2bk0WCaPQrezJw4= github.com/roadrunner-server/endure/v2 v2.6.2 h1:sIB4kTyE7gtT3fDhuYWUYn6Vt/dcPtiA6FoNS1eS+84= github.com/roadrunner-server/endure/v2 v2.6.2/go.mod h1:t/2+xpNYgGBwhzn83y2MDhvhZ19UVq1REcvqn7j7RB8= github.com/roadrunner-server/errors v1.4.1 h1:LKNeaCGiwd3t8IaL840ZNF3UA9yDQlpvHnKddnh0YRQ= github.com/roadrunner-server/errors v1.4.1/go.mod h1:qeffnIKG0e4j1dzGpa+OGY5VKSfMphizvqWIw8s2lAo= github.com/roadrunner-server/events v1.0.1 h1:waCkKhxhzdK3VcI1xG22l+h+0J+Nfdpxjhyy01Un+kI= github.com/roadrunner-server/events v1.0.1/go.mod h1:WZRqoEVaFm209t52EuoT7ISUtvX6BrCi6bI/7pjkVC0= github.com/roadrunner-server/fileserver/v5 v5.1.9 h1:IcEYqlB+jtyb1ZLnId3j4LyOKvifEbh2MJY6oiAGn3o= github.com/roadrunner-server/fileserver/v5 v5.1.9/go.mod h1:P2ww1ycCNdWEp7/lHLukfbIq4vYn4ZbV4INCh128q9s= github.com/roadrunner-server/google-pub-sub/v5 v5.1.9 h1:D8s0JiTPP11HKaPKtcQ6R6+Y/rFJ83KFP4utbDOdgsE= github.com/roadrunner-server/google-pub-sub/v5 v5.1.9/go.mod h1:SvsSxJ2k640krtMP5Q35tDDH1v5LFcrMjXhvFJwbr7o= github.com/roadrunner-server/goridge/v3 v3.8.3 h1:XmjrOFnI6ZbQTPaP39DEk8KwLUNTgjluK3pcZaW6ixQ= github.com/roadrunner-server/goridge/v3 v3.8.3/go.mod h1:4TZU8zgkKIZCsH51qwGMpvyXCT59u/8z6q8sCe4ZGAQ= github.com/roadrunner-server/grpc/v5 v5.3.0 h1:DZj7b6vU+1bo5oAAUil75JUuovAwZzXh41ZS7rUE6xQ= github.com/roadrunner-server/grpc/v5 v5.3.0/go.mod h1:A3G5eJZMQYnbzWWZ9crPSS557wPfwEc3uWr48RtbAVQ= github.com/roadrunner-server/gzip/v5 v5.3.0 h1:l6H4NQmX2RaaTWkeEt0q39ITLcMKL8FmBXm3ENUE4oI= github.com/roadrunner-server/gzip/v5 v5.3.0/go.mod h1:fXhfwelVKIJvon8jEdZrqobbE1zJoQkN3Ov7mO3V5XI= github.com/roadrunner-server/headers/v5 v5.2.0 h1:Q8SYxr+zhj5VskDPKq3JYyZA3XqcGxtYcb5uh4FytFY= github.com/roadrunner-server/headers/v5 v5.2.0/go.mod h1:BEeI6pI0CyVjE8gG6H5LVPRxZ2kDtMFoHupFE2SG2wU= github.com/roadrunner-server/http/v5 v5.3.0 h1:gsNdOQffWF3/hL7T+OTQat36SvCl5ZUjuotz41rONQ4= github.com/roadrunner-server/http/v5 v5.3.0/go.mod h1:JCM8jmgRtaPMFiKPcriOxNQ98ha821uzN5BQ5XJzc1I= github.com/roadrunner-server/informer/v5 v5.1.9 h1:yl334LMqUoWXfeP4299HgY9G7mq6kX6FVCSwT+cYdfQ= github.com/roadrunner-server/informer/v5 v5.1.9/go.mod h1:JPzSsDjLHExdQ9SbT9e8H/oB7pajgCScL/G70saQzSA= github.com/roadrunner-server/jobs/v5 v5.1.9 h1:28biT9tGTYSXV+FWJCLfEOCV3sJZ7VVd46oJLhIfJkM= github.com/roadrunner-server/jobs/v5 v5.1.9/go.mod h1:3qiklMKRqMHl0+TbrFQWzaNrtqDGkPFdgZC/dFqRBNU= github.com/roadrunner-server/kafka/v5 v5.2.5 h1:Abqb9AnFsx5VYsJqP5YwloWHaBbdbGrhX8NkWJk6s74= github.com/roadrunner-server/kafka/v5 v5.2.5/go.mod h1:dAAwqsb+uJSZBOO+jtcM1ENgC7zZSOijxU1xSd4n8/w= github.com/roadrunner-server/kv/v5 v5.2.9 h1:vVUwmt2lxvdf6pILvfOFj57htFBedgQT8jnnwmI7pKU= github.com/roadrunner-server/kv/v5 v5.2.9/go.mod h1:KibOhjOyOk+3ULzPhEEisCmCQQjFxVu5fXJGSVbsJUU= github.com/roadrunner-server/lock/v5 v5.1.9 h1:4juUwNvvS13mVnu131sHlgGRjuS5V0cfPPL+7fT6yt8= github.com/roadrunner-server/lock/v5 v5.1.9/go.mod h1:PNQWcwGMboDvG2XCxXQfn3j3tpCGNi5CHGxGo7cyV4Y= github.com/roadrunner-server/logger/v5 v5.1.9 h1:3Kn+NYXF7Ww5LvkwMZwkv1q99t5qusIPBsreRJL08JI= github.com/roadrunner-server/logger/v5 v5.1.9/go.mod h1:hwct/TWTmxYsVzowLx4g9HkY5z2/gpYOC+UN8btsrAA= github.com/roadrunner-server/memcached/v5 v5.1.9 h1:Pom9dZuywQK6ayWQYHbO7STar08/WeBl5WWckUFP51U= github.com/roadrunner-server/memcached/v5 v5.1.9/go.mod h1:AT1p3Vy1J8eJbCiojB9usKiMHCQpyxkKa+0T1Wd705U= github.com/roadrunner-server/memory/v5 v5.2.9 h1:niLep2dyUaYhrn+I12kTXjGUTUSbVJa+jyvNJrbt/hw= github.com/roadrunner-server/memory/v5 v5.2.9/go.mod h1:UcE3Sf0TYRmAG1HukXmzsztGD0nBi6RRhG75K11Fqpk= github.com/roadrunner-server/metrics/v5 v5.2.0 h1:54MWLO9t0tgx2It3sEx/Zz7rFwpYk6g9rjvApFBXTUQ= github.com/roadrunner-server/metrics/v5 v5.2.0/go.mod h1:GRr9V0zI2XY9CmoCwW3wyUcFSQq98Lm0ENp77jYgzF0= github.com/roadrunner-server/nats/v5 v5.1.9 h1:ZWXMSt+za709hIK6jUBYv6X92REk1DgsYa6zU0jbeKo= github.com/roadrunner-server/nats/v5 v5.1.9/go.mod h1:6R6q7wR7OKl9kxsLZd3Qz2jobwv0t5ohj1EZ/zSBXM8= github.com/roadrunner-server/otel/v5 v5.5.0 h1:IYMw04K110sMANyZpauvQ3rRMnq0zZhwQbISHLisfQE= github.com/roadrunner-server/otel/v5 v5.5.0/go.mod h1:B1EaW4hC2VGUKCKLw9CZYKxuQid+2Zb7ojzXpRUb6oo= github.com/roadrunner-server/pool v1.1.3 h1:KMsiL6yuYBWGk73bdO0akwP+fJ63bxDF972JukCGsxI= github.com/roadrunner-server/pool v1.1.3/go.mod h1:8ceC7NvZKJRciv+KJmcyk5CeDugoel6GD+crm5kBFW0= github.com/roadrunner-server/priority_queue v1.0.6 h1:x8bcMyjWs2Z4ySbO9BTP8Dzy2prCuazJY9HHrVTmUVY= github.com/roadrunner-server/priority_queue v1.0.6/go.mod h1:aJ2D9s18+OGpFfNgwoIduraaFYBGv4FKElnpzqO+TBI= github.com/roadrunner-server/prometheus/v5 v5.2.0 h1:1KOacYOJL6ZN4vCf+SZIREFIqaYZ8WgWOiZifS88gns= github.com/roadrunner-server/prometheus/v5 v5.2.0/go.mod h1:tzN4CZataiCzNyY9V5E0leLqvDmcWySUVnSxcwxFIrc= github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9 h1:GoEgKJ97jozcJRecBMa5orMDWByaCwXUqg2zSUXl2M8= github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9/go.mod h1:DWaHaqpitzUbDVfK1M5ojC7bqUeOn6CAa5oxntU00po= github.com/roadrunner-server/redis/v5 v5.1.10 h1:DLjkYAqmX/08+X8NLI8oAsAE7/uDs1Kzpk5bOde5V6M= github.com/roadrunner-server/redis/v5 v5.1.10/go.mod h1:IhGVdrKOVjf3ag3Dwprn/y+88k6OyvVxI3SLhRq/o0s= github.com/roadrunner-server/resetter/v5 v5.1.9 h1:rH1nxkgvItbMEp1/JFZqcijOkkav4zo0E4wcVXBcXa8= github.com/roadrunner-server/resetter/v5 v5.1.9/go.mod h1:P5TfzCGQMNsUDPTrjOU7XFLdRz+DtFH/FmRla+lUF94= github.com/roadrunner-server/rpc/v5 v5.1.9 h1:AbRd2xEkWY8N3J4GUhoDeL+pnfwzKHazV4k40jZ+YDk= github.com/roadrunner-server/rpc/v5 v5.1.9/go.mod h1:TtbPY1cPvL46Mk9Dh4qwx33WO4R3m1lZkgL2q2MfHtA= github.com/roadrunner-server/send/v5 v5.2.0 h1:nQMaO7u9z+wYD1bInF/6ctImEaCJZFHscd3cjC1zNOc= github.com/roadrunner-server/send/v5 v5.2.0/go.mod h1:HmiJmpB6Lsd3jHLCy8pYG5J6E9R7IHURFr8icokNkog= github.com/roadrunner-server/server/v5 v5.2.10 h1:IshR3mlXJ/fh+zY17tN3Q7GgaanMzA7H9wwEz4PxCSA= github.com/roadrunner-server/server/v5 v5.2.10/go.mod h1:oxXhRo2EDykCk8ujhdjyJCb2rPpoTt8AVy46RHRfeeo= github.com/roadrunner-server/service/v5 v5.1.9 h1:0vVPSflWjAn2afj0QKs8vB1KIi1QQxKWiXWeZxxU15w= github.com/roadrunner-server/service/v5 v5.1.9/go.mod h1:8JiQl8qprQCseLQg0hgnQ4afAIgCdHC7dykOjGOJnd4= github.com/roadrunner-server/sqs/v5 v5.1.9 h1:ZmanM+52tt8cn7AkV/0Zp+9wUwpNjbGkd7riIf4t3ZA= github.com/roadrunner-server/sqs/v5 v5.1.9/go.mod h1:L7Q9m6VCsH7wuODhSO5bWHYK2nvz2xtNGUO8ORxHNN4= github.com/roadrunner-server/static/v5 v5.2.0 h1:xpZgIlvKWdWUJ37BtbNLtL/kN1rNFMEwFZbBnzWDeEM= github.com/roadrunner-server/static/v5 v5.2.0/go.mod h1:jp3eATJDSLmidXI25YDievkKp5XZ35qK6g4vHwpS0Ug= github.com/roadrunner-server/status/v5 v5.1.9 h1:TxDCj88Y7Bzcwbv8zwvNV1dE25n2x7IYw5M+kqadZWk= github.com/roadrunner-server/status/v5 v5.1.9/go.mod h1:9v53l4V680aIPinjTFHr8ZKEGwvFPEeHTFCaJkWjejA= github.com/roadrunner-server/tcp/v5 v5.1.9 h1:Tnyjib87qWXvMdb5r/aMvwxz3t87o0bE8bsVg/lIOk0= github.com/roadrunner-server/tcp/v5 v5.1.9/go.mod h1:3HIqo3kmo1mUxaBatk48n40cOinZsx2nygT505zACZ4= github.com/roadrunner-server/tcplisten v1.5.2 h1:nn8yXYrhRDkfQ9AAu4V075uT4fZRmOnpxkawgE+bWPA= github.com/roadrunner-server/tcplisten v1.5.2/go.mod h1:DufGBz7Dlx2KrNe/4RukEvGMTqZKB0Uve1GztwcyyR8= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/temporalio/roadrunner-temporal/v5 v5.10.0 h1:lVQI9QmkCQuLacq4eqMo1CXQwD3lXjGa2rTblYum6Ys= github.com/temporalio/roadrunner-temporal/v5 v5.10.0/go.mod h1:qBTALd/BxzzatAbkWZc6pc4rAuVY7+zCHnU+RQItG5M= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/twmb/franz-go v1.20.7 h1:P4MGSXJjjAPP3NRGPCks/Lrq+j+twWMVl1qYCVgNmWY= github.com/twmb/franz-go v1.20.7/go.mod h1:0bRX9HZVaoueqFWhPZNi2ODnJL7DNa6mK0HeCrC2bNU= github.com/twmb/franz-go/pkg/kmsg v1.12.0 h1:CbatD7ers1KzDNgJqPbKOq0Bz/WLBdsTH75wgzeVaPc= github.com/twmb/franz-go/pkg/kmsg v1.12.0/go.mod h1:+DPt4NC8RmI6hqb8G09+3giKObE6uD2Eya6CfqBpeJY= github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/uber-go/tally/v4 v4.1.1/go.mod h1:aXeSTDMl4tNosyf6rdU8jlgScHyjEGGtfJ/uwCIf/vM= github.com/uber-go/tally/v4 v4.1.17 h1:C+U4BKtVDXTszuzU+WH8JVQvRVnaVKxzZrROFyDrvS8= github.com/uber-go/tally/v4 v4.1.17/go.mod h1:ZdpiHRGSa3z4NIAc1VlEH4SiknR885fOIF08xmS0gaU= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A= github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.einride.tech/aip v0.79.0 h1:19zdPlZzlUvxOA8syAFw4LkdJdXepzyTl6gt9XEeqdU= go.einride.tech/aip v0.79.0/go.mod h1:E8+wdTApA70odnpFzJgsGogHozC2JCIhFJBKPr8bVig= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache v0.43.0 h1:UBMZi0bClix43Z5bGClUstfycTv5/GwGEpeXrkVCILw= go.opentelemetry.io/contrib/instrumentation/github.com/bradfitz/gomemcache/memcache/otelmemcache v0.43.0/go.mod h1:Y4/69ywKyaWZ5jN/NypeJyGLuoFjSpdyKuWVLClgDgM= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 h1:jP8unWI6q5kcb3gpGLjKDGaUa+JW+nHKWvpS/q+YuWA= go.opentelemetry.io/contrib/propagators/jaeger v1.42.0/go.mod h1:xd89e/pUyPatUP1C4z1UknD9jHptESO99tWyvd4mWD4= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/exporters/zipkin v1.42.0 h1:Z7ARHF7193vyVltPYcmuhSKPLf8dP5rtJZLtTQnbMH4= go.opentelemetry.io/otel/exporters/zipkin v1.42.0/go.mod h1:DW09+gaEg5kydlb9g8kp4Nos3yqo9YSA1uHXkeJihXc= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.temporal.io/api v1.5.0/go.mod h1:BqKxEJJYdxb5dqf0ODfzfMxh8UEQ5L3zKS51FiIYYkA= go.temporal.io/api v1.62.4 h1:XxstCG0LWfAqMsQAMk8kIx92l47FtJlIOKFWF3ydOUE= go.temporal.io/api v1.62.4/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.12.0/go.mod h1:lSp3lH1lI0TyOsus0arnO3FYvjVXBZGi/G7DjnAnm6o= go.temporal.io/sdk v1.41.0 h1:c9tayCQJDM5ZQdrqjGmjqk5ejxUtsEScJGF94sAVYpM= go.temporal.io/sdk v1.41.0/go.mod h1:/InXQT5guZ6AizYzpmzr5avQ/GMgq1ZObcKlKE2AhTc= go.temporal.io/sdk/contrib/opentelemetry v0.7.0 h1:GSna1HP+1ibNXZ9xlVdQU2zFVqdt5VcdF0dzpeaYccQ= go.temporal.io/sdk/contrib/opentelemetry v0.7.0/go.mod h1:oQJC6UIl3FbSYh4f2MlUAIYSE6FPw02X1Tw8/bOvfxg= go.temporal.io/sdk/contrib/tally v0.2.0 h1:XnTJIQcjOv+WuCJ1u8Ve2nq+s2H4i/fys34MnWDRrOo= go.temporal.io/sdk/contrib/tally v0.2.0/go.mod h1:1kpSuCms/tHeJQDPuuKkaBsMqfHnIIRnCtUYlPNXxuE= go.temporal.io/server v1.30.1 h1:e5CPziijuOUxTfdudCSdwU8+r4+SHSOK3RZOag5W9JE= go.temporal.io/server v1.30.1/go.mod h1:LB5uJX9+pco/dQzsnsLqcnoKVjDAXU0+aTK05WFYliM= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA= google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE= google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw= google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js= google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts= google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: go.work ================================================ go 1.26.1 use ( . ./tests ) ================================================ FILE: go.work.sum ================================================ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1/go.mod h1:avRlCjnFzl98VPaeCtJ24RrV/wwHFzB8sWXhj26+n/U= buf.build/go/protovalidate v0.12.0/go.mod h1:q3PFfbzI05LeqxSwq+begW2syjy2Z6hLxZSkP1OH/D0= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go/accessapproval v1.8.8/go.mod h1:RFwPY9JDKseP4gJrX1BlAVsP5O6kI8NdGlTmaeDefmk= cloud.google.com/go/accesscontextmanager v1.9.7/go.mod h1:i6e0nd5CPcrh7+YwGq4bKvju5YB9sgoAip+mXU73aMM= cloud.google.com/go/aiplatform v1.119.0/go.mod h1:27DcZJbaxFntewF6O0HojDE1B8JQOGKYopNjwoICFdI= cloud.google.com/go/aiplatform v1.120.0/go.mod h1:6mDthfmy0oS1EQhVFdijoxkVdI2+HIZkpuGTBpedeCg= cloud.google.com/go/analytics v0.30.1/go.mod h1:V/FnINU5kMOsttZnKPnXfKi6clJUHTEXUKQjHxcNK8A= cloud.google.com/go/apigateway v1.7.7/go.mod h1:j1bCmrUK1BzVHpiIyTApxB7cRyhivKzltqLmp6j6i7U= cloud.google.com/go/apigeeconnect v1.7.7/go.mod h1:ftGK3nca0JePiVLl0A6alaMjKdOc5C+sAkFMyH2RH8U= cloud.google.com/go/apigeeregistry v0.10.0/go.mod h1:SAlF5OhKvyLDuwWAaFAIVJjrEqKRrGTPkJs+TWNnSqg= cloud.google.com/go/appengine v1.9.7/go.mod h1:y1XpGVeAhbsNzHida79cHbr3pFRsym0ob8xnC8yphbo= cloud.google.com/go/area120 v0.9.7/go.mod h1:5nJ0yksmjOMfc4Zpk+okWfJ3A1004FvB82rfia+ZLaY= cloud.google.com/go/area120 v0.10.0/go.mod h1:Xg3fKl4xU3UVai9wsI1FXwNU8wSCDYT7dFZfwJKViAM= cloud.google.com/go/artifactregistry v1.20.0/go.mod h1:0G9wdbGyDFkvrYH+2AlQs9MuTJdbY8Vg45M8VjlI8rc= cloud.google.com/go/asset v1.22.1/go.mod h1:NlvWwmca7CX6BIBEdRNxOocH6DowmBghAAHucOHuHng= cloud.google.com/go/assuredworkloads v1.13.0/go.mod h1:o/oHEOnUlribR+uJWTKQo8A5RhSl9K9FNeMOew4TJ3M= cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo= cloud.google.com/go/automl v1.15.0/go.mod h1:U9zOtQb8zVrFNGTuW3BfxeqmLyeleLgT9B12EaXfODg= cloud.google.com/go/baremetalsolution v1.4.0/go.mod h1:K6C6g4aS8LW95I0fEHZiBsBlh0UxwDLGf+S/vyfXbvg= cloud.google.com/go/batch v1.14.0/go.mod h1:oeQveyG6NDS/ks2ilOP4LzKRmuIaI7GLe0CkR7WF6pk= cloud.google.com/go/beyondcorp v1.2.0/go.mod h1:sszcgxpPPBEfLzbI0aYCTg6tT1tyt3CmKav3NZIUcvI= cloud.google.com/go/bigquery v1.74.0/go.mod h1:iViO7Cx3A/cRKcHNRsHB3yqGAMInFBswrE9Pxazsc90= cloud.google.com/go/bigtable v1.42.0/go.mod h1:oZ30nofVB6/UYGg7lBwGLWSea7NZUvw/WvBBgLY07xU= cloud.google.com/go/billing v1.21.0/go.mod h1:ZGairB3EVnb3i09E2SxFxo50p5unPaMTuo1jh6jW9js= cloud.google.com/go/binaryauthorization v1.10.0/go.mod h1:WOuiaQkI4PU/okwrcREjSAr2AUtjQgVe+PlrXKOmKKw= cloud.google.com/go/certificatemanager v1.9.6/go.mod h1:vWogV874jKZkSRDFCMM3r7wqybv8WXs3XhyNff6o/Zo= cloud.google.com/go/channel v1.21.0/go.mod h1:8v3TwHtgLmFxTpL2U+e10CLFOQN8u/Vr9RhYcJUS3y8= cloud.google.com/go/cloudbuild v1.25.0/go.mod h1:lCu+T6IPkobPo2Nw+vCE7wuaAl9HbXLzdPx/tcF+oWo= cloud.google.com/go/clouddms v1.8.8/go.mod h1:QtCyw+a73dlkDb2q20aTAPvfaTZCepDDi6Gb1AKq0a4= cloud.google.com/go/cloudtasks v1.13.7/go.mod h1:H0TThOUG+Ml34e2+ZtW6k6nt4i9KuH3nYAJ5mxh7OM4= cloud.google.com/go/compute v1.54.0 h1:4CKmnpO+40z44bKG5bdcKxQ7ocNpRtOc9SCLLUzze1w= cloud.google.com/go/compute v1.54.0/go.mod h1:RfBj0L1x/pIM84BrzNX2V21oEv16EKRPBiTcBRRH1Ww= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/contactcenterinsights v1.17.4/go.mod h1:kZe6yOnKDfpPz2GphDHynxk/Spx+53UX/pGf+SmWAKM= cloud.google.com/go/container v1.46.0/go.mod h1:A7gMqdQduTk46+zssWDTKbGS2z46UsJNXfKqvMI1ZO4= cloud.google.com/go/containeranalysis v0.14.2/go.mod h1:FjppROiUtP9cyMegdWdY/TsBSGc6kqh1GjA2NOJXXL8= cloud.google.com/go/datacatalog v1.26.1/go.mod h1:2Qcq8vsHNxMDgjgadRFmFG47Y+uuIVsyEGUrlrKEdrg= cloud.google.com/go/dataflow v0.11.1/go.mod h1:3s6y/h5Qz7uuxTmKJKBifkYZ3zs63jS+6VGtSu8Cf7Y= cloud.google.com/go/dataform v0.13.0/go.mod h1:U3fqrPY5jAcFh1a8rQb4a+PQ7zKlc5qfgotFZ+luKPo= cloud.google.com/go/datafusion v1.8.7/go.mod h1:4dkFb1la41qCEXh1AzYtFwl842bu2ikTUXyKhjvFCb0= cloud.google.com/go/datalabeling v0.9.7/go.mod h1:EEUVn+wNn3jl19P2S13FqE1s9LsKzRsPuuMRq2CMsOk= cloud.google.com/go/dataplex v1.28.0/go.mod h1:VB+xlYJiJ5kreonXsa2cHPj0A3CfPh/mgiHG4JFhbUA= cloud.google.com/go/dataproc/v2 v2.16.0/go.mod h1:HlzFg8k1SK+bJN3Zsy2z5g6OZS1D4DYiDUgJtF0gJnE= cloud.google.com/go/dataqna v0.9.8/go.mod h1:2lHKmGPOqzzuqCc5NI0+Xrd5om4ulxGwPpLB4AnFgpA= cloud.google.com/go/datastore v1.22.0/go.mod h1:aopSX+Whx0lHspWWBj+AjWt68/zjYsPfDe3LjWtqZg8= cloud.google.com/go/datastream v1.15.1/go.mod h1:aV1Grr9LFon0YvqryE5/gF1XAhcau2uxN2OvQJPpqRw= cloud.google.com/go/deploy v1.27.3/go.mod h1:7LFIYYTSSdljYRqY3n+JSmIFdD4lv6aMD5xg0crB5iw= cloud.google.com/go/dialogflow v1.76.0/go.mod h1:mdLkMmSCghfcP85X9dFBlirC1OssS65KE5hrrSz2GXY= cloud.google.com/go/dlp v1.28.0/go.mod h1:C3od1fIK8lf7Kr62aU1Uh0z4OL5Z8s3do3znAiEupAw= cloud.google.com/go/documentai v1.42.0/go.mod h1:CABOUzRNOuvb/QwJS2LS80Hpqbu3UW2afyRKTYuW7bo= cloud.google.com/go/domains v0.10.7/go.mod h1:T3WG/QUAO/52z4tUPooKS8AY7yXaFxPYn1V3F0/JbNQ= cloud.google.com/go/edgecontainer v1.4.4/go.mod h1:yyNVHsCKtsX/0mqFdbljQw0Uo660q2dlMPaiqYiC2Tg= cloud.google.com/go/errorreporting v0.4.0/go.mod h1:dZGEhqzdHZSRxxWLVjC3Ue5CVaROzvP58D9rU6zbBfw= cloud.google.com/go/essentialcontacts v1.7.7/go.mod h1:ytycWAEn/aKUMRKQPMVgMrAtphEMgjbzL8vFwM3tqXs= cloud.google.com/go/eventarc v1.18.0/go.mod h1:/6SDoqh5+9QNUqCX4/oQcJVK16fG/snHBSXu7lrJtO8= cloud.google.com/go/filestore v1.10.3/go.mod h1:94ZGyLTx9j+aWKozPQ6Wbq1DuImie/L/HIdGMshtwac= cloud.google.com/go/firestore v1.21.0/go.mod h1:1xH6HNcnkf/gGyR8udd6pFO4Z7GWJSwLKQMx/u6UrP4= cloud.google.com/go/functions v1.19.7/go.mod h1:xbcKfS7GoIcaXr2FSwmtn9NXal1JR4TV6iYZlgXffwA= cloud.google.com/go/gkebackup v1.8.1/go.mod h1:GAaAl+O5D9uISH5MnClUop2esQW4pDa2qe/95A4l7YQ= cloud.google.com/go/gkeconnect v0.12.5/go.mod h1:wMD2RXcsAWlkREZWJDVeDV70PYka1iEb9stFmgpw+5o= cloud.google.com/go/gkehub v0.16.0/go.mod h1:ADp27Ucor8v81wY+x/5pOxTorxkPj/xswH3AUpN62GU= cloud.google.com/go/gkemulticloud v1.6.0/go.mod h1:bGpd4o/Z5Z/XFlaojkgdVisHRwb+fLJvUPzsmV0I9ok= cloud.google.com/go/gsuiteaddons v1.7.8/go.mod h1:DBKNHH4YXAdd/rd6zVvtOGAJNGo0ekOh+nIjTUDEJ5U= cloud.google.com/go/iap v1.11.3/go.mod h1:+gXO0ClH62k2LVlfhHzrpiHQNyINlEVmGAE3+DB4ShU= cloud.google.com/go/ids v1.5.7/go.mod h1:N3ZQOIgIBwwOu2tzyhmh3JDT+kt8PcoKkn2BRT9Qe4A= cloud.google.com/go/iot v1.8.7/go.mod h1:HvVcypV8LPv1yTXSLCNK+YCtqGHhq+p0F3BXETfpN+U= cloud.google.com/go/kms v1.26.0/go.mod h1:pHKOdFJm63hxBsiPkYtowZPltu9dW0MWvBa6IA4HM58= cloud.google.com/go/language v1.14.6/go.mod h1:7y3J9OexQsfkWNGCxhT+7lb64pa60e12ZCoWDOHxJ1M= cloud.google.com/go/lifesciences v0.10.7/go.mod h1:v3AbTki9iWttEls/Wf4ag3EqeLRHofploOcpsLnu7iY= cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak= cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= cloud.google.com/go/managedidentities v1.7.7/go.mod h1:nwNlMxtBo2YJMvsKXRtAD1bL41qiCI9npS7cbqrsJUs= cloud.google.com/go/maps v1.28.0/go.mod h1:6EWjz3AFh52w3qe2reWShQDmGRtryhP7NAfGolnr9+g= cloud.google.com/go/maps v1.29.0/go.mod h1:FNATcM5ziB2TDE2IVWH4f/yeXc+SbUk1X+bmKjR8HEA= cloud.google.com/go/mediatranslation v0.9.7/go.mod h1:mz3v6PR7+Fd/1bYrRxNFGnd+p4wqdc/fyutqC5QHctw= cloud.google.com/go/memcache v1.11.7/go.mod h1:AU1jYlUqCihxapcJ1GGMtlMWDVhzjbfUWBXqsXa4rBg= cloud.google.com/go/metastore v1.14.8/go.mod h1:h1XI2LpD4ohJhQYn9TwXqKb5sVt6KSo47ft96SiFF1s= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= cloud.google.com/go/networkconnectivity v1.20.0/go.mod h1:9MzGwD4ljiq+Z2Pg3ue27OEewCuHz7IUfw1fITrIdSw= cloud.google.com/go/networkconnectivity v1.21.0/go.mod h1:XC1UJ+tqBsLWz73dqrMc7kUvdTv0FIxtDGv6YntTBO0= cloud.google.com/go/networkmanagement v1.23.0/go.mod h1:QTYCWp5UxUnU280SqF7AX/mf6NhsqKblmLeCALQmx5c= cloud.google.com/go/networksecurity v0.11.0/go.mod h1:JLgDsg4tOyJ3eMO8lypjqMftbfd60SJ+P7T+DUmWBsM= cloud.google.com/go/notebooks v1.12.7/go.mod h1:uR9pxAkKmlNloibMr9Q1t8WhIu4P2JeqJs7c064/0Mo= cloud.google.com/go/optimization v1.7.7/go.mod h1:OY2IAlX23o52qwMAZ0w65wibKuV12a4x6IHDTCq6kcU= cloud.google.com/go/orchestration v1.11.10/go.mod h1:tz7m1s4wNEvhNNIM3JOMH0lYxBssu9+7si5MCPw/4/0= cloud.google.com/go/orgpolicy v1.15.1/go.mod h1:bpvi9YIyU7wCW9WiXL/ZKT7pd2Ovegyr2xENIeRX5q0= cloud.google.com/go/osconfig v1.16.0/go.mod h1:PRmLgZ1loD1hGaqnTBww1nETbqcqAvmTQOLYiIZ7Nvk= cloud.google.com/go/oslogin v1.14.7/go.mod h1:NB6NqBHfDMwznePdBVX+ILllc1oPCdNSGp5u/WIyndY= cloud.google.com/go/phishingprotection v0.9.7/go.mod h1:JTI4HNGyAbWolBoNOoCyCF0e3cqPNrYnlievHU49EwE= cloud.google.com/go/policytroubleshooter v1.11.7/go.mod h1:JP/aQ+bUkt4Gz6lQXBi/+A/6nyNRZ0Pvxui5Xl9ieyk= cloud.google.com/go/privatecatalog v0.10.8/go.mod h1:BkLHi+rtAGYBt5DocXLytHhF0n6F03Tegxgty40Y7aA= cloud.google.com/go/pubsub v1.50.1 h1:fzbXpPyJnSGvWXF1jabhQeXyxdbCIkXTpjXHy7xviBM= cloud.google.com/go/pubsub v1.50.1/go.mod h1:6YVJv3MzWJUVdvQXG081sFvS0dWQOdnV+oTo++q/xFk= cloud.google.com/go/pubsub/v2 v2.0.0/go.mod h1:0aztFxNzVQIRSZ8vUr79uH2bS3jwLebwK6q1sgEub+E= cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= cloud.google.com/go/recaptchaenterprise/v2 v2.21.0/go.mod h1:HxQYqZC2/zl2CvKN7jJEv71vEdDi1GMGNUiZxnpiuVI= cloud.google.com/go/recommendationengine v0.9.7/go.mod h1:snZ/FL147u86Jqpv1j95R+CyU5NvL/UzYiyDo6UByTM= cloud.google.com/go/recommender v1.13.6/go.mod h1:y5/5womtdOaIM3xx+76vbsiA+8EBTIVfWnxHDFHBGJM= cloud.google.com/go/redis v1.18.3/go.mod h1:x8HtXZbvMBDNT6hMHaQ022Pos5d7SP7YsUH8fCJ2Wm4= cloud.google.com/go/resourcemanager v1.10.7/go.mod h1:rScGkr6j2eFwxAjctvOP/8sqnEpDbQ9r5CKwKfomqjs= cloud.google.com/go/resourcesettings v1.8.3/go.mod h1:BzgfXFHIWOOmHe6ZV9+r3OWfpHJgnqXy8jqwx4zTMLw= cloud.google.com/go/retail v1.26.0/go.mod h1:gMfh6s174Mvy1rK4g50J9TH5sRim8px+Krml25kdrqo= cloud.google.com/go/run v1.15.0/go.mod h1:rgFHMdAopLl++57vzeqA+a1o2x0/ILZnEacRD6nC0EA= cloud.google.com/go/scheduler v1.11.8/go.mod h1:bNKU7/f04eoM6iKQpwVLvFNBgGyJNS87RiFN73mIPik= cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q= cloud.google.com/go/security v1.19.2/go.mod h1:KXmf64mnOsLVKe8mk/bZpU1Rsvxqc0Ej0A6tgCeN93w= cloud.google.com/go/securitycenter v1.38.1/go.mod h1:Ge2D/SlG2lP1FrQD7wXHy8qyeloRenvKXeB4e7zO6z0= cloud.google.com/go/servicedirectory v1.12.7/go.mod h1:gOtN+qbuCMH6tj2dqlDY3qQL7w3V0+nkWaZElnJK8Ps= cloud.google.com/go/shell v1.8.7/go.mod h1:OTke7qc3laNEW5Jr5OV9VR3IwU5x5VqGOE6705zFex4= cloud.google.com/go/spanner v1.88.0/go.mod h1:MzulBwuuYwQUVdkZXBBFapmXee3N+sQrj2T/yup6uEE= cloud.google.com/go/speech v1.30.0/go.mod h1:F2+NJujR8uzDLd6bwy5kgtVycxvEq06nzvzz5eQ/gMo= cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= cloud.google.com/go/storagetransfer v1.13.1/go.mod h1:S858w5l383ffkdqAqrAA+BC7KlhCqeNieK3sFf5Bj4Y= cloud.google.com/go/talent v1.8.4/go.mod h1:3yukBXUTVFNyKcJpUExW/k5gqEy8qW6OCNj7WdN0MWo= cloud.google.com/go/texttospeech v1.16.0/go.mod h1:AeSkoH3ziPvapsuyI07TWY4oGxluAjntX+pF4PJ2jy0= cloud.google.com/go/tpu v1.8.4/go.mod h1:ul0cyWSHr6jHGZYElZe6HvQn35VY93RAlwpDiSBRnPA= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= cloud.google.com/go/translate v1.12.7/go.mod h1:wwJp14NZyWvcrFANhIXutXj0pOBkYciBHwSlUOykcjI= cloud.google.com/go/video v1.27.1/go.mod h1:xzfAC77B4vtnbi/TT3UUxEjCa/+Ehy5EA8w470ytOig= cloud.google.com/go/videointelligence v1.12.7/go.mod h1:XAk5hCMY+GihxJ55jNoMdwdXSNZnCl3wGs2+94gK7MA= cloud.google.com/go/vision/v2 v2.9.6/go.mod h1:lJC+vP15D5znJvHQYjEoTKnpToX1L93BUlvBmzM0gyg= cloud.google.com/go/vmmigration v1.10.0/go.mod h1:LDztCWEb+RwS1bPg4Xzt0fcJS9kVrFxa3ejhH7OW9vg= cloud.google.com/go/vmwareengine v1.3.6/go.mod h1:ps0rb+Skgpt9ppHYC0o5DqtJ5ld2FyS8sAqtbHH8t9s= cloud.google.com/go/vpcaccess v1.8.7/go.mod h1:9RYw5bVvk4Z51Rc8vwXT63yjEiMD/l7XyEaDyrNHgmk= cloud.google.com/go/webrisk v1.11.2/go.mod h1:yH44GeXz5iz4HFsIlGeoVvnjwnmfbni7Lwj1SelV4f0= cloud.google.com/go/websecurityscanner v1.7.7/go.mod h1:ng/PzARaus3Bj4Os4LpUnyYHsbtJky1HbBDmz148v1o= cloud.google.com/go/workflows v1.14.3/go.mod h1:CC9+YdVI2Kvp0L58WajHpEfKJxhrtRh3uQ0SYWcmAk4= codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/IBM/sarama v1.43.1/go.mod h1:GG5q1RURtDNPz8xxJs3mgX6Ytak8Z9eLhAkJPObe2xE= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/eapache/go-resiliency v1.6.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198/go.mod h1:DTh/Y2+NbnOVVoypCCQrovMPDKUGp4yZpSbWg5D0XIM= github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-pkcs11 v0.3.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/jordanlewis/gcassert v0.0.0-20250430164644-389ef753e22e/go.mod h1:ZybsQk6DWyN5t7An1MuPm1gtSZ1xDaTXS9ZjIOxvQrk= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/maruel/panicparse/v2 v2.4.0/go.mod h1:nOY2OKe8csO3F3SA5+hsxot05JLgukrF54B9x88fVp4= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0= github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/temporalio/ringpop-go v0.0.0-20250130211428-b97329e994f7/go.mod h1:RE+CHmY+kOZQk47AQaVzwrGmxpflnLgTd6EOK0853j4= github.com/temporalio/sqlparser v0.0.0-20231115171017-f4060bcfa6cb/go.mod h1:143qKdh3G45IgV9p+gbAwp3ikRDI8mxsijFiXDfuxsw= github.com/temporalio/tchannel-go v1.22.1-0.20240528171429-1db37fdea938/go.mod h1:ezRQRwu9KQXy8Wuuv1aaFFxoCNz5CeNbVOOkh3xctbY= github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/uber-common/bark v1.3.0/go.mod h1:5fDe/YcIVP55XhFF9hUihX2lDsDcpFrTZEAwAVwtPDw= github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/gofail v0.2.0/go.mod h1:nL3ILMGfkXTekKI3clMBNazKnjUZjYLKmBHzsVAnC1o= go.opentelemetry.io/collector/pdata v1.34.0/go.mod h1:StPHMFkhLBellRWrULq0DNjv4znCDJZP6La4UuC+JHI= go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0/go.mod h1:Vn3/rlOJ3ntf/Q3zAI0V5lDnTbHGaUsNUeF6nZmm7pA= go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk= go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.temporal.io/api v1.62.1/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c/go.mod h1:TpUTTEp9frx7rTdLpC9gFG9kdI7zVLFTFFlqaH2Cncw= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= gonum.org/v1/plot v0.15.2/go.mod h1:DX+x+DWso3LTha+AdkJEv5Txvi+Tql3KAGkehP0/Ubg= google.golang.org/api v0.259.0/go.mod h1:LC2ISWGWbRoyQVpxGntWwLWN/vLNxxKBK9KuJRI8Te4= google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0= google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:0oz9d7g9QLSdv9/lgbIjowW1JoxMbxmBVNe8i6tORJI= google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20260226221140-a57be14db171/go.mod h1:9amqk/8LQWEC4RjyUxMx1DebyQ7hZB9gvl67bHmgZ2E= google.golang.org/genproto/googleapis/bytestream v0.0.0-20260311181403-84a4fc48630c/go.mod h1:9amqk/8LQWEC4RjyUxMx1DebyQ7hZB9gvl67bHmgZ2E= google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8= modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= ================================================ FILE: internal/cli/doc.go ================================================ // Package cli implements the root Cobra command for the RoadRunner CLI, // including persistent flags, environment variable loading, debug server // initialization, and subcommand registration. package cli ================================================ FILE: internal/cli/jobs/command.go ================================================ package jobs import ( "strings" internalRpc "github.com/roadrunner-server/roadrunner/v2025/internal/rpc" "github.com/roadrunner-server/errors" "github.com/spf13/cobra" ) const ( listRPC string = "jobs.List" pauseRPC string = "jobs.Pause" destroyRPC string = "jobs.Destroy" resumeRPC string = "jobs.Resume" ) // NewCommand creates `jobs` command. func NewCommand(cfgFile *string, override *[]string, silent *bool) *cobra.Command { var ( pausePipes bool destroyPipes bool resumePipes bool listPipes bool ) cmd := &cobra.Command{ Use: "jobs", Short: "Jobs pipelines manipulation", RunE: func(_ *cobra.Command, args []string) error { const op = errors.Op("jobs_command") if cfgFile == nil { return errors.E(op, errors.Str("no configuration file provided")) } client, err := internalRpc.NewClient(*cfgFile, *override) if err != nil { return err } defer func() { _ = client.Close() }() switch { case pausePipes: if len(args) == 0 { return errors.Str("incorrect command usage, should be: rr jobs --pause pipe1,pipe2") } split := strings.Split(strings.Trim(args[0], " "), ",") return pause(client, split, silent) case destroyPipes: if len(args) == 0 { return errors.Str("incorrect command usage, should be: rr jobs --destroy pipe1,pipe2") } split := strings.Split(strings.Trim(args[0], " "), ",") return destroy(client, split, silent) case resumePipes: if len(args) == 0 { return errors.Str("incorrect command usage, should be: rr jobs --resume pipe1,pipe2") } split := strings.Split(strings.Trim(args[0], " "), ",") return resume(client, split, silent) case listPipes: return list(client) default: return errors.Str("command should be in form of: `rr jobs -- pipe1,pipe2`") } }, } // commands cmd.Flags().BoolVar(&pausePipes, "pause", false, "pause pipelines") cmd.Flags().BoolVar(&destroyPipes, "destroy", false, "destroy pipelines") cmd.Flags().BoolVar(&resumePipes, "resume", false, "resume pipelines") cmd.Flags().BoolVar(&listPipes, "list", false, "list pipelines") return cmd } ================================================ FILE: internal/cli/jobs/command_test.go ================================================ package jobs_test import ( "testing" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/jobs" "github.com/stretchr/testify/assert" ) func TestCommandProperties(t *testing.T) { path := "" f := false cmd := jobs.NewCommand(&path, nil, &f) assert.Equal(t, "jobs", cmd.Use) assert.NotNil(t, cmd.RunE) } ================================================ FILE: internal/cli/jobs/doc.go ================================================ // Package jobs implements the "jobs" command for managing job pipelines, // supporting pause, resume, destroy, and list operations via RPC. package jobs ================================================ FILE: internal/cli/jobs/render.go ================================================ package jobs import ( "io" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" ) // JobsCommandsRender uses console renderer to show jobs func renderPipelines(writer io.Writer, pipelines []string) *tablewriter.Table { cfg := tablewriter.Config{ Header: tw.CellConfig{ Formatting: tw.CellFormatting{ AutoFormat: tw.On, AutoWrap: int(tw.Off), }, }, MaxWidth: 150, Row: tw.CellConfig{ Alignment: tw.CellAlignment{ Global: tw.AlignLeft, }, }, } tw := tablewriter.NewTable(writer, tablewriter.WithConfig(cfg)) tw.Header([]string{"Pipeline(s)"}) for i := range pipelines { _ = tw.Append([]string{pipelines[i]}) } return tw } ================================================ FILE: internal/cli/jobs/subcommands.go ================================================ package jobs import ( "net/rpc" "os" jobsv1 "github.com/roadrunner-server/api/v4/build/jobs/v1" ) func pause(client *rpc.Client, pause []string, silent *bool) error { pipes := &jobsv1.Pipelines{Pipelines: pause} er := &jobsv1.Empty{} err := client.Call(pauseRPC, pipes, er) if err != nil { return err } if !*silent { _ = renderPipelines(os.Stdout, pause).Render() } return nil } func resume(client *rpc.Client, resume []string, silent *bool) error { pipes := &jobsv1.Pipelines{Pipelines: resume} er := &jobsv1.Empty{} err := client.Call(resumeRPC, pipes, er) if err != nil { return err } if !*silent { _ = renderPipelines(os.Stdout, resume).Render() } return nil } func destroy(client *rpc.Client, destroy []string, silent *bool) error { pipes := &jobsv1.Pipelines{Pipelines: destroy} resp := &jobsv1.Pipelines{} err := client.Call(destroyRPC, pipes, resp) if err != nil { return err } if !*silent { _ = renderPipelines(os.Stdout, resp.GetPipelines()).Render() } return nil } func list(client *rpc.Client) error { resp := &jobsv1.Pipelines{} er := &jobsv1.Empty{} err := client.Call(listRPC, er, resp) if err != nil { return err } _ = renderPipelines(os.Stdout, resp.GetPipelines()).Render() return nil } ================================================ FILE: internal/cli/reset/command.go ================================================ package reset import ( "log" "sync" internalRpc "github.com/roadrunner-server/roadrunner/v2025/internal/rpc" "github.com/roadrunner-server/roadrunner/v2025/internal/sdnotify" "github.com/roadrunner-server/errors" "github.com/spf13/cobra" ) const ( op = errors.Op("reset_handler") resetterList = "resetter.List" resetterReset = "resetter.Reset" ) // NewCommand creates `reset` command. func NewCommand(cfgFile *string, override *[]string, silent *bool) *cobra.Command { return &cobra.Command{ Use: "reset", Short: "Reset workers of all or specific RoadRunner service", RunE: func(_ *cobra.Command, args []string) error { if cfgFile == nil { return errors.E(op, errors.Str("no configuration file provided")) } client, err := internalRpc.NewClient(*cfgFile, *override) if err != nil { return err } defer func() { _ = client.Close() }() plugins := args // by default, we expect services list from user if len(plugins) == 0 { // but if nothing was passed - request all services list if err = client.Call(resetterList, true, &plugins); err != nil { return err } } _, _ = sdnotify.SdNotify(sdnotify.Reloading) var wg sync.WaitGroup wg.Add(len(plugins)) for _, plugin := range plugins { // simulating some work go func(p string) { if !*silent { log.Printf("resetting plugin: [%s] ", p) } defer wg.Done() var done bool <-client.Go(resetterReset, p, &done, nil).Done if err != nil { log.Println(err) return } if !*silent { log.Printf("plugin reset: [%s]", p) } }(plugin) } wg.Wait() _, _ = sdnotify.SdNotify(sdnotify.Ready) return nil }, } } ================================================ FILE: internal/cli/reset/command_test.go ================================================ package reset_test import ( "testing" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/reset" "github.com/stretchr/testify/assert" ) func TestCommandProperties(t *testing.T) { path := "" f := false cmd := reset.NewCommand(&path, nil, &f) assert.Equal(t, "reset", cmd.Use) assert.NotNil(t, cmd.RunE) } ================================================ FILE: internal/cli/reset/doc.go ================================================ // Package reset implements the "reset" command that resets workers of all // or specific RoadRunner plugins concurrently via RPC. package reset ================================================ FILE: internal/cli/root.go ================================================ package cli import ( "context" stderr "errors" "fmt" "net/http" "os" "os/signal" "path/filepath" "runtime" "strconv" "syscall" "github.com/joho/godotenv" "github.com/roadrunner-server/errors" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/jobs" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/reset" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/serve" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/stop" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/workers" dbg "github.com/roadrunner-server/roadrunner/v2025/internal/debug" "github.com/roadrunner-server/roadrunner/v2025/internal/meta" "github.com/spf13/cobra" ) const ( // env var name: path to the .env file envDotenv string = "DOTENV_PATH" pidFileName string = ".pid" ) // NewCommand creates root command. func NewCommand(cmdName string) *cobra.Command { //nolint:funlen,gocognit // path to the .rr.yaml cfgFile := toPtr("") // pidfile path pidFile := toPtr(false) // force stop RR forceStop := toPtr(false) // override config values override := &[]string{} // do not print startup message silent := toPtr(false) // enable experimental features experimental := toPtr(false) // working directory var workDir string // path to the .env file var dotenv string // debug mode var debug bool cmd := &cobra.Command{ Use: cmdName, Short: "High-performance PHP application server, process manager written in Golang and powered with ❤️ (by SpiralScout)", SilenceErrors: true, SilenceUsage: true, Version: fmt.Sprintf("%s (build time: %s, %s), OS: %s, arch: %s", meta.Version(), meta.BuildTime(), runtime.Version(), runtime.GOOS, runtime.GOARCH), PersistentPreRunE: func(*cobra.Command, []string) error { // cfgFile could be defined by user or default `.rr.yaml` // this check added just to be safe if cfgFile == nil || *cfgFile == "" { return errors.Str("no configuration file provided") } // if user set the wd, change the current wd if workDir != "" { if err := os.Chdir(workDir); err != nil { return err } } // try to get the absolute path to the configuration if absPath, err := filepath.Abs(*cfgFile); err == nil { *cfgFile = absPath // switch a config path to the absolute // if workDir is empty - force working absPath related to config file if workDir == "" { if err = os.Chdir(filepath.Dir(absPath)); err != nil { return err } } } if v, ok := os.LookupEnv(envDotenv); ok { // read a path to the dotenv file from environment variable dotenv = v } if dotenv != "" { err := godotenv.Load(dotenv) if err != nil { return err } } if debug { srv := dbg.NewServer() exit := make(chan os.Signal, 1) stpErr := make(chan error, 1) signal.Notify(exit, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGABRT) go func() { errS := srv.Start(":6061") // errS is always non-nil, this is just double check if errS != nil && stderr.Is(errS, http.ErrServerClosed) { return } // if we have another type of error - record it stpErr <- errS }() go func() { for { select { case e := <-stpErr: // no need to stop the server fmt.Println(fmt.Errorf("[ERROR] debug server stopped with error: %w", e)) return case <-exit: _ = srv.Stop(context.Background()) } } }() } // user wanted to write a .pid file if *pidFile { f, err := os.Create(pidFileName) if err != nil { return err } defer func() { _ = f.Close() }() _, err = f.WriteString(strconv.Itoa(os.Getpid())) if err != nil { return err } } return nil }, } f := cmd.PersistentFlags() f.BoolVarP(experimental, "enable-experimental", "e", false, "enable experimental features") f.BoolVarP(forceStop, "force", "f", false, "force stop") f.BoolVarP(pidFile, "pid", "p", false, "create a .pid file") f.StringVarP(cfgFile, "config", "c", ".rr.yaml", "config file") f.StringVarP(&workDir, "WorkDir", "w", "", "working directory") f.StringVarP(&dotenv, "dotenv", "", "", fmt.Sprintf("dotenv file [$%s]", envDotenv)) f.BoolVarP(&debug, "debug", "d", false, "debug mode") f.BoolVarP(silent, "silent", "s", false, "do not print startup message") f.StringArrayVarP(override, "override", "o", nil, "override config value (dot.notation=value)") cmd.AddCommand( workers.NewCommand(cfgFile, override), reset.NewCommand(cfgFile, override, silent), serve.NewCommand(override, cfgFile, silent, experimental), stop.NewCommand(silent, forceStop), jobs.NewCommand(cfgFile, override, silent), ) return cmd } func toPtr[T any](val T) *T { return &val } ================================================ FILE: internal/cli/root_test.go ================================================ package cli_test import ( "os" "path" "testing" "github.com/roadrunner-server/roadrunner/v2025/internal/cli" "github.com/stretchr/testify/require" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" ) func TestCommandSubcommands(t *testing.T) { cmd := cli.NewCommand("unit test") cases := []struct { giveName string }{ {giveName: "workers"}, {giveName: "reset"}, {giveName: "serve"}, } // get all existing subcommands and put into the map subcommands := make(map[string]*cobra.Command) for _, sub := range cmd.Commands() { subcommands[sub.Name()] = sub } for _, tt := range cases { t.Run(tt.giveName, func(t *testing.T) { if _, exists := subcommands[tt.giveName]; !exists { assert.Failf(t, "command not found", "command [%s] was not found", tt.giveName) } }) } } func TestCommandFlags(t *testing.T) { cmd := cli.NewCommand("unit test") cases := []struct { giveName string wantShorthand string wantDefault string }{ {giveName: "config", wantShorthand: "c", wantDefault: ".rr.yaml"}, {giveName: "WorkDir", wantShorthand: "w", wantDefault: ""}, {giveName: "dotenv", wantShorthand: "", wantDefault: ""}, {giveName: "debug", wantShorthand: "d", wantDefault: "false"}, {giveName: "override", wantShorthand: "o", wantDefault: "[]"}, } for _, tt := range cases { t.Run(tt.giveName, func(t *testing.T) { flag := cmd.Flag(tt.giveName) if flag == nil { assert.Failf(t, "flag not found", "flag [%s] was not found", tt.giveName) return } assert.Equal(t, tt.wantShorthand, flag.Shorthand) assert.Equal(t, tt.wantDefault, flag.DefValue) }) } } func TestCommandSimpleExecuting(t *testing.T) { cmd := cli.NewCommand("unit test") cmd.SetArgs([]string{"-c", "./../../.rr.yaml"}) var executed bool if cmd.Run == nil { // override "Run" property for test (if it was not set) cmd.Run = func(_ *cobra.Command, _ []string) { executed = true } } assert.NoError(t, cmd.Execute()) assert.True(t, executed) } func TestCommandNoEnvFileError(t *testing.T) { cmd := cli.NewCommand("unit test") cmd.SetArgs([]string{"-c", "./../../.rr.yaml", "--dotenv", "foo/bar"}) var executed bool if cmd.Run == nil { // override "Run" property for test (if it was not set) cmd.Run = func(_ *cobra.Command, _ []string) { executed = true } } assert.Error(t, cmd.Execute()) assert.False(t, executed) } func TestCommandNoEnvFileNoError(t *testing.T) { tmp := os.TempDir() cmd := cli.NewCommand("unit test") cmd.SetArgs([]string{"-c", path.Join(tmp, ".rr.yaml"), "--dotenv", path.Join(tmp, ".env")}) var executed bool f, err := os.Create(path.Join(tmp, ".env")) require.NoError(t, err) f2, err := os.Create(path.Join(tmp, ".rr.yaml")) require.NoError(t, err) defer func() { _ = f.Close() _ = f2.Close() }() if cmd.Run == nil { // override "Run" property for test (if it was not set) cmd.Run = func(_ *cobra.Command, _ []string) { executed = true } } assert.NoError(t, cmd.Execute()) assert.True(t, executed) t.Cleanup(func() { _ = os.RemoveAll(path.Join(tmp, ".env")) _ = os.RemoveAll(path.Join(tmp, ".rr.yaml")) }) } func TestCommandWorkingDir(t *testing.T) { tmp := os.TempDir() cmd := cli.NewCommand("serve") cmd.SetArgs([]string{"-w", tmp}) var executed bool var wd string f2, err := os.Create(path.Join(tmp, ".rr.yaml")) require.NoError(t, err) if cmd.Run == nil { // override "Run" property for test (if it was not set) cmd.Run = func(_ *cobra.Command, _ []string) { executed = true wd, _ = os.Getwd() } } assert.NoError(t, cmd.Execute()) assert.True(t, executed) assert.Equal(t, tmp, wd) t.Cleanup(func() { _ = f2.Close() _ = os.RemoveAll(path.Join(tmp, ".rr.yaml")) }) } ================================================ FILE: internal/cli/serve/command.go ================================================ //go:build !windows package serve import ( "fmt" "os" "os/signal" "syscall" "github.com/roadrunner-server/endure/v2" "github.com/roadrunner-server/roadrunner/v2025/container" "github.com/roadrunner-server/roadrunner/v2025/internal/meta" "github.com/roadrunner-server/roadrunner/v2025/internal/sdnotify" configImpl "github.com/roadrunner-server/config/v5" "github.com/roadrunner-server/errors" "github.com/spf13/cobra" ) // log outputs a message to stdout if silent mode is not enabled func log(msg string, silent bool) { if !silent { fmt.Println(msg) } } func NewCommand(override *[]string, cfgFile *string, silent *bool, experimental *bool) *cobra.Command { //nolint:funlen return &cobra.Command{ Use: "serve", Short: "Start RoadRunner server", RunE: func(*cobra.Command, []string) error { const op = errors.Op("handle_serve_command") // just to be safe if cfgFile == nil { return errors.E(op, errors.Str("no configuration file provided")) } // create endure container config containerCfg, err := container.NewConfig(*cfgFile) if err != nil { return errors.E(op, err) } cfg := &configImpl.Plugin{ Path: *cfgFile, Timeout: containerCfg.GracePeriod, Flags: *override, Version: meta.Version(), ExperimentalFeatures: *experimental, } endureOptions := []endure.Options{ endure.GracefulShutdownTimeout(containerCfg.GracePeriod), } if containerCfg.PrintGraph { endureOptions = append(endureOptions, endure.Visualize()) } // create endure container ll, err := container.ParseLogLevel(containerCfg.LogLevel) if err != nil { log(fmt.Sprintf("[WARN] Failed to parse log level, using default (error): %v", err), *silent) } cont := endure.New(ll, endureOptions...) // register plugins err = cont.RegisterAll(append(container.Plugins(), cfg)...) if err != nil { return errors.E(op, err) } // init container and all services err = cont.Init() if err != nil { return errors.E(op, err) } // start serving the graph errCh, err := cont.Serve() if err != nil { return errors.E(op, err) } oss, stop := make(chan os.Signal, 1), make(chan struct{}, 1) signal.Notify(oss, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGABRT, syscall.SIGQUIT) restartCh := make(chan os.Signal, 1) signal.Notify(restartCh, syscall.SIGUSR2) go func() { // first catch - stop the container <-oss // send signal to stop execution stop <- struct{}{} // notify about stopping _, _ = sdnotify.SdNotify(sdnotify.Stopping) // after the first hit we are waiting for the second catch - exit from the process <-oss log("exit forced", *silent) os.Exit(1) }() log(fmt.Sprintf("[INFO] RoadRunner server started; version: %s, buildtime: %s", meta.Version(), meta.BuildTime()), *silent) // at this moment, we're almost sure that the container is running (almost- because we don't know if the plugins won't report an error on the next step) notified, err := sdnotify.SdNotify(sdnotify.Ready) if err != nil { log(fmt.Sprintf("[WARN] sdnotify: %s", err), *silent) } if notified { log("[INFO] sdnotify: notified", *silent) stopCh := make(chan struct{}, 1) if containerCfg.WatchdogSec > 0 { log(fmt.Sprintf("[INFO] sdnotify: watchdog enabled, timeout: %d seconds", containerCfg.WatchdogSec), *silent) sdnotify.StartWatchdog(containerCfg.WatchdogSec, stopCh) } // if notified -> notify about stop defer func() { stopCh <- struct{}{} }() } for { select { case e := <-errCh: return fmt.Errorf("error: %w\nplugin: %s", e.Error, e.VertexID) case <-stop: // stop the container after the first signal log(fmt.Sprintf("stop signal received, grace timeout is: %0.f seconds", containerCfg.GracePeriod.Seconds()), *silent) if err = cont.Stop(); err != nil { return fmt.Errorf("error: %w", err) } return nil case <-restartCh: log("restart signal [SIGUSR2] received", *silent) executable, err := os.Executable() if err != nil { log(fmt.Sprintf("restart failed: %s", err), *silent) return errors.E("failed to restart") } args := os.Args env := os.Environ() if err = cont.Stop(); err != nil { log(fmt.Sprintf("restart failed: %s", err), *silent) return errors.E("failed to restart") } // safe: we're reexecuting the same binary with the same arguments and environment variables, so there is no risk of command injection // nosemgrep err = syscall.Exec(executable, args, env) //nolint:gosec if err != nil { log(fmt.Sprintf("restart failed: %s", err), *silent) return errors.E("failed to restart") } } } }, } } ================================================ FILE: internal/cli/serve/command_test.go ================================================ package serve_test import ( "testing" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/serve" "github.com/stretchr/testify/assert" ) func TestCommandProperties(t *testing.T) { path := "" cmd := serve.NewCommand(nil, &path, nil, nil) assert.Equal(t, "serve", cmd.Use) assert.NotNil(t, cmd.RunE) } func TestCommandNil(t *testing.T) { cmd := serve.NewCommand(nil, nil, nil, nil) assert.Equal(t, "serve", cmd.Use) assert.NotNil(t, cmd.RunE) } func TestExecution(t *testing.T) { t.Skip("Command execution is not implemented yet") } ================================================ FILE: internal/cli/serve/command_windows.go ================================================ //go:build windows package serve import ( "fmt" "os" "os/signal" "syscall" "github.com/roadrunner-server/endure/v2" "github.com/roadrunner-server/roadrunner/v2025/container" "github.com/roadrunner-server/roadrunner/v2025/internal/meta" "github.com/roadrunner-server/roadrunner/v2025/internal/sdnotify" configImpl "github.com/roadrunner-server/config/v5" "github.com/roadrunner-server/errors" "github.com/spf13/cobra" ) // log outputs a message to stdout if silent mode is not enabled func log(msg string, silent bool) { if !silent { fmt.Println(msg) } } func NewCommand(override *[]string, cfgFile *string, silent *bool, experimental *bool) *cobra.Command { //nolint:funlen return &cobra.Command{ Use: "serve", Short: "Start RoadRunner server", RunE: func(*cobra.Command, []string) error { const op = errors.Op("handle_serve_command") // just to be safe if cfgFile == nil { return errors.E(op, errors.Str("no configuration file provided")) } // create endure container config containerCfg, err := container.NewConfig(*cfgFile) if err != nil { return errors.E(op, err) } cfg := &configImpl.Plugin{ Path: *cfgFile, Timeout: containerCfg.GracePeriod, Flags: *override, Version: meta.Version(), ExperimentalFeatures: *experimental, } endureOptions := []endure.Options{ endure.GracefulShutdownTimeout(containerCfg.GracePeriod), } if containerCfg.PrintGraph { endureOptions = append(endureOptions, endure.Visualize()) } // create endure container ll, err := container.ParseLogLevel(containerCfg.LogLevel) if err != nil { log(fmt.Sprintf("[WARN] Failed to parse log level, using default (error): %v", err), *silent) } cont := endure.New(ll, endureOptions...) // register plugins err = cont.RegisterAll(append(container.Plugins(), cfg)...) if err != nil { return errors.E(op, err) } // init container and all services err = cont.Init() if err != nil { return errors.E(op, err) } // start serving the graph errCh, err := cont.Serve() if err != nil { return errors.E(op, err) } oss, stop := make(chan os.Signal, 1), make(chan struct{}, 1) signal.Notify(oss, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGABRT, syscall.SIGQUIT) go func() { // first catch - stop the container <-oss // send signal to stop execution stop <- struct{}{} // notify about stopping _, _ = sdnotify.SdNotify(sdnotify.Stopping) // after the first hit we are waiting for the second catch - exit from the process <-oss log("exit forced", *silent) os.Exit(1) }() log(fmt.Sprintf("[INFO] RoadRunner server started; version: %s, buildtime: %s", meta.Version(), meta.BuildTime()), *silent) // at this moment, we're almost sure that the container is running (almost- because we don't know if the plugins won't report an error on the next step) notified, err := sdnotify.SdNotify(sdnotify.Ready) if err != nil { log(fmt.Sprintf("[WARN] sdnotify: %s", err), *silent) } if notified { log("[INFO] sdnotify: notified", *silent) stopCh := make(chan struct{}, 1) if containerCfg.WatchdogSec > 0 { log(fmt.Sprintf("[INFO] sdnotify: watchdog enabled, timeout: %d seconds", containerCfg.WatchdogSec), *silent) sdnotify.StartWatchdog(containerCfg.WatchdogSec, stopCh) } // if notified -> notify about stop defer func() { stopCh <- struct{}{} }() } for { select { case e := <-errCh: return fmt.Errorf("error: %w\nplugin: %s", e.Error, e.VertexID) case <-stop: // stop the container after the first signal log(fmt.Sprintf("stop signal received, grace timeout is: %0.f seconds", containerCfg.GracePeriod.Seconds()), *silent) if err = cont.Stop(); err != nil { return fmt.Errorf("error: %w", err) } return nil } } }, } } ================================================ FILE: internal/cli/serve/doc.go ================================================ // Package serve implements the "serve" command that starts the RoadRunner // server, manages the Endure container lifecycle, handles OS signals for // graceful shutdown and restart, and integrates with systemd via sdnotify. package serve ================================================ FILE: internal/cli/stop/command.go ================================================ package stop import ( "log" "os" "strconv" "syscall" "time" "github.com/roadrunner-server/errors" "github.com/roadrunner-server/roadrunner/v2025/internal/sdnotify" "github.com/spf13/cobra" ) const ( // sync with root.go pidFileName string = ".pid" ) // NewCommand creates `serve` command. func NewCommand(silent *bool, force *bool) *cobra.Command { return &cobra.Command{ Use: "stop", Short: "Stop RoadRunner server", RunE: func(*cobra.Command, []string) error { const op = errors.Op("rr_stop") _, _ = sdnotify.SdNotify(sdnotify.Stopping) data, err := os.ReadFile(pidFileName) if err != nil { return errors.Errorf("%v, to create a .pid file, you must run RR with the following options: './rr serve -p'", err) } pid, err := strconv.Atoi(string(data)) if err != nil { return errors.E(op, err) } process, err := os.FindProcess(pid) if err != nil { return errors.E(op, err) } if !*silent { log.Printf("stopping process with PID: %d", pid) } err = process.Signal(syscall.SIGTERM) if err != nil { return errors.E(op, err) } if *force { // RR may lose the signal if we immediately send it time.Sleep(time.Second) err = process.Signal(syscall.SIGTERM) if err != nil { return errors.E(op, err) } } return nil }, } } ================================================ FILE: internal/cli/stop/command_test.go ================================================ package stop_test import ( "testing" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/stop" "github.com/stretchr/testify/assert" ) func TestCommandProperties(t *testing.T) { cmd := stop.NewCommand(toPtr(false), toPtr(false)) assert.Equal(t, "stop", cmd.Use) assert.NotNil(t, cmd.RunE) } func TestCommandTrue(t *testing.T) { cmd := stop.NewCommand(toPtr(true), toPtr(true)) assert.Equal(t, "stop", cmd.Use) assert.NotNil(t, cmd.RunE) } func toPtr[T any](val T) *T { return &val } ================================================ FILE: internal/cli/stop/doc.go ================================================ // Package stop implements the "stop" command that gracefully stops the // RoadRunner server by sending SIGTERM to the process identified by the // .pid file. package stop ================================================ FILE: internal/cli/workers/command.go ================================================ package workers import ( "fmt" "net/rpc" "os" "os/signal" "syscall" "time" "github.com/roadrunner-server/api/v4/plugins/v4/jobs" internalRpc "github.com/roadrunner-server/roadrunner/v2025/internal/rpc" tm "github.com/buger/goterm" "github.com/fatih/color" "github.com/roadrunner-server/errors" "github.com/roadrunner-server/informer/v5" "github.com/spf13/cobra" ) // NewCommand creates `workers` command. func NewCommand(cfgFile *string, override *[]string) *cobra.Command { //nolint:funlen // interactive workers updates var interactive bool cmd := &cobra.Command{ Use: "workers", Short: "Show information about active RoadRunner workers", RunE: func(_ *cobra.Command, args []string) error { const ( op = errors.Op("handle_workers_command") informerList = "informer.List" ) if cfgFile == nil { return errors.E(op, errors.Str("no configuration file provided")) } client, err := internalRpc.NewClient(*cfgFile, *override) if err != nil { return err } defer func() { _ = client.Close() }() plugins := args // by default, we expect a plugin list from user if len(plugins) == 0 { // but if nothing was passed - request all informers list if err = client.Call(informerList, true, &plugins); err != nil { return fmt.Errorf("failed to get list of plugins: %w", err) } } if !interactive { showWorkers(plugins, client) return nil } oss := make(chan os.Signal, 1) signal.Notify(oss, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) tm.Clear() tt := time.NewTicker(time.Second) defer tt.Stop() for { select { case <-oss: return nil case <-tt.C: tm.MoveCursor(1, 1) tm.Flush() showWorkers(plugins, client) } } }, } cmd.Flags().BoolVarP( &interactive, "interactive", "i", false, "render interactive workers table", ) return cmd } func showWorkers(plugins []string, client *rpc.Client) { const ( informerWorkers = "informer.Workers" informerJobs = "informer.Jobs" // this is only one exception to Render the workers, service plugin has the same workers as other plugins, // but they are RAW processes and needs to be handled in a different way. We don't need a special RPC call, but // need a special render method. servicePluginName = "service" ) for _, plugin := range plugins { list := &informer.WorkerList{} if err := client.Call(informerWorkers, plugin, &list); err != nil { // this is a special case, when we can't get workers list, we need to render an error message _ = WorkerTable(os.Stdout, list.Workers, fmt.Errorf("failed to receive information about %s plugin: %w", plugin, err)).Render() continue } if len(list.Workers) == 0 { continue } if plugin == servicePluginName { fmt.Printf("Workers of [%s]:\n", color.HiYellowString(plugin)) _ = ServiceWorkerTable(os.Stdout, list.Workers).Render() continue } fmt.Printf("Workers of [%s]:\n", color.HiYellowString(plugin)) _ = WorkerTable(os.Stdout, list.Workers, nil).Render() } for _, plugin := range plugins { var jst []*jobs.State if err := client.Call(informerJobs, plugin, &jst); err != nil { _ = JobsTable(os.Stdout, jst, fmt.Errorf("failed to receive information about %s plugin: %w", plugin, err)).Render() continue } // eq to nil if len(jst) == 0 { continue } fmt.Printf("Jobs of [%s]:\n", color.HiYellowString(plugin)) _ = JobsTable(os.Stdout, jst, nil).Render() } } ================================================ FILE: internal/cli/workers/command_test.go ================================================ package workers_test import ( "testing" "github.com/roadrunner-server/roadrunner/v2025/internal/cli/workers" "github.com/stretchr/testify/assert" ) func TestCommandProperties(t *testing.T) { cmd := workers.NewCommand(nil, nil) assert.Equal(t, "workers", cmd.Use) assert.NotNil(t, cmd.RunE) } func TestCommandFlags(t *testing.T) { cmd := workers.NewCommand(nil, nil) cases := []struct { giveName string wantShorthand string wantDefault string }{ {giveName: "interactive", wantShorthand: "i", wantDefault: "false"}, } for _, tt := range cases { t.Run(tt.giveName, func(t *testing.T) { flag := cmd.Flag(tt.giveName) if flag == nil { assert.Failf(t, "flag not found", "flag [%s] was not found", tt.giveName) return } assert.Equal(t, tt.wantShorthand, flag.Shorthand) assert.Equal(t, tt.wantDefault, flag.DefValue) }) } } func TestExecution(t *testing.T) { t.Skip("Command execution is not implemented yet") } ================================================ FILE: internal/cli/workers/doc.go ================================================ // Package workers implements the "workers" command that displays information // about active RoadRunner workers and job pipelines via RPC, with support // for interactive real-time updates. package workers ================================================ FILE: internal/cli/workers/render.go ================================================ package workers import ( "io" "sort" "strconv" "time" "github.com/dustin/go-humanize" "github.com/fatih/color" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" "github.com/roadrunner-server/api/v4/plugins/v4/jobs" "github.com/roadrunner-server/pool/state/process" ) const ( Ready string = "READY" Paused string = "PAUSED/STOPPED" ) // WorkerTable renders table with information about rr server workers. func WorkerTable(writer io.Writer, workers []*process.State, err error) *tablewriter.Table { cfg := tablewriter.Config{ Header: tw.CellConfig{ Formatting: tw.CellFormatting{ AutoFormat: tw.On, }, }, MaxWidth: 150, Row: tw.CellConfig{ Alignment: tw.CellAlignment{ Global: tw.AlignLeft, }, }, } tw := tablewriter.NewTable(writer, tablewriter.WithConfig(cfg)) tw.Header([]string{"PID", "Status", "Execs", "Memory", "CPU%", "Created"}) if err != nil { _ = tw.Append([]string{ "0", err.Error(), "ERROR", "ERROR", "ERROR", "ERROR", }) return tw } sort.Slice(workers, func(i, j int) bool { return workers[i].Pid < workers[j].Pid }) for i := range workers { _ = tw.Append([]string{ strconv.Itoa(int(workers[i].Pid)), renderStatus(workers[i].StatusStr), renderJobs(workers[i].NumExecs), humanize.Bytes(workers[i].MemoryUsage), renderCPU(workers[i].CPUPercent), renderAlive(time.Unix(0, workers[i].Created)), }) } return tw } // ServiceWorkerTable renders table with information about rr server workers. func ServiceWorkerTable(writer io.Writer, workers []*process.State) *tablewriter.Table { sort.Slice(workers, func(i, j int) bool { return workers[i].Pid < workers[j].Pid }) cfg := tablewriter.Config{ Header: tw.CellConfig{ Formatting: tw.CellFormatting{ AutoFormat: tw.On, }, }, MaxWidth: 150, Row: tw.CellConfig{ Alignment: tw.CellAlignment{ Global: tw.AlignLeft, }, }, } tw := tablewriter.NewTable(writer, tablewriter.WithConfig(cfg)) tw.Header([]string{"PID", "Memory", "CPU%", "Command"}) for i := range workers { _ = tw.Append([]string{ strconv.Itoa(int(workers[i].Pid)), humanize.Bytes(workers[i].MemoryUsage), renderCPU(workers[i].CPUPercent), workers[i].Command, }) } return tw } // JobsTable renders table with information about rr server jobs. func JobsTable(writer io.Writer, jobs []*jobs.State, err error) *tablewriter.Table { cfg := tablewriter.Config{ Header: tw.CellConfig{ Formatting: tw.CellFormatting{ AutoFormat: tw.On, AutoWrap: int(tw.Off), }, }, MaxWidth: 150, Row: tw.CellConfig{ Alignment: tw.CellAlignment{ Global: tw.AlignLeft, }, }, } tw := tablewriter.NewTable(writer, tablewriter.WithConfig(cfg)) tw.Header([]string{"Status", "Pipeline", "Driver", "Queue", "Active", "Delayed", "Reserved"}) if err != nil { _ = tw.Append([]string{ err.Error(), "ERROR", "ERROR", "ERROR", "ERROR", "ERROR", "ERROR", }) return tw } sort.Slice(jobs, func(i, j int) bool { return jobs[i].Pipeline < jobs[j].Pipeline }) for i := range jobs { _ = tw.Append([]string{ renderReady(jobs[i].Ready), jobs[i].Pipeline, jobs[i].Driver, jobs[i].Queue, strconv.Itoa(int(jobs[i].Active)), strconv.Itoa(int(jobs[i].Delayed)), strconv.Itoa(int(jobs[i].Reserved)), }) } return tw } func renderReady(ready bool) string { if ready { return Ready } return Paused } //go:inline func renderCPU(cpu float64) string { return strconv.FormatFloat(cpu, 'f', 2, 64) } func renderStatus(status string) string { switch status { case "inactive": return color.YellowString("inactive") case "ready": return color.CyanString("ready") case "working": return color.GreenString("working") case "invalid": return color.YellowString("invalid") case "stopped": return color.RedString("stopped") case "errored": return color.RedString("errored") default: return status } } func renderJobs(number uint64) string { return humanize.Comma(int64(number)) //nolint:gosec } func renderAlive(t time.Time) string { return humanize.RelTime(t, time.Now(), "ago", "") } ================================================ FILE: internal/debug/doc.go ================================================ // Package debug provides an HTTP server with pprof endpoints for runtime // profiling and diagnostics. package debug ================================================ FILE: internal/debug/server.go ================================================ package debug import ( "context" "net/http" "net/http/pprof" "time" ) // Server is a HTTP server for debugging. type Server struct { srv *http.Server } // NewServer creates new HTTP server for debugging. func NewServer() Server { mux := http.NewServeMux() mux.HandleFunc("/debug/pprof/", pprof.Index) mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) mux.HandleFunc("/debug/pprof/profile", pprof.Profile) mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) mux.HandleFunc("/debug/pprof/trace", pprof.Trace) return Server{srv: &http.Server{ ReadHeaderTimeout: time.Minute * 10, Handler: mux, }} } // Start debug server. func (s *Server) Start(addr string) error { s.srv.Addr = addr return s.srv.ListenAndServe() } // Stop debug server. func (s *Server) Stop(ctx context.Context) error { return s.srv.Shutdown(ctx) } ================================================ FILE: internal/debug/server_test.go ================================================ package debug_test import ( "context" "math/rand" "net" "net/http" "strconv" "testing" "time" "github.com/roadrunner-server/roadrunner/v2025/internal/debug" "github.com/stretchr/testify/assert" ) func TestServer_StartingAndStopping(t *testing.T) { rand.Seed(time.Now().UnixNano()) var ( s = debug.NewServer() port = strconv.Itoa(rand.Intn(10000) + 10000) //nolint:gosec ) go func() { assert.ErrorIs(t, s.Start(":"+port), http.ErrServerClosed) }() defer func() { assert.NoError(t, s.Stop(context.Background())) }() for range 100 { // wait for server started state if l, err := net.Dial("tcp", ":"+port); err != nil { <-time.After(time.Millisecond) } else { _ = l.Close() break } } for _, uri := range []string{ // assert that pprof handlers exists "http://127.0.0.1:" + port + "/debug/pprof/", "http://127.0.0.1:" + port + "/debug/pprof/cmdline", // "http://127.0.0.1:" + port + "/debug/pprof/profile", "http://127.0.0.1:" + port + "/debug/pprof/symbol", // "http://127.0.0.1:" + port + "/debug/pprof/trace", } { ctx, cancel := context.WithTimeout(context.Background(), time.Second) req, _ := http.NewRequestWithContext(ctx, http.MethodHead, uri, http.NoBody) resp, err := http.DefaultClient.Do(req) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) _ = resp.Body.Close() cancel() } } ================================================ FILE: internal/meta/doc.go ================================================ // Package meta holds build-time version and build timestamp metadata // set via linker flags during compilation. package meta ================================================ FILE: internal/meta/meta.go ================================================ package meta import "strings" // next variables will be set during compilation (do NOT rename them). var ( version = "local" buildTime = "development" //nolint:gochecknoglobals ) // Version returns version value (without `v` prefix). func Version() string { v := strings.TrimSpace(version) if len(v) > 1 && ((v[0] == 'v' || v[0] == 'V') && (v[1] >= '0' && v[1] <= '9')) { return v[1:] } return v } // BuildTime returns application building time. func BuildTime() string { return buildTime } ================================================ FILE: internal/meta/meta_test.go ================================================ package meta import ( "testing" "github.com/stretchr/testify/assert" ) func TestVersion(t *testing.T) { for give, want := range map[string]string{ // without changes "vvv": "vvv", "victory": "victory", "voodoo": "voodoo", "foo": "foo", "0.0.0": "0.0.0", "v": "v", "V": "V", // "v" prefix removal "v0.0.0": "0.0.0", "V0.0.0": "0.0.0", "v1": "1", "V1": "1", // with spaces " 0.0.0": "0.0.0", "v0.0.0 ": "0.0.0", " V0.0.0": "0.0.0", "v1 ": "1", " V1": "1", "v ": "v", } { version = give assert.Equal(t, want, Version()) } } func TestBuildTime(t *testing.T) { for give, want := range map[string]string{ "development": "development", "2021-03-26T13:50:31+0500": "2021-03-26T13:50:31+0500", } { buildTime = give assert.Equal(t, want, BuildTime()) } } ================================================ FILE: internal/rpc/client.go ================================================ package rpc import ( "errors" "fmt" "net" "net/rpc" "os" "strings" goridgeRpc "github.com/roadrunner-server/goridge/v3/pkg/rpc" rpcPlugin "github.com/roadrunner-server/rpc/v5" "github.com/spf13/viper" ) const ( rpcKey string = "rpc.listen" // default envs envDefault = ":-" ) // NewClient creates client ONLY for internal usage (communication between our application with RR side). // Client will be connected to the RPC. func NewClient(cfg string, flags []string) (*rpc.Client, error) { v := viper.New() v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.SetConfigFile(cfg) err := v.ReadInConfig() if err != nil { return nil, err } // override config Flags if len(flags) > 0 { for _, f := range flags { key, val, errP := parseFlag(f) if errP != nil { return nil, errP } v.Set(key, val) } } ver := v.Get(versionKey) if ver == nil { return nil, fmt.Errorf("rr configuration file should contain a version e.g: version: 3") } if _, ok := ver.(string); !ok { return nil, fmt.Errorf("version should be a string: `version: \"3\"`, actual type is: %T", ver) } err = handleInclude(ver.(string), v) if err != nil { return nil, fmt.Errorf("failed to handle includes: %w", err) } // rpc.listen might be set by the -o flags or env variable if !v.IsSet(rpcPlugin.PluginName) { return nil, errors.New("rpc service not specified in the configuration. Tip: add\n rpc:\n\r listen: rr_rpc_address") } conn, err := Dialer(v.GetString(rpcKey)) if err != nil { return nil, err } return rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)), nil } // Dialer creates rpc socket Dialer. func Dialer(addr string) (net.Conn, error) { dsn := strings.Split(addr, "://") if len(dsn) != 2 { return nil, errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)") } return net.Dial(dsn[0], dsn[1]) //nolint:noctx } func parseFlag(flag string) (string, string, error) { if !strings.Contains(flag, "=") { return "", "", fmt.Errorf("invalid flag `%s`", flag) } parts := strings.SplitN(strings.TrimLeft(flag, " \"'`"), "=", 2) if len(parts) < 2 { return "", "", errors.New("usage: -o key=value") } if parts[0] == "" { return "", "", errors.New("key should not be empty") } if parts[1] == "" { return "", "", errors.New("value should not be empty") } return strings.Trim(parts[0], " \n\t"), parseValue(strings.Trim(parts[1], " \n\t")), nil } func parseValue(value string) string { escape := []rune(value)[0] if escape == '"' || escape == '\'' || escape == '`' { value = strings.Trim(value, string(escape)) value = strings.ReplaceAll(value, fmt.Sprintf("\\%s", string(escape)), string(escape)) } return value } // ExpandVal replaces ${var} or $var in the string based on the mapping function. // For example, os.ExpandEnv(s) is equivalent to os.Expand(s, os.Getenv). func ExpandVal(s string, mapping func(string) string) string { var buf []byte // ${} is all ASCII, so bytes are fine for this operation. i := 0 for j := 0; j < len(s); j++ { if s[j] == '$' && j+1 < len(s) { if buf == nil { buf = make([]byte, 0, 2*len(s)) } buf = append(buf, s[i:j]...) name, w := getShellName(s[j+1:]) if name == "" && w > 0 { //nolint:revive // Encountered invalid syntax; eat the // characters. } else if name == "" { // Valid syntax, but $ was not followed by a // name. Leave the dollar character untouched. buf = append(buf, s[j]) // parse default syntax } else if idx := strings.Index(s, envDefault); idx != -1 { // ${key:=default} or ${key:-val} substr := strings.Split(name, envDefault) if len(substr) != 2 { return "" } key := substr[0] defaultVal := substr[1] res := mapping(key) if res == "" { res = defaultVal } buf = append(buf, res...) } else { buf = append(buf, mapping(name)...) } j += w i = j + 1 } } if buf == nil { return s } return string(buf) + s[i:] } // getShellName returns the name that begins the string and the number of bytes // consumed to extract it. If the name is enclosed in {}, it's part of a ${} // expansion, and two more bytes are needed than the length of the name. func getShellName(s string) (string, int) { switch { case s[0] == '{': if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == '}' { return s[1:2], 3 } // Scan to closing brace for i := 1; i < len(s); i++ { if s[i] == '}' { if i == 1 { return "", 2 // Bad syntax; eat "${}" } return s[1:i], i + 1 } } return "", 1 // Bad syntax; eat "${" case isShellSpecialVar(s[0]): return s[0:1], 1 } // Scan alphanumerics. var i int for i = 0; i < len(s) && isAlphaNum(s[i]); i++ { //nolint:revive } return s[:i], i } func expandEnvViper(v *viper.Viper) { for _, key := range v.AllKeys() { val := v.Get(key) switch t := val.(type) { case string: // for string expand it v.Set(key, parseEnvDefault(t)) case []any: // for slice -> check if it's a slice of strings strArr := make([]string, 0, len(t)) for i := range t { if valStr, ok := t[i].(string); ok { strArr = append(strArr, parseEnvDefault(valStr)) continue } v.Set(key, val) } // we should set the whole array if len(strArr) > 0 { v.Set(key, strArr) } default: v.Set(key, val) } } } // isShellSpecialVar reports whether the character identifies a special // shell variable such as $*. func isShellSpecialVar(c uint8) bool { switch c { case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return true } return false } // isAlphaNum reports whether the byte is an ASCII letter, number, or underscore. func isAlphaNum(c uint8) bool { return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' } func parseEnvDefault(val string) string { // tcp://127.0.0.1:${RPC_PORT:-36643} // for envs like this, part would be tcp://127.0.0.1: return ExpandVal(val, os.Getenv) } ================================================ FILE: internal/rpc/client_test.go ================================================ package rpc_test import ( "net" "os" "testing" "github.com/roadrunner-server/config/v5" "github.com/roadrunner-server/roadrunner/v2025/internal/rpc" "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" ) func TestNewClient_RpcServiceDisabled(t *testing.T) { cfgPlugin := &config.Plugin{Type: "yaml", ReadInCfg: []byte{}} assert.NoError(t, cfgPlugin.Init()) c, err := rpc.NewClient("test/config_rpc_empty.yaml", nil) assert.Nil(t, c) assert.EqualError(t, err, "rpc service not specified in the configuration. Tip: add\n rpc:\n\r listen: rr_rpc_address") } func TestNewClient_WrongRcpConfiguration(t *testing.T) { c, err := rpc.NewClient("test/config_rpc_wrong.yaml", nil) assert.Nil(t, c) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid socket DSN") } func TestNewClient_ConnectionError(t *testing.T) { c, err := rpc.NewClient("test/config_rpc_conn_err.yaml", nil) assert.Nil(t, c) assert.Error(t, err) assert.Contains(t, err.Error(), "connection refused") } func TestNewClient_SuccessfullyConnected(t *testing.T) { l, err := net.Listen("tcp", "127.0.0.1:55554") //nolint:noctx assert.NoError(t, err) defer func() { assert.NoError(t, l.Close()) }() c, err := rpc.NewClient("test/config_rpc_ok.yaml", nil) assert.NotNil(t, c) assert.NoError(t, err) defer func() { assert.NoError(t, c.Close()) }() } func TestNewClient_WithIncludes(t *testing.T) { l, err := net.Listen("tcp", "127.0.0.1:6010") //nolint:noctx assert.NoError(t, err) defer func() { assert.NoError(t, l.Close()) }() c, err := rpc.NewClient("test/include1/.rr.yaml", nil) assert.NotNil(t, c) assert.NoError(t, err) assert.NoError(t, c.Close()) } func TestNewClient_SuccessfullyConnectedOverride(t *testing.T) { l, err := net.Listen("tcp", "127.0.0.1:55554") //nolint:noctx assert.NoError(t, err) defer func() { assert.NoError(t, l.Close()) }() c, err := rpc.NewClient("test/config_rpc_empty.yaml", []string{"rpc.listen=tcp://127.0.0.1:55554"}) assert.NotNil(t, c) assert.NoError(t, err) defer func() { assert.NoError(t, c.Close()) }() } // ${} syntax func TestNewClient_SuccessfullyConnectedEnvDollarSyntax(t *testing.T) { l, err := net.Listen("tcp", "127.0.0.1:55556") //nolint:noctx assert.NoError(t, err) defer func() { assert.NoError(t, l.Close()) }() require.NoError(t, os.Setenv("RPC", "tcp://127.0.0.1:55556")) c, err := rpc.NewClient("test/config_rpc_ok_env.yaml", nil) assert.NotNil(t, c) assert.NoError(t, err) defer func() { assert.NoError(t, c.Close()) }() } ================================================ FILE: internal/rpc/doc.go ================================================ // Package rpc provides an internal RPC client for CLI-to-server communication. // It handles configuration loading with environment variable substitution, // flag override parsing, config file includes, and network dialing via the // Goridge protocol. This package is for internal use only and should be kept // in sync with the RPC plugin. package rpc ================================================ FILE: internal/rpc/includes.go ================================================ package rpc import ( "github.com/roadrunner-server/errors" "github.com/spf13/viper" ) const ( versionKey string = "version" includeKey string = "include" defaultConfigVersion string = "3" prevConfigVersion string = "2.7" ) func getConfiguration(path string) (map[string]any, string, error) { v := viper.New() v.SetConfigFile(path) err := v.ReadInConfig() if err != nil { return nil, "", err } // get configuration version ver := v.Get(versionKey) if ver == nil { return nil, "", errors.Str("rr configuration file should contain a version e.g: version: 2.7") } if _, ok := ver.(string); !ok { return nil, "", errors.Errorf("type of version should be string, actual: %T", ver) } // automatically inject ENV variables using ${ENV} pattern expandEnvViper(v) return v.AllSettings(), ver.(string), nil } func handleInclude(rootVersion string, v *viper.Viper) error { // automatically inject ENV variables using ${ENV} pattern // root config expandEnvViper(v) ifiles := v.GetStringSlice(includeKey) if ifiles == nil { return nil } for _, file := range ifiles { config, version, err := getConfiguration(file) if err != nil { return err } if version != rootVersion { return errors.Str("version in included file must be the same as in root") } // overriding configuration for key, val := range config { v.Set(key, val) } } return nil } ================================================ FILE: internal/rpc/test/config_rpc_conn_err.yaml ================================================ version: "3" rpc: listen: tcp://127.0.0.1:55554 ================================================ FILE: internal/rpc/test/config_rpc_empty.yaml ================================================ version: "3" ================================================ FILE: internal/rpc/test/config_rpc_ok.yaml ================================================ version: "3" rpc: listen: tcp://127.0.0.1:55554 ================================================ FILE: internal/rpc/test/config_rpc_ok_env.yaml ================================================ version: "3" rpc: listen: ${RPC} ================================================ FILE: internal/rpc/test/config_rpc_wrong.yaml ================================================ version: "3" rpc: $foo bar ================================================ FILE: internal/rpc/test/include1/.rr-include.yaml ================================================ version: "3" rpc: listen: tcp://127.0.0.1:6010 ================================================ FILE: internal/rpc/test/include1/.rr.yaml ================================================ version: "3" server: command: "php app-with-domain-specific-routes.php" logs: level: debug mode: development include: - test/include1/.rr-include.yaml ================================================ FILE: internal/sdnotify/doc.go ================================================ // Package sdnotify provides a Go implementation of the sd_notify protocol. // It can be used to inform systemd of service start-up completion, watchdog // events, and other status changes. // // https://www.freedesktop.org/software/systemd/man/sd_notify.html#Description package sdnotify ================================================ FILE: internal/sdnotify/sdnotify.go ================================================ // Copyright 2014 Docker, Inc. // Copyright 2015-2018 CoreOS, Inc. // Copyright 2025 SpiralScout. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package sdnotify import ( "net" "os" "time" ) type State string const ( // Ready tells the service manager that service startup is finished // or the service finished loading its configuration. // https://www.freedesktop.org/software/systemd/man/sd_notify.html#READY=1 Ready State = "READY=1" // Stopping tells the service manager that the service is beginning // its shutdown. Stopping State = "STOPPING=1" // Reloading tells the service manager that this service is // reloading its configuration. Note that you must call SdNotifyReady when // it completed reloading. Reloading State = "RELOADING=1" // Watchdog tells the service manager to update the watchdog // timestamp for the service. Watchdog = "WATCHDOG=1" ) // SdNotify sends a message to the init daemon. It is common to ignore the error. // If `unsetEnvironment` is true, the environment variable `NOTIFY_SOCKET` // will be unconditionally unset. // // It returns one of the following: // (false, nil) - notification not supported (i.e. NOTIFY_SOCKET is unset) // (false, err) - notification supported, but failure happened (e.g. error connecting to NOTIFY_SOCKET or while sending data) // (true, nil) - notification supported, data has been sent func SdNotify(state State) (bool, error) { socketAddr := &net.UnixAddr{ Name: os.Getenv("NOTIFY_SOCKET"), Net: "unixgram", } // NOTIFY_SOCKET not set if socketAddr.Name == "" { return false, nil } conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr) // Error connecting to NOTIFY_SOCKET if err != nil { return false, err } defer func() { _ = conn.Close() }() if _, err = conn.Write([]byte(state)); err != nil { return false, err } return true, nil } func StartWatchdog(interval int, stopCh <-chan struct{}) { go func() { ticker := time.NewTicker(time.Duration(interval) * time.Second) defer ticker.Stop() for { select { case <-stopCh: return case <-ticker.C: supported, err := SdNotify(Watchdog) if err != nil { return } // notification not supported, stop if !supported { return } // notification supported, data has been sent, continue if supported { continue } } } }() } ================================================ FILE: lib/doc.go ================================================ // Package lib provides a public API for embedding RoadRunner as a library. // It exposes the RR type for programmatic control of the server lifecycle // including starting, stopping, and accessing registered plugins. package lib ================================================ FILE: lib/roadrunner.go ================================================ package lib import ( "fmt" "runtime/debug" configImpl "github.com/roadrunner-server/config/v5" "github.com/roadrunner-server/endure/v2" "github.com/roadrunner-server/roadrunner/v2025/container" ) const ( rrModule string = "github.com/roadrunner-server/roadrunner/v2025" ) type RR struct { container *endure.Endure stop chan struct{} Version string } // NewRR creates a new RR instance that can then be started or stopped by the caller func NewRR(cfgFile string, override []string, pluginList []any) (*RR, error) { // create endure container config containerCfg, err := container.NewConfig(cfgFile) if err != nil { return nil, err } cfg := &configImpl.Plugin{ Path: cfgFile, Timeout: containerCfg.GracePeriod, Flags: override, Version: getRRVersion(), } // create endure container endureOptions := []endure.Options{ endure.GracefulShutdownTimeout(containerCfg.GracePeriod), } if containerCfg.PrintGraph { endureOptions = append(endureOptions, endure.Visualize()) } // create endure container ll, err := container.ParseLogLevel(containerCfg.LogLevel) if err != nil { return nil, err } endureContainer := endure.New(ll, endureOptions...) // register another container plugin err = endureContainer.RegisterAll(append(pluginList, cfg)...) if err != nil { return nil, err } // init container and all services err = endureContainer.Init() if err != nil { return nil, err } return &RR{ container: endureContainer, stop: make(chan struct{}, 1), Version: cfg.Version, }, nil } // Serve starts RR and starts listening for requests. // This is a blocking call that will return an error if / when one occurs in a plugin func (rr *RR) Serve() error { // start serving the graph errCh, err := rr.container.Serve() if err != nil { return err } select { case e := <-errCh: return fmt.Errorf("error: %w\nplugin: %s", e.Error, e.VertexID) case <-rr.stop: return rr.container.Stop() } } func (rr *RR) Plugins() []string { return rr.container.Plugins() } // Stop stops roadrunner func (rr *RR) Stop() { rr.stop <- struct{}{} } // DefaultPluginsList returns all the plugins that RR can run with and are included by default func DefaultPluginsList() []any { return container.Plugins() } // Tries to find the version info for a given module's path // empty string if not found func getRRVersion() string { bi, ok := debug.ReadBuildInfo() if !ok { return "" } for i := range bi.Deps { if bi.Deps[i].Path == rrModule { return bi.Deps[i].Version } } return "" } ================================================ FILE: lib/roadrunner_test.go ================================================ package lib_test import ( "os" "testing" "time" "github.com/roadrunner-server/informer/v5" "github.com/roadrunner-server/resetter/v5" "github.com/roadrunner-server/roadrunner/v2025/lib" "github.com/stretchr/testify/assert" ) func TestNewFailsOnMissingConfig(t *testing.T) { _, err := lib.NewRR("config/file/does/not/exist/.rr.yaml", []string{}, lib.DefaultPluginsList()) assert.NotNil(t, err) } const testConfigWithVersion = ` version: '3' server: command: "php src/index.php" relay: "pipes" endure: grace_period: 1s ` const testConfig = ` server: command: "php src/index.php" relay: "pipes" endure: grace_period: 1s ` func makeConfig(t *testing.T, configYaml string) string { cfgFile := os.TempDir() + "/.rr.yaml" err := os.WriteFile(cfgFile, []byte(configYaml), 0600) assert.NoError(t, err) return cfgFile } func TestNewWithOldConfig(t *testing.T) { cfgFile := makeConfig(t, testConfig) _, err := lib.NewRR(cfgFile, []string{}, lib.DefaultPluginsList()) assert.Error(t, err) t.Cleanup(func() { _ = os.Remove(cfgFile) }) } func TestNewWithConfig(t *testing.T) { cfgFile := makeConfig(t, testConfigWithVersion) rr, err := lib.NewRR(cfgFile, []string{}, lib.DefaultPluginsList()) assert.NoError(t, err) assert.Equal(t, "3", rr.Version) t.Cleanup(func() { _ = os.Remove(cfgFile) }) } func TestServeStop(t *testing.T) { cfgFile := makeConfig(t, testConfigWithVersion) plugins := []any{ &informer.Plugin{}, &resetter.Plugin{}, } rr, err := lib.NewRR(cfgFile, []string{}, plugins) assert.NoError(t, err) errchan := make(chan error, 1) stopchan := make(chan struct{}, 1) go func() { errchan <- rr.Serve() stopchan <- struct{}{} }() rr.Stop() time.Sleep(time.Second * 2) assert.Equal(t, struct{}{}, <-stopchan) assert.Nil(t, <-errchan) t.Cleanup(func() { _ = os.Remove(cfgFile) }) } ================================================ FILE: schemas/config/1.0.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "description": "Version 1.0 is deprecated. Please, upgrade RR up to version 2", "type": "object", "properties": { "env": { "type": "object", "properties": { "key": { "type": "string" } } }, "rpc": { "type": "object", "properties": { "enable": { "type": "boolean" }, "listen": { "type": "string" } } }, "metrics": { "type": "object", "properties": { "address": { "type": "string" }, "collect": { "type": "object", "patternProperties": { "[a-zA-Z0-9-_]": { "type": "object", "properties": { "type": { "type": "string" }, "help": { "type": "string" }, "labels": { "type": "array", "items": {} }, "buckets": { "type": "array", "items": {} } } } } } } }, "http": { "type": "object", "properties": { "address": { "type": "string" }, "ssl": { "type": "object", "properties": { "port": { "type": "integer" }, "redirect": { "type": "boolean" }, "cert": { "type": "string" }, "key": { "type": "string" }, "rootCa": { "type": "string" } } }, "fcgi": { "type": "object", "properties": { "address": { "type": "string" } } }, "http2": { "type": "object", "properties": { "enabled": { "type": "boolean" }, "h2c": { "type": "boolean" }, "maxConcurrentStreams": { "type": "integer" } } }, "maxRequestSize": { "type": "integer" }, "uploads": { "type": "object", "properties": { "forbid": { "type": "array", "items": {} } } }, "trustedSubnets": { "type": "array", "items": {} }, "workers": { "type": "object", "properties": { "command": { "type": "string" }, "relay": { "type": "string" }, "user": { "type": "string" }, "pool": { "type": "object", "properties": { "numWorkers": { "type": "integer" }, "maxJobs": { "type": "integer" }, "allocateTimeout": { "type": "integer" }, "destroyTimeout": { "type": "integer" } } } } } } }, "headers": { "type": "object", "properties": { "cors": { "type": "object", "properties": { "allowedOrigin": { "type": "string" }, "allowedHeaders": { "type": "string" }, "allowedMethods": { "type": "string" }, "allowCredentials": { "type": "boolean" }, "exposedHeaders": { "type": "string" }, "maxAge": { "type": "integer" } } }, "request": { "type": "object", "patternProperties": { "[a-zA-Z0-9-_]": { "type": "string" } } }, "response": { "type": "object", "patternProperties": { "[a-zA-Z0-9-_]": { "type": "string" } } } } }, "limit": { "type": "object", "properties": { "interval": { "type": "integer" }, "services": { "type": "object", "properties": { "http": { "type": "object", "properties": { "maxMemory": { "type": "integer" }, "TTL": { "type": "integer" }, "idleTTL": { "type": "integer" }, "execTTL": { "type": "integer" } } } } } } }, "static": { "type": "object", "properties": { "dir": { "type": "string" }, "forbid": { "type": "array", "items": {} } } }, "health": { "type": "object", "properties": { "address": { "type": "string" } } }, "reload": { "type": "object", "properties": { "interval": { "type": "string" }, "patterns": { "type": "array", "items": {} }, "services": { "type": "object", "properties": { "http": { "type": "object", "properties": { "dirs": { "type": "array", "items": {} }, "recursive": { "type": "boolean" } } } } } } } } } ================================================ FILE: schemas/config/2.0.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Spiral Roadrunner config file schema version 2", "description": "Spiral Roadrunner config file schema version 2.", "type": "object", "additionalProperties": true, "minProperties": 1, "properties": { "version": { "description": "configuration version", "type": "string", "default": "2.7", "enum": [ "2.7" ] }, "centrifuge": { "description": "Centrifugo server plugin. Docs: https://centrifugal.dev/", "type": "object", "properties": { "proxy_address": { "description": "Centrifugo server proxy address (docs: https://centrifugal.dev/docs/server/proxy#grpc-proxy)", "type": "string", "default": "tcp://127.0.0.1:30000" }, "grpc_api_address": { "description": "gRPC server API address (docs: https://centrifugal.dev/docs/server/server_api#grpc-api)", "type": "string", "default": "tcp://127.0.0.1:30000" }, "use_compressor": { "description": "Use gRPC gzip compressor", "type": "boolean", "default": false }, "version": { "description": "Your application version", "type": "string", "default": "v1.0.0" }, "name": { "description": "Your application name", "type": "string", "default": "roadrunner" }, "pool": { "description": "Workers pool settings", "$ref": "#/definitions/WorkersPool" }, "tls": { "description": "TLS settings", "type": "object", "properties": { "cert": { "description": "Path to the cert file", "type": "string", "minLength": 1, "examples": [ "/ssl/server.crt" ] }, "key": { "description": "Path to the cert key file", "type": "string", "minLength": 1, "examples": [ "/ssl/server.key" ] } } } } }, "rpc": { "type": "object", "properties": { "listen": { "description": "TCP address:port for listening", "type": "string", "default": "tcp://127.0.0.1:6001", "examples": [ "tcp://127.0.0.1:6001" ], "pattern": "^tcp:\/\/[0-9a-zA-Z_.-]+:[0-9]{1,5}$" } } }, "server": { "type": "object", "properties": { "on_init": { "description": "Execute command or script before RR starts allocating workers", "type": "object", "properties": { "command": { "description": "Command to execute. It can be script or binary", "type": "string", "examples": [ "php not-worker.php", "sh script.sh", "start script.bat" ] }, "exec_timeout": { "description": "Script execute timeout", "$ref": "#/definitions/Duration", "default": "60s" }, "env": { "description": "Environment variables for the worker processes", "type": "array", "items": { "type": "object", "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string" } }, "additionalProperties": false } } } }, "command": { "description": "Worker starting command, with any required arguments", "type": "string", "examples": [ "php psr-worker.php" ] }, "user": { "description": "User name (not UID) for the worker processes. An empty value means to use the RR process user", "type": "string", "default": "", "examples": [ "www-data" ] }, "group": { "description": "Group name (not GID) for the worker processes. An empty value means to use the RR process user", "type": "string", "default": "", "examples": [ "www-data" ] }, "env": { "description": "Environment variables for the worker processes", "type": "array", "items": { "type": "object", "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string" } }, "additionalProperties": false } }, "relay": { "description": "Worker relay can be: 'pipes', TCP (eg.: tcp://127.0.0.1:6002), or socket (eg.: unix:///var/run/rr.sock)", "type": "string", "default": "pipes", "examples": [ "pipes", "tcp://127.0.0.1:6002", "unix:///var/run/rr.sock" ] }, "relay_timeout": { "description": "Timeout for relay connection establishing (only for socket and TCP port relay)", "$ref": "#/definitions/Duration", "default": "60s" } }, "required": [ "command" ] }, "logs": { "type": "object", "properties": { "mode": { "$ref": "#/definitions/LogMode", "default": "development" }, "level": { "$ref": "#/definitions/LogLevel", "default": "debug" }, "encoding": { "$ref": "#/definitions/LogEncoding", "default": "console" }, "output": { "description": "Output", "$ref": "#/definitions/LogOutput", "default": "stderr" }, "err_output": { "description": "Errors only output", "$ref": "#/definitions/LogOutput", "default": "stderr" }, "channels": { "description": "You can configure each plugin log messages individually", "type": "object", "additionalProperties": { "type": "object", "properties": { "mode": { "$ref": "#/definitions/LogMode" }, "level": { "$ref": "#/definitions/LogLevel" }, "encoding": { "$ref": "#/definitions/LogEncoding" }, "output": { "$ref": "#/definitions/LogOutput" }, "err_output": { "$ref": "#/definitions/LogOutput" } } } }, "file_logger_options": { "description": "file logger options", "type": "object", "properties": { "log_output": { "type": "string", "description": "path to the file" }, "max_size": { "type": "integer", "description": "maximum file size in MB" }, "max_age": { "type": "integer", "description": "The maximum number of days to retain old log files based on the timestamp encoded in their filename" }, "max_backups": { "type": "integer", "description": "The maximum number of old log files to retain" }, "compress": { "type": "boolean", "description": "compress files to save a disk space" } } } } }, "temporal": { "description": "Workflow and activity mesh service, https://docs.temporal.io/docs/php/introduction/", "type": "object", "properties": { "address": { "description": "Address of temporal server", "type": "string", "default": "127.0.0.1:7233" }, "cache_size": { "description": "Sticky cache size. Sticky workflow execution is the affinity between workflow tasks of a specific workflow execution to a specific worker. The benefit of sticky execution is that the workflow does not have to reconstruct state by replaying history from the beginning. The cache is shared between workers running within same process. This must be called before any worker is started. If not called, the default size of 10K (which may change) will be used", "type": "integer", "default": 10000 }, "namespace": { "description": "Namespace name for this client to work with", "type": "string", "default": "default" }, "metrics": { "description": "Temporal metrics", "type": "object", "default": null, "properties": { "driver": { "description": "Metrics driver to use", "type": "string", "enum": [ "prometheus", "statsd" ], "default": "prometheus" } }, "anyOf": [ { "type": "object", "properties": { "address": { "description": "Server metrics address", "type": "string", "default": "127.0.0.1:9091" }, "type": { "type": "string", "description": "Metrics type", "anyOf": [ { "type": "string", "examples": [ "summary", "histogram" ] } ] }, "prefix": { "description": "Temporal metrics prefix", "type": "string", "default": null } } }, { "properties": { "host_port": { "description": "The host and port of the statsd server", "type": "string", "default": "127.0.0.1:8125" }, "prefix": { "description": "The prefix to use in reporting to statsd", "type": "string", "default": null }, "flush_interval": { "description": "FlushInterval is the maximum interval for sending packets", "type": "string", "default": "1s" }, "flush_bytes": { "description": "FlushBytes specifies the maximum udp packet size you wish to send. If FlushBytes is unspecified, it defaults to 1432 bytes, which is considered safe for local traffic.", "type": "integer", "default": 1432 }, "tags": { "description": "Hashmap with tag:value values", "$ref": "#/definitions/Hashmap" }, "tag_prefix": { "description": "Prefix for the tag", "type": "string", "default": null }, "tag_separator": { "description": "TagSeparator allows tags to be appended with a separator. If not specified tag keys and values are embedded to the stat name directly.", "type": "string", "default": null } } } ] }, "activities": { "description": "Activities pool settings", "type": "object", "$ref": "#/definitions/WorkersPool" }, "tls": { "description": "Temporal TLS configuration", "type": "object", "properties": { "key": { "description": "Path to the key file", "type": "string", "default": null }, "cert": { "description": "Path to the certificate", "type": "string", "default": null }, "root_ca": { "description": "Path to the CA certificate", "type": "string", "default": null }, "client_auth_type": { "description": "Client auth type", "type": "string", "default": "no_client_certs", "enum": [ "request_client_cert", "require_any_client_cert", "verify_client_cert_if_given", "no_client_certs", "require_and_verify_client_cert" ] }, "server_name": { "description": "ServerName is used to verify the hostname on the returned certificates unless InsecureSkipVerify is given. It is also included in the client's handshake to support virtual hosting unless it is an IP address.", "type": "string", "default": null } } } } }, "kv": { "description": "Key value storages plugin", "type": "object", "minProperties": 1, "patternProperties": { "[a-zA-Z0-9_-]*": { "anyOf": [ { "type": "object", "description": "boltdb driver", "properties": { "driver": { "type": "string", "description": "Driver which should be used for the storage" }, "config": { "anyOf": [ { "type": "object", "$ref": "#/definitions/BoltDB" }, { "type": "object", "$ref": "#/definitions/Memcached" }, { "type": "object", "$ref": "#/definitions/Redis" }, { "type": "object", "$ref": "#/definitions/Memory" } ] } }, "required": [ "driver" ] } ] } } }, "service": { "description": "Service plugin settings", "type": "object", "patternProperties": { "^[a-zA-Z0-9._-]+$": { "allOf": [ { "description": "User defined services", "type": "object", "$ref": "#/definitions/Service" } ] } } }, "http": { "type": "object", "properties": { "address": { "description": "Host and port to listen on", "$ref": "#/definitions/HostAndPort", "examples": [ "127.0.0.1:8080", ":8080" ] }, "max_request_size": { "description": "Maximal incoming request size in megabytes. Zero means no limit", "type": "integer", "minimum": 0, "default": 0 }, "raw_body": { "description": "Send raw body (unescaped) to the PHP worker for the application/x-www-form-urlencoded content type", "type": "boolean", "default": false }, "access_logs": { "description": "HTTP access logs", "type": "boolean", "default": false }, "middleware": { "description": "Middleware for the http plugin, order is important", "type": "array", "items": { "type": "string", "enum": [ "headers", "gzip", "static", "sendfile", "http_metrics", "cache", "proxy_ip_parser", "otel" ], "pattern": "^[0-9a-zA-Z_]+$" } }, "trusted_subnets": { "description": "Allow incoming requests only from the following subnets", "type": "array", "items": { "type": "string", "examples": [ "10.0.0.0/8", "127.0.0.0/8" ] }, "default": [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] }, "cache": { "description": "RFC 7234 Souin cache: https://github.com/darkweak/souin/tree/master/plugins/roadrunner", "type": "object", "properties": { "api": { "description": "The cache-handler API cache management", "type": "object", "properties": { "basepath": { "description": "Default route basepath for every additional APIs to avoid conflicts with existing routes", "type": "string", "default": null }, "prometheus": { "type": "object", "description": "Prometheus exposed metrics", "properties": { "basepath": { "type": "string", "default": null } } }, "souin": { "type": "object", "description": "Souin listing keys and cache management", "properties": { "basepath": { "type": "string", "default": null } } } } }, "cache_keys": { "type": "object", "patternProperties": { "^[a-zA-Z0-9._-]+$": { "description": "cache keys configuration", "type": "object", "properties": { "disable_body": { "type": "boolean", "default": false }, "disable_host": { "type": "boolean", "default": false }, "disable_method": { "type": "boolean", "default": false } } } } }, "cdn": { "description": "If Souin is set after a CDN fill these information", "type": "object", "properties": { "api_key": { "type": "string", "description": "Your provider API key if mandatory" }, "provider": { "description": "The provider placed before Souin (e.g. fastly, cloudflare, akamai, varnish)", "type": "string", "enum": [ "fastly", "cloudflare", "akamai", "varnish" ] }, "strategy": { "description": "The strategy to purge the CDN cache based on tags (e.g. soft, hard)", "type": "string", "enum": [ "soft", "hard" ] }, "dynamic": { "description": "If true, you'll be able to add custom keys than the ones defined under the surrogate_keys key", "type": "boolean", "default": false } } }, "default_cache": { "type": "object", "properties": { "allowed_http_verbs": { "type": "array", "default": [ "GET", "POST" ] }, "cache_name": { "description": "Override the cache name to use in the Cache-Status header", "type": "string", "default": "roadrunner" }, "distributed": { "type": "boolean", "default": false, "description": "Use Olric or Etcd distributed storage" }, "headers": { "description": "Default headers concatenated in stored keys", "type": "array", "default": null }, "key": { "type": "object", "properties": { "disable_body": { "type": "boolean", "default": false }, "disable_host": { "type": "boolean", "default": false }, "disable_method": { "type": "boolean", "default": false } } }, "etcd": { "description": "If distributed is set to true, you'll have to define either the etcd or olric section", "type": "object", "properties": { "configuration": { "type": "object", "description": "Configure directly the Etcd client", "properties": { "endpoints": { "description": "Define multiple endpoints", "type": "array", "default": null } } } } }, "olric": { "type": "object", "description": "If distributed is set to true, you'll have to define either the etcd or olric section", "properties": { "url": { "description": "Olric server", "type": "string", "default": "http://127.0.0.1:3320" } } }, "regex": { "type": "object", "description": "Regex to exclude from cache", "properties": { "exclude": { "type": "string", "default": null } } }, "stale": { "type": "string", "description": "Stale duration", "default": "1000s" }, "timeout": { "description": "Timeout configuration", "type": "object", "properties": { "backend": { "description": "Backend timeout before returning an HTTP unavailable response", "type": "string", "default": "10s" }, "cache": { "description": "Cache provider (badger, etcd, nutsdb, olric, depending the configuration you set) timeout before returning a miss", "type": "string", "default": "20ms" } } }, "ttl": { "description": "Default TTL", "type": "string", "default": "1000s" }, "default_cache_control": { "description": "Set default value for Cache-Control response header if not set by upstream", "type": "string", "default": "no-store" } } }, "log_level": { "type": "string", "description": "Logs verbosity", "default": "INFO", "enum": [ "DEBUG", "INFO", "WARN", "DPANIC", "PANIC", "ERROR", "FATAL" ] } } }, "uploads": { "type": "object", "properties": { "dir": { "description": "Directory for file uploads. Empty value means to use $TEMP based on your OS", "type": "string", "examples": [ "/tmp" ], "default": "" }, "forbid": { "description": "Deny files with the following extensions to upload", "type": "array", "items": { "type": "string", "examples": [ ".php", ".exe" ] }, "default": [ ".php", ".exe", ".bat" ] }, "allow": { "description": "Allow files with the following extensions to upload", "type": "array", "items": { "type": "string", "examples": [ ".html", ".go" ] }, "default": "" } } }, "headers": { "description": "HTTP headers map", "type": "object", "properties": { "cors": { "description": "Allows to control CORS headers", "type": "object", "properties": { "allowed_origin": { "description": "Controls 'Access-Control-Allow-Origin' header value", "type": "string", "examples": [ "*" ], "default": "" }, "allowed_headers": { "description": "Controls 'Access-Control-Allow-Headers' header value", "type": "string", "examples": [ "*" ], "default": "" }, "allowed_methods": { "description": "Controls 'Access-Control-Allow-Methods' header value", "type": "string", "examples": [ "GET,POST,PUT,DELETE" ], "default": "" }, "allow_credentials": { "description": "Controls 'Access-Control-Allow-Credentials' header value", "type": "boolean", "default": false }, "exposed_headers": { "description": "Controls 'Access-Control-Expose-Headers' header value", "type": "string", "examples": [ "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma" ], "default": "" }, "max_age": { "description": "Controls 'Access-Control-Max-Age' header value (in seconds)", "type": "integer", "examples": [ 600 ], "default": 0 } } }, "request": { "description": "Automatically add headers to every request passed to PHP", "$ref": "#/definitions/Hashmap" }, "response": { "description": "Automatically add headers to every response", "$ref": "#/definitions/Hashmap" } } }, "static": { "description": "Static assets serving settings", "type": "object", "properties": { "dir": { "description": "Path to the directory with static assets", "type": "string", "examples": [ ".", "/var/www/html" ] }, "forbid": { "description": "File extensions that should not be served", "type": "array", "items": { "type": "string", "examples": [ ".php", ".htaccess" ] } }, "allow": { "description": "File extensions which should be served", "type": "array", "items": { "type": "string", "examples": [ ".php", ".htaccess" ] } }, "calculate_etag": { "description": "Turn on etag computation for the static file", "type": "boolean" }, "weak": { "description": "Use a weak generator (/W), it uses only filename to generate a CRC32 sum", "type": "boolean" }, "response": { "description": "Custom headers for the static files", "$ref": "#/definitions/Hashmap" } }, "required": [ "dir" ] }, "pool": { "description": "Workers pool settings", "$ref": "#/definitions/WorkersPool" }, "ssl": { "description": "SSL (Secure Sockets Layer) settings", "type": "object", "properties": { "address": { "description": "Host and port to listen on", "$ref": "#/definitions/HostAndPort", "examples": [ "127.0.0.1:443", ":8443" ] }, "acme": { "description": "ACME certificates provider (Let's encrypt)", "type": "object", "properties": { "certs_dir": { "description": "Directory to use as a certificate/pk, account info storage", "type": "string", "default": "rr_cache" }, "email": { "description": "User email, used to create LE account", "type": "string" }, "alt_http_port": { "description": "Alternate port for the http challenge. Challenge traffic should be redirected to this port if overridden.", "type": "integer", "default": 80 }, "alt_tlsalpn_port": { "description": "Alternate port for the tls-alpn-01 challenge. Challenge traffic should be redirected to this port if overridden.", "type": "integer", "default": 443 }, "challenge_type": { "type": "string", "enum": [ "http-01", "tlsalpn-01" ], "description": "Challenge types", "default": "http-01" }, "use_production_endpoint": { "description": "Use production or staging endpoint. NOTE, try to use staging endpoint to make sure, that everything works correctly.", "type": "boolean", "default": false }, "domains": { "type": "array", "description": "List of your domains to obtain certificates" } }, "required": [ "domains", "email" ] }, "redirect": { "description": "Automatic redirect from http to https schema", "type": "boolean", "default": false }, "cert": { "description": "Path to the cert file", "type": "string", "minLength": 1, "examples": [ "/ssl/server.crt" ] }, "key": { "description": "Path to the cert key file", "type": "string", "minLength": 1, "examples": [ "/ssl/server.key" ] }, "root_ca": { "description": "Path to the root certificate authority file", "type": "string", "minLength": 1, "examples": [ "/ssl/root.crt" ] }, "client_auth_type": { "description": "Client auth type", "type": "string", "default": "no_client_certs", "enum": [ "request_client_cert", "require_any_client_cert", "verify_client_cert_if_given", "no_client_certs", "require_and_verify_client_cert" ] } }, "required": [ "address", "cert", "key" ] }, "fcgi": { "description": "FastCGI frontend support", "type": "object", "properties": { "address": { "description": "FastCGI connection DSN. Supported TCP and Unix sockets. An empty value disables this", "type": "string", "examples": [ "tcp://0.0.0.0:7921" ] } }, "required": [ "address" ] }, "http2": { "description": "HTTP/2 settings", "type": "object", "properties": { "h2c": { "description": "HTTP/2 over non-encrypted TCP connection using H2C", "type": "boolean", "default": false }, "max_concurrent_streams": { "description": "Maximal concurrent streams count", "type": "integer", "default": 128, "minimum": 0 } } }, "otel": { "description": "OpenTelemetry configuration", "type": "object", "properties": { "insecure": { "description": "Use insecure endpoint", "type": "boolean", "default": false }, "compress": { "description": "Use gzip compressor", "type": "boolean", "default": false }, "exporter": { "description": "Provides functionality to emit telemetry to consumers", "type": "string", "items": { "type": "string", "enum": [ "zipkin", "stdout", "otlp", "jaeger", "jaeger_agent" ], "pattern": "^[0-9a-zA-Z_]+$" } }, "custom_url": { "description": "Used for the http client to override the default URL", "type": "string", "default": "" }, "endpoint": { "description": "Consumer's endpoint", "type": "string", "default": "localhost:4318" }, "client": { "description": "Client to send the spans", "type": "string", "items": { "type": "string", "enum": [ "http", "grpc" ], "pattern": "^[0-9a-zA-Z_]+$" } }, "service_name": { "description": "User's service name", "type": "string", "default": "RoadRunner" }, "service_version": { "description": "User's service version", "type": "string", "default": "1.0.0" }, "headers": { "description": "User defined headers", "$ref": "#/definitions/Hashmap" } } } }, "required": [ "address" ] }, "redis": { "description": "Redis section.", "type": "object", "$ref": "#/definitions/Redis" }, "metrics": { "description": "Application metrics in Prometheus format (docs: https://roadrunner.dev/docs/beep-beep-metrics)", "type": "object", "properties": { "address": { "description": "Prometheus client address (path /metrics added automatically).", "type": "string", "default": "127.0.0.1:2112" }, "collect": { "description": "Application-specific metrics (published using an RPC connection to the server)", "type": "object", "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "histogram", "gauge", "counter", "summary" ] }, "help": { "type": "string", "description": "Help message" }, "labels": { "type": "array", "minItems": 1, "description": "Metrics labels" }, "buckets": { "type": "array", "items": { "type": "number" } }, "objectives": { "title": "map[float]float", "type": "array", "items": { "type": "object", "additionalProperties": { "type": "number" } } } } } } } } }, "status": { "description": "Health check endpoint (docs: https://roadrunner.dev/docs/beep-beep-health). If response code is 200 - it means at least one worker ready to serve requests. 500 - there are no workers ready to service requests.", "type": "object", "properties": { "address": { "description": "Host and port to listen on (eg.: `127.0.0.1:2114`). Use the following URL: http://127.0.0.1:2114/health?plugin=http. Multiple plugins must be separated using & - http://127.0.0.1:2114/health?plugin=http&plugin=rpc where http and rpc are active (connected) plugins.", "type": "string", "examples": [ "127.0.0.1:2114" ] }, "unavailable_status_code": { "description": "Response status code if a requested plugin not ready to handle requests. Valid for both /health and /ready endpoints", "type": "integer", "default": 503 } }, "required": [ "address" ] }, "reload": { "description": "Automatically detect PHP file changes and reload connected services", "type": "object", "properties": { "interval": { "description": "Sync interval", "$ref": "#/definitions/Duration", "default": "1s" }, "patterns": { "description": "Global patterns to sync", "type": "array", "items": { "type": "string", "examples": [ ".php", ".json" ] }, "default": [ ".php" ] }, "services": { "description": "List of included for sync services (this is a map, where key name is a plugin name)", "type": "object", "minProperties": 0, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "object", "properties": { "dirs": { "description": "Directories to sync. If recursive is set to true, recursive sync will be applied only to the directories in 'dirs' section. Dot (.) means 'current working directory'", "type": "array", "default": [], "items": { "type": "string", "examples": [ ".", "/app/src" ], "minLength": 1 } }, "recursive": { "description": "Recursive search for file patterns to add", "type": "boolean", "default": false }, "ignore": { "description": "Ignored folders", "type": "array", "default": [], "items": { "type": "string", "examples": [ "vendor", "/app/logs" ], "minLength": 1 } }, "patterns": { "description": "Service specific file pattens to sync", "type": "array", "default": [], "items": { "type": "string", "examples": [ ".php", ".go", ".md" ], "minLength": 1 } } } } }, "additionalProperties": false } } }, "nats": { "$ref": "#/definitions/NATS_J" }, "boltdb": { "$ref": "#/definitions/BoltDB_J" }, "kafka": { "$ref": "#/definitions/KAFKA_J" }, "amqp": { "description": "AMQP jobs driver", "type": "object", "properties": { "addr": { "description": "AMQP Uri to connect to the rabbitmq server https://www.rabbitmq.com/uri-spec.html", "type": "string", "default": "amqp://guest:guest@127.0.0.1:5672" } } }, "beanstalk": { "description": "Beanstalk jobs driver", "type": "object", "properties": { "addr": { "description": "Beanstalk server address", "type": "string", "default": "tcp://127.0.0.1:11300" }, "timeout": { "description": "Beanstalk connect timeout", "type": "string", "$ref": "#/definitions/Duration", "default": "30s" } } }, "sqs": { "description": "SQS jobs driver (https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html)", "type": "object", "properties": { "key": { "description": "AccessKey ID", "type": "string", "default": null }, "secret": { "description": "Secret Access key", "type": "string", "default": null }, "region": { "description": "AWS Region", "type": "string", "default": null }, "session_token": { "description": "AWS Session token", "type": "string", "default": null }, "endpoint": { "description": "AWS SQS endpoint to connect", "type": "string", "default": "http://127.0.0.1:9324" } } }, "jobs": { "description": "JOBS plugin", "type": "object", "properties": { "num_pollers": { "description": "Number of threads which will try to obtain the job from the priority queue. Default is the number of the logical CPU cores", "type": "integer", "examples": [ 10, 32 ] }, "pipeline_size": { "description": "Size of the internal priority queue, if the internal PQ reach the max number of elements, the Push operation will be blocked", "type": "integer", "default": 1000000 }, "consume": { "description": "list of pipelines to be consumed by the server automatically at the start, keep empty if you want to start consuming manually", "type": "array", "items": { "type": "string" } }, "pool": { "description": "JOBS workers pool", "type": "object", "$ref": "#/definitions/WorkersPool" }, "pipelines": { "description": "List of broker pipelines associated with the drivers. This option is not required since you can declare pipelines in the runtime. Pipeline driver should exist.", "type": "object", "properties": { "driver": { "type": "array", "description": "JOBS plugin driver", "items": { "type": "string", "enum": [ "amqp", "sqs", "beanstalk", "boltdb", "memory", "nats", "kafka" ] } }, "config": { "type": "object", "description": "driver configurations", "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "object", "maxProperties": 1, "oneOf": [ { "properties": { "priority": { "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", "type": "integer", "default": 10 }, "prefetch": { "description": "Number of job to prefetch from the driver", "type": "integer", "default": 100000 }, "consume_all": { "description": "Consume all payloads, even not Job structured", "type": "boolean", "default": false }, "queue": { "type": "string", "description": "Queue name", "default": "default" }, "exchange": { "description": "Exchange name", "type": "string", "default": "amqp.default" }, "exchange_durable": { "description": "Durable exchange (rabbitmq option: https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchanges)", "type": "boolean", "default": false }, "exchange_auto_deleted": { "description": "Auto-delete (exchange is deleted when last queue is unbound from it): https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchanges", "type": "boolean", "default": false }, "queue_auto_deleted": { "description": "Auto-delete (queue that has had at least one consumer is deleted when last consumer unsubscribes)", "type": "boolean", "default": false }, "exchange_type": { "description": "Exchange type", "type": "string", "default": "direct" }, "routing_key": { "description": "Routing key for the queue", "type": "string", "default": null }, "exclusive": { "description": "Declare a queue exclusive at the exchange", "type": "boolean", "default": false }, "multiple_ack": { "description": "When multiple is true, this delivery and all prior unacknowledged deliveries on the same channel will be acknowledged. This is useful for batch processing of deliveries", "type": "boolean", "default": false }, "requeue_on_fail": { "description": "Use rabbitmq mechanism to requeue the job on fail", "type": "boolean", "default": false }, "queue_headers": { "description": "Queue declare args, associated array (hashmap, key:val)", "$ref": "#/definitions/Hashmap", "default": null } } }, { "properties": { "priority": { "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", "type": "integer", "default": 10 }, "topic": { "description": "Topic name: https://kafka.apache.org/intro#intro_concepts_and_terms", "type": "string", "default": null }, "partitions_offsets": { "description": "Offsets for the partitions", "type": "object", "properties": { "itemType": { "$ref": "#/definitions/HashMapInt" }, "itemCount": { "$ref": "#/definitions/Hashmap" } }, "default": null }, "group_id": { "type": "string", "description": "Kafka group id", "default": "default" }, "max_open_requests": { "description": "Max number of outstanding requests a connection is allowed to have before sending on it blocks", "type": "integer", "default": 5 }, "client_id": { "description": "A user provided string sent with every request to the brokers for logging, debugging, and auditing purposes.", "type": "string", "default": "roadrunner" }, "kafka_version": { "description": "Kafka version.", "type": "string", "default": "1.0.0.0" }, "create_topics": { "description": "Create topics configuration. If topic doesn't exist, RR may create a topic with provided configuration", "type": "object", "properties": { "replication_factor": { "description": "Replication factor for the data stored across several Kafka brokers.", "type": "integer", "default": 1 }, "replica_assignment": { "type": "object", "description": "Partition replica assigment.", "default": null }, "config_entries": { "type": "object", "description": "Topic creation options. Note: 'compression:type' will be replaced with 'compression.type', so ':' -> '.'. All options should use ':' as the delimiter.", "default": null } } }, "producer_options": { "description": "Kafka producer options", "type": "object", "properties": { "max_message_bytes": { "type": "integer", "default": 1000000 }, "required_acks": { "description": "The level of acknowledgement reliability needed from the broker.", "type": "integer", "default": -1 }, "timeout": { "description": "The maximum duration in seconds the broker will wait the receipt of the number of required_acks.", "default": 10, "type": "integer" }, "compression_codec": { "type": "string", "default": "none", "enum": [ "none", "gzip", "snappy", "lz4", "zstd" ] }, "compression_level": { "description": "The level of compression to use on messages.", "type": "integer", "default": null }, "idempotent": { "description": "If enabled, the producer will ensure that exactly one copy of each message is written.", "type": "boolean", "default": false } } }, "consumer_options": { "description": "Kafka consumer options", "type": "object", "properties": { "min_fetch_message_size": { "description": "The minimum number of message bytes to fetch in a request", "type": "integer", "default": 1 }, "max_fetch_message_size": { "type": "integer", "description": "The default number of message bytes to fetch from the broker in each request", "default": 1000000 }, "session_timeout": { "type": "integer", "description": "The timeout in seconds used to detect consumer failures when using Kafka's group management facility.", "default": 10 }, "heartbeat_interval": { "type": "integer", "description": "The expected time in seconds between heartbeats to the consumer coordinator when using Kafka's group management facilities", "default": 3 } } } } }, { "properties": { "priority": { "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", "type": "integer", "default": 10 }, "prefetch": { "description": "Number of job to prefetch from the driver until ACK/NACK", "type": "integer", "default": 100000 } } }, { "properties": { "priority": { "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", "type": "integer", "default": 10 }, "prefetch": { "description": "Number of job to prefetch from the driver", "type": "integer", "default": 100000 }, "consume_all": { "description": "Consume all payloads, even not Job structured", "type": "boolean", "default": false }, "tube_priority": { "description": "Beanstalk internal tube priority", "type": "integer", "default": 1 }, "tube": { "description": "Tube name", "type": "string", "default": "default" }, "reserve_timeout": { "description": "If no job is available before this timeout has passed, Reserve returns a ConnError recording ErrTimeout", "$ref": "#/definitions/Duration", "default": "5s" } } }, { "properties": { "priority": { "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", "type": "integer", "default": 10 }, "prefetch": { "description": "Number of job to prefetch from the driver until ACK/NACK", "type": "integer", "default": 100000 }, "skip_queue_declaration": { "description": "Get queue URL instead of declaring it", "type": "boolean", "default": false }, "consume_all": { "description": "Consume all payloads, even not Job structured", "type": "boolean", "default": false }, "visibility_timeout": { "type": "integer", "description": "The duration (in seconds) for which the call waits for a message to arrive in the queue before returning. If a message is available, the call returns sooner than WaitTimeSeconds. If no messages are available and the wait time expires, the call returns successfully with an empty list of messages.", "default": 0 }, "wait_time_seconds": { "description": "The duration (in seconds) for which the call waits for a message to arrive in the queue before returning. If a message is available, the call returns sooner than WaitTimeSeconds. If no messages are available and the wait time expires, the call returns successfully with an empty list of messages", "type": "integer", "default": 0 }, "queue": { "description": "Queue name", "type": "string", "default": "default" }, "attributes": { "title": "map[string]number", "type": "array", "items": { "type": "object", "additionalProperties": { "type": "number" } } }, "tags": { "title": "map[string]string", "type": "array", "items": { "type": "object", "additionalProperties": { "type": "string" } } } } }, { "properties": { "priority": { "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority", "type": "integer", "default": 10 }, "prefetch": { "description": "Number of job to prefetch from the driver", "type": "integer", "default": 100000 }, "consume_all": { "description": "Consume all payloads, even not Job structured", "type": "boolean", "default": false }, "subject": { "description": "NATS subject", "type": "string", "default": "default" }, "stream": { "description": "NATS stream", "type": "string", "default": "default-stream" }, "deliver_new": { "description": "The consumer will only start receiving messages that were created after the consumer was created", "type": "string", "default": "default-stream" }, "rate_limit": { "description": "Consumer rate-limiter in bytes https://docs.nats.io/jetstream/concepts/consumers#ratelimit", "type": "integer", "default": 1000 }, "delete_stream_on_stop": { "description": "Delete the stream when after pipeline was stopped", "type": "boolean", "default": false }, "delete_after_ack": { "description": "Delete message from the stream after successful acknowledge", "type": "boolean", "default": false } } } ] } } } } } } }, "tcp": { "type": "object", "description": "Plugin to handle RAW TCP packets, available since RR 2.6.0", "properties": { "servers": { "description": "TCP servers to allocate", "type": "object", "minProperties": 1, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "allOf": [ { "description": "User defined TCP servers", "type": "object", "$ref": "#/definitions/TCPServers" } ] } } }, "pool": { "type": "object", "description": "PHP static workers pool", "$ref": "#/definitions/WorkersPool" } } }, "grpc": { "description": "GRPC plugin", "type": "object", "properties": { "listen": { "description": "GRPC address to listen", "type": "string", "$ref": "#/definitions/HostAndPortWithTCP" }, "proto": { "type": "array", "description": "Proto file to use, multiply files supported [SINCE 2.6]", "items": { "type": "string" } }, "tls": { "description": "GRPC TLS configuration", "type": "object", "properties": { "key": { "description": "Path to the key file", "type": "string", "default": null }, "cert": { "description": "Path to the certificate", "type": "string", "default": null }, "root_ca": { "description": "Path to the CA certificate", "type": "string", "default": null }, "client_auth_type": { "description": "Client auth type", "type": "string", "default": "no_client_certs", "enum": [ "request_client_cert", "require_any_client_cert", "verify_client_cert_if_given", "no_client_certs", "require_and_verify_client_cert" ] } } }, "max_send_msg_size": { "type": "integer", "description": "Maximum send message size", "default": 50 }, "max_recv_msg_size": { "description": "Maximum receive message size", "default": 50, "type": "integer" }, "max_connection_idle": { "description": " MaxConnectionIdle is a duration for the amount of time after which an idle connection would be closed by sending a GoAway. Idleness duration is defined since the most recent time the number of outstanding RPCs became zero or the connection establishment", "$ref": "#/definitions/Duration" }, "max_connection_age": { "description": "MaxConnectionAge is a duration for the maximum amount of time a connection may exist before it will be closed by sending a GoAway. A random jitter of +/-10% will be added to MaxConnectionAge to spread out connection storms", "$ref": "#/definitions/Duration" }, "max_connection_age_grace": { "description": "MaxConnectionAgeGrace is an additive period after MaxConnectionAge after which the connection will be forcibly closed", "$ref": "#/definitions/Duration" }, "max_concurrent_streams": { "description": "MaxConnectionAgeGrace is an additive period after MaxConnectionAge after which the connection will be forcibly closed", "type": "integer", "default": 10 }, "ping_time": { "description": "After a duration of this time if the server doesn't see any activity it pings the client to see if the transport is still alive. If set below 1s, a minimum value of 1s will be used instead", "$ref": "#/definitions/Duration" }, "timeout": { "description": "After having pinged for keepalive check, the server waits for a duration of Timeout and if no activity is seen even after that the connection is closed", "$ref": "#/definitions/Duration" }, "pool": { "description": "GRPC workers pool", "type": "object", "$ref": "#/definitions/WorkersPool" } } }, "fileserver": { "description": "[SINCE 2.6] File server to serve static files", "type": "object", "properties": { "address": {}, "calculate_etag": {}, "weak": {}, "stream_request_body": {}, "serve": { "type": "array", "minItems": 1, "items": { "type": "object", "properties": { "prefix": { "description": "HTTP prefix", "type": "string", "examples": [ "/foo", "/bar/baz" ] }, "root": { "description": "Directory to serve", "default": ".", "type": "string" }, "compress": { "description": "When set to true, the server tries minimizing CPU usage by caching compressed files", "type": "boolean", "default": false }, "cache_duration": { "description": "Expiration duration for inactive file handlers. Units: seconds. Use negative number to disable", "type": "integer", "default": 10 }, "max_age": { "description": "The value for the Cache-Control HTTP-header. Units: seconds", "type": "integer", "default": 10 }, "bytes_range": { "description": "Enable range requests: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests", "type": "boolean", "default": false } }, "required": [ "prefix" ] } } } } }, "required": [ "version" ], "definitions": { "BoltDB": { "description": "BoltDB config section", "type": "object", "properties": { "file": { "description": "file name for the db", "type": "string", "default": "rr.db" }, "permission": { "description": "Access permission for the DB file.", "type": "integer", "default": "0777" }, "interval": { "description": "TTL keys check interval in seconds. It's safe to use 1 second here, but can be a little costly to performance", "type": "integer", "default": 60 } } }, "Memcached": { "description": "In-memory config section", "type": "object", "properties": { "addr": { "description": "Address of the memcached node", "type": "string", "default": "localhost:11211" } } }, "Redis": { "description": "Redis config section", "type": "object", "properties": { "addrs": { "description": "Redis server addresses", "type": "array", "default": "localhost:6379" }, "master_name": { "type": "string", "default": null }, "username": { "type": "string", "default": null }, "password": { "type": "string", "default": null }, "db": { "description": "Redis db number", "type": "integer", "default": 0, "maximum": 10 }, "sentinel_password": { "type": "string", "default": null }, "route_by_latency": { "type": "boolean", "default": false }, "route_randomly": { "type": "boolean", "default": false }, "dial_timeout": { "description": "dial timeout", "$ref": "#/definitions/Duration" }, "max_retries": { "type": "integer", "default": 1 }, "min_retry_backoff": { "$ref": "#/definitions/Duration", "default": 0 }, "max_retry_backoff": { "$ref": "#/definitions/Duration", "default": 0 }, "pool_size": { "type": "integer", "default": 0 }, "min_idle_conns": { "type": "integer", "default": 0 }, "max_conn_age": { "$ref": "#/definitions/Duration" }, "read_timeout": { "$ref": "#/definitions/Duration" }, "write_timeout": { "$ref": "#/definitions/Duration" }, "pool_timeout": { "$ref": "#/definitions/Duration" }, "idle_timeout": { "$ref": "#/definitions/Duration" }, "idle_check_freq": { "$ref": "#/definitions/Duration" }, "read_only": { "type": "boolean", "default": false } } }, "Memory": { "description": "In-memory config section", "type": "object", "properties": { "interval": { "description": "TTL keys check interval in seconds. It's safe to use 1 second here, but can be a little costly to performance", "type": "integer", "default": 60 } } }, "Service": { "type": "object", "description": "User defined service", "properties": { "command": { "description": "Command to execute. Can be any command here which can be executed.", "type": "string" }, "env": { "description": "Environment variables for the process", "type": "object", "$ref": "#/definitions/Hashmap" }, "process_num": { "description": "Number of copies (processes) to start per command", "type": "integer", "default": 1 }, "exec_timeout": { "description": "Allowed time before stop", "type": "string", "$ref": "#/definitions/Duration" }, "remain_after_exit": { "description": "Remain process after exit. In other words, restart process after exit with any exit code", "type": "boolean", "default": false }, "restart_sec": { "description": "Number of seconds to wait before process restart", "type": "integer", "default": 30 } }, "required": [ "command" ] }, "WorkersPool": { "description": "Static pool with PHP workers", "type": "object", "properties": { "debug": { "description": "Pool debug mode. Worker will be created right before RR passes request to it", "type": "boolean", "default": false }, "command": { "type": "string", "default": null, "description": "Command to use for the pool. Will override the server's command" }, "num_workers": { "description": "How many worker processes will be started. Zero (or nothing) means the number of logical CPUs", "type": "integer", "minimum": 0, "default": 0 }, "max_jobs": { "description": "Maximal count of worker executions. Zero (or nothing) means no limit", "type": "integer", "minimum": 0, "default": 0 }, "allocate_timeout": { "description": "Timeout for worker allocation. Zero means the default limit - 60s", "$ref": "#/definitions/Duration", "default": "60s" }, "reset_timeout": { "description": "Timeout for the pool.Reset operation (./rr reset). Zero means the default limit - 60s", "$ref": "#/definitions/Duration", "default": "60s" }, "destroy_timeout": { "description": "Timeout for worker destroying before process killing. Zero means the default limit - 60s", "$ref": "#/definitions/Duration", "default": "60s" }, "supervisor": { "description": "Supervisor is used to control http workers", "type": "object", "properties": { "watch_tick": { "description": "How often to check the state of the workers", "$ref": "#/definitions/Duration", "default": "1s" }, "ttl": { "description": "Maximum time worker is allowed to live (soft limit). Zero means no limit", "$ref": "#/definitions/Duration", "default": "0s" }, "idle_ttl": { "description": "How long worker can spend in IDLE mode after first using (soft limit). Zero means no limit", "$ref": "#/definitions/Duration", "default": "0s" }, "max_worker_memory": { "description": "Maximal worker memory usage in megabytes (soft limit). Zero means no limit", "type": "integer", "minimum": 0, "default": 0 }, "exec_ttl": { "description": "Maximal job lifetime (hard limit). Zero means no limit", "$ref": "#/definitions/Duration", "default": "0s" } } } } }, "TCPServers": { "description": "TCP server", "type": "object", "properties": { "addr": { "description": "Address to listen", "type": "string", "pattern": "^[0-9a-zA-Z_.-]+:[0-9]{1,5}$" }, "delimiter": { "description": "Data packets delimiter. Every send should end either with EOF or with the delimiter", "type": "string", "default": "\r\n" }, "read_buf_size": { "description": "Chunks that RR uses to read the data. In MB. If you expect big payloads on a TCP server, to reduce `read` syscalls, would be a good practice to use a fairly big enough buffer", "type": "integer", "minimum": 1, "maximum": 100, "default": 1 } }, "required": [ "addr" ] }, "Duration": { "description": "Time duration", "type": "string", "pattern": "^([0-9]*(\\.[0-9]*)?(ms|h|m|s))+$", "examples": [ "1h", "2.5h", "2m", ".2m", "30s", "30.03s", "300ms", "1h3m40s500ms" ] }, "HostAndPortWithTCP": { "description": "Host and port with tcp:// prefix", "type": "string", "pattern": "^(((tcp://[0-9a-zA-Z_.-]+|)|\\$\\{([^}]+)\\}):([0-9]{1,5}||\\$\\{([^}]+)\\}))|\\$\\{([^}]+)\\}$", "examples": [ "tcp://127.0.0.1:443", "${TCP:-tcp://127.0.0.1:443}", "tcp://127.0.0.1:${TCP_PORT}" ] }, "HostAndPort": { "description": "Host and port", "type": "string", "pattern": "^((([0-9a-zA-Z_.-]+|)|\\$\\{([^}]+)\\}):([0-9]{1,5})|\\$\\{([^}]+)\\})|\\$\\{([^}]+)\\}$", "examples": [ "127.0.0.1:443", ":8080", "0.0.0.0:${HTTP_PORT:-8080}", "${HTTP_HOST:-127.0.0.1:8000}" ] }, "LogMode": { "description": "Logging mode", "type": "string", "enum": [ "development", "production", "raw" ] }, "LogLevel": { "description": "Logging level", "type": "string", "enum": [ "debug", "info", "warn", "error", "panic" ] }, "LogEncoding": { "description": "Encoding format", "type": "string", "enum": [ "console", "json" ] }, "LogOutput": { "type": "string", "examples": [ "stdout", "stderr", "/var/log/rr_errors.log" ] }, "Hashmap": { "description": "Hashmap", "type": "object", "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string", "examples": [ "Any header value" ] } }, "additionalProperties": false }, "Bucket": { "description": "Hashmap with floats", "type": "object", "patternProperties": { "[+-]?([0-9]*[.])?[0-9]+": { "type": "number", "examples": [ 1.1 ] } }, "additionalProperties": false }, "HashMapInt": { "type": "object", "additionalProperties": { "type": "integer" } }, "NATS_J": { "description": "NATS jobs driver", "type": "object", "properties": { "addr": { "description": "NATS server address", "type": "string", "default": "demo.nats.io" } } }, "KAFKA_J": { "description": "Kafka jobs driver", "type": "object", "properties": { "addr": { "description": "Kafka server addresses", "type": "array" } } }, "BoltDB_J": { "description": "Boltdb jobs driver", "type": "object", "properties": { "permissions": { "type": "integer", "default": "0777" } } } } } ================================================ FILE: schemas/config/3.0.schema.json ================================================ { "$id": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "title": "RoadRunner Main Configuration File", "description": "This is your main RoadRunner configuration file. It should contain all the plugins you want to load and their configuration.", "type": "object", "required": [ "version" ], "properties": { "version": { "description": "RoadRunner configuration file version.", "type": "string", "default": "3", "enum": [ "3" ] }, "amqp": { "$id": "https://raw.githubusercontent.com/roadrunner-server/amqp/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "pipeline": { "type": "object", "required": [ "driver" ], "additionalProperties": false, "properties": { "driver": { "type": "string", "enum": [ "amqp" ] }, "config": { "type": "object", "additionalProperties": false, "description": "Configuration options for the AMQP pipeline.", "properties": { "priority": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/priority" }, "prefetch": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/prefetch" }, "delete_queue_on_stop": { "type": "boolean", "default": false, "description": "Whether to delete the queue when stopping the pipeline." }, "queue": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/queue" }, "exchange": { "description": "The name of the exchange.", "type": "string", "default": "amqp.default" }, "redial_timeout": { "description": "Redial timeout (in seconds). How long to try to reconnect to the AMQP server. Default or zero means 60.", "type": "integer", "default": 60 }, "exchange_durable": { "description": "Whether the exchange is durable. See https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchanges", "type": "boolean", "default": false }, "durable": { "type": "boolean", "default": false, "description": "Whether the queue is durable." }, "consumer_id": { "description": "The unique identifier for the consumer amongst all consumers on this channel.", "type": "string", "default": "roadrunner-" }, "exchange_auto_delete": { "description": "Whether to auto-delete the exchange when the last queue is unbound from it. See https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchanges", "type": "boolean", "default": false }, "queue_auto_delete": { "description": "Whether to auto-delete queues that have had at least one consumer when the last consumer unsubscribes.", "type": "boolean", "default": false }, "exchange_type": { "description": "The type of exchange.", "type": "string", "default": "direct" }, "routing_key": { "description": "Routing key for the queue.", "type": "string" }, "exclusive": { "description": "Declare a queue exclusive at the exchange.", "type": "boolean", "default": false }, "multiple_ack": { "description": "When multiple ACK is enabled, this delivery and all prior unacknowledged deliveries on the same channel will be acknowledged. This is useful for batch processing of deliveries.", "type": "boolean", "default": false }, "requeue_on_fail": { "description": "Whether to use RabbitMQ mechanisms to requeue the job on failure.", "type": "boolean", "default": false }, "queue_headers": { "description": "Queue declare args.", "type": "object", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string", "minLength": 1 } } } } } } }, "driver": { "type": "object", "additionalProperties": false, "description": "Configuration options for the AMQP driver.", "properties": { "addr": { "title": "AMQP Server URI", "description": "AMQP URI to connect to the rabbitmq server. See https://www.rabbitmq.com/uri-spec.html", "type": "string", "default": "amqp://guest:guest@127.0.0.1:5672", "minLength": 1 }, "tls": { "title": "TLS Configuration", "type": "object", "description": "TLS configuration options for AMQP.", "properties": { "key": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/key" }, "cert": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/cert" }, "root_ca": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/root_ca" }, "client_auth_type": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/ClientAuthType" } }, "required": [ "key", "cert" ] } } } }, "allOf": [ { "$ref": "https://raw.githubusercontent.com/roadrunner-server/amqp/refs/heads/master/schema.json#/definitions/driver" } ] }, "beanstalk": { "$id": "https://raw.githubusercontent.com/roadrunner-server/beanstalk/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "pipeline": { "type": "object", "required": [ "driver" ], "additionalProperties": false, "properties": { "driver": { "type": "string", "enum": [ "beanstalk" ] }, "config": { "type": "object", "description": "Configuration options for the Beanstalk pipeline.", "additionalProperties": false, "properties": { "priority": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/priority" }, "prefetch": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/prefetch" }, "tube_priority": { "description": "Beanstalk internal tube priority", "type": "integer", "default": 1 }, "tube": { "description": "Tube name", "type": "string", "default": "default" }, "reserve_timeout": { "description": "If no job is available before this timeout has passed, Reserve returns a ConnError recording ErrTimeout", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration", "default": "5s" } } } } }, "driver": { "type": "object", "additionalProperties": false, "description": "Configuration options for the Beanstalk driver.", "properties": { "addr": { "title": "Beanstalk Server Address", "description": "The address of the Beanstalk server.", "type": "string", "default": "tcp://127.0.0.1:11300" }, "timeout": { "title": "Connection Timeout", "description": "Connection timeout for the Beanstalk server.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration", "default": "30s" } } } }, "allOf": [ { "$ref": "https://raw.githubusercontent.com/roadrunner-server/beanstalk/refs/heads/master/schema.json#/definitions/driver" } ] }, "boltdb": { "$id": "https://raw.githubusercontent.com/roadrunner-server/boltdb/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "permissions": { "description": "Permissions for the BoltDB database file, if created by RR.", "type": "integer", "default": 755, "minimum": 0, "maximum": 777 }, "file": { "description": "BoltDB database file to create or use.", "type": "string", "default": "rr.db" }, "pipeline": { "type": "object", "required": [ "driver" ], "additionalProperties": false, "properties": { "driver": { "type": "string", "enum": [ "boltdb" ] }, "config": { "type": "object", "description": "Configuration for the BoltDB pipeline.", "additionalProperties": false, "properties": { "priority": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/priority" }, "prefetch": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/prefetch" }, "permissions": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/boltdb/refs/heads/master/schema.json#/definitions/permissions" }, "file": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/boltdb/refs/heads/master/schema.json#/definitions/file" } } } } }, "driver": { "type": "object", "additionalProperties": false, "description": "Configuration options for the BoltDB driver.", "properties": { "file": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/boltdb/refs/heads/master/schema.json#/definitions/file" }, "permissions": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/boltdb/refs/heads/master/schema.json#/definitions/permissions" }, "interval": { "description": "TTL keys check interval in seconds. It's safe to use 1 second here, but can be a little costly to performance. If you set this to zero, 60 will be used.", "type": "integer", "minimum": 1, "default": 60 } } } }, "allOf": [ { "$ref": "https://raw.githubusercontent.com/roadrunner-server/boltdb/refs/heads/master/schema.json#/definitions/driver" } ] }, "centrifuge": { "$id": "https://raw.githubusercontent.com/roadrunner-server/centrifuge/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Centrifugo plugin for RoadRunner.", "type": "object", "title": "roadrunner-centrifuge", "additionalProperties": false, "properties": { "proxy_address": { "description": "The address of the Centrifugo proxy server.", "type": "string", "default": "tcp://127.0.0.1:30000", "minLength": 1 }, "grpc_api_address": { "description": "The address/port of the gRPC server API.", "type": "string", "default": "tcp://127.0.0.1:10000", "minLength": 1 }, "use_compressor": { "description": "Whether to use gRPC gzip compressor.", "type": "boolean", "default": false }, "version": { "description": "Your application version.", "type": "string", "default": "v1.0.0", "minLength": 1 }, "name": { "description": "Your application name.", "type": "string", "default": "roadrunner", "minLength": 1 }, "pool": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/pool/refs/heads/master/schema.json" }, "tls": { "description": "TLS settings", "type": "object", "additionalProperties": false, "properties": { "cert": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/cert" }, "key": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/key" } }, "required": [ "cert", "key" ] } } }, "fileserver": { "$id": "https://raw.githubusercontent.com/roadrunner-server/fileserver/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the FileServer plugin for RoadRunner.", "type": "object", "additionalProperties": false, "title": "roadrunner-fileserver", "required": [ "address", "serve" ], "properties": { "address": { "description": "The address to listen on.", "type": "string", "minLength": 1, "examples": [ "127.0.0.1:10101" ] }, "calculate_etag": { "description": "Whether to calculate ETag for the file and add the ETag header. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag", "type": "boolean", "default": false }, "weak": { "description": "Whether to use only the filename when calculating the ETag value. If `false`, the entire file content is used.", "default": false }, "stream_request_body": { "type": "boolean", "description": "Whether to stream files larger than 4KB.", "default": false }, "serve": { "description": "The URL prefixes to serve as static files. At least one entry is required.", "type": "array", "minItems": 1, "items": { "type": "object", "additionalProperties": false, "properties": { "prefix": { "description": "Files matching this prefix will be served with this configuration. Prefixes must begin with a forward slash.", "type": "string", "minLength": 1, "examples": [ "/img", "/assets" ] }, "root": { "description": "Directory to serve these files from. This can be an absolute path or a path relative to the RR application directory. Defaults to the root of the RR application directory.", "default": ".", "type": "string", "examples": [ "./public", "/var/www/html/public" ] }, "compress": { "description": "When set to true, the server attempts to minimize CPU usage by caching compressed files.", "type": "boolean", "default": false }, "cache_duration": { "description": "Expiration duration for inactive file handlers, given in seconds. Use any negative number (e.g. `-1`) to disable.", "type": "integer", "default": 10 }, "max_age": { "description": "The value for the Cache-Control HTTP-header, given in seconds. The header is not sent if this value is zero or undefined.", "type": "integer", "default": 0 }, "bytes_range": { "description": "Enable range requests. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests", "type": "boolean", "default": false } }, "required": [ "prefix" ] } } } }, "grpc": { "$id": "https://raw.githubusercontent.com/roadrunner-server/grpc/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the gRPC plugin for RoadRunner.", "type": "object", "title": "roadrunner-grpc", "additionalProperties": false, "required": [ "proto", "listen" ], "properties": { "listen": { "description": "gRPC address to listen on. Supports both TCP and Unix sockets.", "type": "string", "minLength": 1, "examples": [ "tcp://127.0.0.1:443", "${TCP:-tcp://127.0.0.1:443}", "tcp://127.0.0.1:${TCP_PORT}" ] }, "proto": { "type": "array", "minItems": 1, "description": "Proto file(s) to use. Multiple files are supported. Wildcards are allowed in the proto field.", "items": { "type": "string", "minLength": 1, "examples": [ "*.proto", "first.proto", "second.proto" ] } }, "tls": { "description": "GRPC TLS configuration", "type": "object", "additionalProperties": false, "properties": { "key": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/key" }, "cert": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/cert" }, "root_ca": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/root_ca" }, "client_auth_type": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/ClientAuthType" } }, "required": [ "key", "cert" ] }, "max_send_msg_size": { "type": "integer", "description": "Maximum send message size in MB.", "default": 50 }, "max_recv_msg_size": { "type": "integer", "description": "Maximum receive message size in MB.", "default": 50 }, "max_connection_idle": { "description": " MaxConnectionIdle is a duration for the amount of time after which an idle connection would be closed by sending a GoAway. Idle duration is defined by the most recent time the number of outstanding RPCs became zero or since the connection was established. Defaults to infinite.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/grpc/refs/heads/master/schema.json#/$defs/duration" }, "max_connection_age": { "description": "The maximum duration a connection may exist before it will be closed by sending a GoAway. A random jitter of +/-10% will be added to MaxConnectionAge to spread out connection storms. Defaults to infinite.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/grpc/refs/heads/master/schema.json#/$defs/duration" }, "max_connection_age_grace": { "description": "The duration after MaxConnectionAge after which the connection will be forcibly closed. Defaults to infinite.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/grpc/refs/heads/master/schema.json#/$defs/duration" }, "max_concurrent_streams": { "description": "The maximum number of concurrent streams. Empty or 0 defaults to 10.", "type": "integer", "default": 10 }, "ping_time": { "description": "Duration of no activity after which the server pings the client to see if the transport is still alive. If set below 1s, a minimum value of 1s will be used instead.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/grpc/refs/heads/master/schema.json#/$defs/duration", "default": "2h" }, "timeout": { "description": "The duration to wait for a response to a keepalive check, after which the connection is closed.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/grpc/refs/heads/master/schema.json#/$defs/duration", "default": "20s" }, "pool": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/pool/refs/heads/master/schema.json" } }, "$defs": { "duration": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration" } } }, "http": { "$id": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the http plugin for RoadRunner.", "title": "roadrunner-http", "type": "object", "additionalProperties": false, "properties": { "address": { "description": "Host and/or port to listen on for HTTP traffic. If omitted, RoadRunner will not listen for HTTP requests.", "type": "string", "minLength": 1, "examples": [ "127.0.0.1:8080", ":8080" ] }, "internal_error_code": { "description": "HTTP status code to use for internal RoadRunner errors. Defaults to 500 if omitted.", "type": "integer", "default": 500, "minimum": 100, "maximum": 599 }, "max_request_size": { "description": "Maximum request size in MB. Defaults to 1 GB if zero or omitted.", "type": "integer", "minimum": 0, "default": 1000 }, "raw_body": { "description": "Whether to send the raw, encoded body for `application/x-www-form-urlencoded` content. Defaults to sending decoded content to PHP workers.", "type": "boolean", "default": false }, "access_logs": { "description": "Whether to enable HTTP access logs.", "type": "boolean", "default": false }, "middleware": { "description": "List of middleware to load for the HTTP plugin, executed in the specified order.", "type": "array", "minItems": 1, "items": { "type": "string", "enum": [ "headers", "gzip", "static", "sendfile", "http_metrics", "cache", "proxy_ip_parser", "otel" ] } }, "trusted_subnets": { "description": "List of subnets from which incoming requests are allowed. Defaults to typical private network ranges (192.168.*, 10.0.*, and 172.16.*) and local/loopback interfaces (127.*).", "type": "array", "items": { "type": "string", "examples": [ "10.0.0.0/8", "127.0.0.0/8" ] }, "default": [ "10.0.0.0/8", "127.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7", "fe80::/10" ] }, "uploads": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/Uploads" }, "headers": { "description": "HTTP header configuration.", "type": "object", "additionalProperties": false, "properties": { "cors": { "description": "Controls which CORS headers are returned. Additional headers `Vary: Origin`, `Vary: Access-Control-Request-Method` and `Vary: Access-Control-Request-Headers` will be added to responses. Omit this section to disable CORS headers.", "type": "object", "properties": { "allowed_origin": { "description": "Controls the value of 'Access-Control-Allow-Origin'.", "type": "string", "examples": [ "*" ] }, "allowed_origin_regex": { "description": "Controls the value of 'Access-Control-Allow-Origin' header value, but evaluated as regex.", "type": "string", "examples": [ "^https://foo" ] }, "allowed_headers": { "description": "Controls the value of 'Access-Control-Allow-Headers'.", "type": "string", "examples": [ "*" ] }, "allowed_methods": { "description": "Controls the value of 'Access-Control-Allow-Methods'. Provide a comma-separated string of HTTP verbs.", "type": "string", "examples": [ "GET,POST,PUT,DELETE" ] }, "allow_credentials": { "description": "Controls the value of 'Access-Control-Allow-Credentials'.", "type": "boolean", "default": false }, "exposed_headers": { "description": "Controls the value of 'Access-Control-Expose-Headers'. Provide a comma-separated list of HTTP headers.", "type": "string", "examples": [ "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma" ] }, "max_age": { "description": "Controls the value of 'Access-Control-Max-Age' (in seconds).", "type": "integer", "examples": [ 600 ], "default": 0 } } }, "request": { "description": "Custom HTTP headers to add to every request passed to PHP.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/Headers" }, "response": { "description": "Custom HTTP headers to add to every response from PHP.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/Headers" } } }, "static": { "description": "Configuration options for serving static files.", "type": "object", "additionalProperties": false, "properties": { "dir": { "description": "Path to the directory with static assets. Defaults to the current working directory. Empty/undefined and `.` are equal and are both treated as current directory.", "type": "string", "examples": [ ".", "/var/www/html" ] }, "forbid": { "description": "File extensions of files that must not be served. Empty/undefined disallows no files. If files are specified in both `forbid` and `allow`, they will be disallowed. Defaults to an empty array, disallowing no files.", "type": "array", "items": { "type": "string", "minLength": 1, "examples": [ ".php", ".htaccess", ".sh" ] } }, "allow": { "description": "File extensions of files that may be served. Empty/undefined allows all files, except files specified in `forbid`.", "type": "array", "items": { "type": "string", "minLength": 1, "examples": [ ".jpg", ".png", ".css", ".js" ] } }, "calculate_etag": { "description": "Whether to enable ETag computation for static files.", "type": "boolean", "default": false }, "weak": { "description": "Whether to use a weak generator (/W), which uses only the filename to generate a CRC32 sum for et ETag. Disable to use the file contents.", "type": "boolean", "default": false }, "request": { "description": "Custom HTTP headers to add to every request for static files.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/Headers" }, "response": { "description": "Custom HTTP headers to add to every response from static files.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/Headers" } } }, "pool": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/pool/refs/heads/master/schema.json" }, "ssl": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL" }, "fcgi": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/FCGI" }, "http2": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/HTTP2" }, "http3": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/HTTP3" } }, "$defs": { "Uploads": { "type": "object", "additionalProperties": false, "description": "File upload configuration.", "properties": { "dir": { "description": "Directory for file uploads. Empty/undefined value means the OS default temporary directory ($TEMP) will be used, i.e. `/tmp`.", "type": "string", "examples": [ "/tmp" ] }, "forbid": { "description": "Disallow upload of files with the provided extensions.", "type": "array", "items": { "type": "string", "minLength": 1, "examples": [ ".php", ".sh", ".go" ] } }, "allow": { "description": "Allow only upload of files with the provided extensions. Empty/undefined value means all files except explicitly disallowed (`forbid`) files are allowed.", "type": "array", "items": { "type": "string", "minLength": 1, "examples": [ ".html", ".go" ] }, "default": [] } } }, "SSL": { "title": "SSL/TLS (HTTPS) Configuration", "description": "Settings required to set up manual or automatic HTTPS for your server. Either `key` and `cert` *or* `acme` is required, but not both.", "type": "object", "additionalProperties": false, "dependentRequired": { "key": [ "cert" ], "cert": [ "key" ] }, "properties": { "address": { "description": "Host address/or port to bind to. Defaults to 127.0.0.1:443.", "type": "string", "default": "127.0.0.1:443", "examples": [ "127.0.0.1:443", ":8443" ] }, "acme": { "description": "ACME certificates provider (Let's encrypt). Do not provide this parameter if you use `key` and `cert`.", "type": "object", "additionalProperties": false, "properties": { "cache_dir": { "description": "Directory to use for certificates, private keys, Let's Encrypt configuration etc.", "type": "string", "default": "rr_cache_dir" }, "email": { "description": "User email used to create a Let's Encrypt account. This is required.", "type": "string", "examples": [ "user@domain.com" ] }, "alt_http_port": { "description": "Alternate port for the HTTP challenge. Challenge traffic should be redirected to this port if overridden. See https://letsencrypt.org/docs/challenge-types/#http-01-challenge", "type": "integer", "default": 80 }, "alt_tlsalpn_port": { "description": "Alternate port for the TLS-ALPN-01 challenge. Challenge traffic should be redirected to this port if overridden. See https://letsencrypt.org/docs/challenge-types/#tls-alpn-01", "type": "integer", "default": 443 }, "challenge_type": { "type": "string", "enum": [ "http-01", "tlsalpn-01" ], "description": "Challenge types", "default": "http-01" }, "use_production_endpoint": { "description": "Whether to use the production endpoint. We recommend you use the staging endpoint to make sure everything works correctly before you deploy your certificate.", "type": "boolean", "default": false }, "domains": { "type": "array", "minItems": 1, "items": { "type": "string", "examples": [ "example.com" ] }, "description": "List of domains to obtain certificates for. At least one domain is required." } }, "required": [ "domains", "email" ] }, "redirect": { "description": "Whether to automatically redirect from HTTP to HTTPS.", "type": "boolean", "default": false }, "key": { "description": "Path to the private key for the certificate. Must not be provided if `acme` is set.", "type": "string", "minLength": 1, "examples": [ "/ssl/server/key.pem" ] }, "cert": { "description": "Path to the public certificate file. Must not be provided if `acme` is set.", "type": "string", "minLength": 1, "examples": [ "/ssl/server/cert.crt" ] }, "root_ca": { "description": "Path to the CA certificate, if required. Always required for mTLS. Omit this option if unused. Must not be provided if `acme` is set.", "type": "string", "minLength": 1, "examples": [ "/ssl/server/ca.crt" ] }, "client_auth_type": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/ClientAuthType" } } }, "ClientAuthType": { "description": "Authorization method for mTLS.", "type": "string", "default": "no_client_certs", "enum": [ "request_client_cert", "require_any_client_cert", "verify_client_cert_if_given", "no_client_certs", "require_and_verify_client_cert" ] }, "FCGI": { "description": "Enables FastCGI support. If omitted, RoadRunner will not listen for FCGI requests.", "type": "object", "additionalProperties": false, "properties": { "address": { "description": "Host and/or port to listen on for FCGI requests.", "type": "string", "minLength": 1, "examples": [ "0.0.0.0:9000", "127.0.0.1:9000", "localhost:9000", "unix:/path/to/socket.sock" ] } }, "required": [ "address" ] }, "HTTP2": { "description": "HTTP/2 settings.", "type": "object", "additionalProperties": false, "properties": { "h2c": { "description": "Use HTTP/2 over non-encrypted TCP connection using H2C", "type": "boolean", "default": false }, "max_concurrent_streams": { "description": "Maximum number of concurrent streams. Defaults to 128 if omitted or zero.", "type": "integer", "default": 128, "minimum": 0 } } }, "HTTP3": { "description": "HTTP/3 settings. **Experimental**: Requires that RoadRunner has experimental features enabled. Unless you configured `acme`, you must provide a `key` and `cert` here.", "type": "object", "additionalProperties": false, "required": [ "address" ], "dependentRequired": { "cert": [ "key" ], "key": [ "cert" ] }, "properties": { "address": { "description": "Host and/or port to listen on for HTTP/3.", "type": "string", "minLength": 1, "examples": [ "127.0.0.1:8080", ":8080" ] }, "cert": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/cert" }, "key": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/key" } } }, "Headers": { "type": "object", "minProperties": 1, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string", "minLength": 1 } }, "additionalProperties": false } } }, "jobs": { "$id": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the jobs plugin for RoadRunner.", "title": "roadrunner-jobs", "type": "object", "additionalProperties": false, "properties": { "num_pollers": { "description": "Number of threads which will try to obtain jobs from the priority queue. Default is the number of workers in the pool +1. **Deprecated:** This will be removed in a future version.", "type": "integer", "minimum": 1, "examples": [ 10, 32 ], "deprecated": true }, "timeout": { "description": "Request timeout (in seconds) when attempting to send jobs to the queue. If zero or omitted, this defaults to 60 seconds.", "type": "integer", "default": 60 }, "pipeline_size": { "description": "Size of the internal priority queue. If the internal priority queue is full, you cannot send (push) additional jobs to the queue. If you set this value to zero or omit it, it defaults to 1 million.", "type": "integer", "default": 1000000, "minimum": 0 }, "consume": { "description": "A list of pipelines to be consumed by the server automatically when starting. You can omit this list if you want to start consuming manually. Each item in this list must be defined as a key under `pipelines`.", "type": "array", "items": { "type": "string", "pattern": "^[a-zA-Z0-9._-]+$" } }, "pool": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/pool/refs/heads/master/schema.json" }, "pipelines": { "description": "List of broker pipelines associated with the configured drivers. This option is not required since you can declare pipelines at runtime. The selected pipeline `driver` must be configured in the root of your configuration file.", "type": "object", "patternProperties": { "additionalProperties": false, "^[a-zA-Z0-9._-]+$": { "oneOf": [ { "$ref": "https://raw.githubusercontent.com/roadrunner-server/amqp/refs/heads/master/schema.json#/definitions/pipeline" }, { "$ref": "https://raw.githubusercontent.com/roadrunner-server/beanstalk/refs/heads/master/schema.json#/definitions/pipeline" }, { "$ref": "https://raw.githubusercontent.com/roadrunner-server/boltdb/refs/heads/master/schema.json#/definitions/pipeline" }, { "$ref": "https://raw.githubusercontent.com/roadrunner-server/kafka/refs/heads/master/schema.json#/definitions/pipeline" }, { "$ref": "https://raw.githubusercontent.com/roadrunner-server/memory/refs/heads/master/schema.json#/definitions/pipeline" }, { "$ref": "https://raw.githubusercontent.com/roadrunner-server/nats/refs/heads/master/schema.json#/definitions/pipeline" }, { "$ref": "https://raw.githubusercontent.com/roadrunner-server/sqs/refs/heads/master/schema.json#/definitions/pipeline" } ] } } } }, "definitions": { "PipelineProperties": { "description": "Common configuration options for queues/pipelines.", "priority": { "description": "Pipeline priority. If the job pushed to the pipeline has priority set to 0, it will inherit the pipeline's priority.", "type": "integer", "minimum": 0, "default": 10 }, "prefetch": { "description": "Number of jobs to prefetch from the driver. Please consult the driver for the maximum and default values. For instance, SQS allows only 10.", "type": "integer", "minimum": 0 }, "queue": { "type": "string", "description": "The name of the queue.", "default": "default" } } } }, "kafka": { "$id": "https://raw.githubusercontent.com/roadrunner-server/kafka/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "pipeline": { "type": "object", "required": [ "driver" ], "additionalProperties": false, "properties": { "driver": { "type": "string", "enum": [ "kafka" ] }, "config": { "type": "object", "description": "Configuration for the Kafka pipeline.", "additionalProperties": false, "properties": { "priority": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/priority" }, "auto_create_topics_enable": { "description": "Auto create topic for the consumer/producer", "type": "boolean", "default": false }, "producer_options": { "description": "Kafka producer options.", "type": "object", "additionalProperties": false, "properties": { "disable_idempotent": { "description": "Disables idempotent produce requests, opting out of Kafka server-side deduplication in the face of reissued requests due to transient network problems. Idempotent production is strictly a win, but does require the IDEMPOTENT_WRITE permission on CLUSTER (pre Kafka 3.0), and not all clients can have that permission.", "type": "boolean", "default": false }, "partitioning_strategy": { "description": "The partitioning strategy to use for produced records.", "type": "string", "enum": [ "Manual", "Uniform", "RoundRobin", "LeastBackup", "Sticky" ], "default": "Uniform" }, "required_acks": { "description": "Sets the required acks for produced records", "type": "string", "default": "AllISRAck", "enum": [ "NoAck", "LeaderAck", "AllISRAck" ] }, "max_message_bytes": { "type": "integer", "default": 1000012, "minimum": 0 }, "request_timeout": { "description": "The maximum duration in seconds the broker will wait the receipt of the number of required_acks.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration", "default": "10s" }, "compression_codec": { "description": "The compression codec to use, if any.", "type": "string", "enum": [ "gzip", "snappy", "lz4", "zstd" ] }, "delivery_timeout": { "description": "A rough duration of how long a record can sit around in a batch before timing out, overriding the unlimited default.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration" }, "transaction_timeout": { "description": "The allowed duration for a transaction. It is a good idea to keep this less than a group's session timeout.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration", "default": "40s" } } }, "group_options": { "type": "object", "required": [ "group_id" ], "description": "group_options sets the consumer group for the client to join and consume in. This option is required if using any other group options.", "additionalProperties": false, "properties": { "instance_id": { "description": "InstanceID sets the group consumer's instance ID, switching the group member from 'dynamic' to 'static'.", "type": "string", "minLength": 1 }, "group_id": { "description": "Kafka Group ID", "type": "string", "minLength": 1 }, "block_rebalance_on_poll": { "description": "Switches the client to block rebalances whenever you poll.", "type": "boolean", "default": false } } }, "consumer_options": { "description": "Kafka consumer options", "type": "object", "additionalProperties": false, "properties": { "topics": { "description": "List of the topics to consume. Regex also supported. At least a `topic` or a partition in `consume_partitions` should be provided.", "type": "array", "items": { "type": "string", "minLength": 1 } }, "consume_regexp": { "description": "Sets the client to parse all topics passed to `topics` as regular expressions. When consuming via regex, every metadata request loads *all* topics, so that all topics can be passed to any regular expressions. Every topic is evaluated only once ever across all regular expressions; either it permanently is known to match, or is permanently known to not match.", "type": "boolean", "default": false }, "max_fetch_message_size": { "type": "integer", "default": 50000 }, "min_fetch_message_size": { "type": "integer", "default": 1 }, "consumer_offset": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/kafka/refs/heads/master/schema.json#/definitions/Offset" }, "consume_partitions": { "type": "object", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "description": "Topic to consume.", "type": "object", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[0-9]+$": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/kafka/refs/heads/master/schema.json#/definitions/Offset" } } } } } } }, "sasl_options": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/kafka/refs/heads/master/schema.json#/definitions/SASL" } } } } }, "driver": { "type": "object", "additionalProperties": false, "description": "Configuration options for the Kafka driver.", "properties": { "brokers": { "description": "Kafka broker addresses.", "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1, "examples": [ "127.0.0.1:9092", "127.0.0.1:9002" ] } }, "tls": { "title": "TLS Configuration", "description": "TLS configuration for TLS for Kafka.", "type": "object", "additionalProperties": false, "properties": { "timeout": { "description": "Timeout of TLS connections.", "default": "10s", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration" }, "key": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/key" }, "cert": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/cert" }, "root_ca": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/root_ca" }, "client_auth_type": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/ClientAuthType" } }, "anyOf": [ { "required": [ "key", "cert" ] }, { "required": [ "root_ca" ] } ] }, "sasl": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/kafka/refs/heads/master/schema.json#/definitions/SASL" }, "ping": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/kafka/refs/heads/master/schema.json#/definitions/Ping" } } }, "Offset": { "type": "object", "description": "Sets the offset to start consuming from, or if OffsetOutOfRange is seen while fetching, to restart consuming from.", "required": [ "type" ], "additionalProperties": false, "properties": { "type": { "description": "Partition offset type.", "type": "string", "enum": [ "AtEnd", "At", "AfterMilli", "AtStart", "Relative", "WithEpoch" ] }, "value": { "description": "Value for the: At, AfterMilli, Relative and WithEpoch offsets.", "type": "integer", "default": 0 } } }, "SASL": { "title": "SASL Authentication", "type": "object", "description": "SASL configuration for Kafka.", "additionalProperties": false, "properties": { "mechanism": { "description": "Mechanism used for the authentication.", "type": "string", "enum": [ "aws_msk_iam", "plain", "SCRAM-SHA-256", "SCRAM-SHA-512" ] }, "username": { "description": "Username for authentication.", "type": "string" }, "password": { "description": "Password for authentication.", "type": "string" }, "nonce": { "description": "Optional for the SHA auth types. Empty by default.", "type": "string" }, "is_token": { "description": "If true, suffixes the tokenauth=true extra attribute to the initial authentication message. Set this to true if the user and pass are from a delegation token. Optional for the SHA auth types. Defaults to false.", "type": "boolean", "default": false }, "zid": { "description": "Zid is an optional authorization ID to use in authentication.", "type": "string" }, "access_key": { "description": "AWS Access Key ID", "type": "string" }, "secret_key": { "description": "AWS Access Key Secret", "type": "string" }, "session_token": { "description": "SessionToken, if non-empty, is a session / security token to use for authentication. See the following link for more details: https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html", "type": "string" }, "user_agent": { "description": "UserAgent is the user agent to for the client to use when connecting to Kafka, overriding the default franz-go//. Setting a UserAgent allows authorizing based on the aws:UserAgent condition key; see the following link for more details: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-useragent", "type": "string" } } }, "Ping": { "type": "object", "additionalProperties": false, "properties": { "timeout": { "description": "Timeout when pinging Kafka.", "default": "10s", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration" } } } }, "allOf": [ { "$ref": "https://raw.githubusercontent.com/roadrunner-server/kafka/refs/heads/master/schema.json#/definitions/driver" } ] }, "logs": { "$id": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Logger plugin for RoadRunner.", "type": "object", "title": "roadrunner-logger", "minProperties": 1, "additionalProperties": false, "properties": { "mode": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogMode" }, "level": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogLevel" }, "line_ending": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogLineEnding" }, "encoding": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogEncoding" }, "output": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogOutput" }, "err_output": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogOutput" }, "file_logger_options": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/FileLoggerOptions" }, "channels": { "description": "You can configure logging for each plugin individually. The key is the plugin name and the value is logging options in same format as the parent.", "type": "object", "additionalProperties": false, "minProperties": 1, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "description": "Custom logging options for the plugin specified as this object's key.", "type": "object", "additionalProperties": false, "minProperties": 1, "properties": { "mode": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogMode" }, "level": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogLevel" }, "line_ending": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogLineEnding" }, "encoding": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogEncoding" }, "output": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogOutput" }, "err_output": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/LogOutput" }, "file_logger_options": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/logger/refs/heads/master/schema.json#/$defs/FileLoggerOptions" } } } } } }, "$defs": { "FileLoggerOptions": { "description": "File logger options.", "type": "object", "additionalProperties": false, "properties": { "log_output": { "type": "string", "description": "Path to the log file. Uses -lumberjack.log and the OS temp (i.e. `/tmp`) directory if empty." }, "max_size": { "type": "integer", "description": "Maximum file size in MB.", "minimum": 0, "default": 100 }, "max_age": { "type": "integer", "description": "The maximum number of days to retain old log files based on the timestamp encoded in their filename. Empty or zero defaults to 24 days.", "minimum": 0, "default": 24 }, "max_backups": { "type": "integer", "description": "The maximum number of old log files to retain. Empty or zero defaults to 10.", "minimum": 0, "default": 10 }, "compress": { "type": "boolean", "description": "Whether to compress log files.", "default": false } } }, "LogMode": { "description": "Logging mode", "type": "string", "default": "development", "enum": [ "none", "off", "production", "development", "raw" ] }, "LogLevel": { "description": "Logging level", "type": "string", "default": "debug", "enum": [ "debug", "info", "warn", "error", "panic" ] }, "LogEncoding": { "description": "Encoding format. Default depends on logging mode. For production, `json` is the default, else `console`. Also supports any third-party encodings registered via RegisterEncoder.", "type": "string", "enum": [ "console", "json" ] }, "LogOutput": { "type": "array", "items": { "type": "string", "minLength": 1, "examples": [ "stdout", "stderr", "/var/log/rr_errors.log" ] } }, "LogLineEnding": { "description": "Line-ending to use for logging.", "type": "string", "default": "\n" } } }, "memcached": { "$id": "https://raw.githubusercontent.com/roadrunner-server/memcached/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Memcached plugin for RoadRunner.", "type": "object", "additionalProperties": false, "properties": { "addr": { "description": "Addresses of the memcached node(s).", "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1, "examples": [ "localhost:11211" ] }, "default": [ "localhost:11211" ] } } }, "metrics": { "$id": "https://raw.githubusercontent.com/roadrunner-server/metrics/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Prometheus Metrics plugin for RoadRunner.", "type": "object", "title": "roadrunner-metrics", "additionalProperties": false, "properties": { "address": { "description": "Prometheus client address (path /metrics is appended automatically).", "type": "string", "default": "127.0.0.1:2112", "minLength": 1 }, "collect": { "description": "Application-specific metrics (published using an RPC connection to the server).", "type": "object", "additionalProperties": false, "minProperties": 1, "patternProperties": { "^[a-zA-Z_:][a-zA-Z0-9_:]*$": { "type": "object", "description": "The metrics to set up in Prometheus. See https://prometheus.io/docs/guides/go-application/ for details.", "additionalProperties": false, "properties": { "type": { "description": "The metric type to collect.", "type": "string", "enum": [ "histogram", "gauge", "counter", "summary" ] }, "namespace": { "type": "string", "description": "The collector's namespace." }, "subsystem": { "type": "string", "description": "The collector's subsystem." }, "help": { "type": "string", "description": "The collector's help message." }, "labels": { "description": "The collector's metrics labels. These must be in the format supported by Prometheus. See https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels", "type": "array", "minItems": 1, "items": { "type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$" } }, "buckets": { "description": "The collector's buckets for the histogram type. Values must be in increasing order. The +Inf bucket is added implicitly at the end. If this array is undefined or empty, the default buckets are used.", "type": "array", "uniqueItems": true, "items": { "minimum": 0, "type": "number" }, "default": [ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 ] }, "objectives": { "description": "The collector's objectives for the summary type. Keys in this map must be a number between 0 and 1. The default value is an empty map, resulting in a summary without quantiles.", "type": "object", "additionalProperties": false, "patternProperties": { "^(0(\\.[0-9]+)?|1(\\.0+)?)$": { "type": "number" } } } } } } } } }, "nats": { "$id": "https://raw.githubusercontent.com/roadrunner-server/nats/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "pipeline": { "type": "object", "required": [ "driver" ], "additionalProperties": false, "properties": { "driver": { "type": "string", "enum": [ "nats" ] }, "config": { "type": "object", "description": "Configuration for the NATS driver.", "additionalProperties": false, "properties": { "priority": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/priority" }, "prefetch": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/prefetch" }, "subject": { "description": "NATS subject", "type": "string", "default": "default" }, "stream": { "description": "NATS stream", "type": "string", "default": "default-stream" }, "deliver_new": { "description": "Whether to only receive messages that were created after the consumer was created.", "type": "boolean", "default": false }, "rate_limit": { "description": "Consumer rate-limiter in bytes. See https://docs.nats.io/jetstream/concepts/consumers#ratelimit", "type": "integer", "default": 1000, "minimum": 0 }, "delete_stream_on_stop": { "description": "Whether to delete the stream after the pipeline is stopped.", "type": "boolean", "default": false }, "delete_after_ack": { "description": "Whether to delete messages from the stream after successful acknowledgement.", "type": "boolean", "default": false } } } } }, "driver": { "type": "object", "additionalProperties": false, "description": "Configuration options for the NATS driver.", "properties": { "addr": { "description": "NATS server address.", "type": "string", "default": "nats://127.0.0.1:4222" } } } }, "allOf": [ { "$ref": "https://raw.githubusercontent.com/roadrunner-server/nats/refs/heads/master/schema.json#/definitions/driver" } ] }, "otel": { "$id": "https://raw.githubusercontent.com/roadrunner-server/otel/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the OpenTelemetry plugin for RoadRunner.", "type": "object", "title": "roadrunner-otel", "additionalProperties": false, "required": [ "resource" ], "properties": { "resource": { "type": "object", "additionalProperties": false, "properties": { "service_name": { "description": "The name of the service.", "type": "string", "default": "RoadRunner", "minLength": 1 }, "service_version": { "type": "string", "description": "The version of the service.", "default": "1.0.0", "minLength": 1 }, "service_namespace": { "type": "string", "description": "The namespace of the service.", "default": "-", "minLength": 1 }, "service_instance_id": { "type": "string", "description": "The service instance ID. If not provided or empty, a UUID is generated.", "minLength": 1, "default": "" } } }, "insecure": { "description": "Use insecure endpoint", "type": "boolean", "default": false }, "compress": { "description": "Whether to use gzip compressor.", "type": "boolean", "default": false }, "exporter": { "description": "Provides functionality to emit telemetry to consumers.", "type": "string", "default": "otlp", "enum": [ "zipkin", "stdout", "stderr", "otlp", "jaeger", "jaeger_agent" ] }, "custom_url": { "description": "Overrides the default URL of the HTTP client, if provided.", "type": "string", "minLength": 1 }, "endpoint": { "description": "The endpoint of the consumer. Uses the OTEL default if not provided.", "type": "string", "default": "127.0.0.1:4318", "minLength": 1 }, "client": { "description": "Client to send the spans. Defaults to http if invalid or empty.", "type": "string", "enum": [ "http", "grpc" ] }, "service_name": { "description": "User's service name. **Deprecated**: Use resource.service_name instead.", "type": "string", "default": "RoadRunner", "deprecated": true }, "service_version": { "description": "User's service version. **Deprecated**: Use resource.service_version instead.", "type": "string", "default": "1.0.0", "deprecated": true }, "headers": { "description": "User defined headers for the OTLP protocol.", "type": "object", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string", "minLength": 1 } } } } }, "redis": { "$id": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Redis plugin for RoadRunner.", "type": "object", "additionalProperties": false, "properties": { "addrs": { "title": "Redis Endpoint", "description": "The addresses or hostnames of the Redis server/cluster to connect to. If the number of addresses is 1 and master_name is empty, a single-node Redis Client will be returned, otherwise a ClusterClient or FailoverClient will be returned, depending on whether `master_name` is provided.", "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 }, "default": [ "localhost:6379" ] }, "master_name": { "title": "Redis Master Node Name", "description": "The name of the master Redis node. A Sentinel-backed FailoverClient will be returned if this value is provided.", "type": "string" }, "username": { "title": "Redis AUTH username.", "description": "The username to provide for Redis authentication.", "type": "string", "examples": [ "my_username" ] }, "password": { "title": "Redis AUTH password.", "description": "The password to provide for Redis authentication.", "type": "string", "examples": [ "super-secret-password" ] }, "db": { "title": "Redis DB Index", "description": "The Redis DB index to select when connecting.", "type": "integer", "default": 0, "maximum": 10 }, "sentinel_password": { "title": "Sentinel Password", "description": "The password for Redis Sentinel.", "type": "string", "examples": [ "super-secret-password" ] }, "route_by_latency": { "title": "Route by Latency", "description": "Whether to route traffic to Redis nodes based on latency.", "type": "boolean", "default": false }, "route_randomly": { "title": "Route Randomly", "description": "Whether to randomly route traffic to Redis nodes.", "type": "boolean", "default": false }, "dial_timeout": { "title": "Dial Timeout", "description": "The timeout when attempting to connect to Redis. Default or zero means 5s.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration", "default": 0 }, "max_retries": { "title": "Maximum Retries", "description": "The maximum number of retry attempts when connecting to Redis. Default or zero means 3.", "type": "integer", "default": 0 }, "min_retry_backoff": { "title": "Minimum Retry Backoff", "description": "The minimum backoff duration when retrying connection attempts. Default or zero means 8s.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration", "default": 0 }, "max_retry_backoff": { "title": "Maximum Retry Backoff", "description": "The maximum backoff duration when retrying connection attempts. Default or zero means 512s.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration", "default": 0 }, "pool_size": { "title": "Connection Pool Size", "description": "The number of connections to keep in the Redis connection pool. Default or zero means 10 per logical CPU.", "type": "integer", "default": 0 }, "min_idle_conns": { "title": "Minimum Idle Connections", "description": "The minimum number of connections to keep in the pool. Defaults to 0, which means no idle connection pool.", "type": "integer", "default": 0 }, "max_conn_age": { "title": "Maximum Connection Age", "description": "The maximum age of open Redis connections. Default or zero means no limit.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration", "default": "0s" }, "read_timeout": { "title": "Read Timeout", "description": "The timeout when reading from a Redis node. Default or zero means 3s.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration", "default": "0s" }, "write_timeout": { "title": "Write Timeout", "description": "The timeout when writing to a Redis node. Default or zero means equivalent to `read_timeout`.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration", "default": "0s" }, "pool_timeout": { "title": "Pool Timeout", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration" }, "idle_timeout": { "title": "Idle Timeout", "description": "The timeout of idle connections to Redis. Default or zero means 5m.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration", "default": "0s" }, "idle_check_freq": { "title": "Idle Check Frequency", "description": "The time between checks for idle connections to Redis. Default or zero means 1m.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json#/$defs/duration", "default": "0s" }, "read_only": { "title": "Read-only", "description": "Whether the Redis connection is in read-only mode. See https://redis.io/docs/latest/commands/readonly.", "type": "boolean", "default": false }, "tls": { "description": "GRPC TLS configuration", "type": "object", "additionalProperties": false, "properties": { "key": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/key" }, "cert": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/cert" }, "root_ca": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/root_ca" }, "client_auth_type": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/ClientAuthType" } }, "required": [ "root_ca" ] } }, "$defs": { "duration": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration" } } }, "temporal": { "$id": "https://raw.githubusercontent.com/temporalio/roadrunner-temporal/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Temporal plugin for RoadRunner.", "type": "object", "title": "roadrunner-temporal", "additionalProperties": false, "properties": { "address": { "description": "Address of the Temporal server. Defaults to localhost:7233 if not provided.", "type": "string", "default": "localhost:7233", "minLength": 1 }, "cache_size": { "description": "Sticky cache size. Sticky workflow execution is the affinity between workflow tasks of a specific workflow execution to a specific worker. The benefit of sticky execution is that the workflow does not have to reconstruct state by replaying history from the beginning. The cache is shared between workers running within same process. This must be called before any worker is started. If not called, the default size of 10K (which may change) will be used.", "type": "integer", "default": 10000 }, "namespace": { "description": "Namespace for this client to work with.", "type": "string", "default": "default" }, "metrics": { "oneOf": [ { "type": "object", "additionalProperties": false, "properties": { "driver": { "description": "The Prometheus driver.", "type": "string", "enum": [ "prometheus" ] }, "prometheus": { "$ref": "https://raw.githubusercontent.com/temporalio/roadrunner-temporal/refs/heads/master/schema.json#/$defs/Prometheus" } } }, { "type": "object", "additionalProperties": false, "properties": { "driver": { "description": "The Statsd driver.", "type": "string", "enum": [ "statsd" ] }, "statsd": { "$ref": "https://raw.githubusercontent.com/temporalio/roadrunner-temporal/refs/heads/master/schema.json#/$defs/Statsd" } } } ] }, "activities": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/pool/refs/heads/master/schema.json" }, "tls": { "description": "Temporal TLS configuration.", "type": "object", "required": [ "key", "cert" ], "properties": { "key": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/key" }, "cert": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/cert" }, "root_ca": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/SSL/properties/root_ca" }, "client_auth_type": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/http/refs/heads/master/schema.json#/$defs/ClientAuthType" }, "server_name": { "description": "ServerName is used to verify the hostname on the returned certificates unless InsecureSkipVerify is given. It is also included in the client's handshake to support virtual hosting unless it is an IP address.", "type": "string" } } } }, "$defs": { "Statsd": { "type": "object", "description": "Properties for Temporal Statsd integration.", "additionalProperties": false, "properties": { "host_port": { "description": "The host and port of the statsd server.", "type": "string", "default": "127.0.0.1:8125", "minLength": 1 }, "prefix": { "description": "The prefix to use in reporting to statsd.", "type": "string" }, "flush_interval": { "description": "The maximum interval between sending packets.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration", "default": "1s" }, "flush_bytes": { "description": "The maximum UDP packet size you wish to send. Defaults to 1432 bytes if zero or undefined, which is considered safe for local traffic.", "type": "integer", "default": 1432 }, "tags": { "description": "A map of tags consisting of keys and values.", "type": "object", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string", "minLength": 1 } } }, "tag_prefix": { "description": "Prefix for the tags.", "type": "string" }, "tag_separator": { "description": "The tag separator allows tags to be appended with a separator. If not specified, tag keys and values are embedded in the stat name directly.", "type": "string" } } }, "Prometheus": { "type": "object", "description": "Properties for Temporal Prometheus integration.", "additionalProperties": false, "properties": { "address": { "description": "Server metrics address.", "type": "string", "default": "127.0.0.1:9091", "minLength": 1 }, "type": { "type": "string", "description": "Metrics type to use. Defaults to `summary`.", "enum": [ "summary", "histogram" ], "default": "summary" }, "prefix": { "description": "Temporal metrics prefix.", "type": "string" } } } } }, "rpc": { "$id": "https://raw.githubusercontent.com/roadrunner-server/rpc/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the RPC plugin for RoadRunner.", "type": "object", "title": "roadrunner-rpc", "additionalProperties": false, "properties": { "listen": { "description": "The address and port for the RPC server to bind to. Should contain protocol definition (i.e. `tcp://` or `unix://`).", "type": "string", "default": "tcp://127.0.0.1:6001", "minLength": 1, "examples": [ "tcp://127.0.0.1:6001" ] } } }, "server": { "$id": "https://raw.githubusercontent.com/roadrunner-server/server/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Server plugin for RoadRunner.", "type": "object", "title": "roadrunner-server", "additionalProperties": false, "required": [ "command" ], "properties": { "on_init": { "description": "Arbitrary command to execute before RR starts allocating workers.", "type": "object", "additionalProperties": false, "required": [ "command" ], "properties": { "command": { "description": "Command to execute. It can be any script or binary that RoadRunner has access to.", "type": "string", "minLength": 1, "examples": [ "php not-worker.php", "sh script.sh", "start script.bat" ] }, "exec_timeout": { "description": "Script execution timeout. Zero or empty defaults to 60s.", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration", "default": "60s" }, "exit_on_error": { "description": "Exit RR if the `on_init` command fails.", "type": "boolean", "default": "false" }, "user": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/server/refs/heads/master/schema.json#/$defs/User" }, "env": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/server/refs/heads/master/schema.json#/$defs/EnvironmentVariables" } } }, "command": { "description": "The command used to start workers, including any required arguments. Any plugins that implement a worker pool will inherit this command if they do not specify an override.", "type": "string", "minLength": 1, "examples": [ "php psr-worker.php" ] }, "user": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/server/refs/heads/master/schema.json#/$defs/User" }, "group": { "description": "Group name (not GID) for the worker processes. The RR process user group will be used if not provided. The RoadRunner process must be run as root for this to work.", "type": "string", "minLength": 1, "examples": [ "www-data" ] }, "env": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/server/refs/heads/master/schema.json#/$defs/EnvironmentVariables" }, "relay": { "description": "Worker relay method. Can be 'pipes', a TCP address (e.g. tcp://127.0.0.1:6002) or a socket (e.g. unix:///var/run/rr.sock).", "type": "string", "default": "pipes", "minLength": 1, "examples": [ "pipes", "tcp://127.0.0.1:6002", "unix:///var/run/rr.sock" ] } }, "$defs": { "EnvironmentVariables": { "description": "Environment variables for the process or command.", "type": "object", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string" } } }, "User": { "description": "Username (not UID) of the user from whom the command or process is executed. The RoadRunner process user will be used if not provided. The RoadRunner process must be run as root in order to specify a different user.", "type": "string", "minLength": 1, "examples": [ "www-data" ] } } }, "sqs": { "$id": "https://raw.githubusercontent.com/roadrunner-server/sqs/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "definitions": { "pipeline": { "type": "object", "required": [ "driver" ], "additionalProperties": false, "properties": { "driver": { "type": "string", "enum": [ "sqs" ] }, "config": { "type": "object", "description": "Configuration options for the SQS pipeline.", "additionalProperties": false, "properties": { "priority": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/priority" }, "prefetch": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/prefetch" }, "skip_queue_declaration": { "description": "Whether to skip creation of the SQS queue. If you set to this to `true`, the queue must already exist.", "type": "boolean", "default": false }, "max_messages_in_flight": { "description": "Maximum number of messages that can be in-flight at any given time. This is useful to limit the number of messages that are being processed concurrently. Default is the same value as `prefetch`.", "type": "integer", "default": 10 }, "visibility_timeout": { "type": "integer", "description": "The duration (in seconds) that a message received from a queue (by one consumer) will not be visible to the other message consumers. The visibility timeout begins when Amazon SQS returns a message. If the consumer fails to process and delete the message before the visibility timeout expires, the message becomes visible to other consumers. If a message must be received only once, your consumer must delete it within the duration of the visibility timeout.", "default": 0 }, "error_visibility_timeout": { "type": "integer", "description": "The visibility timeout to set for jobs that fail (NACK). If you set this, you must also set `retain_failed_jobs` to `true`.", "default": 0 }, "retain_failed_jobs": { "type": "boolean", "description": "Whether to keep failed (NACK'ed) jobs on the queue. By default, RR will delete and requeue failed jobs immediately. If you set this to `true`, RR does nothing to NACK'ed jobs on the queue and they will be consumed again after `visibility_timeout` or (`error_visibility_timeout`, if > 0) has passed. Jobs that are consumed multiple times will increment their receive count, which can be used to configure SQS to automatically move the jobs to a dead-letter queue.", "default": false }, "wait_time_seconds": { "description": "The duration (in seconds) the call waits for a message to arrive in the queue before returning. If a message is available, the call returns immediately. If no messages are available and the wait time expires, the call returns with an empty list of messages", "type": "integer", "default": 0, "maximum": 20 }, "queue": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/jobs/refs/heads/master/schema.json#/definitions/PipelineProperties/queue" }, "message_group_id": { "description": "Message Group ID. See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html#SQS-SendMessage-request-MessageGroupId", "type": "string", "default": null }, "attributes": { "title": "AWS queue attributes. Attributes are only applied to the queue if RR creates it. Existing queues will not be modified. Must be any of the attributes listed here: https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SetQueueAttributes.html.", "type": "object", "properties": { "DelaySeconds": { "description": "The length of time, in seconds, for which the delivery of all messages in the queue is delayed.", "type": "integer", "minimum": 0, "maximum": 900, "default": 0 }, "MaximumMessageSize": { "description": "The limit of how many bytes a message can contain before Amazon SQS rejects it.", "type": "integer", "minimum": 1024, "maximum": 262144, "default": 262144 }, "MessageRetentionPeriod": { "description": "The length of time, in seconds, for which Amazon SQS retains a message. When you change a queue's attributes, the change can take up to 60 seconds for most of the attributes to propagate throughout the Amazon SQS system. Changes made to the `MessageRetentionPeriod` attribute can take up to 15 minutes and will impact existing messages in the queue potentially causing them to be expired and deleted if the `MessageRetentionPeriod` is reduced below the age of existing messages.", "type": "integer", "minimum": 60, "maximum": 1209600, "default": 345600 }, "Policy": { "description": "A valid AWS policy. See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html for reference of this object structure." }, "ReceiveMessageWaitTimeSeconds": { "description": "The length of time, in seconds, for which a [`ReceiveMessage`](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html) action waits for a message to arrive.", "type": "integer", "minimum": 0, "maximum": 20, "default": 0 }, "VisibilityTimeout": { "description": "The visibility timeout for the queue, in seconds. For more information about the visibility timeout, see [Visibility Timeout](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html) in the Amazon SQS Developer Guide.", "type": "integer", "minimum": 0, "maximum": 43200, "default": 30 }, "RedrivePolicy": { "description": "**Dead-letter queues only.** The parameters for the dead-letter queue functionality of the source queue as a JSON object.", "type": "object", "properties": { "deadLetterTargetArn": { "type": "string", "description": "The Amazon Resource Name (ARN) of the dead-letter queue to which Amazon SQS moves messages after the value of `maxReceiveCount` is exceeded.", "examples": [ "arn:aws:sqs:us-east-2:123456789012:my_queue" ] }, "maxReceiveCount": { "type": "integer", "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue. When the `ReceiveCount` for a message exceeds the `maxReceiveCount` for a queue, Amazon SQS moves the message to the dead-letter-queue.", "minimum": 1, "default": 10 } } }, "RedriveAllowPolicy": { "description": "**Dead-letter queues only.** The parameters for the permissions for the dead-letter queue redrive permission and which source queues can specify dead-letter queues.", "type": "object", "properties": { "redrivePermission": { "description": "The permission type that defines which source queues can specify the current queue as the dead-letter queue.", "type": "string", "enum": [ "allowAll", "denyAll", "byQueue" ] }, "sourceQueueArns": { "description": "The Amazon Resource Names (ARN)s of the source queues that can specify this queue as the dead-letter queue and redrive messages. You can specify this parameter only when the `redrivePermission` parameter is set to `byQueue`. You can specify up to 10 source queue ARNs. To allow more than 10 source queues to specify dead-letter queues, set the `redrivePermission` parameter to `allowAll`.", "type": "array", "items": { "type": "string", "examples": [ "arn:aws:sqs:us-east-2:123456789012:my_queue", "arn:aws:sqs:us-east-2:123456789012:my_queue2" ] } } } }, "ContentBasedDeduplication": { "description": "**FIFO queues only.** Enables content-based deduplication. For more information, see [Exactly-once processing](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues-exactly-once-processing.html) in the Amazon SQS Developer Guide.", "type": "boolean", "default": false }, "DeduplicationScope": { "description": "**High-throughput FIFO queues only.** Specifies whether message deduplication occurs at the message group or queue level.", "type": "string", "enum": [ "messageGroup", "queue" ] }, "FifoThroughputLimit": { "description": "**High-throughput FIFO queues only.** Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group. The `perMessageGroupId` value is allowed only when the value for `DeduplicationScope` is `messageGroup`.", "type": "string", "enum": [ "perQueue", "perMessageGroupId" ] }, "KmsMasterKeyId": { "description": "**Applies only to server-side-encryption.** The ID of an AWS managed customer master key (CMK) for Amazon SQS or a custom CMK. While the alias of the AWS-managed CMK for Amazon SQS is always `alias/aws/sqs`, the alias of a custom CMK can, for example, be `alias/MyAlias`.", "type": "string" }, "KmsDataKeyReusePeriodSeconds": { "description": "**Applies only to server-side-encryption.** The length of time, in seconds, for which Amazon SQS can reuse a data key to encrypt or decrypt messages before calling AWS KMS again. An integer representing seconds, between 60 seconds (1 minute) and 86,400 seconds (24 hours).", "type": "integer", "minimum": 60, "maximum": 86400, "default": 300 }, "SqsManagedSseEnabled": { "type": "boolean", "default": false, "description": "**Applies only to server-side-encryption.** Enables server-side queue encryption using SQS owned encryption keys. Only one server-side encryption option is supported per queue." } } }, "tags": { "title": "Tags to associate with the queue. Please see https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-queue-tags.html.", "type": "object", "additionalProperties": { "type": "string", "minLength": 1 } } } } } }, "driver": { "type": "object", "additionalProperties": false, "description": "Configuration options for the SQS driver.", "properties": { "key": { "title": "AWS Access Key ID", "description": "This is required unless your environment variables provide AWS credentials.", "type": "string", "examples": [ "ASIAIOSFODNN7EXAMPLE" ] }, "secret": { "title": "AWS Access Key Secret", "description": "This is required unless your environment variables provide AWS credentials.", "type": "string", "examples": [ "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" ] }, "region": { "title": "AWS Region", "description": "The region to connect to. Must be one of the [supported regions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions). This is required unless your environment variables provide an AWS region or you use a self-hosted queue.", "type": "string", "examples": [ "eu-central-1", "us-west-1", "ca-central-1", "ap-northeast-1" ] }, "session_token": { "title": "AWS Session Token", "description": "The short-lived session token to use. This should not be provided for production and has a maximum duration of 12 hours. In most cases, you don't need to provide this value.", "type": "string" }, "endpoint": { "title": "Queue Endpoint", "description": "The endpoint of your queue. You only need to provide this value if you use a self-hosted queue, as the endpoint will be resolved from the provided AWS Region and queue name otherwise.", "type": "string", "examples": [ "http://127.0.0.1:9324" ] } }, "dependentRequired": { "key": [ "secret" ], "secret": [ "key" ] } } }, "allOf": [ { "$ref": "https://raw.githubusercontent.com/roadrunner-server/sqs/refs/heads/master/schema.json#/definitions/driver" } ] }, "status": { "$id": "https://raw.githubusercontent.com/roadrunner-server/status/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Health Check (Status) plugin for RoadRunner.", "type": "object", "title": "roadrunner-status", "additionalProperties": false, "required": [ "address" ], "properties": { "address": { "description": "Host and port to listen on (eg.: `127.0.0.1:2114`). To query a plugin, pass its name as a query parameter called `plugin`, e.g. to check the `http` plugin, request `GET http://127.0.0.1:2114/health?plugin=http`. You can query multiple plugins by appending multiple instances of the `plugin` parameter, e.g. `GET http://127.0.0.1:2114/health?plugin=http&plugin=rpc`.", "type": "string", "minLength": 1, "examples": [ "127.0.0.1:2114" ] }, "unavailable_status_code": { "description": "Response HTTP status code if a requested plugin is not ready to handle requests. Valid for both /health and /ready endpoints. Defaults to 503 if undefined or zero.", "type": "integer", "minimum": 100, "maximum": 599, "default": 503 }, "check_timeout": { "description": "The maximum duration to wait for a complete response from the queried plugin(s), in seconds. Defaults to 60.", "type": "integer", "minimum": 1, "default": 60 } } }, "tcp": { "$id": "https://raw.githubusercontent.com/roadrunner-server/tcp/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the TCP plugin for RoadRunner.", "type": "object", "title": "roadrunner-tcp", "additionalProperties": false, "properties": { "servers": { "description": "The TCP servers to allocate.", "type": "object", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "description": "TCP server", "type": "object", "additionalProperties": false, "properties": { "addr": { "description": "Address to listen on.", "type": "string", "minLength": 1, "examples": [ "127.0.0.1:7778" ] }, "delimiter": { "description": "Data packet delimiter. Every send should end with either EOF or this delimiter.", "type": "string", "default": "\r\n" } }, "required": [ "addr" ] } } }, "pool": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/pool/refs/heads/master/schema.json" }, "read_buf_size": { "description": "Size of the chunks that RR reads data in, in MB. If you expect big payloads on a TCP server, you may reduce `read` system calls by using a big buffer.", "type": "integer", "minimum": 1, "maximum": 100, "default": 1 } } }, "kv": { "$id": "https://raw.githubusercontent.com/roadrunner-server/kv/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the KV plugin for RoadRunner.", "type": "object", "title": "roadrunner-kv", "minProperties": 1, "additionalProperties": false, "patternProperties": { "[a-zA-Z0-9_-]*": { "description": "The name of the key-value storage, as used in your application.", "type": "object", "additionalProperties": false, "required": [ "driver" ], "properties": { "driver": { "description": "The driver to use.", "type": "string", "enum": [ "boltdb", "memcached", "memory", "redis" ] }, "config": { "description": "You may override the global configuration of the driver. If you provided a global configuration for the plugin, this section can be omitted and the global configuration will be used instead. If neither are present, the KV storage will not load.", "type": "object" } }, "if": { "properties": { "driver": { "enum": [ "boltdb" ] } } }, "then": { "properties": { "config": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/boltdb/refs/heads/master/schema.json#/definitions/driver" } } }, "else": { "if": { "properties": { "driver": { "enum": [ "memcached" ] } } }, "then": { "properties": { "config": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/memcached/refs/heads/master/schema.json" } } }, "else": { "if": { "properties": { "driver": { "enum": [ "redis" ] } } }, "then": { "properties": { "config": { "$ref": "https://raw.githubusercontent.com/roadrunner-server/redis/refs/heads/master/schema.json" } } }, "else": { "if": { "properties": { "driver": { "enum": [ "memory" ] } } }, "then": { "required": [ "driver", "config" ], "properties": { "config": { "type": "object", "description": "The memory plugin does not support configuration, but requires an empty config object to be present due to parsing logic and the fact that memory has no global configuration to inherit from.", "additionalProperties": false } } } } } } } } }, "service": { "$id": "https://raw.githubusercontent.com/roadrunner-server/service/refs/heads/master/schema.json", "$schema": "https://json-schema.org/draft/2019-09/schema", "description": "All the valid configuration parameters for the Service plugin for RoadRunner.", "type": "object", "title": "roadrunner-service", "minProperties": 1, "additionalProperties": false, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "object", "description": "The user-defined service.", "additionalProperties": false, "required": [ "command" ], "properties": { "command": { "description": "The command the service should execute. This can be any executable instruction that RoadRunner has access to on the server.", "type": "string", "minLength": 1 }, "env": { "type": "object", "description": "Environment variables to pass to the service.", "additionalProperties": false, "minProperties": 1, "patternProperties": { "^[a-zA-Z0-9._-]+$": { "type": "string" } } }, "timeout_stop_sec": { "description": "Timeout for the service stop operation. If it takes longer for this duration for the service to stop, it will be killed. Zero or empty defaults to 5.", "type": "integer", "minimum": 0, "default": 5 }, "process_num": { "description": "Number of copies (processes) to start per command execution.", "type": "integer", "minimum": 1, "default": 1 }, "exec_timeout": { "description": "The maximum duration the service is allowed to run before RR will kill it. Default/zero means unlimited.", "type": "string", "$ref": "https://raw.githubusercontent.com/roadrunner-server/roadrunner/refs/heads/master/schemas/config/3.0.schema.json#/definitions/Duration", "default": "0s" }, "remain_after_exit": { "description": "Whether to restart the process if it exits, regardless of the exit code.", "type": "boolean", "default": false }, "restart_sec": { "description": "Number of seconds to wait before process restart. Default/zero means 30 seconds.", "type": "integer", "default": 30 }, "service_name_in_log": { "description": "Whether to include the name of the service in logs (e.g. `service.some_service_1`).", "type": "boolean", "default": false } } } } } }, "definitions": { "Duration": { "description": "Time duration", "type": "string", "pattern": "^([0-9]*(\\.[0-9]*)?(ms|h|m|s))+$", "examples": [ "1h", "2.5h", "2m", ".2m", "30s", "30.03s", "300ms", "1h3m40s500ms" ] } } } ================================================ FILE: schemas/package.json ================================================ { "name": "roadrunner-schema-tests", "version": "1.0.0", "main": "test.js", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.7.2", "ajv": "^8.18.0", "js-yaml": "^4.1.1" } } ================================================ FILE: schemas/readme.md ================================================ # Schemas This directory contains public schemas for the most important parts of application. **Do not rename or remove this directory or any file or directory inside.** - You can validate existing config file using the following command from the project root. ```bash docker run --rm -v "$(pwd):/src" -w "/src" node:22-alpine sh -c \ "cd schemas && npm install && node test.js" ``` ================================================ FILE: schemas/test.js ================================================ import $RefParser from "@apidevtools/json-schema-ref-parser"; import Ajv2019 from "ajv/dist/2019.js" import fs from 'fs'; import yaml from 'js-yaml'; function stripIds(schema, first) { if (schema !== null && typeof schema === 'object') { if (!first) { // Every referenced schema we pull in should have its $id and $schema stripped, or ajv complains // Skip the root object, as that should retain the $schema and $id delete schema.$id; delete schema.$schema; } for (const key in schema) { if (Object.hasOwn(schema, key)) { stripIds(schema[key], false); } } } } // Load the main schema and all its referenced schemas const dereferenced = await $RefParser.dereference('./config/3.0.schema.json'); // Remove $id and $schema from anything but the root stripIds(dereferenced, true); const ajv = new Ajv2019({strict: true, allErrors: true}) const validator = ajv.compile(dereferenced) const data = fs.readFileSync('../.rr.yaml', 'utf-8'); const schema = yaml.load(data); // Validate the file if (!validator(schema)) { throw new Error(JSON.stringify(validator.errors, null, 2)) } else { console.log('No errors found in schemas.') } ================================================ FILE: tests/configs/.rr-grpc-otel.yaml ================================================ version: '3' rpc: listen: tcp://127.0.0.1:6206 server: command: "php php_test_files/grpc/worker-grpc.php" relay: "pipes" relay_timeout: "20s" grpc: listen: "tcp://127.0.0.1:9192" proto: - "proto/service/service.proto" max_send_msg_size: 50 max_recv_msg_size: 50 max_concurrent_streams: 10 ping_time: 1s timeout: 200s pool: num_workers: 2 max_jobs: 0 allocate_timeout: 60s destroy_timeout: 60s otel: resource: service_name: "rr-e2e-grpc-otel" service_version: "1.0.0" insecure: true exporter: stdout ================================================ FILE: tests/configs/.rr-grpc.yaml ================================================ version: '3' rpc: listen: tcp://127.0.0.1:6204 server: command: "php php_test_files/grpc/worker-grpc.php" relay: "pipes" relay_timeout: "20s" grpc: listen: "tcp://127.0.0.1:9191" proto: - "proto/service/service.proto" max_send_msg_size: 50 max_recv_msg_size: 50 max_concurrent_streams: 10 ping_time: 1s timeout: 200s pool: num_workers: 2 max_jobs: 0 allocate_timeout: 60s destroy_timeout: 60s ================================================ FILE: tests/configs/.rr-http-middleware.yaml ================================================ version: '3' rpc: listen: tcp://127.0.0.1:6202 server: command: "php php_test_files/http/client.php echo pipes" relay: "pipes" relay_timeout: "20s" http: address: 127.0.0.1:18950 max_request_size: 1024 middleware: ["headers", "gzip", "http_metrics", "proxy_ip_parser", "sendfile"] headers: response: X-Test: "e2e-roadrunner" trusted_subnets: - "10.0.0.0/8" - "127.0.0.0/8" - "172.16.0.0/12" - "192.168.0.0/16" - "::1/128" - "fc00::/7" - "fe80::/10" pool: num_workers: 2 max_jobs: 0 allocate_timeout: 60s destroy_timeout: 1s ================================================ FILE: tests/configs/.rr-http-otel.yaml ================================================ version: '3' rpc: listen: tcp://127.0.0.1:6205 server: command: "php php_test_files/http/client.php echo pipes" relay: "pipes" relay_timeout: "20s" http: address: 127.0.0.1:18952 max_request_size: 1024 middleware: ["otel", "gzip"] pool: num_workers: 2 max_jobs: 0 allocate_timeout: 60s destroy_timeout: 1s otel: resource: service_name: "rr-e2e-test" service_version: "1.0.0" insecure: true exporter: stdout ================================================ FILE: tests/configs/.rr-http-static.yaml ================================================ version: '3' rpc: listen: tcp://127.0.0.1:6203 server: command: "php php_test_files/http/client.php echo pipes" relay: "pipes" relay_timeout: "20s" http: address: 127.0.0.1:18951 max_request_size: 1024 middleware: ["static", "gzip"] static: dir: "testdata" forbid: [] allow: [".txt"] pool: num_workers: 2 max_jobs: 0 allocate_timeout: 60s destroy_timeout: 1s ================================================ FILE: tests/configs/.rr-jobs-memory-otel.yaml ================================================ version: '3' rpc: listen: tcp://127.0.0.1:6207 server: command: "php php_test_files/jobs/jobs_ok.php" relay: "pipes" relay_timeout: "20s" jobs: num_pollers: 10 pipeline_size: 100000 pool: num_workers: 2 max_jobs: 0 allocate_timeout: 60s destroy_timeout: 60s pipelines: test-1: driver: memory config: priority: 10 prefetch: 10000 test-2: driver: memory config: priority: 10 prefetch: 10000 consume: ["test-1", "test-2"] otel: resource: service_name: "rr-e2e-jobs-otel" service_version: "1.0.0" insecure: true exporter: stdout ================================================ FILE: tests/configs/.rr-jobs-memory.yaml ================================================ version: '3' rpc: listen: tcp://127.0.0.1:6201 server: command: "php php_test_files/jobs/jobs_ok.php" relay: "pipes" relay_timeout: "20s" jobs: num_pollers: 10 pipeline_size: 100000 pool: num_workers: 2 max_jobs: 0 allocate_timeout: 60s destroy_timeout: 60s pipelines: test-1: driver: memory config: priority: 10 prefetch: 10000 test-2: driver: memory config: priority: 10 prefetch: 10000 consume: ["test-1", "test-2"] ================================================ FILE: tests/doc.go ================================================ // Package tests contains end-to-end integration tests for RoadRunner. // Tests exercise the full plugin stack via the Endure dependency injection // container, spawning real PHP worker processes and verifying behavior // through RPC calls, HTTP requests, and gRPC clients. package tests ================================================ FILE: tests/e2e_grpc_test.go ================================================ package tests import ( "context" "errors" "fmt" "log/slog" "os" "os/signal" "sync" "syscall" "testing" "time" mocklogger "tests/mock" "tests/proto/service" "github.com/roadrunner-server/config/v5" "github.com/roadrunner-server/endure/v2" grpcPlugin "github.com/roadrunner-server/grpc/v5" rrOtel "github.com/roadrunner-server/otel/v5" rpcPlugin "github.com/roadrunner-server/rpc/v5" "github.com/roadrunner-server/server/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) // TestGrpcPing verifies the full gRPC lifecycle: container startup, PHP worker // registration of the Echo service, sending a Ping RPC, and validating the // response (PHP uppercases the message). func TestGrpcPing(t *testing.T) { cont := endure.New(slog.LevelDebug) cfg := &config.Plugin{ Version: "2024.1.0", Path: "configs/.rr-grpc.yaml", } l, _ := mocklogger.ZapTestLogger(zap.DebugLevel) err := cont.RegisterAll( cfg, &grpcPlugin.Plugin{}, &rpcPlugin.Plugin{}, &server.Plugin{}, l, ) assert.NoError(t, err) err = cont.Init() if err != nil { t.Fatal(err) } ch, err := cont.Serve() if err != nil { t.Fatal(err) } sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) stopCh := make(chan struct{}, 1) errCh := make(chan error, 1) // Background goroutine handles container lifecycle events. // Errors are sent to errCh and checked on the main test goroutine // because testing.T.Fatal/FailNow must not be called from non-test goroutines. wg := &sync.WaitGroup{} wg.Go(func() { for { select { case e := <-ch: stopErr := cont.Stop() errCh <- errors.Join(fmt.Errorf("vertex %s: %w", e.VertexID, e.Error), stopErr) return case <-sig: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return case <-stopCh: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return } } }) time.Sleep(time.Second) t.Run("PingEcho", func(t *testing.T) { conn, errDial := grpc.NewClient( "127.0.0.1:9191", grpc.WithTransportCredentials(insecure.NewCredentials()), ) require.NoError(t, errDial) require.NotNil(t, conn) defer func() { _ = conn.Close() }() ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() client := service.NewEchoClient(conn) resp, errPing := client.Ping(ctx, &service.Message{Msg: "hello"}) require.NoError(t, errPing) require.Equal(t, "HELLO", resp.GetMsg()) }) stopCh <- struct{}{} wg.Wait() select { case err := <-errCh: t.Fatal(err) default: } } // TestGrpcPingWithOtel verifies gRPC + OTEL plugin integration. // The OTEL plugin is registered alongside gRPC and instruments // the gRPC server with tracing (stdout exporter, no external collector). func TestGrpcPingWithOtel(t *testing.T) { cont := endure.New(slog.LevelDebug) cfg := &config.Plugin{ Version: "2024.1.0", Path: "configs/.rr-grpc-otel.yaml", } l, _ := mocklogger.ZapTestLogger(zap.DebugLevel) err := cont.RegisterAll( cfg, &grpcPlugin.Plugin{}, &rpcPlugin.Plugin{}, &server.Plugin{}, &rrOtel.Plugin{}, l, ) assert.NoError(t, err) err = cont.Init() if err != nil { t.Fatal(err) } ch, err := cont.Serve() if err != nil { t.Fatal(err) } sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) stopCh := make(chan struct{}, 1) errCh := make(chan error, 1) // Background goroutine handles container lifecycle events. // Errors are sent to errCh and checked on the main test goroutine // because testing.T.Fatal/FailNow must not be called from non-test goroutines. wg := &sync.WaitGroup{} wg.Go(func() { for { select { case e := <-ch: stopErr := cont.Stop() errCh <- errors.Join(fmt.Errorf("vertex %s: %w", e.VertexID, e.Error), stopErr) return case <-sig: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return case <-stopCh: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return } } }) time.Sleep(time.Second) t.Run("PingEchoWithOtel", func(t *testing.T) { conn, errDial := grpc.NewClient( "127.0.0.1:9192", grpc.WithTransportCredentials(insecure.NewCredentials()), ) require.NoError(t, errDial) require.NotNil(t, conn) defer func() { _ = conn.Close() }() ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() client := service.NewEchoClient(conn) resp, errPing := client.Ping(ctx, &service.Message{Msg: "hello"}) require.NoError(t, errPing) require.Equal(t, "HELLO", resp.GetMsg()) }) stopCh <- struct{}{} wg.Wait() select { case err := <-errCh: t.Fatal(err) default: } } ================================================ FILE: tests/e2e_http_test.go ================================================ package tests import ( compressGzip "compress/gzip" "errors" "fmt" "io" "log/slog" "net/http" "os" "os/signal" "sync" "syscall" "testing" "time" mocklogger "tests/mock" "github.com/roadrunner-server/config/v5" "github.com/roadrunner-server/endure/v2" gzipPlugin "github.com/roadrunner-server/gzip/v5" "github.com/roadrunner-server/headers/v5" httpPlugin "github.com/roadrunner-server/http/v5" rrOtel "github.com/roadrunner-server/otel/v5" "github.com/roadrunner-server/prometheus/v5" proxyIP "github.com/roadrunner-server/proxy_ip_parser/v5" rpcPlugin "github.com/roadrunner-server/rpc/v5" "github.com/roadrunner-server/send/v5" "github.com/roadrunner-server/server/v5" "github.com/roadrunner-server/static/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" ) // newHTTPClient returns an HTTP client with a reasonable request timeout for e2e tests. func newHTTPClient() *http.Client { return &http.Client{Timeout: 5 * time.Second} } // TestHTTPWithMiddleware verifies that the HTTP plugin works end-to-end with // headers, gzip, prometheus metrics, proxy_ip_parser, and sendfile middleware // all wired together via the Endure DI container. func TestHTTPWithMiddleware(t *testing.T) { cont := endure.New(slog.LevelDebug) cfg := &config.Plugin{ Version: "2024.1.0", Path: "configs/.rr-http-middleware.yaml", } l, _ := mocklogger.ZapTestLogger(zap.DebugLevel) err := cont.RegisterAll( cfg, &server.Plugin{}, &rpcPlugin.Plugin{}, &httpPlugin.Plugin{}, &headers.Plugin{}, &gzipPlugin.Plugin{}, &prometheus.Plugin{}, &proxyIP.Plugin{}, &send.Plugin{}, l, ) assert.NoError(t, err) err = cont.Init() if err != nil { t.Fatal(err) } ch, err := cont.Serve() if err != nil { t.Fatal(err) } sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) stopCh := make(chan struct{}, 1) errCh := make(chan error, 1) // Background goroutine handles container lifecycle events. // Errors are sent to errCh and checked on the main test goroutine // because testing.T.Fatal/FailNow must not be called from non-test goroutines. wg := &sync.WaitGroup{} wg.Go(func() { for { select { case e := <-ch: stopErr := cont.Stop() errCh <- errors.Join(fmt.Errorf("vertex %s: %w", e.VertexID, e.Error), stopErr) return case <-sig: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return case <-stopCh: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return } } }) time.Sleep(time.Second) t.Run("EchoWithMiddleware", func(t *testing.T) { req, errReq := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://127.0.0.1:18950/?hello=world", nil) require.NoError(t, errReq) req.Header.Set("Accept-Encoding", "gzip") resp, errDo := newHTTPClient().Do(req) require.NoError(t, errDo) defer func() { _ = resp.Body.Close() }() assert.Equal(t, http.StatusCreated, resp.StatusCode) // Verify the headers middleware added our custom response header. assert.Equal(t, "e2e-roadrunner", resp.Header.Get("X-Test")) // Verify gzip encoding is applied. assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding")) // Decompress and verify the response body. gr, errGz := compressGzip.NewReader(resp.Body) require.NoError(t, errGz) defer func() { _ = gr.Close() }() body, errRead := io.ReadAll(gr) require.NoError(t, errRead) assert.Equal(t, "WORLD", string(body)) }) stopCh <- struct{}{} wg.Wait() select { case err := <-errCh: t.Fatal(err) default: } } // TestHTTPStaticFile verifies that the static middleware serves files from disk, // and that non-static requests fall through to the PHP worker. func TestHTTPStaticFile(t *testing.T) { cont := endure.New(slog.LevelDebug) cfg := &config.Plugin{ Version: "2024.1.0", Path: "configs/.rr-http-static.yaml", } l, _ := mocklogger.ZapTestLogger(zap.DebugLevel) err := cont.RegisterAll( cfg, &server.Plugin{}, &rpcPlugin.Plugin{}, &httpPlugin.Plugin{}, &static.Plugin{}, &gzipPlugin.Plugin{}, l, ) assert.NoError(t, err) err = cont.Init() if err != nil { t.Fatal(err) } ch, err := cont.Serve() if err != nil { t.Fatal(err) } sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) stopCh := make(chan struct{}, 1) errCh := make(chan error, 1) // Background goroutine handles container lifecycle events. // Errors are sent to errCh and checked on the main test goroutine // because testing.T.Fatal/FailNow must not be called from non-test goroutines. wg := &sync.WaitGroup{} wg.Go(func() { for { select { case e := <-ch: stopErr := cont.Stop() errCh <- errors.Join(fmt.Errorf("vertex %s: %w", e.VertexID, e.Error), stopErr) return case <-sig: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return case <-stopCh: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return } } }) time.Sleep(time.Second) t.Run("ServeStaticFile", func(t *testing.T) { req, errReq := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://127.0.0.1:18951/sample.txt", nil) require.NoError(t, errReq) resp, errDo := newHTTPClient().Do(req) require.NoError(t, errDo) defer func() { _ = resp.Body.Close() }() assert.Equal(t, http.StatusOK, resp.StatusCode) body, errRead := io.ReadAll(resp.Body) require.NoError(t, errRead) assert.Contains(t, string(body), "Hello from RoadRunner e2e static file test!") }) t.Run("FallThroughToPHP", func(t *testing.T) { req, errReq := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://127.0.0.1:18951/?hello=world", nil) require.NoError(t, errReq) resp, errDo := newHTTPClient().Do(req) require.NoError(t, errDo) defer func() { _ = resp.Body.Close() }() assert.Equal(t, http.StatusCreated, resp.StatusCode) body, errRead := io.ReadAll(resp.Body) require.NoError(t, errRead) assert.Equal(t, "WORLD", string(body)) }) stopCh <- struct{}{} wg.Wait() select { case err := <-errCh: t.Fatal(err) default: } } // TestHTTPWithOtel verifies HTTP + real OTEL plugin integration. // The OTEL plugin is registered as an HTTP middleware alongside gzip. // This test exercises the full OTEL plugin lifecycle (Init/Serve/Stop) // with stdout exporter (no external collector needed). func TestHTTPWithOtel(t *testing.T) { cont := endure.New(slog.LevelDebug) cfg := &config.Plugin{ Version: "2024.1.0", Path: "configs/.rr-http-otel.yaml", } l, _ := mocklogger.ZapTestLogger(zap.DebugLevel) err := cont.RegisterAll( cfg, &server.Plugin{}, &rpcPlugin.Plugin{}, &httpPlugin.Plugin{}, &gzipPlugin.Plugin{}, &rrOtel.Plugin{}, l, ) assert.NoError(t, err) err = cont.Init() require.NoError(t, err) ch, err := cont.Serve() require.NoError(t, err) sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) stopCh := make(chan struct{}, 1) errCh := make(chan error, 1) // Background goroutine handles container lifecycle events. // Errors are sent to errCh and checked on the main test goroutine // because testing.T.Fatal/FailNow must not be called from non-test goroutines. wg := &sync.WaitGroup{} wg.Go(func() { for { select { case e := <-ch: stopErr := cont.Stop() errCh <- errors.Join(fmt.Errorf("vertex %s: %w", e.VertexID, e.Error), stopErr) return case <-sig: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return case <-stopCh: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return } } }) time.Sleep(time.Second) t.Run("EchoWithOtel", func(t *testing.T) { req, errReq := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://127.0.0.1:18952/?hello=world", nil) require.NoError(t, errReq) resp, errDo := newHTTPClient().Do(req) require.NoError(t, errDo) defer func() { _ = resp.Body.Close() }() assert.Equal(t, http.StatusCreated, resp.StatusCode) body, errRead := io.ReadAll(resp.Body) require.NoError(t, errRead) assert.Equal(t, "WORLD", string(body)) }) stopCh <- struct{}{} wg.Wait() select { case err := <-errCh: t.Fatal(err) default: } } ================================================ FILE: tests/e2e_jobs_test.go ================================================ package tests import ( "errors" "fmt" "log/slog" "os" "os/signal" "sync" "syscall" "testing" "time" mocklogger "tests/mock" "tests/helpers" "github.com/roadrunner-server/config/v5" "github.com/roadrunner-server/endure/v2" "github.com/roadrunner-server/informer/v5" "github.com/roadrunner-server/jobs/v5" "github.com/roadrunner-server/memory/v5" rrOtel "github.com/roadrunner-server/otel/v5" "github.com/roadrunner-server/resetter/v5" rpcPlugin "github.com/roadrunner-server/rpc/v5" "github.com/roadrunner-server/server/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" ) // TestJobsInMemory verifies the full jobs lifecycle using the in-memory driver: // container startup, pipeline consumption, job push, processing, and teardown. func TestJobsInMemory(t *testing.T) { cont := endure.New(slog.LevelDebug) cfg := &config.Plugin{ Version: "2024.1.0", Path: "configs/.rr-jobs-memory.yaml", } l, oLogger := mocklogger.ZapTestLogger(zap.DebugLevel) err := cont.RegisterAll( cfg, &server.Plugin{}, &rpcPlugin.Plugin{}, &jobs.Plugin{}, &memory.Plugin{}, &resetter.Plugin{}, &informer.Plugin{}, l, ) assert.NoError(t, err) err = cont.Init() if err != nil { t.Fatal(err) } ch, err := cont.Serve() if err != nil { t.Fatal(err) } sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) stopCh := make(chan struct{}, 1) errCh := make(chan error, 1) // Background goroutine handles container lifecycle events. // Errors are sent to errCh and checked on the main test goroutine // because testing.T.Fatal/FailNow must not be called from non-test goroutines. wg := &sync.WaitGroup{} wg.Go(func() { for { select { case e := <-ch: stopErr := cont.Stop() errCh <- errors.Join(fmt.Errorf("vertex %s: %w", e.VertexID, e.Error), stopErr) return case <-sig: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return case <-stopCh: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return } } }) time.Sleep(time.Second * 3) t.Run("PushToTest1", helpers.PushToPipe("test-1", false, "127.0.0.1:6201")) t.Run("PushToTest2", helpers.PushToPipe("test-2", false, "127.0.0.1:6201")) time.Sleep(time.Second * 2) t.Run("DestroyPipelines", helpers.DestroyPipelines("127.0.0.1:6201", "test-1", "test-2")) stopCh <- struct{}{} wg.Wait() select { case err := <-errCh: t.Fatal(err) default: } require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("pipeline was started").Len(), 2) require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("pipeline was stopped").Len(), 2) require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("job was pushed successfully").Len(), 2) } // TestJobsInMemoryWithOtel verifies the jobs lifecycle with OTEL tracing enabled. // The OTEL plugin instruments job push/consume operations // (stdout exporter, no external collector). func TestJobsInMemoryWithOtel(t *testing.T) { cont := endure.New(slog.LevelDebug) cfg := &config.Plugin{ Version: "2024.1.0", Path: "configs/.rr-jobs-memory-otel.yaml", } l, oLogger := mocklogger.ZapTestLogger(zap.DebugLevel) err := cont.RegisterAll( cfg, &server.Plugin{}, &rpcPlugin.Plugin{}, &jobs.Plugin{}, &memory.Plugin{}, &resetter.Plugin{}, &informer.Plugin{}, &rrOtel.Plugin{}, l, ) assert.NoError(t, err) err = cont.Init() if err != nil { t.Fatal(err) } ch, err := cont.Serve() if err != nil { t.Fatal(err) } sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) stopCh := make(chan struct{}, 1) errCh := make(chan error, 1) // Background goroutine handles container lifecycle events. // Errors are sent to errCh and checked on the main test goroutine // because testing.T.Fatal/FailNow must not be called from non-test goroutines. wg := &sync.WaitGroup{} wg.Go(func() { for { select { case e := <-ch: stopErr := cont.Stop() errCh <- errors.Join(fmt.Errorf("vertex %s: %w", e.VertexID, e.Error), stopErr) return case <-sig: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return case <-stopCh: if stopErr := cont.Stop(); stopErr != nil { errCh <- stopErr } return } } }) time.Sleep(time.Second * 3) t.Run("PushToTest1WithOtel", helpers.PushToPipe("test-1", false, "127.0.0.1:6207")) t.Run("PushToTest2WithOtel", helpers.PushToPipe("test-2", false, "127.0.0.1:6207")) time.Sleep(time.Second * 2) t.Run("DestroyPipelinesWithOtel", helpers.DestroyPipelines("127.0.0.1:6207", "test-1", "test-2")) stopCh <- struct{}{} wg.Wait() select { case err := <-errCh: t.Fatal(err) default: } require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("pipeline was started").Len(), 2) require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("pipeline was stopped").Len(), 2) require.GreaterOrEqual(t, oLogger.FilterMessageSnippet("job was pushed successfully").Len(), 2) } ================================================ FILE: tests/go.mod ================================================ module tests go 1.26.1 require ( github.com/google/uuid v1.6.0 github.com/roadrunner-server/api/v4 v4.23.0 github.com/roadrunner-server/config/v5 v5.1.9 github.com/roadrunner-server/endure/v2 v2.6.2 github.com/roadrunner-server/goridge/v3 v3.8.3 github.com/roadrunner-server/grpc/v5 v5.3.0 github.com/roadrunner-server/gzip/v5 v5.3.0 github.com/roadrunner-server/headers/v5 v5.2.0 github.com/roadrunner-server/http/v5 v5.3.0 github.com/roadrunner-server/informer/v5 v5.1.9 github.com/roadrunner-server/jobs/v5 v5.1.9 github.com/roadrunner-server/memory/v5 v5.2.9 github.com/roadrunner-server/otel/v5 v5.5.0 github.com/roadrunner-server/prometheus/v5 v5.2.0 github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9 github.com/roadrunner-server/resetter/v5 v5.1.9 github.com/roadrunner-server/rpc/v5 v5.1.9 github.com/roadrunner-server/send/v5 v5.2.0 github.com/roadrunner-server/server/v5 v5.2.10 github.com/roadrunner-server/static/v5 v5.2.0 github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.27.1 google.golang.org/grpc v1.79.2 google.golang.org/protobuf v1.36.11 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/caddyserver/certmagic v0.25.2 // indirect github.com/caddyserver/zerossl v0.1.5 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/proto v1.14.3 // indirect github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.7.0-rc.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.18.4 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/libdns/libdns v1.1.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mholt/acmez v1.2.0 // indirect github.com/mholt/acmez/v3 v3.1.6 // indirect github.com/miekg/dns v1.1.72 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nexus-rpc/sdk-go v0.6.0 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.20.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/roadrunner-server/context v1.3.0 // indirect github.com/roadrunner-server/errors v1.4.1 // indirect github.com/roadrunner-server/events v1.0.1 // indirect github.com/roadrunner-server/pool v1.1.3 // indirect github.com/roadrunner-server/priority_queue v1.0.6 // indirect github.com/roadrunner-server/tcplisten v1.5.2 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/numcpus v0.11.0 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/assert v1.3.1 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 // indirect go.opentelemetry.io/otel v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/exporters/zipkin v1.42.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/otel/sdk v1.42.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.temporal.io/api v1.62.4 // indirect go.temporal.io/sdk v1.41.0 // indirect go.temporal.io/sdk/contrib/opentelemetry v0.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.49.0 // indirect golang.org/x/mod v0.34.0 // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.43.0 // indirect google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) exclude ( github.com/redis/go-redis/v9 v9.15.0 github.com/redis/go-redis/v9 v9.15.1 github.com/spf13/viper v1.18.0 github.com/spf13/viper v1.18.1 go.temporal.io/api v1.26.1 ) ================================================ FILE: tests/go.sum ================================================ code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc= github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q= github.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ= github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo= github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/roadrunner-server/api/v4 v4.23.0 h1:lrVXgP4ozD/H5DrIdT181ldVhD1R9QT5qsi8qWUTDF4= github.com/roadrunner-server/api/v4 v4.23.0/go.mod h1:AlHuVVOklb7XF33Cf7IfmwOn3j4gGg37on9Xi6j08Bg= github.com/roadrunner-server/config/v5 v5.1.9 h1:ReWwts/prEvuC4yVJ0BRDmY5sxw/1c+hGTSdJ71hIQU= github.com/roadrunner-server/config/v5 v5.1.9/go.mod h1:R6YyTWahW61tWHOI2BfdkQU/0Zc/2d6/JbJ/KEvq8F8= github.com/roadrunner-server/context v1.3.0 h1:iyTXVORhPU2/26z7kdzEaggwG5P8yhIKUDLiePjylFQ= github.com/roadrunner-server/context v1.3.0/go.mod h1:KPAzAlnErXekQazW9t4h55U1S42Q2bk0WCaPQrezJw4= github.com/roadrunner-server/endure/v2 v2.6.2 h1:sIB4kTyE7gtT3fDhuYWUYn6Vt/dcPtiA6FoNS1eS+84= github.com/roadrunner-server/endure/v2 v2.6.2/go.mod h1:t/2+xpNYgGBwhzn83y2MDhvhZ19UVq1REcvqn7j7RB8= github.com/roadrunner-server/errors v1.4.1 h1:LKNeaCGiwd3t8IaL840ZNF3UA9yDQlpvHnKddnh0YRQ= github.com/roadrunner-server/errors v1.4.1/go.mod h1:qeffnIKG0e4j1dzGpa+OGY5VKSfMphizvqWIw8s2lAo= github.com/roadrunner-server/events v1.0.1 h1:waCkKhxhzdK3VcI1xG22l+h+0J+Nfdpxjhyy01Un+kI= github.com/roadrunner-server/events v1.0.1/go.mod h1:WZRqoEVaFm209t52EuoT7ISUtvX6BrCi6bI/7pjkVC0= github.com/roadrunner-server/goridge/v3 v3.8.3 h1:XmjrOFnI6ZbQTPaP39DEk8KwLUNTgjluK3pcZaW6ixQ= github.com/roadrunner-server/goridge/v3 v3.8.3/go.mod h1:4TZU8zgkKIZCsH51qwGMpvyXCT59u/8z6q8sCe4ZGAQ= github.com/roadrunner-server/grpc/v5 v5.3.0 h1:DZj7b6vU+1bo5oAAUil75JUuovAwZzXh41ZS7rUE6xQ= github.com/roadrunner-server/grpc/v5 v5.3.0/go.mod h1:A3G5eJZMQYnbzWWZ9crPSS557wPfwEc3uWr48RtbAVQ= github.com/roadrunner-server/gzip/v5 v5.3.0 h1:l6H4NQmX2RaaTWkeEt0q39ITLcMKL8FmBXm3ENUE4oI= github.com/roadrunner-server/gzip/v5 v5.3.0/go.mod h1:fXhfwelVKIJvon8jEdZrqobbE1zJoQkN3Ov7mO3V5XI= github.com/roadrunner-server/headers/v5 v5.2.0 h1:Q8SYxr+zhj5VskDPKq3JYyZA3XqcGxtYcb5uh4FytFY= github.com/roadrunner-server/headers/v5 v5.2.0/go.mod h1:BEeI6pI0CyVjE8gG6H5LVPRxZ2kDtMFoHupFE2SG2wU= github.com/roadrunner-server/http/v5 v5.3.0 h1:gsNdOQffWF3/hL7T+OTQat36SvCl5ZUjuotz41rONQ4= github.com/roadrunner-server/http/v5 v5.3.0/go.mod h1:JCM8jmgRtaPMFiKPcriOxNQ98ha821uzN5BQ5XJzc1I= github.com/roadrunner-server/informer/v5 v5.1.9 h1:yl334LMqUoWXfeP4299HgY9G7mq6kX6FVCSwT+cYdfQ= github.com/roadrunner-server/informer/v5 v5.1.9/go.mod h1:JPzSsDjLHExdQ9SbT9e8H/oB7pajgCScL/G70saQzSA= github.com/roadrunner-server/jobs/v5 v5.1.9 h1:28biT9tGTYSXV+FWJCLfEOCV3sJZ7VVd46oJLhIfJkM= github.com/roadrunner-server/jobs/v5 v5.1.9/go.mod h1:3qiklMKRqMHl0+TbrFQWzaNrtqDGkPFdgZC/dFqRBNU= github.com/roadrunner-server/memory/v5 v5.2.9 h1:niLep2dyUaYhrn+I12kTXjGUTUSbVJa+jyvNJrbt/hw= github.com/roadrunner-server/memory/v5 v5.2.9/go.mod h1:UcE3Sf0TYRmAG1HukXmzsztGD0nBi6RRhG75K11Fqpk= github.com/roadrunner-server/otel/v5 v5.5.0 h1:IYMw04K110sMANyZpauvQ3rRMnq0zZhwQbISHLisfQE= github.com/roadrunner-server/otel/v5 v5.5.0/go.mod h1:B1EaW4hC2VGUKCKLw9CZYKxuQid+2Zb7ojzXpRUb6oo= github.com/roadrunner-server/pool v1.1.3 h1:KMsiL6yuYBWGk73bdO0akwP+fJ63bxDF972JukCGsxI= github.com/roadrunner-server/pool v1.1.3/go.mod h1:8ceC7NvZKJRciv+KJmcyk5CeDugoel6GD+crm5kBFW0= github.com/roadrunner-server/priority_queue v1.0.6 h1:x8bcMyjWs2Z4ySbO9BTP8Dzy2prCuazJY9HHrVTmUVY= github.com/roadrunner-server/priority_queue v1.0.6/go.mod h1:aJ2D9s18+OGpFfNgwoIduraaFYBGv4FKElnpzqO+TBI= github.com/roadrunner-server/prometheus/v5 v5.2.0 h1:1KOacYOJL6ZN4vCf+SZIREFIqaYZ8WgWOiZifS88gns= github.com/roadrunner-server/prometheus/v5 v5.2.0/go.mod h1:tzN4CZataiCzNyY9V5E0leLqvDmcWySUVnSxcwxFIrc= github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9 h1:GoEgKJ97jozcJRecBMa5orMDWByaCwXUqg2zSUXl2M8= github.com/roadrunner-server/proxy_ip_parser/v5 v5.1.9/go.mod h1:DWaHaqpitzUbDVfK1M5ojC7bqUeOn6CAa5oxntU00po= github.com/roadrunner-server/resetter/v5 v5.1.9 h1:rH1nxkgvItbMEp1/JFZqcijOkkav4zo0E4wcVXBcXa8= github.com/roadrunner-server/resetter/v5 v5.1.9/go.mod h1:P5TfzCGQMNsUDPTrjOU7XFLdRz+DtFH/FmRla+lUF94= github.com/roadrunner-server/rpc/v5 v5.1.9 h1:AbRd2xEkWY8N3J4GUhoDeL+pnfwzKHazV4k40jZ+YDk= github.com/roadrunner-server/rpc/v5 v5.1.9/go.mod h1:TtbPY1cPvL46Mk9Dh4qwx33WO4R3m1lZkgL2q2MfHtA= github.com/roadrunner-server/send/v5 v5.2.0 h1:nQMaO7u9z+wYD1bInF/6ctImEaCJZFHscd3cjC1zNOc= github.com/roadrunner-server/send/v5 v5.2.0/go.mod h1:HmiJmpB6Lsd3jHLCy8pYG5J6E9R7IHURFr8icokNkog= github.com/roadrunner-server/server/v5 v5.2.10 h1:IshR3mlXJ/fh+zY17tN3Q7GgaanMzA7H9wwEz4PxCSA= github.com/roadrunner-server/server/v5 v5.2.10/go.mod h1:oxXhRo2EDykCk8ujhdjyJCb2rPpoTt8AVy46RHRfeeo= github.com/roadrunner-server/static/v5 v5.2.0 h1:xpZgIlvKWdWUJ37BtbNLtL/kN1rNFMEwFZbBnzWDeEM= github.com/roadrunner-server/static/v5 v5.2.0/go.mod h1:jp3eATJDSLmidXI25YDievkKp5XZ35qK6g4vHwpS0Ug= github.com/roadrunner-server/tcplisten v1.5.2 h1:nn8yXYrhRDkfQ9AAu4V075uT4fZRmOnpxkawgE+bWPA= github.com/roadrunner-server/tcplisten v1.5.2/go.mod h1:DufGBz7Dlx2KrNe/4RukEvGMTqZKB0Uve1GztwcyyR8= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A= github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 h1:jP8unWI6q5kcb3gpGLjKDGaUa+JW+nHKWvpS/q+YuWA= go.opentelemetry.io/contrib/propagators/jaeger v1.42.0/go.mod h1:xd89e/pUyPatUP1C4z1UknD9jHptESO99tWyvd4mWD4= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/exporters/zipkin v1.42.0 h1:Z7ARHF7193vyVltPYcmuhSKPLf8dP5rtJZLtTQnbMH4= go.opentelemetry.io/otel/exporters/zipkin v1.42.0/go.mod h1:DW09+gaEg5kydlb9g8kp4Nos3yqo9YSA1uHXkeJihXc= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.temporal.io/api v1.62.4 h1:XxstCG0LWfAqMsQAMk8kIx92l47FtJlIOKFWF3ydOUE= go.temporal.io/sdk v1.41.0 h1:c9tayCQJDM5ZQdrqjGmjqk5ejxUtsEScJGF94sAVYpM= go.temporal.io/sdk v1.41.0/go.mod h1:/InXQT5guZ6AizYzpmzr5avQ/GMgq1ZObcKlKE2AhTc= go.temporal.io/sdk/contrib/opentelemetry v0.7.0 h1:GSna1HP+1ibNXZ9xlVdQU2zFVqdt5VcdF0dzpeaYccQ= go.temporal.io/sdk/contrib/opentelemetry v0.7.0/go.mod h1:oQJC6UIl3FbSYh4f2MlUAIYSE6FPw02X1Tw8/bOvfxg= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE= google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw= google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js= google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts= google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: tests/helpers/doc.go ================================================ // Package helpers provides RPC helper functions for end-to-end tests. // Each helper returns a func(t *testing.T) suitable for use with t.Run(), // performing operations like pushing jobs, pausing pipelines, and collecting // statistics via Goridge RPC. package helpers ================================================ FILE: tests/helpers/helpers.go ================================================ package helpers import ( "context" "net" "net/rpc" "testing" "time" "github.com/google/uuid" jobsProto "github.com/roadrunner-server/api/v4/build/jobs/v1" goridgeRpc "github.com/roadrunner-server/goridge/v3/pkg/rpc" "github.com/stretchr/testify/require" ) const ( push = "jobs.Push" pause = "jobs.Pause" destroy = "jobs.Destroy" resume = "jobs.Resume" dialTimeout = 5 * time.Second ) // rpcClient dials the given address with a timeout and returns a Goridge RPC // client. The client is automatically closed via t.Cleanup when the test ends. func rpcClient(t *testing.T, address string) *rpc.Client { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), dialTimeout) defer cancel() conn, err := new(net.Dialer).DialContext(ctx, "tcp", address) require.NoError(t, err) client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) t.Cleanup(func() { _ = client.Close() }) return client } // callPipelines is a generic helper that calls the given RPC method // on the specified pipelines. func callPipelines(t *testing.T, address, method string, pipes []string) { t.Helper() client := rpcClient(t, address) pipe := &jobsProto.Pipelines{Pipelines: make([]string, len(pipes))} for i := range pipes { pipe.GetPipelines()[i] = pipes[i] } er := &jobsProto.Empty{} err := client.Call(method, pipe, er) require.NoError(t, err) } // ResumePipes resumes the specified pipelines via RPC. func ResumePipes(address string, pipes ...string) func(t *testing.T) { return func(t *testing.T) { t.Helper() callPipelines(t, address, resume, pipes) } } // PausePipelines pauses the specified pipelines via RPC. func PausePipelines(address string, pipes ...string) func(t *testing.T) { return func(t *testing.T) { t.Helper() callPipelines(t, address, pause, pipes) } } // PushToPipe pushes a single job to the specified pipeline via RPC. func PushToPipe(pipeline string, autoAck bool, address string) func(t *testing.T) { return func(t *testing.T) { t.Helper() client := rpcClient(t, address) req := &jobsProto.PushRequest{Job: &jobsProto.Job{ Job: "some/php/namespace", Id: uuid.NewString(), Payload: []byte(`{"hello":"world"}`), Headers: map[string]*jobsProto.HeaderValue{"test": {Value: []string{"test2"}}}, Options: &jobsProto.Options{ AutoAck: autoAck, Priority: 1, Pipeline: pipeline, Topic: pipeline, }, }} er := &jobsProto.Empty{} err := client.Call(push, req, er) require.NoError(t, err) } } // DestroyPipelines destroys the specified pipelines via RPC, retrying up to 10 times. func DestroyPipelines(address string, pipes ...string) func(t *testing.T) { return func(t *testing.T) { t.Helper() client := rpcClient(t, address) pipe := &jobsProto.Pipelines{Pipelines: make([]string, len(pipes))} for i := range pipes { pipe.GetPipelines()[i] = pipes[i] } var lastErr error for range 10 { er := &jobsProto.Empty{} lastErr = client.Call(destroy, pipe, er) if lastErr != nil { time.Sleep(time.Second) continue } return } require.NoError(t, lastErr) } } ================================================ FILE: tests/mock/doc.go ================================================ // Package mocklogger provides a mock logger plugin for integration tests. // It implements the Endure plugin interface and captures log entries for // assertion via ObservedLogs. package mocklogger ================================================ FILE: tests/mock/logger.go ================================================ package mocklogger import ( "github.com/roadrunner-server/endure/v2/dep" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // Logger is the interface that the mock logger provides via Endure DI. type Logger interface { NamedLogger(string) *zap.Logger } // ZapLoggerMock is a mock logger plugin for integration tests. // It captures all log entries for later assertion via ObservedLogs. type ZapLoggerMock struct { l *zap.Logger } // ZapTestLogger creates a new mock logger plugin and returns the plugin // instance along with an ObservedLogs for asserting on log messages. func ZapTestLogger(enab zapcore.LevelEnabler) (*ZapLoggerMock, *ObservedLogs) { core, logs := New(enab) obsLog := zap.New(core, zap.Development()) return &ZapLoggerMock{ l: obsLog, }, logs } func (z *ZapLoggerMock) Init() error { return nil } func (z *ZapLoggerMock) Serve() chan error { return make(chan error, 1) } func (z *ZapLoggerMock) Stop() error { return z.l.Sync() } func (z *ZapLoggerMock) Provides() []*dep.Out { return []*dep.Out{ dep.Bind((*Logger)(nil), z.ProvideLogger), } } func (z *ZapLoggerMock) Weight() uint { return 100 } // ProvideLogger returns the Log instance for Endure dependency injection. func (z *ZapLoggerMock) ProvideLogger() *Log { return NewLog(z.l) } // Log wraps a zap.Logger to satisfy the Logger interface. type Log struct { base *zap.Logger } // NewLog creates a new Log from a zap.Logger. func NewLog(log *zap.Logger) *Log { return &Log{ base: log, } } // NamedLogger returns the underlying zap.Logger scoped with the given name. func (l *Log) NamedLogger(name string) *zap.Logger { return l.base.Named(name) } ================================================ FILE: tests/mock/observer.go ================================================ package mocklogger // Copyright (c) 2017 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import ( "strings" "sync" "time" "go.uber.org/zap/zapcore" ) // LoggedEntry is an encoding-agnostic representation of a log message. type LoggedEntry struct { zapcore.Entry Context []zapcore.Field } // ContextMap returns a map for all fields in Context. func (e LoggedEntry) ContextMap() map[string]any { encoder := zapcore.NewMapObjectEncoder() for _, f := range e.Context { f.AddTo(encoder) } return encoder.Fields } // ObservedLogs is a concurrency-safe, ordered collection of observed logs. type ObservedLogs struct { mu sync.RWMutex logs []LoggedEntry } // Len returns the number of items in the collection. func (o *ObservedLogs) Len() int { o.mu.RLock() n := len(o.logs) o.mu.RUnlock() return n } // All returns a copy of all the observed logs. func (o *ObservedLogs) All() []LoggedEntry { o.mu.RLock() ret := make([]LoggedEntry, len(o.logs)) copy(ret, o.logs) o.mu.RUnlock() return ret } // TakeAll returns a copy of all the observed logs, and truncates the observed // slice. func (o *ObservedLogs) TakeAll() []LoggedEntry { o.mu.Lock() ret := o.logs o.logs = nil o.mu.Unlock() return ret } // AllUntimed returns a copy of all the observed logs, but overwrites the // observed timestamps with time.Time's zero value. func (o *ObservedLogs) AllUntimed() []LoggedEntry { ret := o.All() for i := range ret { ret[i].Time = time.Time{} } return ret } // FilterLevelExact filters entries to those logged at exactly the given level. func (o *ObservedLogs) FilterLevelExact(level zapcore.Level) *ObservedLogs { return o.Filter(func(e LoggedEntry) bool { return e.Level == level }) } // FilterMessage filters entries to those that have the specified message. func (o *ObservedLogs) FilterMessage(msg string) *ObservedLogs { return o.Filter(func(e LoggedEntry) bool { return e.Message == msg }) } // FilterMessageSnippet filters entries to those that have a message containing // the specified snippet. func (o *ObservedLogs) FilterMessageSnippet(snippet string) *ObservedLogs { return o.Filter(func(e LoggedEntry) bool { return strings.Contains(e.Message, snippet) }) } // FilterField filters entries to those that have the specified field. func (o *ObservedLogs) FilterField(field zapcore.Field) *ObservedLogs { return o.Filter(func(e LoggedEntry) bool { for _, ctxField := range e.Context { if ctxField.Equals(field) { return true } } return false }) } // FilterFieldKey filters entries to those that have the specified key. func (o *ObservedLogs) FilterFieldKey(key string) *ObservedLogs { return o.Filter(func(e LoggedEntry) bool { for _, ctxField := range e.Context { if ctxField.Key == key { return true } } return false }) } // Filter returns a copy of this ObservedLogs containing only those entries // for which the provided function returns true. func (o *ObservedLogs) Filter(keep func(LoggedEntry) bool) *ObservedLogs { o.mu.RLock() defer o.mu.RUnlock() var filtered []LoggedEntry for _, entry := range o.logs { if keep(entry) { filtered = append(filtered, entry) } } return &ObservedLogs{logs: filtered} } func (o *ObservedLogs) add(log LoggedEntry) { o.mu.Lock() o.logs = append(o.logs, log) o.mu.Unlock() } // New creates a new Core that buffers logs in memory (without any encoding). func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs) { ol := &ObservedLogs{} return &contextObserver{ LevelEnabler: enab, logs: ol, }, ol } type contextObserver struct { zapcore.LevelEnabler logs *ObservedLogs context []zapcore.Field } func (co *contextObserver) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { if co.Enabled(ent.Level) { return ce.AddCore(ent, co) } return ce } func (co *contextObserver) With(fields []zapcore.Field) zapcore.Core { return &contextObserver{ LevelEnabler: co.LevelEnabler, logs: co.logs, context: append(co.context[:len(co.context):len(co.context)], fields...), } } func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error { all := make([]zapcore.Field, 0, len(fields)+len(co.context)) all = append(all, co.context...) all = append(all, fields...) co.logs.add(LoggedEntry{Entry: ent, Context: all}) return nil } func (co *contextObserver) Sync() error { return nil } ================================================ FILE: tests/php_test_files/.gitignore ================================================ vendor/ composer.lock ================================================ FILE: tests/php_test_files/composer.json ================================================ { "name": "test/e2e", "description": "PHP dependencies for RoadRunner e2e tests", "minimum-stability": "dev", "prefer-stable": true, "require": { "nyholm/psr7": "^1.5", "spiral/roadrunner-http": "^3.5", "spiral/roadrunner-worker": "^3.5", "spiral/roadrunner-jobs": "^4.0", "spiral/roadrunner-grpc": "^3.0", "spiral/goridge": "^4.0" }, "autoload": { "psr-4": { "": "grpc/src" } }, "config": { "allow-plugins": { "php-http/discovery": true } } } ================================================ FILE: tests/php_test_files/grpc/src/EchoService.php ================================================ setMsg(strtoupper($in->getMsg())); } } ================================================ FILE: tests/php_test_files/grpc/src/Health/HealthCheckRequest.php ================================================ grpc.health.v1.HealthCheckRequest */ class HealthCheckRequest extends \Google\Protobuf\Internal\Message { /** * Generated from protobuf field string service = 1; */ protected $service = ''; /** * Constructor. * * @param array $data { * Optional. Data for populating the Message object. * * @type string $service * } */ public function __construct($data = NULL) { \GPBMetadata\Health::initOnce(); parent::__construct($data); } /** * Generated from protobuf field string service = 1; * @return string */ public function getService() { return $this->service; } /** * Generated from protobuf field string service = 1; * @param string $var * @return $this */ public function setService($var) { GPBUtil::checkString($var, True); $this->service = $var; return $this; } } ================================================ FILE: tests/php_test_files/grpc/src/Health/HealthCheckResponse/ServingStatus.php ================================================ grpc.health.v1.HealthCheckResponse.ServingStatus */ class ServingStatus { /** * Generated from protobuf enum UNKNOWN = 0; */ const UNKNOWN = 0; /** * Generated from protobuf enum SERVING = 1; */ const SERVING = 1; /** * Generated from protobuf enum NOT_SERVING = 2; */ const NOT_SERVING = 2; /** * Used only by the Watch method. * * Generated from protobuf enum SERVICE_UNKNOWN = 3; */ const SERVICE_UNKNOWN = 3; private static $valueToName = [ self::UNKNOWN => 'UNKNOWN', self::SERVING => 'SERVING', self::NOT_SERVING => 'NOT_SERVING', self::SERVICE_UNKNOWN => 'SERVICE_UNKNOWN', ]; public static function name($value) { if (!isset(self::$valueToName[$value])) { throw new UnexpectedValueException(sprintf( 'Enum %s has no name defined for value %s', __CLASS__, $value)); } return self::$valueToName[$value]; } public static function value($name) { $const = __CLASS__ . '::' . strtoupper($name); if (!defined($const)) { throw new UnexpectedValueException(sprintf( 'Enum %s has no value defined for name %s', __CLASS__, $name)); } return constant($const); } } // Adding a class alias for backwards compatibility with the previous class name. class_alias(ServingStatus::class, \Health\HealthCheckResponse_ServingStatus::class); ================================================ FILE: tests/php_test_files/grpc/src/Health/HealthCheckResponse.php ================================================ grpc.health.v1.HealthCheckResponse */ class HealthCheckResponse extends \Google\Protobuf\Internal\Message { /** * Generated from protobuf field .grpc.health.v1.HealthCheckResponse.ServingStatus status = 1; */ protected $status = 0; /** * Constructor. * * @param array $data { * Optional. Data for populating the Message object. * * @type int $status * } */ public function __construct($data = NULL) { \GPBMetadata\Health::initOnce(); parent::__construct($data); } /** * Generated from protobuf field .grpc.health.v1.HealthCheckResponse.ServingStatus status = 1; * @return int */ public function getStatus() { return $this->status; } /** * Generated from protobuf field .grpc.health.v1.HealthCheckResponse.ServingStatus status = 1; * @param int $var * @return $this */ public function setStatus($var) { GPBUtil::checkEnum($var, \Health\HealthCheckResponse\ServingStatus::class); $this->status = $var; return $this; } } ================================================ FILE: tests/php_test_files/grpc/src/Health/HealthInterface.php ================================================ setStatus(HealthCheckResponse\ServingStatus::SERVING); return $out; } public function Watch(ContextInterface $ctx, HealthCheckRequest $in): HealthCheckResponse { $out = new HealthCheckResponse(); $out->setStatus(HealthCheckResponse\ServingStatus::SERVING); return $out; } } ================================================ FILE: tests/php_test_files/grpc/src/Service/EchoInterface.php ================================================ service.Message */ class Message extends \Google\Protobuf\Internal\Message { /** * Generated from protobuf field string msg = 1; */ protected $msg = ''; /** * Constructor. * * @param array $data { * Optional. Data for populating the Message object. * * @type string $msg * } */ public function __construct($data = NULL) { \GPBMetadata\Service::initOnce(); parent::__construct($data); } /** * Generated from protobuf field string msg = 1; * @return string */ public function getMsg() { return $this->msg; } /** * Generated from protobuf field string msg = 1; * @param string $var * @return $this */ public function setMsg($var) { GPBUtil::checkString($var, True); $this->msg = $var; return $this; } } ================================================ FILE: tests/php_test_files/grpc/worker-grpc.php ================================================ registerService(EchoInterface::class, new EchoService()); $server->registerService(HealthInterface::class, new HealthService()); $server->serve(Worker::create()); ================================================ FILE: tests/php_test_files/http/client.php ================================================ * * handler_name: name of the PHP file in this directory (without .php) * relay_type: "pipes", "tcp", or "unix" */ use Spiral\Goridge; use Spiral\RoadRunner; ini_set('display_errors', 'stderr'); require dirname(__DIR__) . "/vendor/autoload.php"; if (count($argv) < 3) { die("need 2 arguments"); } [$test, $goridge] = [$argv[1], $argv[2]]; switch ($goridge) { case "pipes": $relay = new Goridge\StreamRelay(STDIN, STDOUT); break; case "tcp": $relay = new Goridge\SocketRelay("127.0.0.1", 9007); break; case "unix": $relay = new Goridge\SocketRelay( "sock.unix", null, Goridge\SocketRelay::SOCK_UNIX ); break; default: die("invalid protocol selection"); } $psr7 = new RoadRunner\Http\PSR7Worker( new RoadRunner\Worker($relay), new \Nyholm\Psr7\Factory\Psr17Factory(), new \Nyholm\Psr7\Factory\Psr17Factory(), new \Nyholm\Psr7\Factory\Psr17Factory() ); require_once sprintf("%s/%s.php", __DIR__, $test); while ($req = $psr7->waitRequest()) { try { $psr7->respond(handleRequest($req, new \Nyholm\Psr7\Response())); } catch (\Throwable $e) { $psr7->getWorker()->error((string)$e); } } ================================================ FILE: tests/php_test_files/http/echo.php ================================================ getBody()->write(strtoupper($req->getQueryParams()['hello'] ?? '')); return $resp->withStatus(201); } ================================================ FILE: tests/php_test_files/jobs/jobs_ok.php ================================================ waitTask()) { try { $task->complete(); } catch (\Throwable $e) { $task->error((string)$e); } } ================================================ FILE: tests/proto/service/service.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.0-devel // protoc v3.21.2 // source: service.proto package service import ( reflect "reflect" sync "sync" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type Message struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` } func (x *Message) Reset() { *x = Message{} if protoimpl.UnsafeEnabled { mi := &file_service_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Message) String() string { return protoimpl.X.MessageStringOf(x) } func (*Message) ProtoMessage() {} func (x *Message) ProtoReflect() protoreflect.Message { mi := &file_service_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Message.ProtoReflect.Descriptor instead. func (*Message) Descriptor() ([]byte, []int) { return file_service_proto_rawDescGZIP(), []int{0} } func (x *Message) GetMsg() string { if x != nil { return x.Msg } return "" } var File_service_proto protoreflect.FileDescriptor var file_service_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x1b, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x32, 0x34, 0x0a, 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x2c, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x10, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_service_proto_rawDescOnce sync.Once file_service_proto_rawDescData = file_service_proto_rawDesc ) func file_service_proto_rawDescGZIP() []byte { file_service_proto_rawDescOnce.Do(func() { file_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_service_proto_rawDescData) }) return file_service_proto_rawDescData } var file_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_service_proto_goTypes = []interface{}{ (*Message)(nil), // 0: service.Message } var file_service_proto_depIdxs = []int32{ 0, // 0: service.Echo.Ping:input_type -> service.Message 0, // 1: service.Echo.Ping:output_type -> service.Message 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name } func init() { file_service_proto_init() } func file_service_proto_init() { if File_service_proto != nil { return } if !protoimpl.UnsafeEnabled { file_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Message); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_service_proto_rawDesc, NumEnums: 0, NumMessages: 1, NumExtensions: 0, NumServices: 1, }, GoTypes: file_service_proto_goTypes, DependencyIndexes: file_service_proto_depIdxs, MessageInfos: file_service_proto_msgTypes, }.Build() File_service_proto = out.File file_service_proto_rawDesc = nil file_service_proto_goTypes = nil file_service_proto_depIdxs = nil } ================================================ FILE: tests/proto/service/service.proto ================================================ syntax = "proto3"; package service; option go_package = "./;service"; service Echo { rpc Ping(Message) returns (Message) {} } message Message { string msg = 1; } ================================================ FILE: tests/proto/service/service_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 // - protoc v3.21.2 // source: service.proto package service import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // EchoClient is the client API for Echo service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type EchoClient interface { Ping(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) } type echoClient struct { cc grpc.ClientConnInterface } func NewEchoClient(cc grpc.ClientConnInterface) EchoClient { return &echoClient{cc} } func (c *echoClient) Ping(ctx context.Context, in *Message, opts ...grpc.CallOption) (*Message, error) { out := new(Message) err := c.cc.Invoke(ctx, "/service.Echo/Ping", in, out, opts...) if err != nil { return nil, err } return out, nil } // EchoServer is the server API for Echo service. // All implementations must embed UnimplementedEchoServer // for forward compatibility type EchoServer interface { Ping(context.Context, *Message) (*Message, error) mustEmbedUnimplementedEchoServer() } // UnimplementedEchoServer must be embedded to have forward compatible implementations. type UnimplementedEchoServer struct { } func (UnimplementedEchoServer) Ping(context.Context, *Message) (*Message, error) { return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") } func (UnimplementedEchoServer) mustEmbedUnimplementedEchoServer() {} // UnsafeEchoServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to EchoServer will // result in compilation errors. type UnsafeEchoServer interface { mustEmbedUnimplementedEchoServer() } func RegisterEchoServer(s grpc.ServiceRegistrar, srv EchoServer) { s.RegisterService(&Echo_ServiceDesc, srv) } func _Echo_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Message) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(EchoServer).Ping(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/service.Echo/Ping", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(EchoServer).Ping(ctx, req.(*Message)) } return interceptor(ctx, in, info, handler) } // Echo_ServiceDesc is the grpc.ServiceDesc for Echo service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var Echo_ServiceDesc = grpc.ServiceDesc{ ServiceName: "service.Echo", HandlerType: (*EchoServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Ping", Handler: _Echo_Ping_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "service.proto", } ================================================ FILE: tests/testdata/sample.txt ================================================ Hello from RoadRunner e2e static file test! This file is served by the static middleware plugin.