diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml index 166a38e9cbd3..abea1c6ae277 100644 --- a/.devcontainer/compose.yaml +++ b/.devcontainer/compose.yaml @@ -73,7 +73,7 @@ services: hard: -1 libretranslate: - image: libretranslate/libretranslate:v1.7.3 + image: libretranslate/libretranslate:v1.9.6 restart: unless-stopped volumes: - lt-data:/home/libretranslate/.local diff --git a/.env.vagrant b/.env.vagrant deleted file mode 100644 index 69c1bf1fb3d8..000000000000 --- a/.env.vagrant +++ /dev/null @@ -1,8 +0,0 @@ -VAGRANT=true -LOCAL_DOMAIN=mastodon.local -BIND=0.0.0.0 -DB_HOST=/var/run/postgresql/ - -ES_ENABLED=true -ES_HOST=localhost -ES_PORT=9200 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/2.server_bug_report.yml b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml index 3772b57e06be..dbe56776941f 100644 --- a/.github/ISSUE_TEMPLATE/2.server_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml @@ -59,7 +59,7 @@ body: Any additional technical details you may have, like logs or error traces value: | If this is happening on your own Mastodon server, please fill out those: - - Ruby version: (from `ruby --version`, eg. v4.0.8) + - Ruby version: (from `ruby --version`, eg. v4.0.9) - Node.js version: (from `node --version`, eg. v22.16.0) validations: required: false diff --git a/.github/ISSUE_TEMPLATE/3.troubleshooting.yml b/.github/ISSUE_TEMPLATE/3.troubleshooting.yml index 18136d5aefc7..54d4bb1b7ba6 100644 --- a/.github/ISSUE_TEMPLATE/3.troubleshooting.yml +++ b/.github/ISSUE_TEMPLATE/3.troubleshooting.yml @@ -61,7 +61,7 @@ body: value: | Please at least include those informations: - Operating system: (eg. Ubuntu 24.04.2) - - Ruby version: (from `ruby --version`, eg. v4.0.8) + - Ruby version: (from `ruby --version`, eg. v4.0.9) - Node.js version: (from `node --version`, eg. v22.16.0) validations: required: false diff --git a/.github/actions/setup-javascript/action.yml b/.github/actions/setup-javascript/action.yml index 8c355b6eb248..6df3acefb8d9 100644 --- a/.github/actions/setup-javascript/action.yml +++ b/.github/actions/setup-javascript/action.yml @@ -9,7 +9,7 @@ runs: using: 'composite' steps: - name: Set up Node.js - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7 with: node-version-file: '.nvmrc' @@ -23,7 +23,7 @@ runs: shell: bash run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT - - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} diff --git a/.github/actions/setup-ruby/action.yml b/.github/actions/setup-ruby/action.yml index dece0ad5c2cb..56cba9fa1523 100644 --- a/.github/actions/setup-ruby/action.yml +++ b/.github/actions/setup-ruby/action.yml @@ -1,9 +1,6 @@ name: 'Setup Ruby' description: 'Setup a Ruby environment ready to run the Mastodon code' inputs: - ruby-version: - description: The Ruby version to install - default: '.ruby-version' additional-system-dependencies: description: 'Additional packages to install' @@ -24,7 +21,6 @@ runs: ${{ inputs.additional-system-dependencies }} - name: Set up Ruby - uses: ruby/setup-ruby@e1bd6146574da5e0795075b5319b300edb30c5b0 # v1.333.0 + uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0 with: - ruby-version: ${{ inputs.ruby-version }} bundler-cache: true diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 8ba06ebf0b7c..72355975faed 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,10 +3,12 @@ extends: [ 'config:recommended', 'customManagers:dockerfileVersions', + 'customManagers:githubActionsVersions', 'helpers:pinGitHubActionDigests', ':labels(dependencies)', ':prConcurrentLimitNone', // Remove limit for open PRs at any time. ':enableVulnerabilityAlertsWithLabel(security)', + ':maintainLockFilesWeekly', ], rebaseWhen: 'conflicted', minimumReleaseAge: '3 days', // Wait 3 days after the package has been published before upgrading it @@ -17,19 +19,8 @@ // to `null` after any other rule set it to something. dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).', postUpdateOptions: ['yarnDedupeHighest'], - devcontainer: { - managerFilePatterns: [ - '/^\.devcontainer\/devcontainer\.json$/', - '/^\.devcontainer\.json$/', - '/^\.devcontainer\/devcontainer-lock\.json$/', - ], - }, // The types are now included in recent versions,we ignore them here until we upgrade and remove the dependency ignoreDeps: ['@types/emoji-mart'], - lockFileMaintenance: { - enabled: true, - schedule: ['before 8am on monday'], - }, packageRules: [ { // Disable lock file maintenance for bundler for now, as we do not yet pin gems to their exact versions @@ -66,13 +57,6 @@ matchManagers: ['dockerfile', 'docker-compose'], extends: ['schedule:weekly'], }, - { - // Require Dependency Dashboard Approval for major & minor bumps for the ruby image, this needs to be synced with .ruby-version - matchManagers: ['dockerfile'], - matchPackageNames: ['moritzheiber/ruby-jemalloc'], - matchUpdateTypes: ['minor', 'major'], - dependencyDashboardApproval: true, - }, { // Require Dependency Dashboard Approval for major bumps for the node image, this needs to be synced with .nvmrc matchManagers: ['dockerfile'], @@ -162,15 +146,10 @@ groupName: 'opentelemetry-ruby (non-major)', }, { - // The ruby portion of the Playwright group - matchManagers: ['bundler'], - matchPackageNames: ['playwright-ruby-client'], - groupName: 'Playwright', - }, - { - // The node portion of the Playwright group - matchManagers: ['npm'], - matchPackageNames: ['playwright'], + // Unified Playwright group across Node and Ruby ecosystems + matchManagers: ['npm', 'bundler'], + matchPackageNames: ['playwright', 'playwright-ruby-client'], + matchUpdateTypes: ['patch', 'minor'], groupName: 'Playwright', }, { diff --git a/.github/workflows/build-container-image.yml b/.github/workflows/build-container-image.yml index 7924655f342b..6d3d6fc16941 100644 --- a/.github/workflows/build-container-image.yml +++ b/.github/workflows/build-container-image.yml @@ -26,16 +26,25 @@ on: jobs: # Build each (amd64 and arm64) image separately build-image: - runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} + runs-on: ubuntu-${{ matrix.RUNNER_VERSION }}${{ matrix.SUFFIX }} strategy: fail-fast: false matrix: platform: - linux/amd64 - linux/arm64 + include: + - platform: linux/amd64 + # renovate: datasource=github-runners depName=ubuntu + RUNNER_VERSION: '26.04' + SUFFIX: '' + - platform: linux/arm64 + # renovate: datasource=github-runners depName=ubuntu + RUNNER_VERSION: '26.04' + SUFFIX: '-arm' steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Prepare env: @@ -48,19 +57,19 @@ jobs: image_names_split=${image_names%,} echo "IMAGE_NAMES=${image_names_split,,}" >> $GITHUB_ENV - - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4 id: buildx - name: Log in to Docker Hub if: contains(inputs.push_to_images, 'tootsuite') - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Log in to the GitHub Container registry if: contains(inputs.push_to_images, 'ghcr.io') - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4 with: registry: ghcr.io username: ${{ github.actor }} @@ -68,7 +77,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 if: ${{ inputs.push_to_images != '' }} with: images: ${{ inputs.push_to_images }} @@ -77,7 +86,7 @@ jobs: - name: Build and push by digest id: build - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . file: ${{ inputs.file_to_build }} @@ -101,7 +110,7 @@ jobs: - name: Upload digest if: ${{ inputs.push_to_images != '' }} - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: # `hashFiles` is used to disambiguate between streaming and non-streaming images name: digests-${{ hashFiles(inputs.file_to_build) }}-${{ env.PLATFORM_PAIR }} @@ -112,7 +121,7 @@ jobs: # Then merge the docker images into a single one merge-images: if: ${{ inputs.push_to_images != '' }} - runs-on: ubuntu-24.04 + runs-on: ubuntu-26.04 needs: - build-image @@ -120,10 +129,10 @@ jobs: PUSH_TO_IMAGES: ${{ inputs.push_to_images }} steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Download digests - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 with: path: ${{ runner.temp }}/digests # `hashFiles` is used to disambiguate between streaming and non-streaming images @@ -132,25 +141,25 @@ jobs: - name: Log in to Docker Hub if: contains(inputs.push_to_images, 'tootsuite') - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Log in to the GitHub Container registry if: contains(inputs.push_to_images, 'ghcr.io') - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4 - name: Docker meta id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 if: ${{ inputs.push_to_images != '' }} with: images: ${{ inputs.push_to_images }} diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml index 4a56f720e118..ed731e3b10eb 100644 --- a/.github/workflows/build-nightly.yml +++ b/.github/workflows/build-nightly.yml @@ -10,7 +10,7 @@ permissions: jobs: compute-suffix: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 if: github.repository == 'mastodon/mastodon' steps: - id: version_vars diff --git a/.github/workflows/build-push-pr.yml b/.github/workflows/build-push-pr.yml index f55012bb7983..9cb1f05b301d 100644 --- a/.github/workflows/build-push-pr.yml +++ b/.github/workflows/build-push-pr.yml @@ -9,7 +9,7 @@ permissions: jobs: compute-suffix: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # This is only allowed to run if: # - the PR branch is in the `mastodon/mastodon` repository # - the PR is not a draft @@ -18,7 +18,7 @@ jobs: steps: # Repository needs to be cloned so `git rev-parse` below works - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - id: version_vars run: | echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT diff --git a/.github/workflows/build-releases.yml b/.github/workflows/build-releases.yml index a26c7812bb66..dc2824f5fec2 100644 --- a/.github/workflows/build-releases.yml +++ b/.github/workflows/build-releases.yml @@ -8,13 +8,13 @@ permissions: jobs: check-latest-stable: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: latest: ${{ steps.check.outputs.is_latest_stable }} steps: # Repository needs to be cloned to list branches - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 diff --git a/.github/workflows/build-security.yml b/.github/workflows/build-security.yml index 72729b544b37..fefd8370f73e 100644 --- a/.github/workflows/build-security.yml +++ b/.github/workflows/build-security.yml @@ -8,7 +8,7 @@ permissions: jobs: compute-suffix: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - id: version_vars env: diff --git a/.github/workflows/bundler-audit.yml b/.github/workflows/bundler-audit.yml index 4e2478223fd8..08e252c0e543 100644 --- a/.github/workflows/bundler-audit.yml +++ b/.github/workflows/bundler-audit.yml @@ -5,23 +5,20 @@ on: branches: - 'main' - 'stable-*' - paths: + paths: &paths - 'Gemfile*' - '.ruby-version' - '.github/workflows/bundler-audit.yml' pull_request: - paths: - - 'Gemfile*' - - '.ruby-version' - - '.github/workflows/bundler-audit.yml' + paths: *paths schedule: - cron: '0 5 * * 1' jobs: security: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 env: @@ -29,10 +26,10 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Ruby - uses: ruby/setup-ruby@e1bd6146574da5e0795075b5319b300edb30c5b0 # v1.333.0 + uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0 with: bundler-cache: true diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml index b9ffde5e7be2..45bf92df462d 100644 --- a/.github/workflows/check-i18n.yml +++ b/.github/workflows/check-i18n.yml @@ -2,13 +2,11 @@ name: Check i18n on: push: - branches: + branches: &branches - 'main' - 'stable-*' pull_request: - branches: - - 'main' - - 'stable-*' + branches: *branches env: RAILS_ENV: test @@ -18,11 +16,11 @@ permissions: jobs: check-i18n: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Ruby environment uses: ./.github/actions/setup-ruby @@ -35,21 +33,22 @@ jobs: yarn i18n:extract --throws git diff --exit-code - - name: Check locale file normalization - run: bin/i18n-tasks check-normalized + - parallel: + - name: Check locale file normalization + run: bin/i18n-tasks check-normalized - - name: Check for unused strings - run: bin/i18n-tasks unused -l en + - name: Check for unused strings + run: bin/i18n-tasks unused -l en - - name: Check for missing strings in English YML - run: | - bin/i18n-tasks missing -t used -l en + - name: Check for missing strings in English YML + run: | + bin/i18n-tasks missing -t used -l en - - name: Check for wrong string interpolations - run: bin/i18n-tasks check-consistent-interpolations + - name: Check for wrong string interpolations + run: bin/i18n-tasks check-consistent-interpolations - - name: Check that all required locale files exist - run: bin/rake repo:check_locales_files + - name: Check that all required locale files exist + run: bin/rake repo:check_locales_files - - name: Run yaml/erb linter - run: bin/rake yaml:lint + - name: Run yaml/erb linter + run: bin/rake yaml:lint diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 0068fc2d1231..4dfe9c373f7e 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -11,17 +11,17 @@ on: jobs: pathcheck: name: Check for relevant changes - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 outputs: changed: ${{ steps.filter.outputs.src }} steps: - name: Checkout code - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 - - uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3 + - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4 id: filter with: filters: | @@ -38,13 +38,13 @@ jobs: chromatic: name: Run Chromatic - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 needs: pathcheck if: github.repository == 'mastodon/mastodon' && needs.pathcheck.outputs.changed == 'true' steps: - name: Checkout code - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 @@ -55,7 +55,7 @@ jobs: run: yarn build-storybook - name: Run Chromatic - uses: chromaui/action@98132da0fca2211fb41b3af7b49ce85176007851 # v17 + uses: chromaui/action@f305ef908327aee3b80b0e6b3bbb60a742eaa049 # v18 with: projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} zip: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 60b2f68bc29f..440f4189eb1c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -3,20 +3,18 @@ name: 'CodeQL' on: merge_group: push: - branches: + branches: &branches - 'main' - 'stable-*' pull_request: - branches: - - 'main' - - 'stable-*' + branches: *branches schedule: - cron: '22 6 * * 1' jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 permissions: actions: read @@ -32,11 +30,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4 + uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -46,22 +44,7 @@ jobs: # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4 + uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/crowdin-download-stable.yml b/.github/workflows/crowdin-download-stable.yml index d3af2559be1d..b981789f9725 100644 --- a/.github/workflows/crowdin-download-stable.yml +++ b/.github/workflows/crowdin-download-stable.yml @@ -8,12 +8,12 @@ permissions: jobs: download-translations-stable: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 if: github.repository == 'mastodon/mastodon' steps: - name: Checkout - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Increase Git http.postBuffer # This is needed due to a bug in Ubuntu's cURL version? @@ -24,7 +24,7 @@ jobs: # Download the translation files from Crowdin - name: crowdin action - uses: crowdin/github-action@8f01d54f70f1713ee3f09d82c2bbb2daeac28689 # v2 + uses: crowdin/github-action@959dc74ac104447731cba99a016f85d0ae719788 # v3 with: upload_sources: false upload_translations: false diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index 8b10e701ddb8..bea6fe753d1a 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -10,12 +10,12 @@ permissions: jobs: download-translations: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 if: github.repository == 'mastodon/mastodon' steps: - name: Checkout - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Increase Git http.postBuffer # This is needed due to a bug in Ubuntu's cURL version? @@ -26,7 +26,7 @@ jobs: # Download the translation files from Crowdin - name: crowdin action - uses: crowdin/github-action@8f01d54f70f1713ee3f09d82c2bbb2daeac28689 # v2 + uses: crowdin/github-action@959dc74ac104447731cba99a016f85d0ae719788 # v3 with: upload_sources: false upload_translations: false diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml index 4685423b1f1b..8ed5f822ee49 100644 --- a/.github/workflows/crowdin-upload.yml +++ b/.github/workflows/crowdin-upload.yml @@ -18,15 +18,15 @@ on: jobs: upload-translations: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 if: github.repository == 'mastodon/mastodon' steps: - name: Checkout - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: crowdin action - uses: crowdin/github-action@8f01d54f70f1713ee3f09d82c2bbb2daeac28689 # v2 + uses: crowdin/github-action@959dc74ac104447731cba99a016f85d0ae719788 # v3 with: upload_sources: true upload_translations: false diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index ee4b262acdde..59479f2a148b 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -9,12 +9,12 @@ on: jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 steps: - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Javascript environment uses: ./.github/actions/setup-javascript diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index e69fdc15e830..dce8c5e55b2a 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -5,7 +5,7 @@ on: branches: - 'main' - 'stable-*' - paths: + paths: &paths - 'package.json' - 'yarn.lock' - '.nvmrc' @@ -15,23 +15,16 @@ on: - '.github/workflows/lint-css.yml' pull_request: - paths: - - 'package.json' - - 'yarn.lock' - - '.nvmrc' - - 'stylelint.config.js' - - '**/*.css' - - '**/*.scss' - - '.github/workflows/lint-css.yml' + paths: *paths jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 steps: - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Javascript environment uses: ./.github/actions/setup-javascript diff --git a/.github/workflows/lint-haml.yml b/.github/workflows/lint-haml.yml index bdddfe993e6b..a033721df143 100644 --- a/.github/workflows/lint-haml.yml +++ b/.github/workflows/lint-haml.yml @@ -5,7 +5,7 @@ on: branches: - 'main' - 'stable-*' - paths: + paths: &paths - '.github/workflows/lint-haml.yml' - '.haml-lint*.yml' - '.rubocop*.yml' @@ -14,17 +14,11 @@ on: - 'Gemfile*' pull_request: - paths: - - '.github/workflows/lint-haml.yml' - - '.haml-lint*.yml' - - '.rubocop*.yml' - - '.ruby-version' - - '**/*.haml' - - 'Gemfile*' + paths: *paths jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 env: @@ -32,10 +26,10 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Ruby - uses: ruby/setup-ruby@e1bd6146574da5e0795075b5319b300edb30c5b0 # v1.333.0 + uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0 with: bundler-cache: true diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 65c305604c8b..1eec8369880c 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -5,7 +5,7 @@ on: branches: - 'main' - 'stable-*' - paths: + paths: &paths - 'package.json' - 'yarn.lock' - 'tsconfig.json' @@ -18,32 +18,23 @@ on: - '.github/workflows/lint-js.yml' pull_request: - paths: - - 'package.json' - - 'yarn.lock' - - 'tsconfig.json' - - '.nvmrc' - - 'eslint.config.mjs' - - '**/*.js' - - '**/*.jsx' - - '**/*.ts' - - '**/*.tsx' - - '.github/workflows/lint-js.yml' + paths: *paths jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 steps: - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Javascript environment uses: ./.github/actions/setup-javascript - - name: ESLint - run: yarn workspaces foreach --all --parallel run lint:js --max-warnings 0 + - parallel: + - name: JS Linting + run: yarn workspaces foreach --all --parallel run lint:js --max-warnings 0 - - name: Typecheck - run: yarn typecheck + - name: Typecheck + run: yarn typecheck diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml index d0e4585434db..ac79c6048c07 100644 --- a/.github/workflows/lint-ruby.yml +++ b/.github/workflows/lint-ruby.yml @@ -5,7 +5,7 @@ on: branches: - 'main' - 'stable-*' - paths: + paths: &paths - 'Gemfile*' - '.rubocop*.yml' - '.ruby-version' @@ -15,18 +15,11 @@ on: - '.github/workflows/lint-ruby.yml' pull_request: - paths: - - 'Gemfile*' - - '.rubocop*.yml' - - '.ruby-version' - - 'bin/rubocop' - - '**/*.rb' - - '**/*.rake' - - '.github/workflows/lint-ruby.yml' + paths: *paths jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 env: @@ -34,16 +27,16 @@ jobs: steps: - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Ruby - uses: ruby/setup-ruby@e1bd6146574da5e0795075b5319b300edb30c5b0 # v1.333.0 + uses: ruby/setup-ruby@de492cf36cbab201753d720ce4f4e336cd688c94 # v1.336.0 with: bundler-cache: true - - name: Run rubocop - run: bin/rubocop --format github + - parallel: + - name: Run rubocop + run: bin/rubocop --format github - - name: Run brakeman - if: always() # Run both checks, even if the first failed - run: bin/brakeman + - name: Run brakeman + run: bin/brakeman diff --git a/.github/workflows/rebase-needed.yml b/.github/workflows/rebase-needed.yml index 0dc1d33ca2b8..2bf729fa416c 100644 --- a/.github/workflows/rebase-needed.yml +++ b/.github/workflows/rebase-needed.yml @@ -9,7 +9,7 @@ permissions: jobs: label-rebase-needed: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 if: github.repository == 'mastodon/mastodon' concurrency: diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 5a3c57889995..062ce0602b7c 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -5,7 +5,7 @@ on: branches: - 'main' - 'stable-*' - paths: + paths: &paths - 'package.json' - 'yarn.lock' - '.nvmrc' @@ -17,25 +17,16 @@ on: - '.github/workflows/test-js.yml' pull_request: - paths: - - 'package.json' - - 'yarn.lock' - - '.nvmrc' - - '**/*.js' - - '**/*.jsx' - - '**/*.ts' - - '**/*.tsx' - - '**/*.snap' - - '.github/workflows/test-js.yml' + paths: *paths jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 steps: - name: Clone repository - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Javascript environment uses: ./.github/actions/setup-javascript diff --git a/.github/workflows/test-migrations.yml b/.github/workflows/test-migrations.yml index 0cb80e0dd700..93f19dcdc946 100644 --- a/.github/workflows/test-migrations.yml +++ b/.github/workflows/test-migrations.yml @@ -6,7 +6,7 @@ on: branches: - 'main' - 'stable-*' - paths: + paths: &paths - 'Gemfile*' - '.ruby-version' - '**/*.rb' @@ -15,16 +15,11 @@ on: - 'lib/tasks/db.rake' pull_request: - paths: - - 'Gemfile*' - - '.ruby-version' - - '**/*.rb' - - '.github/workflows/test-migrations.yml' - - 'lib/tasks/tests.rake' + paths: *paths jobs: test: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 strategy: @@ -73,7 +68,7 @@ jobs: BUNDLE_RETRY: 3 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Ruby environment uses: ./.github/actions/setup-ruby diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 55090c5ac5ca..87e0685099e7 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -19,7 +19,7 @@ concurrency: jobs: build: name: build (production) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 env: @@ -28,7 +28,7 @@ jobs: SECRET_KEY_BASE_DUMMY: 1 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Set up Ruby environment uses: ./.github/actions/setup-ruby @@ -39,7 +39,7 @@ jobs: onlyProduction: 'true' - name: Cache assets from compilation - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | public/assets @@ -61,7 +61,7 @@ jobs: run: | tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* - - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: |- ./artifacts.tar.gz @@ -70,7 +70,7 @@ jobs: test: name: test (.ruby-version) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 60 needs: @@ -121,9 +121,9 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 with: path: './' name: ${{ github.sha }} @@ -144,7 +144,7 @@ jobs: bin/flatware fan bin/rails db:test:prepare - name: Cache RSpec persistence file - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: | tmp/rspec/examples.txt @@ -158,7 +158,7 @@ jobs: - run: bin/flatware rspec - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6 + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7 with: files: coverage/lcov/*.lcov env: @@ -166,7 +166,7 @@ jobs: test-e2e: name: End to End testing (.ruby-version) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 30 needs: @@ -209,9 +209,9 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 with: path: './' name: ${{ github.sha }} @@ -234,7 +234,7 @@ jobs: - name: Cache Playwright Chromium browser id: playwright-cache - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.cache/ms-playwright key: playwright-browsers-${{ runner.os }}-${{ hashFiles('yarn.lock') }} @@ -250,14 +250,14 @@ jobs: - run: bin/rspec spec/system --tag streaming --tag js - name: Archive logs - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 if: failure() with: name: e2e-logs path: log/ - name: Archive test screenshots - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 if: failure() with: name: e2e-screenshots @@ -265,7 +265,7 @@ jobs: test-search: name: Elastic Search integration testing - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 30 needs: @@ -337,16 +337,13 @@ jobs: matrix: search-image: - docker.elastic.co/elasticsearch/elasticsearch:7.17.29 - include: - - ruby-version: '.ruby-version' - search-image: docker.elastic.co/elasticsearch/elasticsearch:8.19.2 - - ruby-version: '.ruby-version' - search-image: opensearchproject/opensearch:2 + - docker.elastic.co/elasticsearch/elasticsearch:8.19.23 + - opensearchproject/opensearch:2 steps: - - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 with: path: './' name: ${{ github.sha }} @@ -366,7 +363,7 @@ jobs: - run: bin/rspec --tag search - name: Archive logs - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 if: failure() with: name: test-search-logs diff --git a/.gitignore b/.gitignore index 4727d9ec27f9..ee2a3125376f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,9 +29,6 @@ stats.html node_modules/ /build/ -# Ignore Vagrant files -.vagrant/ - # Ignore IDE files .vscode/ .idea/ @@ -65,9 +62,6 @@ yarn-debug.log !.yarn/sdks !.yarn/versions -# Ignore vagrant log files -*-cloudimg-console.log - # Ignore Docker option files docker-compose.override.yml diff --git a/.nvmrc b/.nvmrc index b26dba76ed35..35b0a47e5219 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -26.12 +26.13 diff --git a/.rubocop.yml b/.rubocop.yml index 3077a4fb4a06..153c97ea91f6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,6 @@ AllCops: CacheRootDirectory: tmp DisplayStyleGuide: true Exclude: - - Vagrantfile - config/initializers/json_ld* - lib/mastodon/migration_helpers.rb - lib/json-canonicalization/floats_fix.rb diff --git a/.ruby-version b/.ruby-version index a2cec7aff418..7919852fe104 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -4.0.8 +4.0.9 diff --git a/.watchmanconfig b/.watchmanconfig deleted file mode 100644 index 29e4f231e9e8..000000000000 --- a/.watchmanconfig +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ignore_dirs": ["node_modules/", "public/"] -} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ea55f109ebf..5fac62537350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,108 @@ All notable changes to this project will be documented in this file. +## [5.0.0] - UNRELEASED + +### New web interface + +**Mastodon has a new look!** + +This version contains a redesign of Mastodon's web interface. This includes a refreshed visual design (with a new color theme setting to personalise it further), a new page layout and a new composer. + +This is only the start of the journey and this version is laying out the basis for many new things to come. + +You can read all about it in our [dedicated blog post](https://blog.joinmastodon.org/2026/10/5.0-a-closer-look/). + +### Added + +- Add a setting to pick a color theme for the web interface (#41088, #41094, #41153 by @diondiondion and @ClearlyClaire) +- Add a user setting to have the Content Warning field on by default (#40824 by @ClearlyClaire) +- Add a user setting to hide your own posts and boosts (#40813 and #40867 by @arte7) + - Your own boosts are now hidden by default. +- Add ability to follow hashtag from the menu (#40640 by @ChaosExAnima) +- **Add new Server Health admin page** (#41101 and #41282 by @jhbabon) +- **Add bloom filters to limit which servers account deletion notices are sent to** (#40283 by @ClearlyClaire) + - This heavily reduces the number of deletion notices sent to other servers by storing the list of servers which fetched an account in a bloom filter. This is strongly recommended on large servers which regularly delete many accounts as it heavily decreases the number of federation Delete activities. This feature introduces a new setting to require signed requests for accounts in order to know where the request comes from. +- Add server setting to postpone confirmation emails after accounts are approved (#40690, #40702, #41099 by @ClearlyClaire) +- Add `DISABLE_ANNOUNCEMENT_REACTIONS` environment variable to disable emoji reactions in announcements (#40931 by @ClearlyClaire) +- Add `RACK_COMPRESS` env to enable `Rack::Deflater` middleware (#39927 by @shleeable) +- Add server directory and default server opt-in options (#40750 by @arte7) +- Add new tab to server settings for external discovery (#40768 by @arte7) +- Add moderation domain block/allow list synchronization feature (#40136, #41349 by @ClearlyClaire and @zunda) + - In this version, this is behind an experimental feature flag to allow us to gather feedback and test it in the real world. We expect this to be stable and enabled for all servers in our next release. +- Add the app used for registration in the moderation view of an account (#40567 by @jhbabon) +- Add canned response (formerly warning presets) categories to restrict them to a specific action (#41092, #41264 by @arte7) +- Add information about who handled an appeal in moderation interface (#41186, #41427 by @arte7 and @ClearlyClaire) +- Add information about trends and follow recommendation blocks in the account moderation page (#40398 by @jhbabon) +- Add support for verifying ML-DSA-44 HTTP Message Signatures (#41119 by @ClearlyClaire) +- Add support for signing-up through the OAuth flow (#40659 by @ClearlyClaire) + - You can read more about the new flow [in our documentation](https://docs.joinmastodon.org/methods/oauth/#authorize). The previous registration flow (`POST /api/v1/accounts`) is deprecated and will be removed in a future version. +- Add `allow_nonfunctional` parameter to `GET /api/v1/accounts/verify_credentials` (#40803 by @ClearlyClaire) +- Add server-side filtering to home feed endpoint (#40468, #41031 by @ClearlyClaire and @cacheflow) +- Add counters to track which features result in users following each other (#40226, #40258, #40340 by @Gargron) + +### Fixed + +- Fix terms of service and privacy policy API failing on percent signs (#41343 by @dualfroz) +- Fix size of date-of-birth input fields (#41021 by @ClearlyClaire) +- Fix invisible links in server announcements (#40949 by @diondiondion) +- Fixes infinite requests loop on hovering over broken emojis (#40884 by @FFederi) +- Fix HTTP 200 response code on paths to remote accounts that don't exist (#40341 by @Gargron) +- Fix performance of media attachment queries in admin instance page (#40515 by @crafkaz) +- Fix deprecated nginx HTTP/2 directive (#36149 by @sirodoht) +- Fix typo in new locales for admin account trending info (#40438 by @jhbabon) +- Fix follows and followers pages to show "Today" instead of hours (#40367 by @FFederi) +- Fix long dropdown menus being cut off on smaller screens (#41026 by @diondiondion) +- Fix the moderation interface to allow seeing media reports of suspended accounts (#40274 by @jhbabon) + +### Changed + +- Change authentication errors to return HTTP 422 instead of HTTP 200 (#41503 by @ClearlyClaire) +- Change account confirmation e-mail to not redirect to app (#41066 and #41181 by @ClearlyClaire and @oneiros) +- Change empty search requests to return a HTTP 400 error (#40196 and #40214 by @Gargron) +- Change warning presets to canned responses and move the navigation item (#41045 by @arte7) +- Change FASP configuration to individual toggles for capabilities (#40764 and #40787 by @oneiros) +- Change the web interface to check media attachment size limit before uploading (#30475 by @rgroothuijsen) + +### Removed + +- Remove discovery area in settings (#41258 by @ChaosExAnima) +- Remove deprecated `tootctl accounts fix-duplicate` command (#40232 by @ClearlyClaire) +- Remove support for node version 22 (#41152 by @mjankowski) +- Remove support for ruby 3.3 and 3.4, only support a single version (#40603 by @mjankowski) +- Remove support for pre-4.7 zero-downtime migrations (#40235 by @ClearlyClaire) + +## [4.7.5] - 2026-11-03 + +### Security + +- Fix password authentication bypass for inactive accounts ([GHSA-3xjw-gv43-3w62](https://github.com/mastodon/mastodon/security/advisories/GHSA-3xjw-gv43-3w62)) +- Fix DoS through JSON-LD compaction (#41482 by @ClearlyClaire, [GHSA-cf49-73r7-rxj6](https://github.com/mastodon/mastodon/security/advisories/GHSA-cf49-73r7-rxj6)) +- Fix `Forwarded` header taking precedence over `X-Forwarded-For` (#41481 by @ClearlyClaire, [GHSA-qp4r-p842-mw5c](https://github.com/mastodon/mastodon/security/advisories/GHSA-qp4r-p842-mw5c)) + - If your reverse-proxy setup uses `Forwarded` instead of `X-Forwarded-For`, please make sure it ignores user-provided `X-Forwarded-For` headers. + - If you are using our provided `nginx.conf`, please consider updating it. +- Updated dependencies, in particular to address security advisories in `addressable`, `loofah`, `rack`, and `rails-html-sanitizer` + +### Changed + +- Change invites use counts to not decrease if an invited user deletes their account (#41458 by @ClearlyClaire) + +## [4.7.4] - 2026-10-29 + +### Security + +- Fix insufficient ActivityPub identifier verification ([GHSA-288h-6j8h-6x5j](https://github.com/mastodon/mastodon/security/advisories/GHSA-288h-6j8h-6x5j)) +- Added security challenge e-mail to users that try to log in after long period of inactivity (#40745 by @jhbabon) +- Updated dependencies, in particular to address security advisories in Ruby, NodeJS, `excon`, `css_parser`, `pghero`, and `sanitize` + +### Fixed + +- Fix inefficiency in some hashtag timeline queries (#41132 by @hugogameiro) +- Fix minor inefficiency in timeline deliveries (#41079 by @isidzukuri) +- Fix account timeline with `exclude_direct` also excluding followers-only posts for non-followed users (#41019 by @ClearlyClaire) +- Fix error in `dev:populate_sample_data` task (#40995 and #41051 by @isidzukuri and @mjankowski) +- Fix local collection item revocation not being properly federated (#40930 by @oneiros) + ## [4.7.3] - 2026-10-01 ### Security diff --git a/Dockerfile b/Dockerfile index 4f1d8c5efa6d..b4b648815a6c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# syntax=docker/dockerfile:1.18 +# syntax=docker/dockerfile:1.28 # This file is designed for production server deployment, not local development work # For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/docs/DEVELOPMENT.md#docker @@ -13,7 +13,7 @@ ARG BASE_REGISTRY="docker.io" # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="4.0.x"] # renovate: datasource=docker depName=docker.io/ruby -ARG RUBY_VERSION="4.0.8" +ARG RUBY_VERSION="4.0.9" # # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="26"] # renovate: datasource=node-version depName=node ARG NODE_MAJOR_VERSION="26" diff --git a/Gemfile b/Gemfile index ef26fddfe9b2..70c350100401 100644 --- a/Gemfile +++ b/Gemfile @@ -92,7 +92,7 @@ gem 'stoplight' gem 'strong_migrations' gem 'tty-prompt', '~> 0.23', require: false gem 'tzinfo-data', '~> 1.2023' -gem 'uts58', '~> 0.2.3' +gem 'uts58', '~> 0.2.6' gem 'webauthn', '~> 3.0' gem 'webpush', github: 'mastodon/webpush', ref: '9631ac63045cfabddacc69fc06e919b4c13eb913' diff --git a/Gemfile.lock b/Gemfile.lock index b0923cb384e1..fa0a9f2622c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,29 +12,29 @@ GEM specs: action_text-trix (2.1.19) railties - actioncable (8.1.4) - actionpack (= 8.1.4) - activesupport (= 8.1.4) + actioncable (8.1.4.1) + actionpack (= 8.1.4.1) + activesupport (= 8.1.4.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.4) - actionpack (= 8.1.4) - activejob (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + actionmailbox (8.1.4.1) + actionpack (= 8.1.4.1) + activejob (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) mail (>= 2.8.0) - actionmailer (8.1.4) - actionpack (= 8.1.4) - actionview (= 8.1.4) - activejob (= 8.1.4) - activesupport (= 8.1.4) + actionmailer (8.1.4.1) + actionpack (= 8.1.4.1) + actionview (= 8.1.4.1) + activejob (= 8.1.4.1) + activesupport (= 8.1.4.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.4) - actionview (= 8.1.4) - activesupport (= 8.1.4) + actionpack (8.1.4.1) + actionview (= 8.1.4.1) + activesupport (= 8.1.4.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -42,41 +42,41 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.4) + actiontext (8.1.4.1) action_text-trix (~> 2.1.15) - actionpack (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + actionpack (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.4) - activesupport (= 8.1.4) + actionview (8.1.4.1) + activesupport (= 8.1.4.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) - rails-html-sanitizer (~> 1.6) + rails-html-sanitizer (~> 1.6, >= 1.7.2) active_model_serializers (0.10.16) actionpack (>= 4.1) activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (8.1.4) - activesupport (= 8.1.4) + activejob (8.1.4.1) + activesupport (= 8.1.4.1) globalid (>= 0.3.6) - activemodel (8.1.4) - activesupport (= 8.1.4) - activerecord (8.1.4) - activemodel (= 8.1.4) - activesupport (= 8.1.4) + activemodel (8.1.4.1) + activesupport (= 8.1.4.1) + activerecord (8.1.4.1) + activemodel (= 8.1.4.1) + activesupport (= 8.1.4.1) timeout (>= 0.4.0) - activestorage (8.1.4) - actionpack (= 8.1.4) - activejob (= 8.1.4) - activerecord (= 8.1.4) - activesupport (= 8.1.4) + activestorage (8.1.4.1) + actionpack (= 8.1.4.1) + activejob (= 8.1.4.1) + activerecord (= 8.1.4.1) + activesupport (= 8.1.4.1) marcel (~> 1.0) - activesupport (8.1.4) + activesupport (8.1.4.1) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -89,18 +89,18 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) - addressable (2.9.0) + addressable (2.9.2) public_suffix (>= 2.0.2, < 8.0) aes_key_wrap (1.1.0) android_key_attestation (0.3.0) - annotaterb (4.25.0) + annotaterb (4.26.0) activerecord (>= 6.0.0) activesupport (>= 6.0.0) ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.4.1) - aws-partitions (1.1298.0) - aws-sdk-core (3.260.0) + aws-partitions (1.1302.0) + aws-sdk-core (3.264.0) aws-eventstream (~> 1, >= 1.4.1) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -109,11 +109,11 @@ GEM jmespath (~> 1, >= 1.6.1) logger rexml (~> 3.4, >= 3.4.2) - aws-sdk-kms (1.135.0) - aws-sdk-core (~> 3, >= 3.260.0) + aws-sdk-kms (1.139.0) + aws-sdk-core (~> 3, >= 3.264.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.235.0) - aws-sdk-core (~> 3, >= 3.258.0) + aws-sdk-s3 (1.238.0) + aws-sdk-core (~> 3, >= 3.264.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) aws-sdk-ssm (1.186.0) @@ -139,7 +139,7 @@ GEM bloom_fit (1.2.0) msgpack (~> 1.0) blurhash (0.1.8) - bootsnap (1.26.0) + bootsnap (1.26.1) msgpack (~> 1.5) brakeman (8.1.0) racc @@ -157,7 +157,7 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - capybara-playwright-driver (0.5.13) + capybara-playwright-driver (0.5.14) addressable capybara playwright-ruby-client (>= 1.16.0) @@ -188,16 +188,16 @@ GEM bigdecimal rexml crass (1.0.8) - css_parser (3.3.0) + css_parser (3.4.0) addressable base64 ssrf_filter (~> 1.5) - csv (3.3.6) + csv (3.3.7) database_cleaner-active_record (2.2.2) activerecord (>= 5.a) database_cleaner-core (~> 2.0) database_cleaner-core (2.1.0) - date (3.5.1) + date (3.6.0) debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) @@ -219,7 +219,7 @@ GEM diff-lcs (1.6.2) discard (2.0.0) activerecord (>= 7.0, < 9.0) - domain_name (0.6.20260921) + domain_name (0.7.20261031) doorkeeper (5.9.9) railties (>= 5) dotenv (3.2.0) @@ -238,25 +238,24 @@ GEM erubi (1.13.1) et-orbi (1.4.2) tzinfo - excon (1.8.0) - logger + excon (1.10.0) fabrication (3.0.0) faker (3.8.0) i18n (>= 1.8.11, < 2) - faraday (2.14.4) - faraday-net_http (>= 2.0, < 3.5) + faraday (2.15.1) + faraday-net_http (>= 2.0, < 3.6) json logger faraday-follow_redirects (0.5.0) faraday (>= 1, < 3) faraday-httpclient (2.0.2) httpclient (>= 2.2) - faraday-net_http (3.4.4) + faraday-net_http (3.5.0) net-http (~> 0.5) fast_blank (1.0.1) - fastimage (2.4.1) + fastimage (2.4.3) ffi (1.17.4) - ffi-compiler (1.4.2) + ffi-compiler (1.4.3) ffi (>= 1.15.5) rake flatware (2.4.0) @@ -272,9 +271,9 @@ GEM excon (~> 1.0) formatador (>= 0.2, < 2.0) mime-types - fog-json (1.4.0) + fog-json (1.5.0) fog-core - json (~> 2.19) + json (>= 2.19, < 4) fog-openstack (1.1.5) fog-core (~> 2.1) fog-json (>= 1.0) @@ -289,9 +288,9 @@ GEM google-protobuf (4.36.2) bigdecimal rake (~> 13.3) - googleapis-common-protos-types (1.23.0) + googleapis-common-protos-types (1.25.0) google-protobuf (~> 4.26) - haml (7.5.1) + haml (7.5.2) prism (>= 1.1.0) temple (>= 0.8.2) thor @@ -308,7 +307,7 @@ GEM rubocop (>= 1.0) sysexits (~> 1.1) hashdiff (1.2.1) - hashie (5.1.0) + hashie (5.2.0) logger hcaptcha (7.1.0) json @@ -327,7 +326,7 @@ GEM domain_name (~> 0.5) http-form_data (2.3.0) http_accept_language (2.1.1) - httpclient (2.9.0) + httpclient (2.10.0) mutex_m httplog (1.8.0) benchmark @@ -351,7 +350,7 @@ GEM inline_svg (1.10.0) activesupport (>= 3.0) nokogiri (>= 1.6) - io-console (0.9.4) + io-console (0.9.5) ipaddr (1.3.0) irb (1.18.0) pp (>= 0.6.0) @@ -389,18 +388,18 @@ GEM jsonapi-renderer (0.2.2) jwt (2.10.3) base64 - kaminari (1.3.0) + kaminari (1.3.3) activesupport (>= 4.1.0) - kaminari-actionview (= 1.3.0) - kaminari-activerecord (= 1.3.0) - kaminari-core (= 1.3.0) - kaminari-actionview (1.3.0) + kaminari-actionview (= 1.3.3) + kaminari-activerecord (= 1.3.3) + kaminari-core (= 1.3.3) + kaminari-actionview (1.3.3) actionview - kaminari-core (= 1.3.0) - kaminari-activerecord (1.3.0) + kaminari-core (= 1.3.3) + kaminari-activerecord (1.3.3) activerecord - kaminari-core (= 1.3.0) - kaminari-core (1.3.0) + kaminari-core (= 1.3.3) + kaminari-core (1.3.3) kt-paperclip (8.0.1) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -437,9 +436,9 @@ GEM logger railties (>= 4) request_store (~> 1.0) - loofah (2.25.2) + loofah (2.25.3) crass (~> 1.0.2) - nokogiri (>= 1.12.0) + nokogiri (>= 1.19.5) mail (2.9.1) logger mini_mime (>= 0.1.1) @@ -454,18 +453,18 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2026.1013) + mime-types-data (3.2026.1103) mini_mime (1.1.5) mini_portile2 (2.8.9) minitest (6.0.6) drb (~> 2.0) prism (~> 1.5) - msgpack (1.8.5) + msgpack (1.8.7) multi_json (1.21.2) mutex_m (0.3.0) net-http (0.9.1) uri (>= 0.11.1) - net-imap (0.6.7) + net-imap (0.6.8) date net-protocol net-ldap (0.21.0) @@ -478,14 +477,14 @@ GEM net-smtp (0.5.2) net-protocol nio4r (2.7.5) - nokogiri (1.19.4) + nokogiri (1.19.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) - omniauth (2.1.4) - hashie (>= 3.4.6) + omniauth (2.3.1) + hashie (>= 3.5.2) logger - rack (>= 2.2.3) - rack-protection + rack (>= 2.2.8) + rack-protection (>= 2.2.0) omniauth-cas (3.0.2) addressable (~> 2.8) nokogiri (~> 1.12) @@ -526,7 +525,7 @@ GEM opentelemetry-exporter-otlp-common (~> 0.2) opentelemetry-sdk (~> 1.10) opentelemetry-semantic_conventions - opentelemetry-exporter-otlp-common (0.3.0) + opentelemetry-exporter-otlp-common (0.4.0) google-protobuf (>= 3.18) googleapis-common-protos-types (~> 1.3) opentelemetry-api (~> 1.1) @@ -552,9 +551,11 @@ GEM opentelemetry-instrumentation-active_support (~> 0.10) opentelemetry-instrumentation-active_support (0.12.1) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-base (0.26.1) + opentelemetry-instrumentation-base (0.27.0) opentelemetry-api (~> 1.7) opentelemetry-common (~> 0.21) + opentelemetry-logs-api (~> 0.6) + opentelemetry-metrics-api (~> 0.9) opentelemetry-registry (~> 0.1) opentelemetry-instrumentation-concurrent_ruby (0.25.0) opentelemetry-instrumentation-base (~> 0.25) @@ -587,6 +588,10 @@ GEM opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-sidekiq (0.29.0) opentelemetry-instrumentation-base (~> 0.25) + opentelemetry-logs-api (0.7.0) + opentelemetry-api (~> 1.0) + opentelemetry-metrics-api (0.10.0) + opentelemetry-api (~> 1.0) opentelemetry-registry (0.6.3) opentelemetry-api (~> 1.1) opentelemetry-sdk (1.13.1) @@ -602,14 +607,14 @@ GEM ox (2.14.29) bigdecimal (>= 3.0) parallel (2.3.0) - parser (3.3.12.0) + parser (3.3.13.0) ast (~> 2.4.1) racc parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) - pg (1.6.3) - pghero (4.0.1) + pg (1.7.0) + pghero (4.1.0) activerecord (>= 7.2) playwright-ruby-client (1.64.0) base64 @@ -617,15 +622,14 @@ GEM mime-types (>= 3.0) pp (0.6.4) prettyprint - premailer (1.31.0) + premailer (1.33.0) addressable - css_parser (>= 1.19.0) - htmlentities (>= 4.0.0) + css_parser (>= 3.1.0) premailer-rails (1.12.0) actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) - prettyprint (0.2.0) + prettyprint (0.2.1) prism (1.9.0) prometheus_exporter (2.3.1) webrick @@ -640,7 +644,7 @@ GEM activesupport (>= 3.0.0) raabro (1.5.0) racc (1.8.1) - rack (3.2.7) + rack (3.2.8) rack-attack (6.8.0) rack (>= 1.0, < 4) rack-cors (3.0.0) @@ -657,42 +661,42 @@ GEM base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) - rack-proxy (1.0.3) + rack-proxy (2.0.1) rack (>= 2.0, < 4) rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) - rack-test (2.2.0) + rack-test (2.3.0) rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.1.4) - actioncable (= 8.1.4) - actionmailbox (= 8.1.4) - actionmailer (= 8.1.4) - actionpack (= 8.1.4) - actiontext (= 8.1.4) - actionview (= 8.1.4) - activejob (= 8.1.4) - activemodel (= 8.1.4) - activerecord (= 8.1.4) - activestorage (= 8.1.4) - activesupport (= 8.1.4) + rails (8.1.4.1) + actioncable (= 8.1.4.1) + actionmailbox (= 8.1.4.1) + actionmailer (= 8.1.4.1) + actionpack (= 8.1.4.1) + actiontext (= 8.1.4.1) + actionview (= 8.1.4.1) + activejob (= 8.1.4.1) + activemodel (= 8.1.4.1) + activerecord (= 8.1.4.1) + activestorage (= 8.1.4.1) + activesupport (= 8.1.4.1) bundler (>= 1.15.0) - railties (= 8.1.4) + railties (= 8.1.4.1) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.7.1) - loofah (~> 2.25, >= 2.25.2) + rails-html-sanitizer (1.7.2) + loofah (~> 2.25, >= 2.25.3) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-i18n (8.1.0) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.1.4) - actionpack (= 8.1.4) - activesupport (= 8.1.4) + railties (8.1.4.1) + actionpack (= 8.1.4.1) + activesupport (= 8.1.4.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -735,7 +739,7 @@ GEM actionpack (>= 7.0) railties (>= 7.0) rexml (3.4.4) - rotp (6.3.0) + rotp (6.4.0) rouge (5.1.0) strscan (~> 3.1) rpam2 (4.0.2) @@ -757,7 +761,7 @@ GEM rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (8.0.4) + rspec-rails (8.0.5) actionpack (>= 7.2) activesupport (>= 7.2) railties (>= 7.2) @@ -771,7 +775,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.7) - rubocop (1.92.0) + rubocop (1.92.2) json (>= 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -824,9 +828,9 @@ GEM fugit (~> 1.1, >= 1.11.1) safety_net_attestation (0.5.0) jwt (>= 2.0, < 4.0) - sanitize (7.0.0) - crass (~> 1.0.2) - nokogiri (>= 1.16.8) + sanitize (7.0.2) + crass (~> 1.0.8) + nokogiri (>= 1.19.7) scenic (1.9.0) activerecord (>= 4.0.0) railties (>= 4.0.0) @@ -857,7 +861,7 @@ GEM activemodel (>= 7.0) simplecov (1.3.2) simplecov-lcov (0.9.0) - simpleidn (0.2.3) + simpleidn (0.3.0) ssrf_filter (1.6.0) stackprof (0.2.28) starry (0.2.0) @@ -901,14 +905,14 @@ GEM tty-screen (0.8.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2026.5) + tzinfo-data (1.2026.6) tzinfo (>= 1.0.0) unicode-display_width (3.3.0) unicode-emoji (~> 4.3) unicode-emoji (4.3.0) uri (1.1.2) useragent (0.16.11) - uts58 (0.2.3) + uts58 (0.2.6) public_suffix (>= 6.0, < 8) simpleidn (~> 0.2) validate_url (1.0.15) @@ -916,7 +920,7 @@ GEM public_suffix warden (1.2.9) rack (>= 2.0.9) - webauthn (3.4.3) + webauthn (3.5.0) android_key_attestation (~> 0.3.0) bindata (~> 2.4) cbor (~> 0.5.9) @@ -928,7 +932,7 @@ GEM activesupport faraday (~> 2.0) faraday-follow_redirects - webmock (3.26.4) + webmock (3.34.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -1098,7 +1102,7 @@ DEPENDENCIES thor (~> 1.2) tty-prompt (~> 0.23) tzinfo-data (~> 1.2023) - uts58 (~> 0.2.3) + uts58 (~> 0.2.6) webauthn (~> 3.0) webmock (~> 3.18) webpush! @@ -1106,7 +1110,7 @@ DEPENDENCIES xorcist (~> 1.1) RUBY VERSION - ruby 4.0.8 + ruby 4.0.9 BUNDLED WITH - 4.0.22 + 4.0.24 diff --git a/README.md b/README.md index f537470953a4..9f6d887ebb17 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Mastodon is a **free, open-source social network server** based on [ActivityPub] - **Ruby** 4.0 - **PostgreSQL** 14+ -- **Redis** 7.0+ +- **Redis** 7.2+ - **Node.js** 24+ - **FFmpeg** 5.1+ diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 3367d0e25e2e..000000000000 --- a/Vagrantfile +++ /dev/null @@ -1,201 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -ENV["PORT"] ||= "3000" - -$provisionA = <