Compare commits

...

74 Commits

Author SHA1 Message Date
Ginger 48cbe20aeb fix: Remove legacy check for u. prefix 2025-09-15 12:51:09 +00:00
Ginger 95d87bd209 fix: Slightly more parallelism 2025-09-15 12:51:09 +00:00
Ginger 8bce2c4481 fix: Remove needless async marker 2025-09-15 12:51:09 +00:00
Ginger 60904ed069 fix: Fix incorrect deserialization of MSC4133 profile fields 2025-09-15 12:51:09 +00:00
Ginger 24aaf660ab chore: Code cleanup 2025-09-15 12:51:09 +00:00
Ginger 5d00f3bfa1 fix: Nuke explicit references to the MSC4175 tz profile field 2025-09-15 12:51:09 +00:00
Renovate Bot f872210b20 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.113.4 2025-09-15 05:01:40 +00:00
Renovate Bot 3dd04bd9df chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.113.2 2025-09-14 05:03:21 +00:00
Ginger af45c348a4 fix: Properly deserialize changes to legacy fields made with MSC4133 endpoints 2025-09-14 01:28:08 +00:00
nexy7574 36dabecb82 chore(1014): Include MSC4155 in build features to resolve build errors 2025-09-14 00:53:43 +00:00
nexy7574 50cd1081ba chore(1014): Bump ruwuma 2025-09-14 00:53:43 +00:00
nexy7574 14df55e5c5 style(1014): Remove unnecessary commented code 2025-09-14 00:53:43 +00:00
nexy7574 d9d0d1a465 fix(!1014): Don't prematurely return during registration 2025-09-14 00:53:43 +00:00
Tom Foster 81b6b3547c 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.
2025-09-13 17:12:09 +01:00
Renovate Bot 0bbc3c4e05 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.112.0 2025-09-12 21:11:13 +00:00
Jade 0f09fa3d31 chore(renovate): Specify automerge strategy 2025-09-12 21:02:25 +00:00
Tom Foster 3d5355dfc3 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.
2025-09-12 17:50:08 +01:00
Renovate Bot 2547eb3a90 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.109.0 2025-09-12 13:29:47 +00:00
Renovate Bot 51ba41823f chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.106.0 2025-09-12 13:23:28 +00:00
Tom Foster 542dff50bd 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.
2025-09-12 12:43:19 +01:00
Tom Foster 9c147b182f ci: Fix BuildKit cache invalidation and add Haswell-optimised builds
The workflow was rebuilding dependencies unnecessarily despite timelord
restoring timestamps because TARGET_CPU and RUST_PROFILE weren't passed
to Docker, creating inconsistent cache keys. Now passes both arguments
for proper cache reuse.

Adds Haswell-optimised builds alongside baseline builds using -march=haswell
for PCLMUL instruction support. Recent build improvements reducing compile
times from 15-20 minutes to ~5 minutes make this additional CPU variant
feasible. Users can pull optimised images with -haswell suffix.
2025-09-11 13:59:43 +01:00
Renovate Bot 7e76ca45c1 chore(deps): lock file maintenance 2025-09-11 12:28:11 +00:00
Tom Foster 5126cb4554 fix: Use forgejo/upload-artifact@v4 for artifact consistency
Follow-on to correct #1009. The previous fix downgraded upload-artifact
to v3 but kept download-artifact@v4, creating incompatible storage
formats that prevented artifact pattern filtering from working.

Update all upload-artifact actions to v4 and adjust renovate
configuration to disable automatic updates for forgejo artifact
actions to maintain version consistency.
2025-09-11 11:57:04 +01:00
Renovate Bot 4d05d0f677 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.99.9 2025-09-11 09:56:48 +00:00
Tom Foster 0673ac1a6c fix: Fix artifact action compatibility and add digest debugging
Resolve upload-artifact v4 GHES compatibility errors by downgrading to v3.
Switch to standard forgejo/download-artifact@v4 for pattern filtering support.
Update renovate configuration to prevent future incompatible upgrades.

Add diagnostic output to digest export step to troubleshoot zero-byte
artifact uploads preventing manifest creation. Include CI triggers for
Element workflow to test changes in pull requests.
2025-09-11 10:44:11 +01:00
Jade Ellis ad11417145 chore(deps): Replace serde_yaml with serde_yml 2025-09-10 20:20:45 +01:00
Renovate Bot 0de904ffe4 chore(deps): update rust crate const-str to 0.7.0 2025-09-10 18:05:00 +00:00
Renovate Bot d74b9de221 chore(deps): update dependency cargo-bins/cargo-binstall to v1.15.4 2025-09-10 17:44:44 +00:00
Renovate Bot e7ac5988cb chore(deps): update https://github.com/actions/setup-node action to v5 2025-09-10 17:06:45 +00:00
Jade Ellis 571f05017c chore: Update resolv git hash 2025-09-10 17:50:37 +01:00
Jade Ellis a339e73eb5 chore: Unify actions versions 2025-09-10 17:39:25 +01:00
Jade Ellis 72b78ed6d4 chore: Fix nightly-only clippy lints 2025-09-10 17:35:17 +01:00
nexy7574 baa89586e2 fix(MSC4277): Undo refuted response changes 2025-09-10 16:25:06 +00:00
nexy7574 7ad8ff2e45 style(MSC4277): Run lints to satisfy checks 2025-09-10 16:25:06 +00:00
nexy7574 2046b1e2f6 feat(MSC4277): Unify reporting endpoint behaviours
* reporting rooms now always returns 200 OK
* reporting an event returns OK if we don't know about the reported event
* removed the score parameter (needs a followup ruwuma update)
2025-09-10 16:25:06 +00:00
Renovate Bot 2cb980cd4c chore(deps): update ghcr.io/renovatebot/renovate docker tag to v41.99.7 2025-09-10 16:16:34 +00:00
Jade Ellis 27e0ef7b2e chore: Update renovate CI
- Fixes some issues with the action - Enables OSV vuln scanning -
Enables updating the dockerfile tool versions
2025-09-10 16:53:59 +01:00
Jade Ellis 7091882887 chore: Update cargo lockfile 2025-09-10 16:47:20 +01:00
Jade Ellis a81546374d ci: Make timelord docker work locally 2025-09-10 16:40:55 +01:00
Tom Foster 7950e2cc7f ci: Refactor timelord action to use git-warp-time fallback
Updates the timelord action to fall back to git-warp-time when the cache
is completely empty, enabling timestamp restoration even on fresh builds.
When git-warp-time is used, performs an unshallow fetch to get full history,
while subsequent runs use normal fetches. Simplifies the interface by making
inputs optional with sensible defaults.

Adds binary caching for timelord-cli and git-warp-time tools to avoid
repeated installations, and updates paths to use /usr/share/rust/.cargo/bin/
for the catthehacker runner image used by the dind profile (may need updating
if/when switching to standard image).

The main timelord restore now happens inside the Dockerfile itself, as Docker
intentionally wipes all file mtimes on COPY/ADD operations.
2025-09-08 08:34:29 +00:00
Renovate Bot 8f186cd770 chore(deps): update https://github.com/renovatebot/github-action action to v43.0.11 2025-09-08 05:02:33 +00:00
Ginger 5d3e10a048 fix: Make RA use the full feature 2025-09-07 18:07:03 -04:00
Ginger 1e541875ad fix: Nuke src/api/client/utils.rs 2025-09-07 18:06:11 -04:00
nexy7574 90fd92977e style: Run clippy 2025-09-07 21:20:26 +00:00
Ginger e27ef7f5ec feat: Do not persist remote PDUs fetched with admin commands 2025-09-07 21:20:26 +00:00
Ginger 16f4efa708 fix: Fix pagination tokens being corrupted for backfilled PDUs 2025-09-07 21:20:26 +00:00
Ginger e38dec5864 fix: Put the output of !admin query room-timeline pdus in a codeblock 2025-09-07 21:20:26 +00:00
Ginger f3824ffc3d fix: Use handle_incoming_pdu directly to keep remote PDUs as outliers 2025-09-07 21:20:26 +00:00
nexy7574 e3fbf7a143 feat: Ask remote servers for individual unknown events 2025-09-07 21:20:26 +00:00
nexy7574 09de586dc7 feat(PR977): Log more things in the join process 2025-09-07 22:01:07 +01:00
nexy7574 d1fff1d09f perf(pr977): Remove redundant ACL check in send_join 2025-09-07 22:01:07 +01:00
nexy7574 f47474d12a fix(PR977): Adjust some log levels 2025-09-07 22:01:07 +01:00
nexy7574 53da294e53 fix(PR977): Omitting redundant entries from the auth_chain caused problems 2025-09-07 22:01:07 +01:00
nexy7574 2cdccbf2fe feat(PR977): Support omitting members in the send_join response 2025-09-07 22:01:07 +01:00
Tom Foster 6cf3c839e4 ci(release-image): Skip digest upload when not pushing images
After #992, builds without registry credentials skip Docker image output
but still extract binary artifacts. However, we were still trying to
upload digests for images that weren't created. Add conditional check
to only upload digests when actually pushing to registry.
2025-09-07 21:27:56 +01:00
Tom Foster 4a1091dd06 ci(release-image): Unify binary extraction using BuildKit local output
Fork PRs currently fail binary extraction with 'invalid reference format'
and 'must specify at least one container source' errors. This replaces the
registry-specific docker create/copy method with BuildKit's local output
feature for all builds.

Uses multiple outputs in single build: image export plus local binary
extraction from /sbin. Speeds up extracting binary artifacts and saves a
couple of extra workflow steps in the process.
2025-09-07 20:46:11 +01:00
Tom Foster 1e9701f379 ci(release-image): Skip setup steps when using persistent BuildKit
When BUILDKIT_ENDPOINT is set, builds run on a persistent BuildKit instance,
making runner setup steps unnecessary. Skip Rust toolchain installation,
QEMU setup, caching steps, and timelord to eliminate ~7 operations per job.

Also adds output to git SHA and timestamp steps for visibility.

Cuts at least a minute off average build time through fewer installs,
cache restores, and cache saves.
2025-09-07 18:59:05 +01:00
Tom Foster 2cedf0d2e1 fix(ci): Use image output instead of docker for fork PRs
Docker exporter doesn't support manifest lists (multi-platform builds).
For fork PRs without registry credentials, use 'type=image,push=false'
instead of 'type=docker' to build multi-platform images locally without pushing.
2025-09-07 18:32:38 +01:00
Tom Foster 84fdcd326a fix(ci): Resolve registry push failures for fork PRs
Fork PRs now fail during Docker image build with 'tag is needed when
pushing to registry' because BUILTIN_REGISTRY_ENABLED evaluates to false
without proper credentials, leaving the images list empty. This appears
to be due to recent Forgejo permission changes affecting fork access to
repository secrets.

Add fallback to official registry when credentials unavailable, skip
registry login and push operations for forks, and make merge job
conditional since no digests exist without push. This allows forks to
test Docker builds whilst avoiding authentication failures.
2025-09-07 17:39:18 +01:00
Tom Foster d640853f9d ci(docs): Optimise build performance with caching and conditional Node.js
Skip installing Node.js entirely if v20+ is already available, otherwise
install v22. Add npm dependency caching with OS-specific cache keys using
the custom detect-runner-os action for proper cache isolation between
runners. Dependencies normally take just under 10s, so this should more
than halve the doc build time to free up runner slots.
2025-09-07 14:51:10 +01:00
Tom Foster fff9629b0f fix(docker): Resolve liburing.so.2 loading error for non-root users
Container failed to start when running as non-root (user 1000:1000) because
copied directories had restrictive 770 permissions, likely due to different
umask in persistent BuildKit. Non-root users couldn't access /usr/lib to
load required dynamic libraries.

Introduces prepper stage using Ubuntu to organize files into layered structure
with explicit 755 directory permissions before copying to scratch image.
Also fixes workflow syntax error and removes docker/** from paths-ignore to
ensure Docker changes trigger CI builds.
2025-09-07 14:13:14 +01:00
Tom Foster 1a3107c20a fix(ci): Replace Mozilla sccache action with token-free alternative
Replace mozilla-actions/sccache-action with a custom Forgejo-specific
implementation that eliminates GitHub token dependencies and rate limiting
issues for all contributors regardless of repository permissions.

The new action mirrors sccache binaries to the Forgejo package registry
and queries that instead of GitHub releases, maintaining identical functionality
including hostedtoolcache support.
2025-09-07 09:29:32 +01:00
aviac 969d7cbb66 feat(nix): remove rocksdb from flake.nix inputs
Consuming this flake is pretty annoying since the rocksdb input is
fetched on every build which takes ~ 10 - 20 sec. By removing it and
replacing it with a `pkgs.fetchFromGitea`, we create an intermediate
derivation which is better for caching reasons.
2025-09-06 17:40:31 +00:00
Jade Ellis cd238b05de fix: Remove bad colon in workflow 2025-09-06 16:21:21 +01:00
Jade Ellis c0e3829fed feat: Replace Jaeger with OTLP 2025-09-06 16:19:56 +01:00
Jade Ellis 1d7dda6cf5 chore: Upgrade ctor, cbor 2025-09-06 16:19:56 +01:00
Jade Ellis 6f19931c5b chore(deps): Upgrade minor incompatible dependencies 2025-09-06 16:19:56 +01:00
Tom Foster 2516e783ba ci: Support optional persistent BuildKit endpoints in Docker builds
Allows us to use runners with persistent BuildKit containers for improved
caching and faster build times. Falls back to standard docker-container
driver when BUILDKIT_ENDPOINT environment variable is not set.
2025-09-06 16:05:51 +01:00
Jade Ellis fdf5771387 ci: Fix CI not triggering on external pull requests 2025-09-06 15:21:39 +01:00
Ginger 58bbc0e676 fix: Move packaging files from dist/ to pkg/ 2025-09-06 14:03:57 +00:00
Ginger 0d58e660a2 fix: Remove unnecessary user and directory modifications
systemd creates a dynamic user for
continuwuity and manages directories for
it automatically, so the debian postinst
script no longer needs to do that.
2025-09-06 14:03:57 +00:00
Ginger e7124edb73 fix: Update debian systemd unit path 2025-09-06 14:03:57 +00:00
Ginger d19e0f0d97 feat: Move packaging scripts into dist/ and consolidate the service files 2025-09-06 14:03:57 +00:00
nex 467aed3028 chore: Add Ginger's GH noreply email to mailmap 2025-09-02 16:36:56 +00:00
82 changed files with 2041 additions and 1801 deletions
@@ -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
+20 -1
View File
@@ -13,6 +13,12 @@ outputs:
slug:
description: 'Combined OS slug (e.g. Ubuntu-22.04)'
value: ${{ steps.detect.outputs.slug }}
node_major:
description: 'Major version of Node.js if available (e.g. 22)'
value: ${{ steps.detect.outputs.node_major }}
node_version:
description: 'Full Node.js version if available (e.g. 22.19.0)'
value: ${{ steps.detect.outputs.node_version }}
runs:
using: composite
@@ -30,7 +36,20 @@ runs:
# Create combined slug
OS_SLUG="${OS_NAME}-${OS_VERSION}"
# Set outputs
# Detect Node.js version if available
if command -v node >/dev/null 2>&1; then
NODE_VERSION=$(node --version | sed 's/v//')
NODE_MAJOR=$(echo $NODE_VERSION | cut -d. -f1)
echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT
echo "node_major=${NODE_MAJOR}" >> $GITHUB_OUTPUT
echo "🔍 Detected Node.js: v${NODE_VERSION}"
else
echo "node_version=" >> $GITHUB_OUTPUT
echo "node_major=" >> $GITHUB_OUTPUT
echo "🔍 Node.js not found"
fi
# Set OS outputs
echo "name=${OS_NAME}" >> $GITHUB_OUTPUT
echo "version=${OS_VERSION}" >> $GITHUB_OUTPUT
echo "slug=${OS_SLUG}" >> $GITHUB_OUTPUT
@@ -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 }}
+2 -8
View File
@@ -2,20 +2,14 @@ name: sccache
description: |
Install sccache for caching builds in GitHub Actions.
inputs:
token:
description: 'A Github PAT'
required: false
runs:
using: composite
steps:
- name: Install sccache
uses: https://github.com/mozilla-actions/sccache-action@v0.0.9
with:
token: ${{ inputs.token }}
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Configure sccache
uses: https://github.com/actions/github-script@v7
uses: https://github.com/actions/github-script@v8
with:
script: |
core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
@@ -57,7 +57,7 @@ runs:
- name: Check for LLVM cache
id: cache
uses: https://github.com/actions/cache@v4
uses: actions/cache@v4
with:
path: |
/usr/bin/clang-*
+4 -14
View File
@@ -65,7 +65,7 @@ runs:
- name: Cache Cargo registry and git
id: registry-cache
uses: https://github.com/actions/cache@v4
uses: actions/cache@v4
with:
path: |
.cargo/registry/index
@@ -79,7 +79,7 @@ runs:
- name: Cache toolchain binaries
id: toolchain-cache
uses: https://github.com/actions/cache@v4
uses: actions/cache@v4
with:
path: |
.cargo/bin
@@ -88,23 +88,13 @@ runs:
# Shared toolchain cache across all Rust versions
key: toolchain-${{ steps.runner-os.outputs.slug }}
- name: Debug GitHub token availability
shell: bash
run: |
if [ -z "${{ inputs.github-token }}" ]; then
echo "⚠️ No GitHub token provided - sccache will use fallback download method"
else
echo "✅ GitHub token provided for sccache"
fi
- name: Setup sccache
uses: https://github.com/mozilla-actions/sccache-action@v0.0.9
with:
token: ${{ inputs.github-token }}
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Cache build artifacts
id: build-cache
uses: https://github.com/actions/cache@v4
uses: actions/cache@v4
with:
path: |
target/**/deps
+86 -28
View File
@@ -1,46 +1,104 @@
name: timelord
description: |
Use timelord to set file timestamps
Use timelord to set file timestamps with git-warp-time fallback for cache misses
inputs:
key:
description: |
The key to use for caching the timelord data.
This should be unique to the repository and the runner.
required: true
default: timelord-v0
required: false
default: ''
path:
description: |
The path to the directory to be timestamped.
This should be the root of the repository.
required: true
default: .
required: false
default: ''
outputs:
database-path:
description: Path to timelord database
value: '${{ env.TIMELORD_CACHE_PATH }}'
runs:
using: composite
steps:
- name: Cache timelord-cli installation
id: cache-timelord-bin
uses: actions/cache@v3
with:
path: ~/.cargo/bin/timelord
key: timelord-cli-v3.0.1
- name: Install timelord-cli
uses: https://github.com/cargo-bins/cargo-binstall@main
if: steps.cache-timelord-bin.outputs.cache-hit != 'true'
- run: cargo binstall timelord-cli@3.0.1
- name: Set defaults
shell: bash
if: steps.cache-timelord-bin.outputs.cache-hit != 'true'
run: |
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: Load timelord files
uses: actions/cache/restore@v3
- name: Restore binary cache
id: binary-cache
uses: actions/cache/restore@v4
with:
path: /timelord/
key: ${{ inputs.key }}
- name: Run timelord to set timestamps
path: |
/usr/share/rust/.cargo/bin
~/.cargo/bin
key: timelord-binaries-v2
- name: Check if binaries need installation
shell: bash
run: timelord sync --source-dir ${{ inputs.path }} --cache-dir /timelord/
- name: Save timelord
uses: actions/cache/save@v3
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"
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"
NEED_INSTALL=true
fi
echo "need-install=$NEED_INSTALL" >> $GITHUB_OUTPUT
- name: Install timelord-cli and git-warp-time
if: steps.check-binaries.outputs.need-install == 'true'
uses: https://github.com/taiki-e/install-action@v2
with:
path: /timelord/
key: ${{ inputs.key }}
tool: git-warp-time,timelord-cli@3.0.1
- name: Save binary cache
if: steps.check-binaries.outputs.need-install == 'true'
uses: actions/cache/save@v4
with:
path: |
/usr/share/rust/.cargo/bin
~/.cargo/bin
key: timelord-binaries-v2
- name: Restore timelord cache with fallbacks
id: timelord-restore
uses: actions/cache/restore@v4
with:
path: ${{ env.TIMELORD_CACHE_PATH }}
key: ${{ env.TIMELORD_KEY }}
restore-keys: |
timelord-v1-${{ github.repository }}-
- name: Initialize timestamps on complete cache miss
if: steps.timelord-restore.outputs.cache-hit != 'true'
shell: bash
run: |
echo "Complete timelord cache miss - running git-warp-time"
git fetch --unshallow
if [ "${{ env.TIMELORD_PATH }}" = "." ]; then
git-warp-time --quiet
else
git-warp-time --quiet ${{ env.TIMELORD_PATH }}
fi
echo "Git timestamps restored"
- name: Run timelord sync
shell: bash
run: |
mkdir -p ${{ 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
with:
path: ${{ env.TIMELORD_CACHE_PATH }}
key: ${{ env.TIMELORD_KEY }}
@@ -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
+16 -3
View File
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Sync repository
uses: https://github.com/actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
fetch-depth: 0
@@ -49,10 +49,23 @@ jobs:
cp ./docs/static/_headers ./public/_headers
echo "Copied .well-known files and _headers to ./public"
- name: Detect runner environment
id: runner-env
uses: ./.forgejo/actions/detect-runner-os
- name: Setup Node.js
uses: https://github.com/actions/setup-node@v4
if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20'
uses: https://github.com/actions/setup-node@v5
with:
node-version: 20
node-version: 22
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ steps.runner-env.outputs.slug }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ steps.runner-env.outputs.slug }}-node-
- name: Install dependencies
run: npm install --save-dev wrangler@latest
+10 -2
View File
@@ -4,6 +4,14 @@ on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
pull_request:
paths:
- ".forgejo/workflows/element.yml"
push:
branches:
- main
paths:
- ".forgejo/workflows/element.yml"
concurrency:
group: "element-${{ github.ref }}"
@@ -16,7 +24,7 @@ jobs:
steps:
- name: 📦 Setup Node.js
uses: https://github.com/actions/setup-node@v4
uses: https://github.com/actions/setup-node@v5
with:
node-version: "22"
@@ -101,7 +109,7 @@ jobs:
cat ./element-web/webapp/config.json
- name: 📤 Upload Artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
uses: forgejo/upload-artifact@v4
with:
name: element-web
path: ./element-web/webapp/
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
+6 -2
View File
@@ -1,7 +1,11 @@
name: Checks / Prek
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
@@ -12,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
@@ -43,7 +47,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
persist-credentials: false
+143 -235
View File
@@ -3,15 +3,25 @@ concurrency:
group: "release-image-${{ github.ref }}"
on:
push:
pull_request:
paths-ignore:
- "*.md"
- "**/*.md"
- ".gitlab-ci.yml"
- ".gitignore"
- "renovate.json"
- "debian/**"
- "docker/**"
- "pkg/**"
- "docs/**"
push:
branches:
- main
paths-ignore:
- "*.md"
- "**/*.md"
- ".gitlab-ci.yml"
- ".gitignore"
- "renovate.json"
- "pkg/**"
- "docs/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@@ -19,46 +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:
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
uses: https://github.com/actions/github-script@v7
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)
}
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:
build-release:
name: "Build ${{ matrix.slug }} (release)"
runs-on: dind
needs: define-variables
permissions:
contents: read
packages: write
@@ -66,116 +42,28 @@ jobs:
id-token: write
strategy:
matrix:
{
"target_cpu": ["base"],
"profile": ["release"],
"include":
[
{ "platform": "linux/amd64", "slug": "linux-amd64" },
{ "platform": "linux/arm64", "slug": "linux-arm64" },
],
"platform": ["linux/amd64", "linux/arm64"],
}
include:
- platform: "linux/amd64"
slug: "linux-amd64"
- platform: "linux/arm64"
slug: "linux-arm64"
steps:
- 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@v4
uses: actions/checkout@v5
with:
persist-credentials: false
- name: Install rust
id: rust-toolchain
uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up QEMU
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
uses: docker/login-action@v3
- name: Prepare 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 }}
# 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
- name: Get Git commit timestamps
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
- uses: ./.forgejo/actions/timelord
with:
key: timelord-v0
path: .
- name: Cache Rust registry
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
id: cache-cargo-target
uses: actions/cache@v3
with:
path: |
cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
key: cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}}
- name: Cache apt cache
id: cache-apt
uses: actions/cache@v3
with:
path: |
var-cache-apt-${{ matrix.slug }}
key: var-cache-apt-${{ matrix.slug }}
- name: Cache apt lib
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
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-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}": {
"target": "/app/target",
"id": "cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}"
},
"var-cache-apt-${{ matrix.slug }}": "/var/cache/apt",
"var-lib-apt-${{ matrix.slug }}": "/var/lib/apt"
}
skip-extraction: ${{ steps.cache.outputs.cache-hit }}
platform: ${{ matrix.platform }}
slug: ${{ matrix.slug }}
target_cpu: ""
profile: "release"
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
id: build
uses: docker/build-push-action@v6
@@ -183,114 +71,134 @@ jobs:
context: .
file: "docker/Dockerfile"
build-args: |
GIT_COMMIT_HASH=${{ github.sha }})
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=
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
outputs: type=image,"name=${{ needs.define-variables.outputs.images_list }}",push-by-digest=true,name-canonical=true,push=true
outputs: |
${{ 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 }}
# For publishing multi-platform manifests
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Extract binary from container (image)
id: extract-binary-image
run: |
mkdir -p /tmp/binaries
digest="${{ steps.build.outputs.digest }}"
echo "container_id=$(docker create --platform ${{ matrix.platform }} ${{ needs.define-variables.outputs.images_list }}@$digest)" >> $GITHUB_OUTPUT
- name: Extract binary from container (copy)
run: docker cp ${{ steps.extract-binary-image.outputs.container_id }}:/sbin/conduwuit /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
- name: Extract binary from container (cleanup)
run: docker rm ${{ steps.extract-binary-image.outputs.container_id }}
- name: Upload binary artifact
uses: forgejo/upload-artifact@v4
- name: Upload Docker artifacts
uses: ./.forgejo/actions/upload-docker-artifacts
with:
name: conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
path: /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ 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
uses: forgejo/upload-artifact@v4
with:
name: digests-${{ matrix.slug }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 5
merge:
merge-release:
name: "Create Multi-arch Release Manifest"
runs-on: dind
needs: [define-variables, build-image]
needs: build-release
steps:
- name: Download digests
uses: forgejo/download-artifact@v4
- name: Checkout repository
uses: actions/checkout@v5
with:
path: /tmp/digests
pattern: digests-*
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
uses: docker/login-action@v3
persist-credentials: false
- name: Create multi-platform manifest
uses: ./.forgejo/actions/create-docker-manifest
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
digest_pattern: "digests-linux-{amd64,arm64}"
tag_suffix: ""
images: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
build-maxperf:
name: "Build ${{ matrix.slug }} (max-perf)"
runs-on: dind
needs: build-release
permissions:
contents: read
packages: write
attestations: write
id-token: write
strategy:
matrix:
include:
- platform: "linux/amd64"
slug: "linux-amd64"
target_cpu: "haswell"
- platform: "linux/arm64"
slug: "linux-arm64"
target_cpu: ""
- name: Extract metadata (tags) for Docker
id: meta
uses: docker/metadata-action@v5
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
tags: |
type=semver,pattern={{version}},prefix=v
type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }},prefix=v
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }},prefix=v
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }}
type=ref,event=pr
type=sha,format=long
type=raw,value=latest,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
persist-credentials: false
- name: Prepare max-perf Docker build environment
id: prepare
uses: ./.forgejo/actions/prepare-docker-build
with:
platform: ${{ matrix.platform }}
slug: ${{ matrix.slug }}
target_cpu: ${{ matrix.target_cpu }}
profile: "release-max-perf"
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
id: build
uses: docker/build-push-action@v6
with:
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', env.IMAGE_PATH) || format('type=image,"name={0}",push=false', env.IMAGE_PATH) }}
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
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
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: 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: ${{ env.IMAGE_PATH }}
registry_user: ${{ vars.BUILTIN_REGISTRY_USER || github.actor }}
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
+36 -15
View File
@@ -1,5 +1,7 @@
name: Maintenance / Renovate
enable-email-notifications: true
on:
schedule:
# Run at 5am UTC daily to avoid late-night dev
@@ -10,10 +12,10 @@ on:
dryRun:
description: 'Dry run mode'
required: false
default: null
default: ''
type: choice
options:
- null
- ''
- 'extract'
- 'lookup'
- 'full'
@@ -23,6 +25,7 @@ on:
default: 'info'
type: choice
options:
- 'debug'
- 'info'
- 'warning'
- 'critical'
@@ -40,11 +43,11 @@ jobs:
name: Renovate
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:41
image: ghcr.io/renovatebot/renovate:41.113.4@sha256:472f1a1d07a823da002be9ddf8507275517d3dc1bdcba4606ee3e62f1c27204a
options: --tmpfs /tmp:exec
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
show-progress: false
@@ -52,7 +55,7 @@ jobs:
run: /usr/local/renovate/node -e 'console.log(`node heap limit = ${require("v8").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'
- name: Restore renovate repo cache
uses: https://github.com/actions/cache@v4
uses: actions/cache/restore@v4
with:
path: |
/tmp/renovate/cache/renovate/repository
@@ -61,7 +64,7 @@ jobs:
repo-cache-
- name: Restore renovate package cache
uses: https://github.com/actions/cache@v4
uses: actions/cache/restore@v4
with:
path: |
/tmp/renovate/cache/renovate/renovate-cache-sqlite
@@ -69,8 +72,17 @@ jobs:
restore-keys: |
package-cache-
- name: Restore renovate OSV cache
uses: actions/cache/restore@v4
with:
path: |
/tmp/osv
key: osv-cache-${{ github.run_id }}
restore-keys: |
osv-cache-
- name: Self-hosted Renovate
uses: https://github.com/renovatebot/github-action@v43.0.9
run: renovate
env:
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}
RENOVATE_DRY_RUN: ${{ inputs.dryRun || 'false' }}
@@ -84,28 +96,37 @@ jobs:
RENOVATE_REQUIRE_CONFIG: 'required'
RENOVATE_ONBOARDING: 'false'
RENOVATE_PR_COMMITS_PER_RUN_LIMIT: 3
RENOVATE_INHERIT_CONFIG: 'true'
RENOVATE_GITHUB_TOKEN_WARN: 'false'
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
GITHUB_COM_TOKEN: ${{ secrets.GH_PUBLIC_RO }}
GITHUB_COM_TOKEN: ${{ secrets.GH_PUBLIC_RO || secrets.GH_TOKEN }}
RENOVATE_REPOSITORY_CACHE: 'enabled'
RENOVATE_X_SQLITE_PACKAGE_CACHE: true
RENOVATE_X_SQLITE_PACKAGE_CACHE: 'true'
OSV_OFFLINE_ROOT_DIR: /tmp/osv
- name: Save renovate repo cache
if: always() && env.RENOVATE_DRY_RUN != 'full'
uses: https://github.com/actions/cache@v4
if: always()
uses:
actions/cache/save@v4
with:
path: |
/tmp/renovate/cache/renovate/repository
key: repo-cache-${{ github.run_id }}
- name: Save renovate package cache
if: always() && env.RENOVATE_DRY_RUN != 'full'
uses: https://github.com/actions/cache@v4
if: always()
uses: actions/cache/save@v4
with:
path: |
/tmp/renovate/cache/renovate/renovate-cache-sqlite
key: package-cache-${{ github.run_id }}
- name: Save renovate OSV cache
if: always()
uses: actions/cache/save@v4
with:
path: |
/tmp/osv
key: osv-cache-${{ github.run_id }}
+1
View File
@@ -13,3 +13,4 @@ Rudi Floren <rudi.floren@gmail.com> <rudi.floren@googlemail.com>
Tamara Schmitz <tamara.zoe.schmitz@posteo.de> <15906939+tamara-schmitz@users.noreply.github.com>
Timo Kösters <timo@koesters.xyz>
x4u <xi.zhu@protonmail.ch> <14617923-x4u@users.noreply.gitlab.com>
Ginger <ginger@gingershaped.computer> <75683114+gingershaped@users.noreply.github.com>
+3
View File
@@ -13,6 +13,9 @@ extend-ignore-re = [
"[0-9+][A-Za-z0-9+]{30,}[a-z0-9+]",
"\\$[A-Z0-9+][A-Za-z0-9+]{6,}[a-z0-9+]",
"\\b[a-z0-9+/=][A-Za-z0-9+/=]{7,}[a-z0-9+/=][A-Z]\\b",
# In the renovate config
".ontainer"
]
[default.extend-words]
+2 -1
View File
@@ -7,5 +7,6 @@
"continuwuity",
"homeserver",
"homeservers"
]
],
"rust-analyzer.cargo.features": ["full"]
}
Generated
+706 -614
View File
File diff suppressed because it is too large Load Diff
+25 -40
View File
@@ -45,18 +45,18 @@ version = "0.3"
features = ["ffi", "std", "union"]
[workspace.dependencies.const-str]
version = "0.6.2"
version = "0.7.0"
[workspace.dependencies.ctor]
version = "0.2.9"
version = "0.5.0"
[workspace.dependencies.cargo_toml]
version = "0.21"
version = "0.22"
default-features = false
features = ["features"]
[workspace.dependencies.toml]
version = "0.8.14"
version = "0.9.5"
default-features = false
features = ["parse"]
@@ -166,8 +166,8 @@ default-features = false
features = ["raw_value"]
# Used for appservice registration files
[workspace.dependencies.serde_yaml]
version = "0.9.34"
[workspace.dependencies.serde_yml]
version = "0.0.12"
# Used to load forbidden room/user regex from config
[workspace.dependencies.serde_regex]
@@ -352,7 +352,7 @@ version = "0.1.2"
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes"
rev = "8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
rev = "78135227dceb9ce5eb9515b4ae85d452cdcabac8"
features = [
"compat",
"rand",
@@ -387,6 +387,7 @@ features = [
"unstable-msc4210", # remove legacy mentions
"unstable-extensible-events",
"unstable-pdu",
"unstable-msc4155"
]
[workspace.dependencies.rust-rocksdb]
@@ -411,25 +412,28 @@ default-features = false
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
[workspace.dependencies.opentelemetry]
version = "0.21.0"
version = "0.30.0"
[workspace.dependencies.tracing-flame]
version = "0.2.0"
[workspace.dependencies.tracing-opentelemetry]
version = "0.22.0"
version = "0.31.0"
[workspace.dependencies.opentelemetry_sdk]
version = "0.21.2"
version = "0.30.0"
features = ["rt-tokio"]
[workspace.dependencies.opentelemetry-jaeger]
version = "0.20.0"
features = ["rt-tokio"]
[workspace.dependencies.opentelemetry-otlp]
version = "0.30.0"
features = ["http", "trace", "logs", "metrics"]
[workspace.dependencies.opentelemetry-jaeger-propagator]
version = "0.30.0"
# optional sentry metrics for crash/panic reporting
[workspace.dependencies.sentry]
version = "0.37.0"
version = "0.42.0"
default-features = false
features = [
"backtrace",
@@ -445,9 +449,9 @@ features = [
]
[workspace.dependencies.sentry-tracing]
version = "0.37.0"
version = "0.42.0"
[workspace.dependencies.sentry-tower]
version = "0.37.0"
version = "0.42.0"
# jemalloc usage
[workspace.dependencies.tikv-jemalloc-sys]
@@ -476,7 +480,7 @@ features = ["use_std"]
version = "0.4"
[workspace.dependencies.nix]
version = "0.29.0"
version = "0.30.1"
default-features = false
features = ["resource"]
@@ -498,7 +502,7 @@ version = "0.4.3"
default-features = false
[workspace.dependencies.termimad]
version = "0.31.2"
version = "0.34.0"
default-features = false
[workspace.dependencies.checked_ops]
@@ -536,11 +540,11 @@ version = "0.2"
version = "0.2"
[workspace.dependencies.minicbor]
version = "0.26.3"
version = "2.1.1"
features = ["std"]
[workspace.dependencies.minicbor-serde]
version = "0.4.1"
version = "0.6.0"
features = ["std"]
[workspace.dependencies.maplit]
@@ -599,7 +603,7 @@ rev = "e4ae7628fe4fcdacef9788c4c8415317a4489941"
# Use 1-indexed line numbers when displaying parse error messages
[patch.crates-io.resolv-conf]
git = "https://forgejo.ellis.link/continuwuation/resolv-conf"
rev = "56251316cc4127bcbf36e68ce5e2093f4d33e227"
rev = "ebbbec1cb965b487a0150f5d007e96c05e3d72af"
#
# Our crates
@@ -764,25 +768,6 @@ incremental = true
[profile.dev.package.conduwuit_core]
inherits = "dev"
#rustflags = [
# '--cfg', 'conduwuit_mods',
# '-Ztime-passes',
# '-Zmir-opt-level=0',
# '-Ztls-model=initial-exec',
# '-Cprefer-dynamic=true',
# '-Zstaticlib-prefer-dynamic=true',
# '-Zstaticlib-allow-rdylib-deps=true',
# '-Zpacked-bundled-libs=false',
# '-Zplt=true',
# '-Clink-arg=-Wl,--as-needed',
# '-Clink-arg=-Wl,--allow-shlib-undefined',
# '-Clink-arg=-Wl,-z,lazy',
# '-Clink-arg=-Wl,-z,unique',
# '-Clink-arg=-Wl,-z,nodlopen',
# '-Clink-arg=-Wl,-z,nodelete',
#]
[profile.dev.package.xtask-generate-commands]
inherits = "dev"
[profile.dev.package.conduwuit]
inherits = "dev"
#rustflags = [
-84
View File
@@ -1,84 +0,0 @@
[Unit]
Description=Continuwuity - Matrix homeserver
Wants=network-online.target
After=network-online.target
Documentation=https://continuwuity.org/
RequiresMountsFor=/var/lib/private/conduwuit
Alias=matrix-conduwuit.service
[Service]
DynamicUser=yes
Type=notify-reload
ReloadSignal=SIGUSR1
TTYPath=/dev/tty25
DeviceAllow=char-tty
StandardInput=tty-force
StandardOutput=tty
StandardError=journal+console
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit"
TTYReset=yes
# uncomment to allow buffer to be cleared every restart
TTYVTDisallocate=no
TTYColumns=120
TTYRows=40
AmbientCapabilities=
CapabilityBoundingSet=
DevicePolicy=closed
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
#ProcSubset=pid
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectProc=invisible
ProtectSystem=strict
PrivateDevices=yes
PrivateMounts=yes
PrivateTmp=yes
PrivateUsers=yes
PrivateIPC=yes
RemoveIPC=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service @resources
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
SystemCallErrorNumber=EPERM
StateDirectory=conduwuit
RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750
Environment=CONTINUWUITY_CONFIG=%d/config.toml
LoadCredential=config.toml:/etc/conduwuit/conduwuit.toml
BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit
BindPaths=/var/lib/private/conduwuit:/var/lib/private/matrix-conduit
ExecStart=/usr/bin/conduwuit
Restart=on-failure
RestartSec=5
TimeoutStopSec=4m
TimeoutStartSec=4m
StartLimitInterval=1m
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
+13 -7
View File
@@ -79,9 +79,9 @@
# This is the only directory where continuwuity will save its data,
# including media. Note: this was previously "/var/lib/matrix-conduit".
#
# YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a `systemd` service.
# The service file sets it to `/var/lib/conduwuit` using an environment variable
# and also grants write access.
# YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a
# `systemd` service. The service file sets it to `/var/lib/conduwuit`
# using an environment variable and also grants write access.
#
# example: "/var/lib/conduwuit"
#
@@ -591,13 +591,19 @@
#
#default_room_version = 11
# This item is undocumented. Please contribute documentation for it.
# Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
# Jaeger exporter. Traces will be sent via OTLP to a collector (such as
# Jaeger) that supports the OpenTelemetry Protocol.
#
#allow_jaeger = false
# Configure your OTLP endpoint using the OTEL_EXPORTER_OTLP_ENDPOINT
# environment variable (defaults to http://localhost:4318).
#
#allow_otlp = false
# This item is undocumented. Please contribute documentation for it.
# Filter for OTLP tracing spans. This controls which spans are exported
# to the OTLP collector.
#
#jaeger_filter = "info"
#otlp_filter = "info"
# If the 'perf_measurements' compile-time feature is enabled, enables
# collecting folded stack trace profile of tracing spans using
-71
View File
@@ -1,71 +0,0 @@
[Unit]
Description=Continuwuity - Matrix homeserver
Wants=network-online.target
After=network-online.target
Documentation=https://continuwuity.org/
Alias=matrix-conduwuit.service
[Service]
DynamicUser=yes
User=conduwuit
Group=conduwuit
Type=notify
Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit"
ExecStart=/usr/sbin/conduwuit
ReadWritePaths=/var/lib/conduwuit /etc/conduwuit
AmbientCapabilities=
CapabilityBoundingSet=
DevicePolicy=closed
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
#ProcSubset=pid
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectProc=invisible
ProtectSystem=strict
PrivateDevices=yes
PrivateMounts=yes
PrivateTmp=yes
PrivateUsers=yes
PrivateIPC=yes
RemoveIPC=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service @resources
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
SystemCallErrorNumber=EPERM
#StateDirectory=conduwuit
RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750
Restart=on-failure
RestartSec=5
TimeoutStopSec=2m
TimeoutStartSec=2m
StartLimitInterval=1m
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
-44
View File
@@ -1,44 +0,0 @@
#!/bin/sh
set -e
# TODO: implement debconf support that is maintainable without duplicating the config
#. /usr/share/debconf/confmodule
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
case "$1" in
configure)
# Create the `conduwuit` user if it does not exist yet.
if ! getent passwd conduwuit > /dev/null ; then
echo 'Adding system user for the conduwuit Matrix homeserver' 1>&2
adduser --system --group --quiet \
--home "$CONDUWUIT_DATABASE_PATH" \
--disabled-login \
--shell "/usr/sbin/nologin" \
conduwuit
fi
# Create the database path if it does not exist yet and fix up ownership
# and permissions for the config.
mkdir -v -p "$CONDUWUIT_DATABASE_PATH"
# symlink the previous location for compatibility if it does not exist yet.
if ! test -L "/var/lib/matrix-conduit" ; then
ln -s -v "$CONDUWUIT_DATABASE_PATH" "/var/lib/matrix-conduit"
fi
chown -v conduwuit:conduwuit -R "$CONDUWUIT_DATABASE_PATH"
chown -v conduwuit:conduwuit -R "$CONDUWUIT_CONFIG_PATH"
chmod -v 740 "$CONDUWUIT_DATABASE_PATH"
echo ''
echo 'Make sure you edit the example config at /etc/conduwuit/conduwuit.toml before starting!'
echo 'To start the server, run: systemctl start conduwuit.service'
echo ''
;;
esac
#DEBHELPER#
+55 -21
View File
@@ -48,11 +48,13 @@ EOF
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.13.0
ENV BINSTALL_VERSION=1.15.4
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
ENV LDDTREE_VERSION=0.3.7
# renovate: datasource=crate depName=timelord-cli
ENV TIMELORD_VERSION=3.0.1
# Install unpackaged tools
RUN <<EOF
@@ -60,6 +62,7 @@ RUN <<EOF
curl --retry 5 -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm cargo-sbom --version $CARGO_SBOM_VERSION
cargo binstall --no-confirm lddtree --version $LDDTREE_VERSION
cargo binstall --no-confirm timelord-cli --version $TIMELORD_VERSION
EOF
# Set up xx (cross-compilation scripts)
@@ -81,8 +84,9 @@ RUN rustc --version \
&& xx-cargo --setup-target-triple
# Build binary
# We disable incremental compilation to save disk space, as it only produces a minimal speedup for this case.
RUN echo "CARGO_INCREMENTAL=0" >> /etc/environment
# Configure incremental compilation based on build context
ARG CARGO_INCREMENTAL=0
RUN echo "CARGO_INCREMENTAL=${CARGO_INCREMENTAL}" >> /etc/environment
# Configure pkg-config
RUN <<EOF
@@ -133,6 +137,11 @@ FROM toolchain AS builder
# Get source
COPY . .
# Restore timestamps from timelord cache if available
RUN --mount=type=cache,target=/timelord/ \
echo "Restoring timestamps from timelord cache"; \
timelord sync --source-dir /app --cache-dir /timelord;
ARG TARGETPLATFORM
# Verify environment configuration
@@ -172,8 +181,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
for BINARY in "${BINARIES[@]}"; do
echo $BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY /out/sbin/$BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY /out/sbin/$BINARY
done
EOF
@@ -199,32 +208,57 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
EOF
# Extract dynamically linked dependencies
RUN <<EOF
RUN <<'DEPS_EOF'
set -o xtrace
mkdir /out/libs
mkdir /out/libs-root
mkdir /out/libs /out/libs-root
# Process each binary
for BINARY in /out/sbin/*; do
lddtree "$BINARY" | awk '{print $(NF-0) " " $1}' | sort -u -k 1,1 | awk '{print "install", "-D", $1, (($2 ~ /^\//) ? "/out/libs-root" $2 : "/out/libs/" $2)}' | xargs -I {} sh -c {}
if lddtree_output=$(lddtree "$BINARY" 2>/dev/null) && [ -n "$lddtree_output" ]; then
echo "$lddtree_output" | awk '{print $(NF-0) " " $1}' | sort -u -k 1,1 | \
awk '{dest = ($2 ~ /^\//) ? "/out/libs-root" $2 : "/out/libs/" $2; print "install -D " $1 " " dest}' | \
while read cmd; do eval "$cmd"; done
fi
done
EOF
# Show what will be copied to runtime
echo "=== Libraries being copied to runtime image:"
find /out/libs* -type f 2>/dev/null | sort || echo "No libraries found"
DEPS_EOF
FROM ubuntu:latest AS prepper
# Create layer structure
RUN mkdir -p /layer1/etc/ssl/certs \
/layer2/usr/lib \
/layer3/sbin /layer3/sbom
# Copy SSL certs and root-path libraries to layer1 (ultra-stable)
COPY --from=base /etc/ssl/certs /layer1/etc/ssl/certs
COPY --from=builder /out/libs-root/ /layer1/
# Copy application libraries to layer2 (semi-stable)
COPY --from=builder /out/libs/ /layer2/usr/lib/
# Copy binaries and SBOM to layer3 (volatile)
COPY --from=builder /out/sbin/ /layer3/sbin/
COPY --from=builder /out/sbom/ /layer3/sbom/
# Fix permissions after copying
RUN chmod -R 755 /layer1 /layer2 /layer3
FROM scratch
WORKDIR /
# Copy root certs for tls into image
# You can also mount the certs from the host
# --volume /etc/ssl/certs:/etc/ssl/certs:ro
COPY --from=base /etc/ssl/certs /etc/ssl/certs
# Copy ultra-stable layer (SSL certs, system libraries)
COPY --from=prepper /layer1/ /
# Copy our build
COPY --from=builder /out/sbin/ /sbin/
# Copy SBOM
COPY --from=builder /out/sbom/ /sbom/
# Copy semi-stable layer (application libraries)
COPY --from=prepper /layer2/ /
# Copy dynamic libraries to root
COPY --from=builder /out/libs-root/ /
COPY --from=builder /out/libs/ /usr/lib/
# Copy volatile layer (binaries, SBOM)
COPY --from=prepper /layer3/ /
# Inform linker where to find libraries
ENV LD_LIBRARY_PATH=/usr/lib
+1 -1
View File
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.13.0
ENV BINSTALL_VERSION=1.15.4
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
+3 -16
View File
@@ -9,24 +9,11 @@ ## Example configuration
</details>
## Debian systemd unit file
## systemd unit file
<details>
<summary>Debian systemd unit file</summary>
<summary>systemd unit file</summary>
```
{{#include ../../debian/conduwuit.service}}
{{#include ../../pkg/conduwuit.service}}
```
</details>
## Arch Linux systemd unit file
<details>
<summary>Arch Linux systemd unit file</summary>
```
{{#include ../../arch/conduwuit.service}}
```
</details>
+1 -1
View File
@@ -1 +1 @@
{{#include ../../debian/README.md}}
{{#include ../../pkg/debian/README.md}}
Generated
+79 -141
View File
@@ -10,11 +10,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1751403276,
"narHash": "sha256-V0EPQNsQko1a8OqIWc2lLviLnMpR1m08Ej00z5RVTfs=",
"lastModified": 1756403898,
"narHash": "sha256-S4SJDmVTtbcXaJkYrMFkcA5SDrpfRHlBbzwp6IRRPAw=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "896ad88fa57ad5dbcd267c0ac51f1b71ccfcb4dd",
"rev": "2524dd1c007bc7a0a9e9c863a1b02de8d54b319b",
"type": "github"
},
"original": {
@@ -29,14 +29,14 @@
"devenv": "devenv",
"flake-compat": "flake-compat_2",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_4"
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1748883665,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
"lastModified": 1756385612,
"narHash": "sha256-+NU5MMhuPHHRyvZZWNFG7zt+leRSPsJu1MwhOUzkPUk=",
"owner": "cachix",
"repo": "cachix",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
"rev": "dc24688cd67518c3711d511fa369c0f5a131063a",
"type": "github"
},
"original": {
@@ -58,16 +58,21 @@
],
"git-hooks": [
"cachix",
"devenv"
"devenv",
"git-hooks"
],
"nixpkgs": "nixpkgs_2"
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1744206633,
"narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=",
"lastModified": 1748883665,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
"owner": "cachix",
"repo": "cachix",
"rev": "8a60090640b96f9df95d1ab99e5763a586be1404",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
"type": "github"
},
"original": {
@@ -78,18 +83,12 @@
}
},
"crane": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1722960479,
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
"lastModified": 1751562746,
"narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
"rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd",
"type": "github"
},
"original": {
@@ -100,11 +99,11 @@
},
"crane_2": {
"locked": {
"lastModified": 1750266157,
"narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=",
"lastModified": 1757183466,
"narHash": "sha256-kTdCCMuRE+/HNHES5JYsbRHmgtr+l9mOtf5dpcMppVc=",
"owner": "ipetkov",
"repo": "crane",
"rev": "e37c943371b73ed87faf33f7583860f81f1d5a48",
"rev": "d599ae4847e7f87603e7082d73ca673aa93c916d",
"type": "github"
},
"original": {
@@ -132,11 +131,11 @@
]
},
"locked": {
"lastModified": 1748273445,
"narHash": "sha256-5V0dzpNgQM0CHDsMzh+ludYeu1S+Y+IMjbaskSSdFh0=",
"lastModified": 1754404745,
"narHash": "sha256-BdbW/iTImczgcuATgQIa9sPGuYIBxVq2xqcvICsa2AQ=",
"owner": "cachix",
"repo": "devenv",
"rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb",
"rev": "6563b21105168f90394dfaf58284b078af2d7275",
"type": "github"
},
"original": {
@@ -153,11 +152,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1755585599,
"narHash": "sha256-tl/0cnsqB/Yt7DbaGMel2RLa7QG5elA8lkaOXli6VdY=",
"lastModified": 1757400094,
"narHash": "sha256-5Rcs6juMoMTaMJSR1glravl4QB9yLAFBD8s7KLi4kdQ=",
"owner": "nix-community",
"repo": "fenix",
"rev": "6ed03ef4c8ec36d193c18e06b9ecddde78fb7e42",
"rev": "0682b9b518792c9428865c511a4c40c9ad85c243",
"type": "github"
},
"original": {
@@ -170,11 +169,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
@@ -224,11 +223,11 @@
]
},
"locked": {
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"lastModified": 1751413152,
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
"type": "github"
},
"original": {
@@ -247,11 +246,11 @@
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"lastModified": 1733312601,
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
"type": "github"
},
"original": {
@@ -292,11 +291,11 @@
]
},
"locked": {
"lastModified": 1747372754,
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
"lastModified": 1750779888,
"narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
"type": "github"
},
"original": {
@@ -327,31 +326,24 @@
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": [
"cachix",
"devenv"
"devenv",
"flake-compat"
],
"flake-parts": "flake-parts_2",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_3",
"git-hooks-nix": [
"cachix",
"devenv",
"git-hooks"
],
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
],
"nixpkgs-23-11": [
"cachix",
"devenv"
@@ -359,23 +351,19 @@
"nixpkgs-regression": [
"cachix",
"devenv"
],
"pre-commit-hooks": [
"cachix",
"devenv"
]
},
"locked": {
"lastModified": 1745930071,
"narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
"owner": "domenkozar",
"lastModified": 1752773918,
"narHash": "sha256-dOi/M6yNeuJlj88exI+7k154z+hAhFcuB8tZktiW7rg=",
"owner": "cachix",
"repo": "nix",
"rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
"rev": "031c3cf42d2e9391eee373507d8c12e0f9606779",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"owner": "cachix",
"ref": "devenv-2.30",
"repo": "nix",
"type": "github"
}
@@ -404,11 +392,11 @@
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"lastModified": 1737420293,
"narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9",
"type": "github"
},
"original": {
@@ -419,11 +407,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1726042813,
"narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=",
"lastModified": 1751949589,
"narHash": "sha256-mgFxAPLWw0Kq+C8P3dRrZrOYEQXOtKuYVlo9xvPntt8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "159be5db480d1df880a0135ca0bfed84c2f88353",
"rev": "9b008d60392981ad674e04016d25619281550a9d",
"type": "github"
},
"original": {
@@ -435,27 +423,27 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1724316499,
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
"lastModified": 1751741127,
"narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841",
"rev": "29e290002bfff26af1db6f64d070698019460302",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"lastModified": 1754214453,
"narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376",
"type": "github"
},
"original": {
@@ -467,43 +455,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"lastModified": 1757034884,
"narHash": "sha256-PgLSZDBEWUHpfTRfFyklmiiLBE1i1aGCtz4eRA3POao=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1748190013,
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1751498133,
"narHash": "sha256-QWJ+NQbMU+NcU2xiyo7SNox1fAuwksGlQhpzBl76g1I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d55716bb59b91ae9d1ced4b1ccdea7a442ecbfdb",
"rev": "ca77296380960cd497a765102eeb1356eb80fed0",
"type": "github"
},
"original": {
@@ -513,23 +469,6 @@
"type": "github"
}
},
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1753385396,
"narHash": "sha256-/Hvy1yTH/0D5aa7bc+/uqFugCQq4InTdwlRw88vA5IY=",
"ref": "10.4.fb",
"rev": "28d4b7276c16ed3e28af1bd96162d6442ce25923",
"revCount": 13318,
"type": "git",
"url": "https://forgejo.ellis.link/continuwuation/rocksdb"
},
"original": {
"ref": "10.4.fb",
"type": "git",
"url": "https://forgejo.ellis.link/continuwuation/rocksdb"
}
},
"root": {
"inputs": {
"attic": "attic",
@@ -539,18 +478,17 @@
"flake-compat": "flake-compat_3",
"flake-utils": "flake-utils",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_5",
"rocksdb": "rocksdb"
"nixpkgs": "nixpkgs_3"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1755504847,
"narHash": "sha256-VX0B9hwhJypCGqncVVLC+SmeMVd/GAYbJZ0MiiUn2Pk=",
"lastModified": 1757362324,
"narHash": "sha256-/PAhxheUq4WBrW5i/JHzcCqK5fGWwLKdH6/Lu1tyS18=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "a905e3b21b144d77e1b304e49f3264f6f8d4db75",
"rev": "9edc9cbe5d8e832b5864e09854fa94861697d2fd",
"type": "github"
},
"original": {
+8 -6
View File
@@ -16,10 +16,6 @@
flake-utils.url = "github:numtide/flake-utils?ref=main";
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixpkgs-unstable";
rocksdb = {
url = "git+https://forgejo.ellis.link/continuwuation/rocksdb?ref=10.4.fb";
flake = false;
};
};
outputs =
@@ -48,7 +44,7 @@
pkgs.lib.makeScope pkgs.newScope (self: {
inherit pkgs inputs;
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (_: toolchain);
main = self.callPackage ./nix/pkgs/main { };
main = self.callPackage ./pkg/nix/pkgs/main { };
liburing = pkgs.liburing.overrideAttrs {
# Tests weren't building
outputs = [
@@ -65,7 +61,13 @@
inherit (self) liburing;
}).overrideAttrs
(old: {
src = inputs.rocksdb;
src = pkgsHost.fetchFromGitea {
domain = "forgejo.ellis.link";
owner = "continuwuation";
repo = "rocksdb";
rev = "10.4.fb";
sha256 = "sha256-/Hvy1yTH/0D5aa7bc+/uqFugCQq4InTdwlRw88vA5IY=";
};
version = "v10.4.fb";
cmakeFlags =
pkgs.lib.subtractLists [
@@ -9,7 +9,8 @@ Alias=matrix-conduwuit.service
DynamicUser=yes
User=conduwuit
Group=conduwuit
Type=notify
Type=notify-reload
ReloadSignal=SIGUSR1
Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
@@ -59,8 +60,8 @@ RuntimeDirectoryMode=0750
Restart=on-failure
RestartSec=5
TimeoutStopSec=2m
TimeoutStartSec=2m
TimeoutStopSec=4m
TimeoutStartSec=4m
StartLimitInterval=1m
StartLimitBurst=5
View File
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
set -e
# TODO: implement debconf support that is maintainable without duplicating the config
#. /usr/share/debconf/confmodule
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
case "$1" in
configure)
echo ''
echo 'Make sure you edit the example config at /etc/conduwuit/conduwuit.toml before starting!'
echo 'To start the server, run: systemctl start conduwuit.service'
echo ''
;;
esac
#DEBHELPER#
+2 -8
View File
@@ -20,24 +20,18 @@ case $1 in
if [ -d "$CONDUWUIT_CONFIG_PATH" ]; then
if test -L "$CONDUWUIT_CONFIG_PATH"; then
echo "Deleting conduwuit configuration files"
echo "Deleting continuwuity configuration files"
rm -v -r "$CONDUWUIT_CONFIG_PATH"
fi
fi
if [ -d "$CONDUWUIT_DATABASE_PATH" ]; then
if test -L "$CONDUWUIT_DATABASE_PATH"; then
echo "Deleting conduwuit database directory"
echo "Deleting continuwuity database directory"
rm -r "$CONDUWUIT_DATABASE_PATH"
fi
fi
if [ -d "$CONDUWUIT_DATABASE_PATH_SYMLINK" ]; then
if test -L "$CONDUWUIT_DATABASE_SYMLINK"; then
echo "Removing matrix-conduit symlink"
rm -r "$CONDUWUIT_DATABASE_PATH_SYMLINK"
fi
fi
;;
esac
@@ -51,7 +51,7 @@ find .cargo/registry/ -executable -name "*.rs" -exec chmod -x {} +
%install
install -Dpm0755 target/rpm/conduwuit -t %{buildroot}%{_bindir}
install -Dpm0644 fedora/conduwuit.service -t %{buildroot}%{_unitdir}
install -Dpm0644 pkg/conduwuit.service -t %{buildroot}%{_unitdir}
install -Dpm0644 conduwuit-example.toml %{buildroot}%{_sysconfdir}/conduwuit/conduwuit.toml
%files
+44 -15
View File
@@ -1,10 +1,12 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"extends": ["config:recommended", "replacements:all"],
"osvVulnerabilityAlerts": true,
"lockFileMaintenance": {
"enabled": true,
"schedule": ["at any time"]
},
"platformAutomerge": true,
"nix": {
"enabled": true
},
@@ -29,10 +31,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",
@@ -40,20 +47,42 @@
"matchPackageNames": ["rust", "rustc", "cargo"],
"groupName": "rust-toolchain"
},
{
"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,
"automergeStrategy": "fast-forward"
},
{
"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
}
],
"customManagers": [
{
"customType": "regex",
"description": "Update _VERSION variables in Dockerfiles",
"managerFilePatterns": [
"/(^|/)([Dd]ocker|[Cc]ontainer)file[^/]*$/",
"/(^|/|\\.)([Dd]ocker|[Cc]ontainer)file$/"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>[a-z-.]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?(?: registryUrl=(?<registryUrl>[^\\s]+?))?\\s+(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_VERSION[ =][\"']?(?<currentValue>.+?)[\"']?\\s"
]
}
]
}
+2 -1
View File
@@ -85,10 +85,11 @@ futures.workspace = true
log.workspace = true
ruma.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
serde_yml.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
ctor.workspace = true
[lints]
workspace = true
+2 -2
View File
@@ -16,7 +16,7 @@ pub(super) async fn register(&self) -> Result {
let range = 1..checked!(body_len - 1)?;
let appservice_config_body = body[range].join("\n");
let parsed_config = serde_yaml::from_str(&appservice_config_body);
let parsed_config = serde_yml::from_str(&appservice_config_body);
match parsed_config {
| Err(e) => return Err!("Could not parse appservice config as YAML: {e}"),
| Ok(registration) => match self
@@ -57,7 +57,7 @@ pub(super) async fn show_appservice_config(&self, appservice_identifier: String)
{
| None => return Err!("Appservice does not exist."),
| Some(config) => {
let config_str = serde_yaml::to_string(&config)?;
let config_str = serde_yml::to_string(&config)?;
write!(self, "Config for {appservice_identifier}:\n\n```yaml\n{config_str}\n```")
},
}
+1 -8
View File
@@ -281,15 +281,8 @@ pub(super) async fn get_remote_pdu(
vec![(event_id, value, room_id)]
};
info!("Attempting to handle event ID {event_id} as backfilled PDU");
self.services
.rooms
.timeline
.backfill_pdu(&server, response.pdu)
.await?;
let text = serde_json::to_string_pretty(&json)?;
let msg = "Got PDU from specified server and handled as backfilled";
let msg = "Got PDU from specified server:";
write!(self, "{msg}. Event body:\n```json\n{text}\n```")
},
}
+2
View File
@@ -29,6 +29,8 @@
pub(crate) const PAGE_SIZE: usize = 100;
use ctor::{ctor, dtor};
conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {}
conduwuit::rustc_flags_capture! {}
+1 -1
View File
@@ -57,5 +57,5 @@ pub(super) async fn pdus(
.try_collect()
.await?;
self.write_str(&format!("{result:#?}")).await
self.write_str(&format!("```\n{result:#?}\n```")).await
}
+1
View File
@@ -93,6 +93,7 @@ serde.workspace = true
sha1.workspace = true
tokio.workspace = true
tracing.workspace = true
ctor.workspace = true
[lints]
workspace = true
+27 -32
View File
@@ -405,41 +405,36 @@ pub(crate) async fn register_route(
)
.await?;
if (!is_guest && body.inhibit_login)
// 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 +578,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,
})
+1 -1
View File
@@ -130,7 +130,7 @@ pub(crate) async fn get_context_route(
let state_at = events_after
.last()
.map(ref_at!(1))
.map_or(body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
let state_ids = services
.rooms
+5 -6
View File
@@ -35,7 +35,6 @@
};
use tracing::warn;
use super::utils::{count_to_token, parse_pagination_token as parse_token};
use crate::Ruma;
/// list of safe and common non-state events to ignore if the user is ignored
@@ -85,14 +84,14 @@ pub(crate) async fn get_message_events_route(
let from: PduCount = body
.from
.as_deref()
.map(parse_token)
.map(str::parse)
.transpose()?
.unwrap_or_else(|| match body.dir {
| Direction::Forward => PduCount::min(),
| Direction::Backward => PduCount::max(),
});
let to: Option<PduCount> = body.to.as_deref().map(parse_token).transpose()?;
let to: Option<PduCount> = body.to.as_deref().map(str::parse).transpose()?;
let limit: usize = body
.limit
@@ -181,8 +180,8 @@ pub(crate) async fn get_message_events_route(
.collect();
Ok(get_message_events::v3::Response {
start: count_to_token(from),
end: next_token.map(count_to_token),
start: from.to_string(),
end: next_token.as_ref().map(PduCount::to_string),
chunk,
state,
})
@@ -321,7 +320,7 @@ pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Opti
filter.matches(pdu).then_some(item)
}
#[cfg_attr(debug_assertions, conduwuit::ctor)]
#[cfg_attr(debug_assertions, ctor::ctor)]
fn _is_sorted() {
debug_assert!(
IGNORED_MESSAGE_TYPES.is_sorted(),
-1
View File
@@ -37,7 +37,6 @@
pub(super) mod unstable;
pub(super) mod unversioned;
pub(super) mod user_directory;
pub(super) mod utils;
pub(super) mod voip;
pub(super) mod well_known;
+4 -20
View File
@@ -1,5 +1,3 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduwuit::{
Err, Result,
@@ -226,7 +224,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 +259,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 +272,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,
});
}
@@ -288,21 +283,11 @@ pub(crate) async fn get_profile_route(
return Err!(Request(NotFound("Profile was not found.")));
}
let mut custom_profile_fields: BTreeMap<String, serde_json::Value> = services
.users
.all_profile_keys(&body.user_id)
.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, 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.timezone(&body.user_id).ok(),
services.users.all_profile_keys(&body.user_id).collect(),
)
.await;
@@ -310,7 +295,6 @@ pub(crate) async fn get_profile_route(
avatar_url,
blurhash,
displayname,
tz,
custom_profile_fields,
})
}
+3 -4
View File
@@ -18,7 +18,6 @@
events::{TimelineEventType, relation::RelationType},
};
use super::utils::{count_to_token, parse_pagination_token as parse_token};
use crate::Ruma;
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
@@ -111,14 +110,14 @@ async fn paginate_relations_with_filter(
dir: Direction,
) -> Result<get_relating_events::v1::Response> {
let start: PduCount = from
.map(parse_token)
.map(str::parse)
.transpose()?
.unwrap_or_else(|| match dir {
| Direction::Forward => PduCount::min(),
| Direction::Backward => PduCount::max(),
});
let to: Option<PduCount> = to.map(parse_token).transpose()?;
let to: Option<PduCount> = to.map(str::parse).transpose()?;
// Use limit or else 30, with maximum 100
let limit: usize = limit
@@ -193,7 +192,7 @@ async fn paginate_relations_with_filter(
| Direction::Forward => events.last(),
| Direction::Backward => events.first(),
}
.map(|(count, _)| count_to_token(*count))
.map(|(count, _)| count.to_string())
} else {
None
};
+10 -20
View File
@@ -1,4 +1,4 @@
use std::{fmt::Write as _, ops::Mul, time::Duration};
use std::{fmt::Write as _, time::Duration};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
@@ -12,7 +12,6 @@
room::{report_content, report_room},
},
events::{Mentions, room::message::RoomMessageEventContent},
int,
};
use tokio::time::sleep;
@@ -25,7 +24,6 @@ struct Report {
user_id: Option<OwnedUserId>,
report_type: String,
reason: Option<String>,
score: Option<ruma::Int>,
}
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
@@ -50,6 +48,15 @@ pub(crate) async fn report_room_route(
delay_response().await;
// We log this early in case the room ID does actually exist, in which case
// admins who scan their logs can see the report and choose to investigate at
// their discretion.
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
if !services
.rooms
.state_cache
@@ -60,11 +67,6 @@ pub(crate) async fn report_room_route(
"Room does not exist to us, no local users have joined at all"
)));
}
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
let report = Report {
sender: sender_user.to_owned(),
@@ -73,7 +75,6 @@ pub(crate) async fn report_room_route(
user_id: None,
report_type: "room".to_owned(),
reason: body.reason.clone(),
score: None,
};
services.admin.send_message(build_report(report)).await.ok();
@@ -109,7 +110,6 @@ pub(crate) async fn report_event_route(
&body.room_id,
sender_user,
body.reason.as_ref(),
body.score,
&pdu,
)
.await?;
@@ -127,7 +127,6 @@ pub(crate) async fn report_event_route(
user_id: None,
report_type: "event".to_owned(),
reason: body.reason.clone(),
score: body.score,
};
services.admin.send_message(build_report(report)).await.ok();
@@ -166,7 +165,6 @@ pub(crate) async fn report_user_route(
user_id: Some(body.user_id.clone()),
report_type: "user".to_owned(),
reason: body.reason.clone(),
score: None,
};
info!(
@@ -192,7 +190,6 @@ async fn is_event_report_valid(
room_id: &RoomId,
sender_user: &UserId,
reason: Option<&String>,
score: Option<ruma::Int>,
pdu: &PduEvent,
) -> Result<()> {
debug_info!(
@@ -204,10 +201,6 @@ async fn is_event_report_valid(
return Err!(Request(NotFound("Event ID does not belong to the reported room",)));
}
if score.is_some_and(|s| s > int!(0) || s < int!(-100)) {
return Err!(Request(InvalidParam("Invalid score, must be within 0 to -100",)));
}
if reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err!(Request(
InvalidParam("Reason too long, should be 750 characters or fewer",)
@@ -240,9 +233,6 @@ fn build_report(report: Report) -> RoomMessageEventContent {
if report.event_id.is_some() {
let _ = writeln!(text, "- Reported Event ID: `{}`", report.event_id.unwrap());
}
if let Some(score) = report.score {
let _ = writeln!(text, "- User-supplied offensiveness score: {}%", score.mul(int!(-1)));
}
if let Some(reason) = report.reason {
let _ = writeln!(text, "- Report Reason: {reason}");
}
+1 -6
View File
@@ -18,7 +18,7 @@ pub(crate) async fn get_room_event_route(
let event = services
.rooms
.timeline
.get_pdu(event_id)
.get_remote_pdu(room_id, event_id)
.map_err(|_| err!(Request(NotFound("Event {} not found.", event_id))));
let visible = services
@@ -33,11 +33,6 @@ pub(crate) async fn get_room_event_route(
return Err!(Request(Forbidden("You don't have permission to view this event.")));
}
debug_assert!(
event.event_id() == event_id && event.room_id() == room_id,
"Fetched PDU must match requested"
);
event.add_age().ok();
Ok(get_room_event::v3::Response { event: event.into_format() })
+11 -135
View File
@@ -2,18 +2,14 @@
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, 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 +56,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<crate::State>,
body: Ruma<delete_timezone_key::unstable::Request>,
) -> Result<delete_timezone_key::unstable::Response> {
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<crate::State>,
body: Ruma<set_timezone_key::unstable::Request>,
) -> Result<set_timezone_key::unstable::Response> {
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.
@@ -150,19 +90,14 @@ 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")));
}
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<OwnedRoomId> = services
.rooms
.state_cache
@@ -174,12 +109,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<OwnedRoomId> = services
.rooms
@@ -268,70 +206,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<crate::State>,
body: Ruma<get_timezone_key::unstable::Request>,
) -> Result<get_timezone_key::unstable::Response> {
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<crate::State>,
body: Ruma<get_profile_key::unstable::Request>,
@@ -367,10 +247,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());
-28
View File
@@ -1,28 +0,0 @@
use conduwuit::{
Result, err,
matrix::pdu::{PduCount, ShortEventId},
};
/// Parse a pagination token, trying ShortEventId first, then falling back to
/// PduCount
pub(crate) fn parse_pagination_token(token: &str) -> Result<PduCount> {
// Try parsing as ShortEventId first
if let Ok(shorteventid) = token.parse::<ShortEventId>() {
// ShortEventId maps directly to a PduCount in our database
Ok(PduCount::Normal(shorteventid))
} else if let Ok(count) = token.parse::<u64>() {
// Fallback to PduCount for backwards compatibility
Ok(PduCount::Normal(count))
} else if let Ok(count) = token.parse::<i64>() {
// Also handle negative counts for backfilled events
Ok(PduCount::from_signed(count))
} else {
Err(err!(Request(InvalidParam("Invalid pagination token"))))
}
}
/// Convert a PduCount to a token string (using the underlying ShortEventId)
pub(crate) fn count_to_token(count: PduCount) -> String {
// The PduCount's unsigned value IS the ShortEventId
count.into_unsigned().to_string()
}
-3
View File
@@ -22,12 +22,9 @@
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
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)
+2 -5
View File
@@ -20,9 +20,7 @@
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(_) => {
-7
View File
@@ -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,
})
}
+82 -31
View File
@@ -1,12 +1,13 @@
#![allow(deprecated)]
use std::borrow::Borrow;
use std::{borrow::Borrow, time::Instant, vec};
use axum::extract::State;
use conduwuit::{
Err, Result, at, err,
Err, Event, Result, at, debug, err, info,
matrix::event::gen_event_id_canonical_json,
utils::stream::{IterStream, TryBroadbandExt},
trace,
utils::stream::{BroadbandExt, IterStream, TryBroadbandExt},
warn,
};
use conduwuit_service::Services;
@@ -25,12 +26,14 @@
use crate::Ruma;
/// helper method for /send_join v1 and v2
#[tracing::instrument(skip(services, pdu, omit_members), fields(room_id = room_id.as_str(), origin = origin.as_str()))]
async fn create_join_event(
services: &Services,
origin: &ServerName,
room_id: &RoomId,
pdu: &RawJsonValue,
) -> Result<create_join_event::v1::RoomState> {
omit_members: bool,
) -> Result<create_join_event::v2::RoomState> {
if !services.rooms.metadata.exists(room_id).await {
return Err!(Request(NotFound("Room is unknown to this server.")));
}
@@ -53,8 +56,10 @@ async fn create_join_event(
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
trace!("Getting room version");
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
trace!("Generating event ID and converting to canonical json");
let Ok((event_id, mut value)) = gen_event_id_canonical_json(pdu, &room_version_id) else {
// Event could not be converted to canonical json
return Err!(Request(BadJson("Could not convert event to canonical json.")));
@@ -103,7 +108,6 @@ async fn create_join_event(
)));
}
// ACL check sender user server name
let sender: OwnedUserId = serde_json::from_value(
value
.get("sender")
@@ -113,12 +117,6 @@ async fn create_join_event(
)
.map_err(|e| err!(Request(BadJson(warn!("sender property is not a valid user ID: {e}")))))?;
services
.rooms
.event_handler
.acl_check(sender.server_name(), room_id)
.await?;
// check if origin server is trying to send for another server
if sender.server_name() != origin {
return Err!(Request(Forbidden("Not allowed to join on behalf of another server.")));
@@ -180,11 +178,6 @@ async fn create_join_event(
}
}
services
.server_keys
.hash_and_sign_event(&mut value, &room_version_id)
.map_err(|e| err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}")))))?;
let origin: OwnedServerName = serde_json::from_value(
value
.get("origin")
@@ -194,6 +187,12 @@ async fn create_join_event(
)
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
trace!("Signing send_join event");
services
.server_keys
.hash_and_sign_event(&mut value, &room_version_id)
.map_err(|e| err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}")))))?;
let mutex_lock = services
.rooms
.event_handler
@@ -201,6 +200,7 @@ async fn create_join_event(
.lock(room_id)
.await;
trace!("Acquired send_join mutex, persisting join event");
let pdu_id = services
.rooms
.event_handler
@@ -210,7 +210,7 @@ async fn create_join_event(
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
drop(mutex_lock);
trace!("Fetching current state IDs");
let state_ids: Vec<OwnedEventId> = services
.rooms
.state_accessor
@@ -219,9 +219,23 @@ async fn create_join_event(
.collect()
.await;
trace!(%omit_members, "Constructing current state");
let state = state_ids
.iter()
.try_stream()
.broad_filter_map(|event_id| async move {
if omit_members {
if let Ok(e) = event_id.as_ref() {
let pdu = services.rooms.timeline.get_pdu(e).await;
if pdu.is_ok_and(|p| p.kind().to_cow_str() == "m.room.member") {
trace!("omitting member event {e:?} from returned state");
// skip members
return None;
}
}
}
Some(event_id)
})
.broad_and_then(|event_id| services.rooms.timeline.get_pdu_json(event_id))
.broad_and_then(|pdu| {
services
@@ -234,6 +248,7 @@ async fn create_join_event(
.await?;
let starting_events = state_ids.iter().map(Borrow::borrow);
trace!("Constructing auth chain");
let auth_chain = services
.rooms
.auth_chain
@@ -250,13 +265,37 @@ async fn create_join_event(
.try_collect()
.boxed()
.await?;
info!(fast_join = %omit_members, "Sending join event to other servers");
services.sending.send_pdu_room(room_id, &pdu_id).await?;
Ok(create_join_event::v1::RoomState {
debug!("Finished sending join event");
let servers_in_room: Option<Vec<_>> = if !omit_members {
None
} else {
trace!("Fetching list of servers in room");
let servers: Vec<String> = services
.rooms
.state_cache
.room_servers(room_id)
.map(|sn| sn.as_str().to_owned())
.collect()
.await;
// If there's no servers, just add us
let servers = if servers.is_empty() {
warn!("Failed to find any servers, adding our own server name as a last resort");
vec![services.globals.server_name().to_string()]
} else {
trace!("Found {} servers in room", servers.len());
servers
};
Some(servers)
};
debug!("Returning send_join data");
Ok(create_join_event::v2::RoomState {
auth_chain,
state,
event: to_raw_value(&CanonicalJsonValue::Object(value)).ok(),
members_omitted: omit_members,
servers_in_room,
})
}
@@ -294,11 +333,23 @@ pub(crate) async fn create_join_event_v1_route(
}
}
let room_state = create_join_event(&services, body.origin(), &body.room_id, &body.pdu)
let now = Instant::now();
let room_state = create_join_event(&services, body.origin(), &body.room_id, &body.pdu, false)
.boxed()
.await?;
let transformed = create_join_event::v1::RoomState {
auth_chain: room_state.auth_chain,
state: room_state.state,
event: room_state.event,
};
info!(
"Finished sending a join for {} in {} in {:?}",
body.origin(),
&body.room_id,
now.elapsed()
);
Ok(create_join_event::v1::Response { room_state })
Ok(create_join_event::v1::Response { room_state: transformed })
}
/// # `PUT /_matrix/federation/v2/send_join/{roomId}/{eventId}`
@@ -329,17 +380,17 @@ pub(crate) async fn create_join_event_v2_route(
}
}
let create_join_event::v1::RoomState { auth_chain, state, event } =
create_join_event(&services, body.origin(), &body.room_id, &body.pdu)
let now = Instant::now();
let room_state =
create_join_event(&services, body.origin(), &body.room_id, &body.pdu, body.omit_members)
.boxed()
.await?;
let room_state = create_join_event::v2::RoomState {
members_omitted: false,
auth_chain,
state,
event,
servers_in_room: None,
};
info!(
"Finished sending a join for {} in {} in {:?}",
body.origin(),
&body.room_id,
now.elapsed()
);
Ok(create_join_event::v2::Response { room_state })
}
+1 -1
View File
@@ -92,7 +92,7 @@ ruma.workspace = true
sanitize-filename.workspace = true
serde_json.workspace = true
serde_regex.workspace = true
serde_yaml.workspace = true
serde_yml.workspace = true
serde.workspace = true
smallvec.workspace = true
smallstr.workspace = true
+17 -8
View File
@@ -126,9 +126,9 @@ pub struct Config {
/// This is the only directory where continuwuity will save its data,
/// including media. Note: this was previously "/var/lib/matrix-conduit".
///
/// YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a `systemd` service.
/// The service file sets it to `/var/lib/conduwuit` using an environment variable
/// and also grants write access.
/// YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a
/// `systemd` service. The service file sets it to `/var/lib/conduwuit`
/// using an environment variable and also grants write access.
///
/// example: "/var/lib/conduwuit"
pub database_path: PathBuf,
@@ -714,12 +714,21 @@ pub struct Config {
#[serde(default)]
pub well_known: WellKnownConfig,
#[serde(default)]
pub allow_jaeger: bool,
/// Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
/// Jaeger exporter. Traces will be sent via OTLP to a collector (such as
/// Jaeger) that supports the OpenTelemetry Protocol.
///
/// Configure your OTLP endpoint using the OTEL_EXPORTER_OTLP_ENDPOINT
/// environment variable (defaults to http://localhost:4318).
#[serde(default, alias = "allow_jaeger")]
pub allow_otlp: bool,
/// Filter for OTLP tracing spans. This controls which spans are exported
/// to the OTLP collector.
///
/// default: "info"
#[serde(default = "default_jaeger_filter")]
pub jaeger_filter: String,
#[serde(default = "default_otlp_filter", alias = "jaeger_filter")]
pub otlp_filter: String,
/// If the 'perf_measurements' compile-time feature is enabled, enables
/// collecting folded stack trace profile of tracing spans using
@@ -2367,7 +2376,7 @@ fn default_tracing_flame_filter() -> String {
.to_owned()
}
fn default_jaeger_filter() -> String {
fn default_otlp_filter() -> String {
cfg!(debug_assertions)
.then_some("trace,h2=off")
.unwrap_or("info")
+1 -1
View File
@@ -83,7 +83,7 @@ pub enum Error {
#[error(transparent)]
TypedHeader(#[from] axum_extra::typed_header::TypedHeaderRejection),
#[error(transparent)]
Yaml(#[from] serde_yaml::Error),
Yaml(#[from] serde_yml::Error),
// ruma/conduwuit
#[error("Arithmetic operation failed: {0}")]
+1 -1
View File
@@ -27,5 +27,5 @@ fn init_user_agent() -> String { format!("{}/{}", name(), version()) }
fn init_version() -> String {
conduwuit_build_metadata::version_tag()
.map_or(SEMANTIC.to_owned(), |extra| format!("{SEMANTIC} ({extra})"))
.map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC} ({extra})"))
}
+5 -1
View File
@@ -1,6 +1,10 @@
#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss, clippy::as_conversions)]
use std::{cmp::Ordering, fmt, fmt::Display, str::FromStr};
use std::{
cmp::Ordering,
fmt::{self, Display},
str::FromStr,
};
use ruma::api::Direction;
+5 -4
View File
@@ -585,10 +585,11 @@ struct GetThirdPartyInvite {
(target_membership == MembershipState::Join).then_some(&power_levels.users_default)
});
let mut join_rules = JoinRule::Invite;
if let Some(jr) = &join_rules_event {
join_rules = from_json_str::<RoomJoinRulesEventContent>(jr.content().get())?.join_rule;
}
let join_rules = if let Some(jr) = &join_rules_event {
from_json_str::<RoomJoinRulesEventContent>(jr.content().get())?.join_rule
} else {
JoinRule::Invite
};
let power_levels_event_id = power_levels_event.as_ref().map(Event::event_id);
let sender_membership_event_id = sender_membership_event.as_ref().map(Event::event_id);
+2 -2
View File
@@ -65,7 +65,7 @@ fn expect_false(self, msg: &str) -> Self { (!self).then_some(false).expect(msg)
fn into_option(self) -> Option<()> { self.then_some(()) }
#[inline]
fn into_result(self) -> Result<(), ()> { self.ok_or(()) }
fn into_result(self) -> Result<(), ()> { BoolExt::ok_or(self, ()) }
#[inline]
fn map<T, F: FnOnce(Self) -> T>(self, f: F) -> T
@@ -77,7 +77,7 @@ fn map<T, F: FnOnce(Self) -> T>(self, f: F) -> T
#[inline]
fn map_ok_or<T, E, F: FnOnce() -> T>(self, err: E, f: F) -> Result<T, E> {
self.ok_or(err).map(|()| f())
BoolExt::ok_or(self, err).map(|()| f())
}
#[inline]
+1
View File
@@ -66,6 +66,7 @@ serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tracing.workspace = true
ctor.workspace = true
[lints]
workspace = true
+2
View File
@@ -3,6 +3,8 @@
extern crate conduwuit_core as conduwuit;
extern crate rust_rocksdb as rocksdb;
use ctor::{ctor, dtor};
conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {}
conduwuit::rustc_flags_capture! {}
+2 -2
View File
@@ -13,13 +13,13 @@ pub(super) fn flags_capture(args: TokenStream) -> TokenStream {
let ret = quote! {
pub static RUSTC_FLAGS: [&str; #flag_len] = [#( #flag ),*];
#[conduwuit_core::ctor]
#[ctor]
fn _set_rustc_flags() {
conduwuit_core::info::rustc::FLAGS.lock().insert(#crate_name, &RUSTC_FLAGS);
}
// static strings have to be yanked on module unload
#[conduwuit_core::dtor]
#[dtor]
fn _unset_rustc_flags() {
conduwuit_core::info::rustc::FLAGS.lock().remove(#crate_name);
}
+12 -7
View File
@@ -32,12 +32,12 @@ a cool hard fork of Conduit, a Matrix homeserver written in Rust"""
section = "net"
priority = "optional"
conf-files = ["/etc/conduwuit/conduwuit.toml"]
maintainer-scripts = "../../debian/"
systemd-units = { unit-name = "conduwuit", start = false }
maintainer-scripts = "../../pkg/debian/"
systemd-units = { unit-name = "conduwuit", start = false, unit-scripts = "../../pkg/" }
assets = [
["../../debian/README.md", "usr/share/doc/conduwuit/README.Debian", "644"],
["../../pkg/debian/README.md", "usr/share/doc/conduwuit/README.Debian", "644"],
["../../README.md", "usr/share/doc/conduwuit/", "644"],
["../../target/release/conduwuit", "usr/sbin/conduwuit", "755"],
["../../target/release/conduwuit", "usr/bin/conduwuit", "755"],
["../../conduwuit-example.toml", "etc/conduwuit/conduwuit.toml", "640"],
]
@@ -126,7 +126,8 @@ perf_measurements = [
"dep:tracing-flame",
"dep:tracing-opentelemetry",
"dep:opentelemetry_sdk",
"dep:opentelemetry-jaeger",
"dep:opentelemetry-otlp",
"dep:opentelemetry-jaeger-propagator",
"conduwuit-core/perf_measurements",
"conduwuit-core/sentry_telemetry",
]
@@ -202,11 +203,14 @@ clap.workspace = true
console-subscriber.optional = true
console-subscriber.workspace = true
const-str.workspace = true
ctor.workspace = true
log.workspace = true
opentelemetry-jaeger.optional = true
opentelemetry-jaeger.workspace = true
opentelemetry.optional = true
opentelemetry.workspace = true
opentelemetry-otlp.optional = true
opentelemetry-otlp.workspace = true
opentelemetry-jaeger-propagator.optional = true
opentelemetry-jaeger-propagator.workspace = true
opentelemetry_sdk.optional = true
opentelemetry_sdk.workspace = true
sentry-tower.optional = true
@@ -226,6 +230,7 @@ tracing-subscriber.workspace = true
tracing.workspace = true
tracing-journald = { workspace = true, optional = true }
[target.'cfg(all(not(target_env = "msvc"), target_os = "linux"))'.dependencies]
hardened_malloc-rs.workspace = true
hardened_malloc-rs.optional = true
+21 -14
View File
@@ -7,6 +7,8 @@
log::{ConsoleFormat, ConsoleWriter, LogLevelReloadHandles, capture, fmt_span},
result::UnwrapOrErr,
};
#[cfg(feature = "perf_measurements")]
use opentelemetry::trace::TracerProvider;
use tracing_subscriber::{EnvFilter, Layer, Registry, fmt, layer::SubscriberExt, reload};
#[cfg(feature = "perf_measurements")]
@@ -87,30 +89,35 @@ pub(crate) fn init(
(None, None)
};
let jaeger_filter = EnvFilter::try_new(&config.jaeger_filter)
.map_err(|e| err!(Config("jaeger_filter", "{e}.")))?;
let otlp_filter = EnvFilter::try_new(&config.otlp_filter)
.map_err(|e| err!(Config("otlp_filter", "{e}.")))?;
let jaeger_layer = config.allow_jaeger.then(|| {
let otlp_layer = config.allow_otlp.then(|| {
opentelemetry::global::set_text_map_propagator(
opentelemetry_jaeger::Propagator::new(),
opentelemetry_jaeger_propagator::Propagator::new(),
);
let tracer = opentelemetry_jaeger::new_agent_pipeline()
.with_auto_split_batch(true)
.with_service_name(conduwuit_core::name())
.install_batch(opentelemetry_sdk::runtime::Tokio)
.expect("jaeger agent pipeline");
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_http()
.build()
.expect("Failed to create OTLP exporter");
let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.build();
let tracer = provider.tracer(conduwuit_core::name());
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
let (jaeger_reload_filter, jaeger_reload_handle) =
reload::Layer::new(jaeger_filter.clone());
reload_handles.add("jaeger", Box::new(jaeger_reload_handle));
let (otlp_reload_filter, otlp_reload_handle) =
reload::Layer::new(otlp_filter.clone());
reload_handles.add("otlp", Box::new(otlp_reload_handle));
Some(telemetry.with_filter(jaeger_reload_filter))
Some(telemetry.with_filter(otlp_reload_filter))
});
let subscriber = subscriber.with(flame_layer).with(jaeger_layer);
let subscriber = subscriber.with(flame_layer).with(otlp_layer);
(subscriber, flame_guard)
};
+1
View File
@@ -13,6 +13,7 @@
mod server;
mod signal;
use ctor::{ctor, dtor};
use server::Server;
rustc_flags_capture! {}
+1
View File
@@ -125,6 +125,7 @@ tokio.workspace = true
tower.workspace = true
tower-http.workspace = true
tracing.workspace = true
ctor.workspace = true
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
sd-notify.workspace = true
+1
View File
@@ -12,6 +12,7 @@
use conduwuit::{Error, Result, Server};
use conduwuit_service::Services;
use ctor::{ctor, dtor};
use futures::{Future, FutureExt, TryFutureExt};
conduwuit::mod_ctor! {}
+2 -1
View File
@@ -105,7 +105,7 @@ rustyline-async.workspace = true
rustyline-async.optional = true
serde_json.workspace = true
serde.workspace = true
serde_yaml.workspace = true
serde_yml.workspace = true
sha2.workspace = true
termimad.workspace = true
termimad.optional = true
@@ -117,6 +117,7 @@ webpage.optional = true
blurhash.workspace = true
blurhash.optional = true
recaptcha-verify = { version = "0.1.5", default-features = false }
ctor.workspace = true
[lints]
workspace = true
+1 -1
View File
@@ -271,7 +271,7 @@ pub async fn get_db_registration(&self, id: &str) -> Result<Registration> {
.id_appserviceregistrations
.get(id)
.await
.and_then(|ref bytes| serde_yaml::from_slice(bytes).map_err(Into::into))
.and_then(|ref bytes| serde_yml::from_slice(bytes).map_err(Into::into))
.map_err(|e| err!(Database("Invalid appservice {id:?} registration: {e:?}")))
}
+11 -12
View File
@@ -29,20 +29,19 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
let db = Data::new(&args);
let config = &args.server.config;
let turn_secret =
config
.turn_secret_file
.as_ref()
.map_or(config.turn_secret.clone(), |path| {
std::fs::read_to_string(path).unwrap_or_else(|e| {
error!("Failed to read the TURN secret file: {e}");
let turn_secret = config.turn_secret_file.as_ref().map_or_else(
|| config.turn_secret.clone(),
|path| {
std::fs::read_to_string(path).unwrap_or_else(|e| {
error!("Failed to read the TURN secret file: {e}");
config.turn_secret.clone()
})
});
config.turn_secret.clone()
})
},
);
let registration_token = config.registration_token_file.as_ref().map_or(
config.registration_token.clone(),
let registration_token = config.registration_token_file.as_ref().map_or_else(
|| config.registration_token.clone(),
|path| {
let Ok(token) = std::fs::read_to_string(path).inspect_err(|e| {
error!("Failed to read the registration token file: {e}");
+1
View File
@@ -33,6 +33,7 @@
extern crate conduwuit_core as conduwuit;
extern crate conduwuit_database as database;
use ctor::{ctor, dtor};
pub(crate) use service::{Args, Dep, Service};
pub use crate::services::Services;
+4 -13
View File
@@ -439,13 +439,7 @@ async fn send_notice<E>(
notifi.counts = NotificationCounts::default();
}
if event_id_only {
self.send_request(
&http.url,
send_event_notification::v1::Request::new(notifi),
)
.await?;
} else {
if !event_id_only {
if *event.kind() == TimelineEventType::RoomEncrypted
|| tweaks
.iter()
@@ -480,14 +474,11 @@ async fn send_notice<E>(
.get_canonical_alias(event.room_id())
.await
.ok();
self.send_request(
&http.url,
send_event_notification::v1::Request::new(notifi),
)
.await?;
}
self.send_request(&http.url, send_event_notification::v1::Request::new(notifi))
.await?;
Ok(())
},
// TODO: Handle email
+1 -1
View File
@@ -452,7 +452,7 @@ async fn get_statediff(&self, shortstatehash: ShortStateHash) -> Result<StateDif
.ok()
.take_if(|parent| *parent != 0);
debug_assert!(value.len() % STRIDE == 0, "value not aligned to stride");
debug_assert!(value.len().is_multiple_of(STRIDE), "value not aligned to stride");
let _num_values = value.len() / STRIDE;
let mut add_mode = true;
+121 -4
View File
@@ -1,7 +1,8 @@
use std::iter::once;
use conduwuit::{Err, PduEvent};
use conduwuit_core::{
Result, debug, debug_warn, implement, info,
Result, debug, debug_warn, err, implement, info,
matrix::{
event::Event,
pdu::{PduCount, PduId, RawPduId},
@@ -11,7 +12,7 @@
};
use futures::{FutureExt, StreamExt};
use ruma::{
RoomId, ServerName,
CanonicalJsonObject, EventId, RoomId, ServerName,
api::federation,
events::{
StateEventType, TimelineEventType, room::power_levels::RoomPowerLevelsEventContent,
@@ -100,7 +101,7 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
.boxed();
while let Some(ref backfill_server) = servers.next().await {
info!("Asking {backfill_server} for backfill");
info!("Asking {backfill_server} for backfill in {room_id}");
let response = self
.services
.sending
@@ -128,10 +129,126 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
}
}
info!("No servers could backfill, but backfill was needed in room {room_id}");
warn!("No servers could backfill, but backfill was needed in room {room_id}");
Ok(())
}
#[implement(super::Service)]
#[tracing::instrument(name = "get_remote_pdu", level = "debug", skip(self))]
pub async fn get_remote_pdu(&self, room_id: &RoomId, event_id: &EventId) -> Result<PduEvent> {
let local = self.get_pdu(event_id).await;
if local.is_ok() {
// We already have this PDU, no need to backfill
debug!("We already have {event_id} in {room_id}, no need to backfill.");
return local;
}
debug!("Preparing to fetch event {event_id} in room {room_id} from remote servers.");
// Similar to backfill_if_required, but only for a single PDU
// Fetch a list of servers to try
if self
.services
.state_cache
.room_joined_count(room_id)
.await
.is_ok_and(|count| count <= 1)
&& !self
.services
.state_accessor
.is_world_readable(room_id)
.await
{
// Room is empty (1 user or none), there is no one that can backfill
return Err!(Request(NotFound("No one can backfill this PDU, room is empty.")));
}
let power_levels: RoomPowerLevelsEventContent = self
.services
.state_accessor
.room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "")
.await
.unwrap_or_default();
let room_mods = power_levels.users.iter().filter_map(|(user_id, level)| {
if level > &power_levels.users_default && !self.services.globals.user_is_local(user_id) {
Some(user_id.server_name())
} else {
None
}
});
let canonical_room_alias_server = once(
self.services
.state_accessor
.get_canonical_alias(room_id)
.await,
)
.filter_map(Result::ok)
.map(|alias| alias.server_name().to_owned())
.stream();
let mut servers = room_mods
.stream()
.map(ToOwned::to_owned)
.chain(canonical_room_alias_server)
.chain(
self.services
.server
.config
.trusted_servers
.iter()
.map(ToOwned::to_owned)
.stream(),
)
.ready_filter(|server_name| !self.services.globals.server_is_ours(server_name))
.filter_map(|server_name| async move {
self.services
.state_cache
.server_in_room(&server_name, room_id)
.await
.then_some(server_name)
})
.boxed();
while let Some(ref backfill_server) = servers.next().await {
info!("Asking {backfill_server} for event {}", event_id);
let value = self
.services
.sending
.send_federation_request(backfill_server, federation::event::get_event::v1::Request {
event_id: event_id.to_owned(),
include_unredacted_content: Some(false),
})
.await
.and_then(|response| {
serde_json::from_str::<CanonicalJsonObject>(response.pdu.get()).map_err(|e| {
err!(BadServerResponse(debug_warn!(
"Error parsing incoming event {e:?} from {backfill_server}"
)))
})
});
let pdu = match value {
| Ok(value) => {
self.services
.event_handler
.handle_incoming_pdu(backfill_server, room_id, event_id, value, false)
.boxed()
.await?;
debug!("Successfully backfilled {event_id} from {backfill_server}");
Some(self.get_pdu(event_id).await)
},
| Err(e) => {
warn!("{backfill_server} failed to provide backfill for room {room_id}: {e}");
None
},
};
if let Some(pdu) = pdu {
debug!("Fetched {event_id} from {backfill_server}");
return pdu;
}
}
Err!("No servers could be used to fetch {} in {}.", room_id, event_id)
}
#[implement(super::Service)]
#[tracing::instrument(skip(self, pdu), level = "debug")]
pub async fn backfill_pdu(&self, origin: &ServerName, pdu: Box<RawJsonValue>) -> Result<()> {
+1 -2
View File
@@ -3,8 +3,7 @@
use conduwuit::{
Err, PduCount, PduEvent, Result, at, err,
result::{LogErr, NotFound},
utils,
utils::stream::TryReadyExt,
utils::{self, stream::TryReadyExt},
};
use database::{Database, Deserialized, Json, KeyVal, Map};
use futures::{FutureExt, Stream, TryFutureExt, TryStreamExt, future::select_ok, pin_mut};
+35 -31
View File
@@ -1102,6 +1102,34 @@ pub async fn find_from_login_token(&self, token: &str) -> Result<OwnedUserId> {
Ok(user_id)
}
#[inline]
fn parse_profile_kv(
&self,
user_id: &UserId,
key: &str,
value: Vec<u8>,
) -> Result<serde_json::Value> {
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,
@@ -1113,7 +1141,7 @@ pub async fn profile_key(
.useridprofilekey_value
.qry(&key)
.await
.deserialized()
.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
@@ -1121,14 +1149,18 @@ pub fn all_profile_keys<'a>(
&'a self,
user_id: &'a UserId,
) -> impl Stream<Item = (String, serde_json::Value)> + '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))
.map(|((_, key), value): KeyVal<'_>| {
let value = self.parse_profile_kv(user_id, &key, value.to_vec())?;
Ok((key, value))
})
.ignore_err()
}
/// Sets a new profile key value, removes the key if value is None
@@ -1148,34 +1180,6 @@ pub fn set_profile_key(
}
}
/// Get the timezone of a user.
pub async fn timezone(&self, user_id: &UserId) -> Result<String> {
// 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<String>) {
// 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<Vec<(String, bool)>> {
Err!(FeatureDisabled("ldap"))