From 542dff50bddd1a875457e6f1d7c77450cfdc1228 Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Fri, 12 Sep 2025 10:45:06 +0100 Subject: [PATCH 01/22] ci: Split Docker builds into sequential release and max-perf stages Separate fast release builds from slow max-perf builds to optimise runner utilisation and provide quicker feedback. Release builds complete first with standard optimisations, followed by Haswell-optimised dragrace builds once the safe builds pass successfully. Extract build logic into focused composite actions for better log visibility in Forgejo UI. Split monolithic build action into prepare-docker-build, inline docker build step, and upload-docker-artifacts to ensure each phase completes independently and shows logs immediately. Creates separate manifests at each stage to avoid waiting for all builds before publishing. --- .../actions/create-docker-manifest/action.yml | 104 +++++ .../actions/prepare-docker-build/action.yml | 169 ++++++++ .../upload-docker-artifacts/action.yml | 70 ++++ .forgejo/workflows/release-image.yml | 367 ++++++------------ 4 files changed, 461 insertions(+), 249 deletions(-) create mode 100644 .forgejo/actions/create-docker-manifest/action.yml create mode 100644 .forgejo/actions/prepare-docker-build/action.yml create mode 100644 .forgejo/actions/upload-docker-artifacts/action.yml diff --git a/.forgejo/actions/create-docker-manifest/action.yml b/.forgejo/actions/create-docker-manifest/action.yml new file mode 100644 index 000000000..10c9953aa --- /dev/null +++ b/.forgejo/actions/create-docker-manifest/action.yml @@ -0,0 +1,104 @@ +name: create-manifest +description: | + Create and push a multi-platform Docker manifest from individual platform digests. + Handles downloading digests, creating manifest lists, and pushing to registry. + +inputs: + digest_pattern: + description: Glob pattern to match digest artifacts (e.g. "digests-linux-{amd64,arm64}") + required: true + tag_suffix: + description: Suffix to add to all Docker tags (e.g. "-maxperf") + required: false + default: "" + images: + description: Container registry images (newline-separated) + required: true + registry_user: + description: Registry username for authentication + required: false + registry_password: + description: Registry password for authentication + required: false + +outputs: + version: + description: The version tag created for the manifest + value: ${{ steps.meta.outputs.version }} + tags: + description: All tags created for the manifest + value: ${{ steps.meta.outputs.tags }} + +runs: + using: composite + steps: + - name: Download digests + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + uses: forgejo/download-artifact@v4 + with: + path: /tmp/digests + pattern: ${{ inputs.digest_pattern }} + merge-multiple: true + + - name: Login to builtin registry + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + uses: docker/login-action@v3 + with: + registry: ${{ env.BUILTIN_REGISTRY }} + username: ${{ inputs.registry_user }} + password: ${{ inputs.registry_password }} + + - name: Set up Docker Buildx + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + uses: docker/setup-buildx-action@v3 + with: + # Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125) + driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }} + endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }} + + - name: Extract metadata (tags) for Docker + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + id: meta + uses: docker/metadata-action@v5 + with: + tags: | + type=semver,pattern={{version}},prefix=v,suffix=${{ inputs.tag_suffix }} + type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }},prefix=v,suffix=${{ inputs.tag_suffix }} + type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }},prefix=v,suffix=${{ inputs.tag_suffix }} + type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }},suffix=${{ inputs.tag_suffix }} + type=ref,event=pr,suffix=${{ inputs.tag_suffix }} + type=sha,format=short,suffix=${{ inputs.tag_suffix }} + type=raw,value=latest${{ inputs.tag_suffix }},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + images: ${{ inputs.images }} + # default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509 + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: index + + - name: Create manifest list and push + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + working-directory: /tmp/digests + shell: bash + env: + IMAGES: ${{ inputs.images }} + run: | + IFS=$'\n' + IMAGES_LIST=($IMAGES) + ANNOTATIONS_LIST=($DOCKER_METADATA_OUTPUT_ANNOTATIONS) + TAGS_LIST=($DOCKER_METADATA_OUTPUT_TAGS) + for REPO in "${IMAGES_LIST[@]}"; do + docker buildx imagetools create \ + $(for tag in "${TAGS_LIST[@]}"; do echo "--tag"; echo "$tag"; done) \ + $(for annotation in "${ANNOTATIONS_LIST[@]}"; do echo "--annotation"; echo "$annotation"; done) \ + $(for reference in *; do printf "$REPO@sha256:%s\n" $reference; done) + done + + - name: Inspect image + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + shell: bash + env: + IMAGES: ${{ inputs.images }} + run: | + IMAGES_LIST=($IMAGES) + for REPO in "${IMAGES_LIST[@]}"; do + docker buildx imagetools inspect $REPO:${{ steps.meta.outputs.version }} + done diff --git a/.forgejo/actions/prepare-docker-build/action.yml b/.forgejo/actions/prepare-docker-build/action.yml new file mode 100644 index 000000000..6a397b8a4 --- /dev/null +++ b/.forgejo/actions/prepare-docker-build/action.yml @@ -0,0 +1,169 @@ +name: prepare-docker-build +description: | + Prepare the Docker build environment for Continuwuity builds. + Sets up Rust toolchain, Docker Buildx, caching, and extracts metadata for Docker builds. + +inputs: + platform: + description: Target platform (e.g. linux/amd64, linux/arm64) + required: true + slug: + description: Platform slug for artifact naming (e.g. linux-amd64, linux-arm64) + required: true + target_cpu: + description: Target CPU architecture (e.g. haswell, empty for base) + required: false + default: "" + profile: + description: Cargo build profile (release or release-max-perf) + required: true + images: + description: Container registry images (newline-separated) + required: true + registry_user: + description: Registry username for authentication + required: false + registry_password: + description: Registry password for authentication + required: false + +outputs: + cpu_suffix: + description: CPU suffix for artifact naming + value: ${{ steps.cpu-suffix.outputs.suffix }} + metadata_labels: + description: Docker labels for the image + value: ${{ steps.meta.outputs.labels }} + metadata_annotations: + description: Docker annotations for the image + value: ${{ steps.meta.outputs.annotations }} + +runs: + using: composite + steps: + - name: Set CPU suffix variable + id: cpu-suffix + shell: bash + run: | + if [[ -n "${{ inputs.target_cpu }}" ]]; then + echo "suffix=-${{ inputs.target_cpu }}" >> $GITHUB_OUTPUT + echo "CPU_SUFFIX=-${{ inputs.target_cpu }}" >> $GITHUB_ENV + else + echo "suffix=" >> $GITHUB_OUTPUT + echo "CPU_SUFFIX=" >> $GITHUB_ENV + fi + + - name: Echo matrix configuration + shell: bash + run: | + echo "Platform: ${{ inputs.platform }}" + echo "Slug: ${{ inputs.slug }}" + echo "Target CPU: ${{ inputs.target_cpu }}" + echo "Profile: ${{ inputs.profile }}" + + - name: Install rust + if: ${{ env.BUILDKIT_ENDPOINT == '' }} + id: rust-toolchain + uses: ./.forgejo/actions/rust-toolchain + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + # Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125) + driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }} + endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }} + + - name: Set up QEMU + if: ${{ env.BUILDKIT_ENDPOINT == '' }} + uses: docker/setup-qemu-action@v3 + + - name: Login to builtin registry + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + uses: docker/login-action@v3 + with: + registry: ${{ env.BUILTIN_REGISTRY }} + username: ${{ inputs.registry_user }} + password: ${{ inputs.registry_password }} + + - name: Extract metadata (labels, annotations) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.images }} + # default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509 + env: + DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index + + - name: Get short git commit SHA + id: sha + shell: bash + run: | + calculatedSha=$(git rev-parse --short ${{ github.sha }}) + echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV + echo "Short SHA: $calculatedSha" + + - name: Get Git commit timestamps + shell: bash + run: | + timestamp=$(git log -1 --pretty=%ct) + echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV + echo "Commit timestamp: $timestamp" + + - uses: ./.forgejo/actions/timelord + id: timelord + + - name: Cache Rust registry + if: ${{ env.BUILDKIT_ENDPOINT == '' }} + uses: actions/cache@v3 + with: + path: | + .cargo/git + .cargo/git/checkouts + .cargo/registry + .cargo/registry/src + key: rust-registry-image-${{hashFiles('**/Cargo.lock') }} + + - name: Cache cargo target + if: ${{ env.BUILDKIT_ENDPOINT == '' }} + id: cache-cargo-target + uses: actions/cache@v3 + with: + path: | + cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }} + key: cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}} + + - name: Cache apt cache + if: ${{ env.BUILDKIT_ENDPOINT == '' }} + id: cache-apt + uses: actions/cache@v3 + with: + path: | + var-cache-apt-${{ inputs.slug }} + key: var-cache-apt-${{ inputs.slug }} + + - name: Cache apt lib + if: ${{ env.BUILDKIT_ENDPOINT == '' }} + id: cache-apt-lib + uses: actions/cache@v3 + with: + path: | + var-lib-apt-${{ inputs.slug }} + key: var-lib-apt-${{ inputs.slug }} + + - name: inject cache into docker + if: ${{ env.BUILDKIT_ENDPOINT == '' }} + uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.0 + with: + cache-map: | + { + ".cargo/registry": "/usr/local/cargo/registry", + ".cargo/git/db": "/usr/local/cargo/git/db", + "cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}": { + "target": "/app/target", + "id": "cargo-target${{ env.CPU_SUFFIX }}-${{ inputs.slug }}-${{ inputs.profile }}" + }, + "var-cache-apt-${{ inputs.slug }}": "/var/cache/apt", + "var-lib-apt-${{ inputs.slug }}": "/var/lib/apt", + "${{ steps.timelord.outputs.database-path }}":"/timelord" + } + skip-extraction: ${{ steps.cache.outputs.cache-hit }} diff --git a/.forgejo/actions/upload-docker-artifacts/action.yml b/.forgejo/actions/upload-docker-artifacts/action.yml new file mode 100644 index 000000000..ac91e31a1 --- /dev/null +++ b/.forgejo/actions/upload-docker-artifacts/action.yml @@ -0,0 +1,70 @@ +name: upload-docker-artifacts +description: | + Upload Docker build artifacts including binary and digest files. + Handles artifact naming and conditional digest uploads for registry publishing. + +inputs: + slug: + description: Platform slug for artifact naming (e.g. linux-amd64, linux-arm64) + required: true + cpu_suffix: + description: CPU suffix for artifact naming (e.g. -haswell) + required: false + default: "" + artifact_suffix: + description: Suffix for binary artifacts (e.g. -maxperf) + required: false + default: "" + digest_suffix: + description: Suffix for digest artifacts (e.g. -maxperf) + required: false + default: "" + digest: + description: The digest of the built Docker image + required: true + +outputs: + binary_artifact_name: + description: The name of the uploaded binary artifact + value: conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }} + +runs: + using: composite + steps: + - name: Export digest + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + shell: bash + run: | + mkdir -p /tmp/digests + digest="${{ inputs.digest }}" + echo "🔍 Build step digest output: '$digest'" + if [[ -z "$digest" ]]; then + echo "❌ ERROR: No digest found from build step" + exit 1 + fi + digest_file="/tmp/digests/${digest#sha256:}" + echo "📁 Creating digest file: $digest_file" + touch "$digest_file" + echo "✅ Digest file created successfully" + echo "📋 Contents of /tmp/digests:" + ls -la /tmp/digests/ + + - name: Rename extracted binary + shell: bash + run: mv /tmp/binaries/sbin/conduwuit /tmp/binaries/conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }} + + - name: Upload binary artifact + uses: forgejo/upload-artifact@v4 + with: + name: conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }} + path: /tmp/binaries/conduwuit${{ inputs.cpu_suffix }}-${{ inputs.slug }}${{ inputs.artifact_suffix }} + if-no-files-found: error + + - name: Upload digest + if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} + uses: forgejo/upload-artifact@v4 + with: + name: digests${{ inputs.digest_suffix }}-${{ inputs.slug }}${{ inputs.cpu_suffix }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 5 diff --git a/.forgejo/workflows/release-image.yml b/.forgejo/workflows/release-image.yml index b35195bf0..c7accff38 100644 --- a/.forgejo/workflows/release-image.yml +++ b/.forgejo/workflows/release-image.yml @@ -32,12 +32,12 @@ env: jobs: define-variables: + name: "Setup Variables" runs-on: ubuntu-latest outputs: images: ${{ steps.var.outputs.images }} images_list: ${{ steps.var.outputs.images_list }} - build_matrix: ${{ steps.var.outputs.build_matrix }} steps: - name: Setting variables @@ -59,18 +59,9 @@ jobs: } core.setOutput('images', images.join("\n")) core.setOutput('images_list', images.join(",")) - const platforms = ['linux/amd64', 'linux/arm64'] - core.setOutput('build_matrix', JSON.stringify({ - platform: platforms, - target_cpu: ['base'], - include: platforms.map(platform => { return { - platform, - slug: platform.replace('/', '-') - }}) - })) - build-image: - name: "Build ${{ matrix.slug }}${{ matrix.target_cpu != '' && format('-{0}', matrix.target_cpu) || '' }}" + build-release: + name: "Build ${{ matrix.slug }} (release)" runs-on: dind needs: define-variables permissions: @@ -83,139 +74,25 @@ jobs: include: - platform: "linux/amd64" slug: "linux-amd64" - target_cpu: "" - profile: "release" - - platform: "linux/amd64" - slug: "linux-amd64" - target_cpu: "haswell" - profile: "release-max-perf" - platform: "linux/arm64" slug: "linux-arm64" - target_cpu: "" - profile: "release" steps: - - name: Set CPU suffix variable - run: | - if [[ -n "${{ matrix.target_cpu }}" ]]; then - echo "CPU_SUFFIX=-${{ matrix.target_cpu }}" >> $GITHUB_ENV - else - echo "CPU_SUFFIX=" >> $GITHUB_ENV - fi - - - name: Echo strategy - run: echo '${{ toJSON(fromJSON(needs.define-variables.outputs.build_matrix)) }}' - - name: Echo matrix - run: echo '${{ toJSON(matrix) }}' - - name: Checkout repository uses: actions/checkout@v5 with: persist-credentials: false - - name: Install rust - if: ${{ env.BUILDKIT_ENDPOINT == '' }} - id: rust-toolchain - uses: ./.forgejo/actions/rust-toolchain - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Prepare Docker build environment + id: prepare + uses: ./.forgejo/actions/prepare-docker-build with: - # Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125) - driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }} - endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }} - - name: Set up QEMU - if: ${{ env.BUILDKIT_ENDPOINT == '' }} - uses: docker/setup-qemu-action@v3 - # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - - name: Login to builtin registry - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - uses: docker/login-action@v3 - with: - registry: ${{ env.BUILTIN_REGISTRY }} - username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} - password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} - - # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - - name: Extract metadata (labels, annotations) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{needs.define-variables.outputs.images}} - # default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509 - env: - DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index - - # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. - # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. - # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - # It will not push images generated from a pull request - - name: Get short git commit SHA - id: sha - run: | - calculatedSha=$(git rev-parse --short ${{ github.sha }}) - echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV - echo "Short SHA: $calculatedSha" - - name: Get Git commit timestamps - run: | - timestamp=$(git log -1 --pretty=%ct) - echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV - echo "Commit timestamp: $timestamp" - - - uses: ./.forgejo/actions/timelord - id: timelord - - - name: Cache Rust registry - if: ${{ env.BUILDKIT_ENDPOINT == '' }} - uses: actions/cache@v3 - with: - path: | - .cargo/git - .cargo/git/checkouts - .cargo/registry - .cargo/registry/src - key: rust-registry-image-${{hashFiles('**/Cargo.lock') }} - - name: Cache cargo target - if: ${{ env.BUILDKIT_ENDPOINT == '' }} - id: cache-cargo-target - uses: actions/cache@v3 - with: - path: | - cargo-target${{ env.CPU_SUFFIX }}-${{ matrix.slug }}-${{ matrix.profile }} - key: cargo-target${{ env.CPU_SUFFIX }}-${{ matrix.slug }}-${{ matrix.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}} - - name: Cache apt cache - if: ${{ env.BUILDKIT_ENDPOINT == '' }} - id: cache-apt - uses: actions/cache@v3 - with: - path: | - var-cache-apt-${{ matrix.slug }} - key: var-cache-apt-${{ matrix.slug }} - - name: Cache apt lib - if: ${{ env.BUILDKIT_ENDPOINT == '' }} - id: cache-apt-lib - uses: actions/cache@v3 - with: - path: | - var-lib-apt-${{ matrix.slug }} - key: var-lib-apt-${{ matrix.slug }} - - name: inject cache into docker - if: ${{ env.BUILDKIT_ENDPOINT == '' }} - uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.0 - with: - cache-map: | - { - ".cargo/registry": "/usr/local/cargo/registry", - ".cargo/git/db": "/usr/local/cargo/git/db", - "cargo-target${{ env.CPU_SUFFIX }}-${{ matrix.slug }}-${{ matrix.profile }}": { - "target": "/app/target", - "id": "cargo-target${{ env.CPU_SUFFIX }}-${{ matrix.slug }}-${{ matrix.profile }}" - }, - "var-cache-apt-${{ matrix.slug }}": "/var/cache/apt", - "var-lib-apt-${{ matrix.slug }}": "/var/lib/apt", - "{{ steps.timelord.outputs.database-path }}":"/timelord" - } - skip-extraction: ${{ steps.cache.outputs.cache-hit }} - + platform: ${{ matrix.platform }} + slug: ${{ matrix.slug }} + target_cpu: "" + profile: "release" + images: ${{ needs.define-variables.outputs.images }} + registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} + registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} - name: Build and push Docker image by digest id: build uses: docker/build-push-action@v6 @@ -228,11 +105,11 @@ jobs: GIT_REMOTE_URL=${{github.event.repository.html_url }} GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }} CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }} - TARGET_CPU=${{ matrix.target_cpu }} - RUST_PROFILE=${{ matrix.profile }} + TARGET_CPU= + RUST_PROFILE=release platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - annotations: ${{ steps.meta.outputs.annotations }} + labels: ${{ steps.prepare.outputs.metadata_labels }} + annotations: ${{ steps.prepare.outputs.metadata_annotations }} cache-from: type=gha # cache-to: type=gha,mode=max sbom: true @@ -241,124 +118,116 @@ jobs: type=local,dest=/tmp/binaries env: SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }} - - # For publishing multi-platform manifests - - name: Export digest - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - echo "🔍 Build step digest output: '$digest'" - if [[ -z "$digest" ]]; then - echo "❌ ERROR: No digest found from build step" - exit 1 - fi - digest_file="/tmp/digests/${digest#sha256:}" - echo "📁 Creating digest file: $digest_file" - touch "$digest_file" - echo "✅ Digest file created successfully" - echo "📋 Contents of /tmp/digests:" - ls -la /tmp/digests/ - - # Binary extracted via local output for all builds - - name: Rename extracted binary - run: mv /tmp/binaries/sbin/conduwuit /tmp/binaries/conduwuit${{ env.CPU_SUFFIX }}-${{ matrix.slug }}-${{ matrix.profile }} - - - name: Upload binary artifact - uses: forgejo/upload-artifact@v4 + - name: Upload Docker artifacts + uses: ./.forgejo/actions/upload-docker-artifacts with: - name: conduwuit${{ env.CPU_SUFFIX }}-${{ matrix.slug }}-${{ matrix.profile }} - path: /tmp/binaries/conduwuit${{ env.CPU_SUFFIX }}-${{ matrix.slug }}-${{ matrix.profile }} - if-no-files-found: error + slug: ${{ matrix.slug }} + cpu_suffix: ${{ steps.prepare.outputs.cpu_suffix }} + artifact_suffix: "" + digest_suffix: "" + digest: ${{ steps.build.outputs.digest }} - - name: Upload digest - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - uses: forgejo/upload-artifact@v4 - with: - name: digests-${{ matrix.slug }}${{ env.CPU_SUFFIX }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 5 - - merge: - name: "Create ${{ matrix.target_cpu == '' && 'Multi-arch' || 'Haswell' }} Manifest" + merge-release: + name: "Create Multi-arch Release Manifest" runs-on: dind - needs: [define-variables, build-image] + needs: [define-variables, build-release] + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Create multi-platform manifest + uses: ./.forgejo/actions/create-docker-manifest + with: + digest_pattern: "digests-linux-{amd64,arm64}" + tag_suffix: "" + images: ${{ needs.define-variables.outputs.images }} + registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} + registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} + + build-maxperf: + name: "Build ${{ matrix.slug }} (max-perf)" + runs-on: dind + needs: [define-variables, build-release] + permissions: + contents: read + packages: write + attestations: write + id-token: write strategy: matrix: include: - - target_cpu: "" - digest_pattern: "digests-linux-{amd64,arm64}" - - target_cpu: "haswell" - digest_pattern: "digests-linux-amd64-haswell" + - platform: "linux/amd64" + slug: "linux-amd64" + target_cpu: "haswell" + - platform: "linux/arm64" + slug: "linux-arm64" + target_cpu: "" + steps: - - name: Download digests - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - uses: forgejo/download-artifact@v4 + - name: Checkout repository + uses: actions/checkout@v5 with: - path: /tmp/digests - pattern: ${{ matrix.digest_pattern }} - merge-multiple: true - # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - - name: Login to builtin registry - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - uses: docker/login-action@v3 + persist-credentials: false + - name: Prepare max-perf Docker build environment + id: prepare + uses: ./.forgejo/actions/prepare-docker-build with: - registry: ${{ env.BUILTIN_REGISTRY }} - username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} - password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - uses: docker/setup-buildx-action@v3 + platform: ${{ matrix.platform }} + slug: ${{ matrix.slug }} + target_cpu: ${{ matrix.target_cpu }} + profile: "release-max-perf" + images: ${{ needs.define-variables.outputs.images }} + registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} + registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} + - name: Build and push max-perf Docker image by digest + id: build + uses: docker/build-push-action@v6 with: - # Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125) - driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }} - endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }} - - - name: Extract metadata (tags) for Docker - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - id: meta - uses: docker/metadata-action@v5 - with: - tags: | - type=semver,pattern={{version}},prefix=v,suffix=${{ matrix.target_cpu != '' && format('-{0}', matrix.target_cpu) || '' }} - type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }},prefix=v,suffix=${{ matrix.target_cpu != '' && format('-{0}', matrix.target_cpu) || '' }} - type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }},prefix=v,suffix=${{ matrix.target_cpu != '' && format('-{0}', matrix.target_cpu) || '' }} - type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }},suffix=${{ matrix.target_cpu != '' && format('-{0}', matrix.target_cpu) || '' }} - type=ref,event=pr,suffix=${{ matrix.target_cpu != '' && format('-{0}', matrix.target_cpu) || '' }} - type=sha,format=short,suffix=${{ matrix.target_cpu != '' && format('-{0}', matrix.target_cpu) || '' }} - type=raw,value=latest${{ matrix.target_cpu != '' && format('-{0}', matrix.target_cpu) || '' }},enable=${{ startsWith(github.ref, 'refs/tags/v') }} - images: ${{needs.define-variables.outputs.images}} - # default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509 + context: . + file: "docker/Dockerfile" + build-args: | + GIT_COMMIT_HASH=${{ github.sha }} + GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }} + GIT_REMOTE_URL=${{github.event.repository.html_url }} + GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }} + CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }} + TARGET_CPU=${{ matrix.target_cpu }} + RUST_PROFILE=release-max-perf + platforms: ${{ matrix.platform }} + labels: ${{ steps.prepare.outputs.metadata_labels }} + annotations: ${{ steps.prepare.outputs.metadata_annotations }} + cache-from: type=gha + # cache-to: type=gha,mode=max + sbom: true + outputs: | + ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', needs.define-variables.outputs.images_list) || format('type=image,"name={0}",push=false', needs.define-variables.outputs.images_list) }} + type=local,dest=/tmp/binaries env: - DOCKER_METADATA_ANNOTATIONS_LEVELS: index + SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }} + - name: Upload max-perf Docker artifacts + uses: ./.forgejo/actions/upload-docker-artifacts + with: + slug: ${{ matrix.slug }} + cpu_suffix: ${{ steps.prepare.outputs.cpu_suffix }} + artifact_suffix: "-maxperf" + digest_suffix: "-maxperf" + digest: ${{ steps.build.outputs.digest }} - - name: Create manifest list and push - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - working-directory: /tmp/digests - env: - IMAGES: ${{needs.define-variables.outputs.images}} - shell: bash - run: | - IFS=$'\n' - IMAGES_LIST=($IMAGES) - ANNOTATIONS_LIST=($DOCKER_METADATA_OUTPUT_ANNOTATIONS) - TAGS_LIST=($DOCKER_METADATA_OUTPUT_TAGS) - for REPO in "${IMAGES_LIST[@]}"; do - docker buildx imagetools create \ - $(for tag in "${TAGS_LIST[@]}"; do echo "--tag"; echo "$tag"; done) \ - $(for annotation in "${ANNOTATIONS_LIST[@]}"; do echo "--annotation"; echo "$annotation"; done) \ - $(for reference in *; do printf "$REPO@sha256:%s\n" $reference; done) - done - - - name: Inspect image - if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} - env: - IMAGES: ${{needs.define-variables.outputs.images}} - shell: bash - run: | - IMAGES_LIST=($IMAGES) - for REPO in "${IMAGES_LIST[@]}"; do - docker buildx imagetools inspect $REPO:${{ steps.meta.outputs.version }} - done + merge-maxperf: + name: "Create Max-Perf Manifest" + runs-on: dind + needs: [define-variables, build-maxperf] + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + persist-credentials: false + - name: Create max-perf manifest + uses: ./.forgejo/actions/create-docker-manifest + with: + digest_pattern: "digests-maxperf-linux-{amd64-haswell,arm64}" + tag_suffix: "-maxperf" + images: ${{ needs.define-variables.outputs.images }} + registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} + registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} From 51ba41823fe834effc8efce3900979c257ec6a7d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 12 Sep 2025 05:04:19 +0000 Subject: [PATCH 02/22] chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.106.0 --- .forgejo/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index ba02c3edb..58d6e19c8 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -43,7 +43,7 @@ jobs: name: Renovate runs-on: ubuntu-latest container: - image: ghcr.io/renovatebot/renovate:41.99.9@sha256:8eef7d1db0368b6085d5556a226a518a85dedf51fe9a22c2c66b570ca6169750 + image: ghcr.io/renovatebot/renovate:41.106.0@sha256:6b4b25af910b0427dfaf8c01966f4cba9d1dbf57d1ea227240b5c0ffd91283ce options: --tmpfs /tmp:exec steps: - name: Checkout From 2547eb3a90e804bab93252a83d01a86206a019f4 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 12 Sep 2025 13:29:47 +0000 Subject: [PATCH 03/22] chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.109.0 --- .forgejo/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 58d6e19c8..8491c5bb0 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -43,7 +43,7 @@ jobs: name: Renovate runs-on: ubuntu-latest container: - image: ghcr.io/renovatebot/renovate:41.106.0@sha256:6b4b25af910b0427dfaf8c01966f4cba9d1dbf57d1ea227240b5c0ffd91283ce + image: ghcr.io/renovatebot/renovate:41.109.0@sha256:235e98998f67bc17ac10118850605f26cf91fcbf6e6b823680160b69797cf3a0 options: --tmpfs /tmp:exec steps: - name: Checkout From 3d5355dfc379d48438e09a789d49180ac4516081 Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Fri, 12 Sep 2025 17:50:08 +0100 Subject: [PATCH 04/22] chore(renovate): Add auto-merge for renovatebot and reorganise package rules Enable automatic merging of ghcr.io/renovatebot/renovate docker image updates to reduce manual maintenance overhead. Reorganise package rules by manager type (cargo, github-actions, docker) and add missing description for cargo concurrency limit rule to improve config maintainability. --- renovate.json | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/renovate.json b/renovate.json index ca85cfe78..8e5735b11 100644 --- a/renovate.json +++ b/renovate.json @@ -30,10 +30,15 @@ }, "packageRules": [ { - "description": "Batch minor and patch GitHub Actions updates", - "matchManagers": ["github-actions"], - "matchUpdateTypes": ["minor", "patch"], - "groupName": "github-actions-non-major" + "description": "Batch patch-level Rust dependency updates", + "matchManagers": ["cargo"], + "matchUpdateTypes": ["patch"], + "groupName": "rust-patch-updates" + }, + { + "description": "Limit concurrent Cargo PRs", + "matchManagers": ["cargo"], + "prConcurrentLimit": 5 }, { "description": "Group Rust toolchain updates into a single PR", @@ -42,25 +47,27 @@ "groupName": "rust-toolchain" }, { - "description": "Group lockfile updates into a single PR", - "matchUpdateTypes": ["lockFileMaintenance"], - "groupName": "lockfile-maintenance" - }, - { - "description": "Batch patch-level Rust dependency updates", - "matchManagers": ["cargo"], - "matchUpdateTypes": ["patch"], - "groupName": "rust-patch-updates" - }, - { - "matchManagers": ["cargo"], - "prConcurrentLimit": 5 + "description": "Batch minor and patch GitHub Actions updates", + "matchManagers": ["github-actions"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "github-actions-non-major" }, { "description": "Pin forgejo artifact actions to prevent breaking changes", "matchManagers": ["github-actions"], "matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"], "enabled": false + }, + { + "description": "Auto-merge renovatebot docker image updates", + "matchDatasources": ["docker"], + "matchPackageNames": ["ghcr.io/renovatebot/renovate"], + "automerge": true + }, + { + "description": "Group lockfile updates into a single PR", + "matchUpdateTypes": ["lockFileMaintenance"], + "groupName": "lockfile-maintenance" } ], "customManagers": [ From 0f09fa3d31b0d8b4e0fae8c2a8f9dbc657014cb7 Mon Sep 17 00:00:00 2001 From: Jade Date: Fri, 12 Sep 2025 21:02:25 +0000 Subject: [PATCH 05/22] chore(renovate): Specify automerge strategy --- renovate.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 8e5735b11..8c3c35c44 100644 --- a/renovate.json +++ b/renovate.json @@ -6,6 +6,7 @@ "enabled": true, "schedule": ["at any time"] }, + "platformAutomerge": true, "nix": { "enabled": true }, @@ -62,7 +63,8 @@ "description": "Auto-merge renovatebot docker image updates", "matchDatasources": ["docker"], "matchPackageNames": ["ghcr.io/renovatebot/renovate"], - "automerge": true + "automerge": true, + "automergeStrategy": "fast-forward" }, { "description": "Group lockfile updates into a single PR", From 0bbc3c4e05a22c5ba59eddb90a2bd15511937029 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 12 Sep 2025 21:11:13 +0000 Subject: [PATCH 06/22] chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.112.0 --- .forgejo/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 8491c5bb0..bfa150927 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -43,7 +43,7 @@ jobs: name: Renovate runs-on: ubuntu-latest container: - image: ghcr.io/renovatebot/renovate:41.109.0@sha256:235e98998f67bc17ac10118850605f26cf91fcbf6e6b823680160b69797cf3a0 + image: ghcr.io/renovatebot/renovate:41.112.0@sha256:5a9ac81e00a7ea99b648bb858a84ab42bf3ff6db5a037d2cc7547c54713821bc options: --tmpfs /tmp:exec steps: - name: Checkout From 81b6b3547c97eef7b8e89e835c580ad6c3370107 Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Sat, 13 Sep 2025 16:17:28 +0100 Subject: [PATCH 07/22] fix: Resolve Forgejo runner v11 matrix job execution failure Matrix jobs stopped starting after upgrading from runner v9 to v11 due to changes in job dependency resolution. Remove redundant define-variables job that computed static image paths and replace with IMAGE_PATH environment variable. Also fix timelord action binary caching for compatibility between different runner images that install cargo binaries in different locations. --- .forgejo/actions/timelord/action.yml | 27 ++++++++------- .forgejo/workflows/release-image.yml | 49 ++++++---------------------- 2 files changed, 26 insertions(+), 50 deletions(-) diff --git a/.forgejo/actions/timelord/action.yml b/.forgejo/actions/timelord/action.yml index 40c6d0a6c..240cb2c30 100644 --- a/.forgejo/actions/timelord/action.yml +++ b/.forgejo/actions/timelord/action.yml @@ -27,25 +27,28 @@ runs: echo "TIMELORD_KEY=${{ inputs.key || format('timelord-v1-{0}-{1}', github.repository, hashFiles('**/*.rs', '**/Cargo.toml', '**/Cargo.lock')) }}" >> $GITHUB_ENV echo "TIMELORD_PATH=${{ inputs.path || '.' }}" >> $GITHUB_ENV echo "TIMELORD_CACHE_PATH=$HOME/.cache/timelord" >> $GITHUB_ENV + echo "PATH=/usr/share/rust/.cargo/bin:$HOME/.cargo/bin:$PATH" >> $GITHUB_ENV - name: Restore binary cache id: binary-cache uses: actions/cache/restore@v4 with: - path: /usr/share/rust/.cargo/bin - key: timelord-binaries-v1 + path: | + /usr/share/rust/.cargo/bin + ~/.cargo/bin + key: timelord-binaries-v2 - name: Check if binaries need installation shell: bash id: check-binaries run: | NEED_INSTALL=false - if [ ! -e /usr/share/rust/.cargo/bin/timelord ]; then - echo "timelord-cli not found at /usr/share/rust/.cargo/bin/timelord, needs installation" + if [ ! -e /usr/share/rust/.cargo/bin/timelord ] && [ ! -e ~/.cargo/bin/timelord ]; then + echo "timelord-cli not found in /usr/share/rust/.cargo/bin or ~/.cargo/bin, needs installation" NEED_INSTALL=true fi - if [ ! -e /usr/share/rust/.cargo/bin/git-warp-time ]; then - echo "git-warp-time not found at /usr/share/rust/.cargo/bin/git-warp-time, needs installation" + if [ ! -e /usr/share/rust/.cargo/bin/git-warp-time ] && [ ! -e ~/.cargo/bin/git-warp-time ]; then + echo "git-warp-time not found in /usr/share/rust/.cargo/bin or ~/.cargo/bin, needs installation" NEED_INSTALL=true fi echo "need-install=$NEED_INSTALL" >> $GITHUB_OUTPUT @@ -60,8 +63,10 @@ runs: if: steps.check-binaries.outputs.need-install == 'true' uses: actions/cache/save@v4 with: - path: /usr/share/rust/.cargo/bin - key: timelord-binaries-v1 + path: | + /usr/share/rust/.cargo/bin + ~/.cargo/bin + key: timelord-binaries-v2 - name: Restore timelord cache with fallbacks @@ -80,9 +85,9 @@ runs: echo "Complete timelord cache miss - running git-warp-time" git fetch --unshallow if [ "${{ env.TIMELORD_PATH }}" = "." ]; then - /usr/share/rust/.cargo/bin/git-warp-time --quiet + git-warp-time --quiet else - /usr/share/rust/.cargo/bin/git-warp-time --quiet ${{ env.TIMELORD_PATH }} + git-warp-time --quiet ${{ env.TIMELORD_PATH }} fi echo "Git timestamps restored" @@ -90,7 +95,7 @@ runs: shell: bash run: | mkdir -p ${{ env.TIMELORD_CACHE_PATH }} - /usr/share/rust/.cargo/bin/timelord sync --source-dir ${{ env.TIMELORD_PATH }} --cache-dir ${{ env.TIMELORD_CACHE_PATH }} + timelord sync --source-dir ${{ env.TIMELORD_PATH }} --cache-dir ${{ env.TIMELORD_CACHE_PATH }} - name: Save updated timelord cache immediately uses: actions/cache/save@v4 diff --git a/.forgejo/workflows/release-image.yml b/.forgejo/workflows/release-image.yml index c7accff38..90ad64904 100644 --- a/.forgejo/workflows/release-image.yml +++ b/.forgejo/workflows/release-image.yml @@ -29,41 +29,12 @@ on: env: BUILTIN_REGISTRY: forgejo.ellis.link BUILTIN_REGISTRY_ENABLED: "${{ ((vars.BUILTIN_REGISTRY_USER && secrets.BUILTIN_REGISTRY_PASSWORD) || (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)) && 'true' || 'false' }}" + IMAGE_PATH: forgejo.ellis.link/continuwuation/continuwuity jobs: - define-variables: - name: "Setup Variables" - runs-on: ubuntu-latest - - outputs: - images: ${{ steps.var.outputs.images }} - images_list: ${{ steps.var.outputs.images_list }} - - steps: - - name: Setting variables - uses: https://github.com/actions/github-script@v8 - id: var - with: - script: | - const githubRepo = '${{ github.repository }}'.toLowerCase() - const repoId = githubRepo.split('/')[1] - - core.setOutput('github_repository', githubRepo) - const builtinImage = '${{ env.BUILTIN_REGISTRY }}/' + githubRepo - let images = [] - if (process.env.BUILTIN_REGISTRY_ENABLED === "true") { - images.push(builtinImage) - } else { - // Fallback to official registry for forks/PRs without credentials - images.push('forgejo.ellis.link/continuwuation/continuwuity') - } - core.setOutput('images', images.join("\n")) - core.setOutput('images_list', images.join(",")) - build-release: name: "Build ${{ matrix.slug }} (release)" runs-on: dind - needs: define-variables permissions: contents: read packages: write @@ -90,7 +61,7 @@ jobs: slug: ${{ matrix.slug }} target_cpu: "" profile: "release" - images: ${{ needs.define-variables.outputs.images }} + images: ${{ env.IMAGE_PATH }} registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} - name: Build and push Docker image by digest @@ -114,7 +85,7 @@ jobs: # cache-to: type=gha,mode=max sbom: true outputs: | - ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', needs.define-variables.outputs.images_list) || format('type=image,"name={0}",push=false', needs.define-variables.outputs.images_list) }} + ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', env.IMAGE_PATH) || format('type=image,"name={0}",push=false', env.IMAGE_PATH) }} type=local,dest=/tmp/binaries env: SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }} @@ -130,7 +101,7 @@ jobs: merge-release: name: "Create Multi-arch Release Manifest" runs-on: dind - needs: [define-variables, build-release] + needs: build-release steps: - name: Checkout repository uses: actions/checkout@v5 @@ -141,14 +112,14 @@ jobs: with: digest_pattern: "digests-linux-{amd64,arm64}" tag_suffix: "" - images: ${{ needs.define-variables.outputs.images }} + images: ${{ env.IMAGE_PATH }} registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} build-maxperf: name: "Build ${{ matrix.slug }} (max-perf)" runs-on: dind - needs: [define-variables, build-release] + needs: build-release permissions: contents: read packages: write @@ -177,7 +148,7 @@ jobs: slug: ${{ matrix.slug }} target_cpu: ${{ matrix.target_cpu }} profile: "release-max-perf" - images: ${{ needs.define-variables.outputs.images }} + images: ${{ env.IMAGE_PATH }} registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} - name: Build and push max-perf Docker image by digest @@ -201,7 +172,7 @@ jobs: # cache-to: type=gha,mode=max sbom: true outputs: | - ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', needs.define-variables.outputs.images_list) || format('type=image,"name={0}",push=false', needs.define-variables.outputs.images_list) }} + ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', env.IMAGE_PATH) || format('type=image,"name={0}",push=false', env.IMAGE_PATH) }} type=local,dest=/tmp/binaries env: SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }} @@ -217,7 +188,7 @@ jobs: merge-maxperf: name: "Create Max-Perf Manifest" runs-on: dind - needs: [define-variables, build-maxperf] + needs: build-maxperf steps: - name: Checkout repository uses: actions/checkout@v5 @@ -228,6 +199,6 @@ jobs: with: digest_pattern: "digests-maxperf-linux-{amd64-haswell,arm64}" tag_suffix: "-maxperf" - images: ${{ needs.define-variables.outputs.images }} + images: ${{ env.IMAGE_PATH }} registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }} registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} From d9d0d1a4652629569edc06c8f87d8e77254abd15 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Thu, 11 Sep 2025 17:21:20 +0100 Subject: [PATCH 08/22] fix(!1014): Don't prematurely return during registration --- src/api/client/account.rs | 74 ++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/src/api/client/account.rs b/src/api/client/account.rs index 67268c9f0..3c9cc035b 100644 --- a/src/api/client/account.rs +++ b/src/api/client/account.rs @@ -405,41 +405,51 @@ pub(crate) async fn register_route( ) .await?; - if (!is_guest && body.inhibit_login) + // if (!is_guest && body.inhibit_login) + // || body + // .appservice_info + // .as_ref() + // .is_some_and(|appservice| appservice.registration.device_management) + // { + // return Ok(register::v3::Response { + // access_token: None, + // user_id, + // device_id: None, + // refresh_token: None, + // expires_in: None, + // }); + // } + + // Generate new device id if the user didn't specify one + let no_device = body.inhibit_login || body .appservice_info .as_ref() - .is_some_and(|appservice| appservice.registration.device_management) - { - return Ok(register::v3::Response { - access_token: None, - user_id, - device_id: None, - refresh_token: None, - expires_in: None, - }); - } + .is_some_and(|aps| aps.registration.device_management); + let (token, device) = if !no_device { + // Don't create a device for inhibited logins + let device_id = if is_guest { None } else { body.device_id.clone() } + .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into()); - // Generate new device id if the user didn't specify one - let device_id = if is_guest { None } else { body.device_id.clone() } - .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into()); + // Generate new token for the device + let new_token = utils::random_string(TOKEN_LENGTH); - // Generate new token for the device - let token = utils::random_string(TOKEN_LENGTH); - - // Create device for this account - services - .users - .create_device( - &user_id, - &device_id, - &token, - body.initial_device_display_name.clone(), - Some(client.to_string()), - ) - .await?; - - debug_info!(%user_id, %device_id, "User account was created"); + // Create device for this account + services + .users + .create_device( + &user_id, + &device_id, + &new_token, + body.initial_device_display_name.clone(), + Some(client.to_string()), + ) + .await?; + debug_info!(%user_id, %device_id, "User account was created"); + (Some(new_token), Some(device_id)) + } else { + (None, None) + }; let device_display_name = body.initial_device_display_name.as_deref().unwrap_or(""); @@ -583,9 +593,9 @@ pub(crate) async fn register_route( } Ok(register::v3::Response { - access_token: Some(token), + access_token: token, user_id, - device_id: Some(device_id), + device_id: device, refresh_token: None, expires_in: None, }) From 14df55e5c5728905fb2d990ee7d866a076fb25a3 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Thu, 11 Sep 2025 17:23:45 +0100 Subject: [PATCH 09/22] style(1014): Remove unnecessary commented code --- src/api/client/account.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/api/client/account.rs b/src/api/client/account.rs index 3c9cc035b..dde3af667 100644 --- a/src/api/client/account.rs +++ b/src/api/client/account.rs @@ -405,21 +405,6 @@ pub(crate) async fn register_route( ) .await?; - // if (!is_guest && body.inhibit_login) - // || body - // .appservice_info - // .as_ref() - // .is_some_and(|appservice| appservice.registration.device_management) - // { - // return Ok(register::v3::Response { - // access_token: None, - // user_id, - // device_id: None, - // refresh_token: None, - // expires_in: None, - // }); - // } - // Generate new device id if the user didn't specify one let no_device = body.inhibit_login || body From 50cd1081baa2a779029fdd332b082979a7bf5d5f Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Thu, 11 Sep 2025 17:35:04 +0100 Subject: [PATCH 10/22] chore(1014): Bump ruwuma --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95844e548..5b646b374 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4107,7 +4107,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "ruma" version = "0.10.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "assign", "js_int", @@ -4127,7 +4127,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.10.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "js_int", "ruma-common", @@ -4139,7 +4139,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "as_variant", "assign", @@ -4162,7 +4162,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "as_variant", "base64 0.22.1", @@ -4194,7 +4194,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "as_variant", "indexmap 2.11.1", @@ -4219,7 +4219,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "bytes", "headers", @@ -4241,7 +4241,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "js_int", "thiserror 2.0.16", @@ -4250,7 +4250,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "js_int", "ruma-common", @@ -4260,7 +4260,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "cfg-if", "proc-macro-crate", @@ -4275,7 +4275,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "js_int", "ruma-common", @@ -4287,7 +4287,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.15.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" dependencies = [ "base64 0.22.1", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 2c9f3bc39..b56deef54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -352,7 +352,7 @@ version = "0.1.2" [workspace.dependencies.ruma] git = "https://forgejo.ellis.link/continuwuation/ruwuma" #branch = "conduwuit-changes" -rev = "8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" +rev = "196574e4102b3c93427909aa229be0ceacaa8980" features = [ "compat", "rand", From 36dabecb82e14d17f2e238d09390b15b9d21d410 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Thu, 11 Sep 2025 17:47:47 +0100 Subject: [PATCH 11/22] chore(1014): Include MSC4155 in build features to resolve build errors --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index b56deef54..0cb415b75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -387,6 +387,7 @@ features = [ "unstable-msc4210", # remove legacy mentions "unstable-extensible-events", "unstable-pdu", + "unstable-msc4155" ] [workspace.dependencies.rust-rocksdb] From af45c348a435ff0286575780f408b0b502dfe71d Mon Sep 17 00:00:00 2001 From: Ginger Date: Fri, 12 Sep 2025 17:59:05 -0400 Subject: [PATCH 12/22] fix: Properly deserialize changes to legacy fields made with MSC4133 endpoints --- src/api/client/unstable.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/api/client/unstable.rs b/src/api/client/unstable.rs index f8703ff33..d03300c74 100644 --- a/src/api/client/unstable.rs +++ b/src/api/client/unstable.rs @@ -163,6 +163,9 @@ pub(crate) async fn set_profile_key_route( } if body.key_name == "displayname" { + let Some(display_name) = profile_key_value.as_str() else { + return Err!(Request(BadJson("displayname must be a string"))); + }; let all_joined_rooms: Vec = services .rooms .state_cache @@ -174,12 +177,15 @@ pub(crate) async fn set_profile_key_route( update_displayname( &services, &body.user_id, - Some(profile_key_value.to_string()), + Some(display_name.to_owned()), &all_joined_rooms, ) .await; } else if body.key_name == "avatar_url" { - let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string()); + let Some(avatar_url) = profile_key_value.as_str() else { + return Err!(Request(BadJson("avatar_url must be a string"))); + }; + let mxc = ruma::OwnedMxcUri::from(avatar_url); let all_joined_rooms: Vec = services .rooms From 3dd04bd9dfe5bc49876f1e90a74592a12f642131 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 14 Sep 2025 05:03:21 +0000 Subject: [PATCH 13/22] chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.113.2 --- .forgejo/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index bfa150927..6d022122d 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -43,7 +43,7 @@ jobs: name: Renovate runs-on: ubuntu-latest container: - image: ghcr.io/renovatebot/renovate:41.112.0@sha256:5a9ac81e00a7ea99b648bb858a84ab42bf3ff6db5a037d2cc7547c54713821bc + image: ghcr.io/renovatebot/renovate:41.113.2@sha256:f32285b620eb1fba4cf21d1d145de5c817c0cb73178970c7fb91485fedad7e13 options: --tmpfs /tmp:exec steps: - name: Checkout From f872210b20fb3886f9f500390112ef419ff97e42 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 15 Sep 2025 05:01:40 +0000 Subject: [PATCH 14/22] chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.113.4 --- .forgejo/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 6d022122d..d0ff9703a 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -43,7 +43,7 @@ jobs: name: Renovate runs-on: ubuntu-latest container: - image: ghcr.io/renovatebot/renovate:41.113.2@sha256:f32285b620eb1fba4cf21d1d145de5c817c0cb73178970c7fb91485fedad7e13 + image: ghcr.io/renovatebot/renovate:41.113.4@sha256:472f1a1d07a823da002be9ddf8507275517d3dc1bdcba4606ee3e62f1c27204a options: --tmpfs /tmp:exec steps: - name: Checkout From a120a4fa9531f1bfe66d6d7b971de7bf9328e235 Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Mon, 15 Sep 2025 16:17:32 +0100 Subject: [PATCH 15/22] fix: Handle runner cargo bin path migration in timelord action Runner images have migrated from /usr/share/rust/.cargo/bin to standard ~/.cargo/bin location. Action now checks old location first and migrates binaries if found, maintaining compatibility with both paths. Bump cache key to v3 to ensure fresh binary cache after path changes. --- .forgejo/actions/timelord/action.yml | 30 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/.forgejo/actions/timelord/action.yml b/.forgejo/actions/timelord/action.yml index 240cb2c30..43ad6c095 100644 --- a/.forgejo/actions/timelord/action.yml +++ b/.forgejo/actions/timelord/action.yml @@ -27,7 +27,7 @@ runs: echo "TIMELORD_KEY=${{ inputs.key || format('timelord-v1-{0}-{1}', github.repository, hashFiles('**/*.rs', '**/Cargo.toml', '**/Cargo.lock')) }}" >> $GITHUB_ENV echo "TIMELORD_PATH=${{ inputs.path || '.' }}" >> $GITHUB_ENV echo "TIMELORD_CACHE_PATH=$HOME/.cache/timelord" >> $GITHUB_ENV - echo "PATH=/usr/share/rust/.cargo/bin:$HOME/.cargo/bin:$PATH" >> $GITHUB_ENV + echo "PATH=$HOME/.cargo/bin:/usr/share/rust/.cargo/bin:$PATH" >> $GITHUB_ENV - name: Restore binary cache id: binary-cache @@ -36,21 +36,37 @@ runs: path: | /usr/share/rust/.cargo/bin ~/.cargo/bin - key: timelord-binaries-v2 + key: timelord-binaries-v3 - name: Check if binaries need installation shell: bash id: check-binaries run: | NEED_INSTALL=false - if [ ! -e /usr/share/rust/.cargo/bin/timelord ] && [ ! -e ~/.cargo/bin/timelord ]; then - echo "timelord-cli not found in /usr/share/rust/.cargo/bin or ~/.cargo/bin, needs installation" + + # Ensure ~/.cargo/bin exists + mkdir -p ~/.cargo/bin + + # Check and move timelord if needed + if [ -f /usr/share/rust/.cargo/bin/timelord ] && [ ! -f ~/.cargo/bin/timelord ]; then + echo "Moving timelord from /usr/share/rust/.cargo/bin to ~/.cargo/bin" + mv /usr/share/rust/.cargo/bin/timelord ~/.cargo/bin/ + fi + if [ ! -f ~/.cargo/bin/timelord ]; then + echo "timelord-cli not found, needs installation" NEED_INSTALL=true fi - if [ ! -e /usr/share/rust/.cargo/bin/git-warp-time ] && [ ! -e ~/.cargo/bin/git-warp-time ]; then - echo "git-warp-time not found in /usr/share/rust/.cargo/bin or ~/.cargo/bin, needs installation" + + # Check and move git-warp-time if needed + if [ -f /usr/share/rust/.cargo/bin/git-warp-time ] && [ ! -f ~/.cargo/bin/git-warp-time ]; then + echo "Moving git-warp-time from /usr/share/rust/.cargo/bin to ~/.cargo/bin" + mv /usr/share/rust/.cargo/bin/git-warp-time ~/.cargo/bin/ + fi + if [ ! -f ~/.cargo/bin/git-warp-time ]; then + echo "git-warp-time not found, needs installation" NEED_INSTALL=true fi + echo "need-install=$NEED_INSTALL" >> $GITHUB_OUTPUT - name: Install timelord-cli and git-warp-time @@ -66,7 +82,7 @@ runs: path: | /usr/share/rust/.cargo/bin ~/.cargo/bin - key: timelord-binaries-v2 + key: timelord-binaries-v3 - name: Restore timelord cache with fallbacks From 6b7f35a8b876009022838c056a3625f8a9b9809d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 16 Sep 2025 05:01:56 +0000 Subject: [PATCH 16/22] chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.115.0 --- .forgejo/workflows/renovate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index d0ff9703a..c3d3032f3 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -43,7 +43,7 @@ jobs: name: Renovate runs-on: ubuntu-latest container: - image: ghcr.io/renovatebot/renovate:41.113.4@sha256:472f1a1d07a823da002be9ddf8507275517d3dc1bdcba4606ee3e62f1c27204a + image: ghcr.io/renovatebot/renovate:41.115.0@sha256:7a84af6c88502752f670c16520f869f93789e387ff617d7ed92b1a9c00297f66 options: --tmpfs /tmp:exec steps: - name: Checkout From e6fd3c970b130d3445e1d7a4ff395f964abdd478 Mon Sep 17 00:00:00 2001 From: Ginger Date: Fri, 12 Sep 2025 21:14:34 -0400 Subject: [PATCH 17/22] fix: Nuke explicit references to the MSC4175 `tz` profile field --- Cargo.lock | 22 +++---- Cargo.toml | 2 +- src/api/client/profile.rs | 17 ++--- src/api/client/unstable.rs | 125 +------------------------------------ src/api/router.rs | 3 - src/api/router/auth.rs | 7 +-- src/api/server/query.rs | 7 --- src/service/users/mod.rs | 28 --------- 8 files changed, 20 insertions(+), 191 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b646b374..6d67e8615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4107,7 +4107,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "ruma" version = "0.10.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "assign", "js_int", @@ -4127,7 +4127,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.10.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "js_int", "ruma-common", @@ -4139,7 +4139,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "as_variant", "assign", @@ -4162,7 +4162,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "as_variant", "base64 0.22.1", @@ -4194,7 +4194,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "as_variant", "indexmap 2.11.1", @@ -4219,7 +4219,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "bytes", "headers", @@ -4241,7 +4241,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "js_int", "thiserror 2.0.16", @@ -4250,7 +4250,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "js_int", "ruma-common", @@ -4260,7 +4260,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "cfg-if", "proc-macro-crate", @@ -4275,7 +4275,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "js_int", "ruma-common", @@ -4287,7 +4287,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.15.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=196574e4102b3c93427909aa229be0ceacaa8980#196574e4102b3c93427909aa229be0ceacaa8980" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=78135227dceb9ce5eb9515b4ae85d452cdcabac8#78135227dceb9ce5eb9515b4ae85d452cdcabac8" dependencies = [ "base64 0.22.1", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 0cb415b75..cba2d433a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -352,7 +352,7 @@ version = "0.1.2" [workspace.dependencies.ruma] git = "https://forgejo.ellis.link/continuwuation/ruwuma" #branch = "conduwuit-changes" -rev = "196574e4102b3c93427909aa229be0ceacaa8980" +rev = "78135227dceb9ce5eb9515b4ae85d452cdcabac8" features = [ "compat", "rand", diff --git a/src/api/client/profile.rs b/src/api/client/profile.rs index eaa66e70f..bf6a6d939 100644 --- a/src/api/client/profile.rs +++ b/src/api/client/profile.rs @@ -10,7 +10,7 @@ use conduwuit::{ use conduwuit_service::Services; use futures::{ StreamExt, TryStreamExt, - future::{join, join3, join4}, + future::{join, join3}, }; use ruma::{ OwnedMxcUri, OwnedRoomId, UserId, @@ -226,7 +226,8 @@ pub(crate) async fn get_avatar_url_route( /// # `GET /_matrix/client/v3/profile/{userId}` /// -/// Returns the displayname, avatar_url, blurhash, and tz of the user. +/// Returns the displayname, avatar_url, blurhash, and custom profile fields of +/// the user. /// /// - If user is on another server and we do not have a local copy already, /// fetch profile over federation. @@ -260,9 +261,6 @@ pub(crate) async fn get_profile_route( services .users .set_blurhash(&body.user_id, response.blurhash.clone()); - services - .users - .set_timezone(&body.user_id, response.tz.clone()); for (profile_key, profile_key_value) in &response.custom_profile_fields { services.users.set_profile_key( @@ -276,7 +274,6 @@ pub(crate) async fn get_profile_route( displayname: response.displayname, avatar_url: response.avatar_url, blurhash: response.blurhash, - tz: response.tz, custom_profile_fields: response.custom_profile_fields, }); } @@ -294,15 +291,10 @@ pub(crate) async fn get_profile_route( .collect() .await; - // services.users.timezone will collect the MSC4175 timezone key if it exists - custom_profile_fields.remove("us.cloke.msc4175.tz"); - custom_profile_fields.remove("m.tz"); - - let (avatar_url, blurhash, displayname, tz) = join4( + let (avatar_url, blurhash, displayname) = join3( services.users.avatar_url(&body.user_id).ok(), services.users.blurhash(&body.user_id).ok(), services.users.displayname(&body.user_id).ok(), - services.users.timezone(&body.user_id).ok(), ) .await; @@ -310,7 +302,6 @@ pub(crate) async fn get_profile_route( avatar_url, blurhash, displayname, - tz, custom_profile_fields, }) } diff --git a/src/api/client/unstable.rs b/src/api/client/unstable.rs index d03300c74..f702de9c8 100644 --- a/src/api/client/unstable.rs +++ b/src/api/client/unstable.rs @@ -10,10 +10,7 @@ use ruma::{ client::{ error::ErrorKind, membership::mutual_rooms, - profile::{ - delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key, - set_profile_key, set_timezone_key, - }, + profile::{delete_profile_key, get_profile_key, set_profile_key}, }, federation, }, @@ -60,62 +57,6 @@ pub(crate) async fn get_mutual_rooms_route( }) } -/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz` -/// -/// Deletes the `tz` (timezone) of a user, as per MSC4133 and MSC4175. -/// -/// - Also makes sure other users receive the update using presence EDUs -pub(crate) async fn delete_timezone_key_route( - State(services): State, - body: Ruma, -) -> Result { - let sender_user = body.sender_user(); - - if *sender_user != body.user_id && body.appservice_info.is_none() { - return Err!(Request(Forbidden("You cannot update the profile of another user"))); - } - - services.users.set_timezone(&body.user_id, None); - - if services.config.allow_local_presence { - // Presence update - services - .presence - .ping_presence(&body.user_id, &PresenceState::Online) - .await?; - } - - Ok(delete_timezone_key::unstable::Response {}) -} - -/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz` -/// -/// Updates the `tz` (timezone) of a user, as per MSC4133 and MSC4175. -/// -/// - Also makes sure other users receive the update using presence EDUs -pub(crate) async fn set_timezone_key_route( - State(services): State, - body: Ruma, -) -> Result { - let sender_user = body.sender_user(); - - if *sender_user != body.user_id && body.appservice_info.is_none() { - return Err!(Request(Forbidden("You cannot update the profile of another user"))); - } - - services.users.set_timezone(&body.user_id, body.tz.clone()); - - if services.config.allow_local_presence { - // Presence update - services - .presence - .ping_presence(&body.user_id, &PresenceState::Online) - .await?; - } - - Ok(set_timezone_key::unstable::Response {}) -} - /// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}` /// /// Updates the profile key-value field of a user, as per MSC4133. @@ -274,70 +215,12 @@ pub(crate) async fn delete_profile_key_route( Ok(delete_profile_key::unstable::Response {}) } -/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz` -/// -/// Returns the `timezone` of the user as per MSC4133 and MSC4175. -/// -/// - If user is on another server and we do not have a local copy already fetch -/// `timezone` over federation -pub(crate) async fn get_timezone_key_route( - State(services): State, - body: Ruma, -) -> Result { - if !services.globals.user_is_local(&body.user_id) { - // Create and update our local copy of the user - if let Ok(response) = services - .sending - .send_federation_request( - body.user_id.server_name(), - federation::query::get_profile_information::v1::Request { - user_id: body.user_id.clone(), - field: None, // we want the full user's profile to update locally as well - }, - ) - .await - { - if !services.users.exists(&body.user_id).await { - services.users.create(&body.user_id, None, None).await?; - } - - services - .users - .set_displayname(&body.user_id, response.displayname.clone()); - - services - .users - .set_avatar_url(&body.user_id, response.avatar_url.clone()); - - services - .users - .set_blurhash(&body.user_id, response.blurhash.clone()); - - services - .users - .set_timezone(&body.user_id, response.tz.clone()); - - return Ok(get_timezone_key::unstable::Response { tz: response.tz }); - } - } - - if !services.users.exists(&body.user_id).await { - // Return 404 if this user doesn't exist and we couldn't fetch it over - // federation - return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found.")); - } - - Ok(get_timezone_key::unstable::Response { - tz: services.users.timezone(&body.user_id).await.ok(), - }) -} - /// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{userId}/{field}}` /// /// Gets the profile key-value field of a user, as per MSC4133. /// /// - If user is on another server and we do not have a local copy already fetch -/// `timezone` over federation +/// the value over federation pub(crate) async fn get_profile_key_route( State(services): State, body: Ruma, @@ -373,10 +256,6 @@ pub(crate) async fn get_profile_key_route( .users .set_blurhash(&body.user_id, response.blurhash.clone()); - services - .users - .set_timezone(&body.user_id, response.tz.clone()); - match response.custom_profile_fields.get(&body.key_name) { | Some(value) => { profile_key_value.insert(body.key_name.clone(), value.clone()); diff --git a/src/api/router.rs b/src/api/router.rs index 42934f700..4ab9780b1 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -22,12 +22,9 @@ use crate::{client, server}; pub fn build(router: Router, server: &Server) -> Router { let config = &server.config; let mut router = router - .ruma_route(&client::get_timezone_key_route) .ruma_route(&client::get_profile_key_route) .ruma_route(&client::set_profile_key_route) .ruma_route(&client::delete_profile_key_route) - .ruma_route(&client::set_timezone_key_route) - .ruma_route(&client::delete_timezone_key_route) .ruma_route(&client::appservice_ping) .ruma_route(&client::get_supported_versions_route) .ruma_route(&client::get_register_available_route) diff --git a/src/api/router/auth.rs b/src/api/router/auth.rs index 44afc3ef8..4baf0148e 100644 --- a/src/api/router/auth.rs +++ b/src/api/router/auth.rs @@ -20,9 +20,7 @@ use ruma::{ client::{ directory::get_public_rooms, error::ErrorKind, - profile::{ - get_avatar_url, get_display_name, get_profile, get_profile_key, get_timezone_key, - }, + profile::{get_avatar_url, get_display_name, get_profile, get_profile_key}, voip::get_turn_server_info, }, federation::{authentication::XMatrix, openid::get_openid_userinfo}, @@ -89,8 +87,7 @@ pub(super) async fn auth( | &get_profile::v3::Request::METADATA | &get_profile_key::unstable::Request::METADATA | &get_display_name::v3::Request::METADATA - | &get_avatar_url::v3::Request::METADATA - | &get_timezone_key::unstable::Request::METADATA => { + | &get_avatar_url::v3::Request::METADATA => { if services.server.config.require_auth_for_profile_requests { match token { | Token::Appservice(_) | Token::User(_) => { diff --git a/src/api/server/query.rs b/src/api/server/query.rs index 9d4fcf738..bd68afda6 100644 --- a/src/api/server/query.rs +++ b/src/api/server/query.rs @@ -83,7 +83,6 @@ pub(crate) async fn get_profile_information_route( let mut displayname = None; let mut avatar_url = None; let mut blurhash = None; - let mut tz = None; let mut custom_profile_fields = BTreeMap::new(); match &body.field { @@ -107,7 +106,6 @@ pub(crate) async fn get_profile_information_route( displayname = services.users.displayname(&body.user_id).await.ok(); avatar_url = services.users.avatar_url(&body.user_id).await.ok(); blurhash = services.users.blurhash(&body.user_id).await.ok(); - tz = services.users.timezone(&body.user_id).await.ok(); custom_profile_fields = services .users .all_profile_keys(&body.user_id) @@ -116,15 +114,10 @@ pub(crate) async fn get_profile_information_route( }, } - // services.users.timezone will collect the MSC4175 timezone key if it exists - custom_profile_fields.remove("us.cloke.msc4175.tz"); - custom_profile_fields.remove("m.tz"); - Ok(get_profile_information::v1::Response { displayname, avatar_url, blurhash, - tz, custom_profile_fields, }) } diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index 6ddd8d798..72fa7c9ad 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -1148,34 +1148,6 @@ impl Service { } } - /// Get the timezone of a user. - pub async fn timezone(&self, user_id: &UserId) -> Result { - // TODO: transparently migrate unstable key usage to the stable key once MSC4133 - // and MSC4175 are stable, likely a remove/insert in this block. - - // first check the unstable prefix then check the stable prefix - let unstable_key = (user_id, "us.cloke.msc4175.tz"); - let stable_key = (user_id, "m.tz"); - self.db - .useridprofilekey_value - .qry(&unstable_key) - .or_else(|_| self.db.useridprofilekey_value.qry(&stable_key)) - .await - .deserialized() - } - - /// Sets a new timezone or removes it if timezone is None. - pub fn set_timezone(&self, user_id: &UserId, timezone: Option) { - // TODO: insert to the stable MSC4175 key when it's stable - let key = (user_id, "us.cloke.msc4175.tz"); - - if let Some(timezone) = timezone { - self.db.useridprofilekey_value.put_raw(key, &timezone); - } else { - self.db.useridprofilekey_value.del(key); - } - } - #[cfg(not(feature = "ldap"))] pub async fn search_ldap(&self, _user_id: &UserId) -> Result> { Err!(FeatureDisabled("ldap")) From e3415a500d596cc1406f8d067a7b16998fe784cb Mon Sep 17 00:00:00 2001 From: Ginger Date: Sun, 14 Sep 2025 13:23:52 -0400 Subject: [PATCH 18/22] chore: Code cleanup --- src/api/client/profile.rs | 2 +- src/api/client/unstable.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/api/client/profile.rs b/src/api/client/profile.rs index bf6a6d939..0b291db7d 100644 --- a/src/api/client/profile.rs +++ b/src/api/client/profile.rs @@ -285,7 +285,7 @@ pub(crate) async fn get_profile_route( return Err!(Request(NotFound("Profile was not found."))); } - let mut custom_profile_fields: BTreeMap = services + let custom_profile_fields: BTreeMap = services .users .all_profile_keys(&body.user_id) .collect() diff --git a/src/api/client/unstable.rs b/src/api/client/unstable.rs index f702de9c8..ab967bedf 100644 --- a/src/api/client/unstable.rs +++ b/src/api/client/unstable.rs @@ -2,13 +2,12 @@ use std::collections::BTreeMap; use axum::extract::State; use axum_client_ip::InsecureClientIp; -use conduwuit::{Err, Error, Result}; +use conduwuit::{Err, Result}; use futures::StreamExt; use ruma::{ OwnedRoomId, api::{ client::{ - error::ErrorKind, membership::mutual_rooms, profile::{delete_profile_key, get_profile_key, set_profile_key}, }, From c9117e6ee4b4933f4452d3926b2cbf35e3927b3b Mon Sep 17 00:00:00 2001 From: Ginger Date: Sun, 14 Sep 2025 15:37:04 -0400 Subject: [PATCH 19/22] fix: Fix incorrect deserialization of MSC4133 profile fields --- src/service/users/mod.rs | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index 72fa7c9ad..daeda6a52 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -1102,6 +1102,34 @@ impl Service { Ok(user_id) } + #[inline] + async fn parse_profile_kv( + &self, + user_id: &UserId, + key: &str, + value: Vec, + ) -> Result { + match serde_json::from_slice(&value) { + | Ok(value) => Ok(value), + | Err(error) => { + // Due to an old bug, some conduwuit databases have `us.cloke.msc4175.tz` user + // profile fields with raw strings instead of quoted JSON ones. + if key == "us.cloke.msc4175.tz" { + // TODO insert a hint about this being a cold path + debug_warn!( + "Fixing corrupt `us.cloke.msc4175.tz` field in the profile of {}", + user_id + ); + let raw_tz = serde_json::Value::String(String::from_utf8(value)?); + self.set_profile_key(user_id, "us.cloke.msc4175.tz", Some(raw_tz.clone())); + Ok(raw_tz) + } else { + Err(error.into()) + } + }, + } + } + /// Gets a specific user profile key pub async fn profile_key( &self, @@ -1112,8 +1140,8 @@ impl Service { self.db .useridprofilekey_value .qry(&key) + .and_then(|handle| self.parse_profile_kv(user_id, profile_key, handle.to_vec())) .await - .deserialized() } /// Gets all the user's profile keys and values in an iterator @@ -1121,14 +1149,18 @@ impl Service { &'a self, user_id: &'a UserId, ) -> impl Stream + 'a + Send { - type KeyVal = ((Ignore, String), serde_json::Value); + type KeyVal<'a> = ((Ignore, String), &'a [u8]); let prefix = (user_id, Interfix); self.db .useridprofilekey_value .stream_prefix(&prefix) .ignore_err() - .map(|((_, key), val): KeyVal| (key, val)) + .then(async |((_, key), value): KeyVal<'_>| { + let value = self.parse_profile_kv(user_id, &key, value.to_vec()).await?; + Ok((key, value)) + }) + .ignore_err() } /// Sets a new profile key value, removes the key if value is None From d6b10556838b351deffc52fbcc3e57d6457d1728 Mon Sep 17 00:00:00 2001 From: Ginger Date: Sun, 14 Sep 2025 15:42:48 -0400 Subject: [PATCH 20/22] fix: Remove needless `async` marker --- src/service/users/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index daeda6a52..1c69f0b79 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -1103,7 +1103,7 @@ impl Service { } #[inline] - async fn parse_profile_kv( + fn parse_profile_kv( &self, user_id: &UserId, key: &str, @@ -1140,8 +1140,8 @@ impl Service { self.db .useridprofilekey_value .qry(&key) - .and_then(|handle| self.parse_profile_kv(user_id, profile_key, handle.to_vec())) .await + .and_then(|handle| self.parse_profile_kv(user_id, profile_key, handle.to_vec())) } /// Gets all the user's profile keys and values in an iterator @@ -1156,8 +1156,8 @@ impl Service { .useridprofilekey_value .stream_prefix(&prefix) .ignore_err() - .then(async |((_, key), value): KeyVal<'_>| { - let value = self.parse_profile_kv(user_id, &key, value.to_vec()).await?; + .map(|((_, key), value): KeyVal<'_>| { + let value = self.parse_profile_kv(user_id, &key, value.to_vec())?; Ok((key, value)) }) .ignore_err() From 8e27d74c4acd380e943db1a038b12b1fd9e9bcfa Mon Sep 17 00:00:00 2001 From: Ginger Date: Sun, 14 Sep 2025 15:53:05 -0400 Subject: [PATCH 21/22] fix: Slightly more parallelism --- src/api/client/profile.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/api/client/profile.rs b/src/api/client/profile.rs index 0b291db7d..e83e747ca 100644 --- a/src/api/client/profile.rs +++ b/src/api/client/profile.rs @@ -1,5 +1,3 @@ -use std::collections::BTreeMap; - use axum::extract::State; use conduwuit::{ Err, Result, @@ -10,7 +8,7 @@ use conduwuit::{ use conduwuit_service::Services; use futures::{ StreamExt, TryStreamExt, - future::{join, join3}, + future::{join, join3, join4}, }; use ruma::{ OwnedMxcUri, OwnedRoomId, UserId, @@ -285,16 +283,11 @@ pub(crate) async fn get_profile_route( return Err!(Request(NotFound("Profile was not found."))); } - let custom_profile_fields: BTreeMap = services - .users - .all_profile_keys(&body.user_id) - .collect() - .await; - - let (avatar_url, blurhash, displayname) = join3( + let (avatar_url, blurhash, displayname, custom_profile_fields) = join4( services.users.avatar_url(&body.user_id).ok(), services.users.blurhash(&body.user_id).ok(), services.users.displayname(&body.user_id).ok(), + services.users.all_profile_keys(&body.user_id).collect(), ) .await; From a0b0ff9d5c98a10f47ed8012ff6875048a4a1fe4 Mon Sep 17 00:00:00 2001 From: Ginger Date: Sun, 14 Sep 2025 21:30:20 -0400 Subject: [PATCH 22/22] fix: Remove legacy check for `u.` prefix --- src/api/client/unstable.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/api/client/unstable.rs b/src/api/client/unstable.rs index ab967bedf..be10c13c4 100644 --- a/src/api/client/unstable.rs +++ b/src/api/client/unstable.rs @@ -90,14 +90,6 @@ pub(crate) async fn set_profile_key_route( ))); }; - if body - .kv_pair - .keys() - .any(|key| key.starts_with("u.") && !profile_key_value.is_string()) - { - return Err!(Request(BadJson("u.* profile key fields must be strings"))); - } - if body.kv_pair.keys().any(|key| key.len() > 128) { return Err!(Request(BadJson("Key names cannot be longer than 128 bytes"))); }