Compare commits

...
79 Commits
Author SHA1 Message Date
Erwan LeboucherandEllis Git 18139ea891 feat(sync): Add MSC4508 typing extension 2026-08-08 14:07:08 +00:00
Renovate BotandEllis Git 9080494464 chore(deps): update rust crate validator to 0.21.0 2026-08-08 12:25:34 +00:00
Renovate BotandEllis Git e13c6990d2 chore(deps): update node-patch-updates to v2.0.19 2026-08-08 12:24:11 +00:00
Renovate BotandEllis Git 26d5c03a0d chore(deps): update rust-non-major 2026-08-08 12:23:54 +00:00
stratself 9c8c38496d chore: Add changelog because it seems important 2026-08-08 05:44:06 +00:00
stratself 534ff1fe07 fix(docs): Use canonical Caddyfile location 2026-08-08 05:35:35 +00:00
Michel-Marie MAUDETandEllis Git f0493a7ba8 fix(oauth): Use unauthorized_client for unregistered grant types
The token endpoint already refused grant types the client had not
registered, but reported it as `invalid_grant`. RFC 6749 section 5.2
reserves `invalid_grant` for an authorization grant which is "invalid,
expired, revoked, does not match the redirection URI used in the
authorization request, or was issued to another client", and defines
`unauthorized_client` for a client which "is not authorized to use this
authorization grant type".

Report the condition with the error code the specification assigns to it,
matching the device authorization endpoint.
2026-08-07 08:52:20 +00:00
Michel-Marie MAUDETandEllis Git 8c1bbf3d1a fix(oauth): Reject device code requests from unauthorised clients
The device authorization endpoint issued a device code to any registered
client, without checking that the client had registered the
`urn:ietf:params:oauth:grant-type:device_code` grant type. Such a client
could therefore start a device authorization flow and have the server
show an approval prompt to a user, even though the subsequent token
request was always going to be refused.

RFC 8628 section 3.2 states that, in the event of an error such as an
invalidly configured client, the device authorization endpoint responds
in the same way as the token endpoint specified in RFC 6749 section 5.2,
which defines `unauthorized_client` as "the authenticated client is not
authorized to use this authorization grant type".

Apply the same grant type check the token endpoint already performs, and
add the `unauthorized_client` error code it requires.
2026-08-07 08:52:20 +00:00
renegadespork 02bee2e898 docs: Reference latest version of matrix spec 2026-08-06 15:34:46 -07:00
renegadesporkandEllis Git 1c23cf3a93 docs: Fix servernameevent_data_cache_capacity default description. 2026-08-06 14:18:32 +00:00
renegadesporkandEllis Git 30be07fe8f chore: Fix changelog news fragment formatting 2026-08-06 14:18:32 +00:00
renegadesporkandEllis Git a530eaa3a8 chore: Add changelog for config documentation. 2026-08-06 14:18:32 +00:00
renegadesporkandEllis Git 97d37a9bb3 docs: Corrected description of auth chain logic. 2026-08-06 14:18:32 +00:00
renegadesporkandEllis Git 4ff7a88df0 docs: Add missing documentation to config parameters. 2026-08-06 14:18:32 +00:00
Erwan LeboucherandEllis Git 1b6bc83b9f fix(sync): Preserve batched read receipts 2026-08-06 14:01:52 +00:00
PerformativeJadeandEllis Git cdf07a8811 chore: Add changelog line 2026-08-06 12:18:26 +00:00
PerformativeJadeandEllis Git 69b6431213 refactor: Swapped admin command names and aliases 2026-08-06 12:18:26 +00:00
timedoutandEllis Git 21ea9d3ea7 feat: Validate event references in prev and auth events
Also threw in some lovely unit tests, as a treat.
2026-08-05 21:11:05 +00:00
timedoutandEllis Git d79ffe6ef0 chore: Throwing newsfrag! 2026-08-05 21:11:05 +00:00
timedoutandEllis Git 8f0c7a99d7 fix: Don't accidentally continue with fallback room version
Fixes a potential issue where continuwuity correctly defaults to room v1
when encountering an unknown room version, but then fails to *stop*
processing when it encounters the room version it doesn't actually
understand.
2026-08-05 21:11:05 +00:00
Jacob TaylorandEllis Git f1755dd36f fix: Fix link preview unfurl source 2026-08-05 18:02:14 +00:00
stratselfandEllis Git 3b4665a171 fix: Small grammar 2026-08-05 18:00:11 +00:00
stratselfandEllis Git 9c9d15d601 fix(docs): Incorporate change requests from PR comments II 2026-08-05 18:00:11 +00:00
stratselfandEllis Git 5402c4f208 chore: Add changelog for docker command change 2026-08-05 18:00:11 +00:00
stratselfandEllis Git c7160edc13 docs: Replace docker-compose with docker compose 2026-08-05 18:00:11 +00:00
stratselfandEllis Git 2144317933 fix(docs): Incorporate change requests from PR comments
These are mostly grammar fixes. Furthermore:

* docs(docker,delegation): Use `important` admonitions in some places
* docs(maintenance): Elaborate on `immutable` directive in media header
2026-08-05 18:00:11 +00:00
stratselfandEllis Git e19f634ffa chore: Add changelogs for some important doc things 2026-08-05 18:00:11 +00:00
stratselfandEllis Git 7b020e8563 docs(maintenance): Remove S3 and MMR reference
* Also subsectioned btrfs stuff for database
2026-08-05 18:00:11 +00:00
stratselfandEllis Git d24245a68e fix: Minor changes from feedback of PR 2026-08-05 18:00:11 +00:00
stratselfandEllis Git 3c3e3b5316 fix(docs): Small fixes 2026-08-05 18:00:11 +00:00
stratselfandEllis Git d0ca302347 docs(maintenance): Update the database backup section
* Also update some links and media mgmt functionality
2026-08-05 18:00:11 +00:00
stratselfandEllis Git 37ddb8f37f docs(troubleshooting,performance): Small changes
Add links to admin command reference pages and reword some nits
2026-08-05 18:00:11 +00:00
stratselfandEllis Git a5fe5b531d docs(docker): Add update instructions 2026-08-05 18:00:11 +00:00
stratselfandEllis Git 144ff89447 docs(docker,delegation): Edit compose examples with Caddy
* Use 2-spaces indentation instead of 4
* Use `proxy` as network name instead of `caddy` to be consistent
* Mount Caddyfile for delegated example as well
2026-08-05 18:00:11 +00:00
stratselfandEllis Git 6915bca11c docs(dns): Add workaround notes for connecting to containers via IPs
* docs(appservices): Fix registration command and also add Docker DNS caveats
* docs(docker): A word
2026-08-05 18:00:11 +00:00
stratselfandEllis Git 519ebc96cc docs(docker): Use Caddyfile mount for Caddy example
The `configs.content` approach only works for Docker, so doing this
would be more inclusive for Podman users

Also removed Nexy's forge as it is currently limited access
2026-08-05 18:00:11 +00:00
stratselfandEllis Git 39ca63afe5 docs(delegation): Improve structure and wording
* Delineate 2 options for serving well-knowns
* Move SRV stuff to Appendix section
* Other small changes
2026-08-05 18:00:11 +00:00
stratselfandEllis Git 77c22ca82d docs: Add title and version caveat for config file reference 2026-08-05 18:00:11 +00:00
Renovate Botandtimedout f463cbab4f chore(deps): update pre-commit hook crate-ci/typos to v1.49.0 2026-08-05 13:04:52 +01:00
nexandEllis Git 80e98a2299 fix: Fixup v26.7.2 link in announcement 2026-08-04 23:35:20 +00:00
nexandEllis Git 20cdcee68c chore: Backdate release notes
I am a very responsible and organised maintainer and this was a deliberate plan to see how many users are keeping an active watch on the project's releases /j
2026-08-04 21:29:27 +00:00
alexiaandEllis Git 6647d405f4 chore(ci): fix formatting so prek/CI doesn't complain 2026-08-04 19:03:13 +00:00
alexiaandEllis Git 4e9ed55c6b chore: add changelog 2026-08-04 19:03:13 +00:00
alexiaandEllis Git 679b75eb66 chore: update generated config file 2026-08-04 19:03:13 +00:00
alexiaandEllis Git c3bd6d697a docs: fix default user-agent example being outdated
is it fine that this is wrapped? :p
2026-08-04 19:03:13 +00:00
stratselfandEllis Git 4fea19b0e3 fix: Separate changelogs for important configuration changes 2026-08-04 18:18:54 +00:00
stratselfandEllis Git ecb52af225 fix: Fix grammatical vulnerability reported by @thetayloredman 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 61be6aa7c6 chore: Fix changelog to better reflect changes 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 95dfa2c389 docs(livekit): Add more Element Call UI error cases 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 5ca21de3ca docs(livekit): Make room.auto_create false following recs
Recs from lk-jwt-service README page, also:

* Remove statement about Docker-only LiveKit
* Retitle some section
2026-08-04 18:18:54 +00:00
stratselfandEllis Git c11a30bb0d docs(turn,livekit): Add troubleshooting steps for incorrect IPs 2026-08-04 18:18:54 +00:00
stratselfandEllis Git a73cdbeff2 docs(turn,livekit): Small changes
* Small wording changes on calls.mdx
* Add env var sample for livekit foci
* Simplify TURNS-over-443 Caddyfile (livekit + turn)
* Improve TURNS-over-443 wordings (livekit + turn)
* Link to MRTC room on turn documentation
2026-08-04 18:18:54 +00:00
stratselfandEllis Git e14a86026a chore: Update changelogs 2026-08-04 18:18:54 +00:00
stratselfandEllis Git e62d74197e docs(turn): Use coturn for compose 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 7b4729e48c docs(livekit): Add caveat for livekit + caddy-in-docker 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 3b5aa44229 docs(livekit): Rewrite TURN stuff with 443-TLS and move to Appendix section 2026-08-04 18:18:54 +00:00
stratselfandEllis Git da547a7b89 docs: Remove guest access section due to feature removal 2026-08-04 18:18:54 +00:00
stratselfandEllis Git bb2d93e42e docs(turn,livekit): Add references to example conf files and ref URLs indirectly 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 0d7c82cf5f docs(turn): Add proxy protocol example for TURNS SNI routing 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 486b50f328 fix(docs): Put lengthy Caddyfile in details div section 2026-08-04 18:18:54 +00:00
stratselfandEllis Git c2641488bf chore: Update changelog in detail 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 04ae2cb7fc docs(turn): Add instructions on TURNS-over-443 and L4 multiplexing 2026-08-04 18:18:54 +00:00
stratselfandEllis Git a7b5fa4edc chore: Add changelog and fix trailing whitespace 2026-08-04 18:18:54 +00:00
stratselfandEllis Git 52293ad55a docs: Refactor TURN documentation with many changes (see below)
* Make all links indirect for better maintenance
* Add Domain section with `coturn.example.com` and use it in subsequent
  examples
* Add standard ports for TURN and TURNS
* Remove TURNS-over-UDP (unknown whether it is supported)
* Add section on opening ports with ufw commands
* IMPORTANT: Move password auth/guest access instructions into the
  Appendix as they are very insecure
* Small improvements in Testing section
* Rewrite Troubleshooting section to be more concise and cut down on
  errors
* Add note to Eturnal in Appendix section
2026-08-04 18:18:54 +00:00
theS1LV3RandEllis Git 3bdfaa55dc chore: Update resolvematrix 2026-08-04 16:03:03 +00:00
theS1LV3RandEllis Git e6ae6b83e6 chore: Fix errors for resolvematrix update 2026-08-04 16:03:03 +00:00
theS1LV3RandEllis Git 668a9c9ada chore: Update resolvematrix
Temporarily using revision until a full release can be made.

This update includes more trace logging to help with debugging
possible resolution errors.
2026-08-04 16:03:03 +00:00
Henry-HilesandEllis Git 9f20633457 fix: Add missing double quote in about template 2026-08-04 15:16:47 +00:00
Renovate Bot f0ff4dbcc9 chore(deps): update github-actions-digest 2026-08-04 05:02:51 +00:00
Erwan LeboucherandEllis Git aa884fad6b feat(sync): Add MSC4480 sticky events 2026-08-03 14:35:53 +00:00
timedout 98bc859023 fix: Actually default to core count for sender_workers
Closes #2092
2026-08-03 14:18:32 +01:00
Renovate Bot 618b1c1a11 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v43.288.0 2026-08-03 05:01:33 +00:00
Erwan LeboucherandEllis Git 43197f695c feat(sync): Add MSC4354 sticky events 2026-08-02 17:28:00 +00:00
Erwan Leboucher 7789399ba5 fix(sync): Apply MSC4186 room filters 2026-08-02 18:33:57 +02:00
Erwan Leboucher 27a559a751 fix(sync): Wake sync loops on typing updates 2026-08-02 18:05:27 +02:00
Erwan Leboucher 74591d24e5 fix(sync): Avoid missed long-poll wake-ups 2026-08-02 15:22:18 +00:00
Jacob Taylor 4146e0f1da fix: Resolve membership service by correct name for auto-join 2026-08-01 22:32:25 -07:00
Erwan Leboucher 8481efb482 chore(deps): Update Ruma 2026-08-01 23:50:48 +02:00
timedout 3f69334f9a chore: Update changelog 2026-07-30 19:10:53 +01:00
96 changed files with 3205 additions and 1129 deletions
@@ -44,7 +44,7 @@ runs:
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
@@ -79,7 +79,7 @@ runs:
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
with:
registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }}
+1 -1
View File
@@ -71,7 +71,7 @@ runs:
- name: Install timelord-cli and git-warp-time
if: steps.check-binaries.outputs.need-install == 'true'
uses: https://github.com/taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2
uses: https://github.com/taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2
with:
tool: git-warp-time,timelord-cli@3.0.1
+1 -1
View File
@@ -55,7 +55,7 @@ jobs:
# repositories: continuwuity
- name: Install regsync
uses: https://github.com/regclient/actions/regsync-installer@c48159175b58fb90cccd0f98410927a81e04a166 # main
uses: https://github.com/regclient/actions/regsync-installer@5c882eb04fcca27ebb4f5904e0da01f0780063ea # main
- name: Check what images need mirroring
run: |
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
name: Renovate
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:43.281.1@sha256:34c2dd58f58e8976be2024a24fec23bbee805f0bf887837d9aaee7daeb09ccfc
image: ghcr.io/renovatebot/renovate:43.288.0@sha256:bcde7a061ab42d1ae6d4500769d80727e5694bb889dbcbf1005e9f4e741ab76d
options: --tmpfs /tmp:exec
steps:
- name: Checkout
+1 -1
View File
@@ -24,7 +24,7 @@ repos:
- id: check-added-large-files
- repo: https://github.com/crate-ci/typos
rev: v1.48.0
rev: v1.49.0
hooks:
- id: typos
- id: typos
+102 -115
View File
@@ -1,3 +1,11 @@
# Continuwuity v26.7.2 (2026-07-30)
## Bugfixes
- Sliding sync no longer continues streaming data to a user after they are removed from a room. Contributed by @eleboucher. (SEC10)
- Valid redaction events no longer arbitrarily soft-fail and are applied correctly as expected.
- Fixed a regression that caused the server to process events for rooms it no longer has any local users in, which caused users to be reset back *into* rooms.
# Continuwuity 26.7.0 (2026-07-27)
## Features
@@ -13,7 +21,8 @@ ## Features
## Bugfixes
- Fix joining restricted rooms over federation failing with signature verification error. (fix-federation-signature)
- Fixed the client space hierarchy endpoint returning a 500 "Space hierarchy is unreasonably large" error for cyclic space graphs (e.g. a space containing itself). Rooms are now deduplicated during traversal as required by the spec, and the traversal depth is bounded even when the client does not specify `max_depth`. (space-hierarchy-cycle)
- Fixed the client space hierarchy endpoint returning a 500 "Space hierarchy is unreasonably large" error for cyclic space graphs (e.g. a space containing itself). Rooms are now deduplicated during
traversal as required by the spec, and the traversal depth is bounded even when the client does not specify `max_depth`. (space-hierarchy-cycle)
- Fixed simplified sliding sync holding account data for up to 30 seconds, which made encryption setup and cross-signing resets appear to hang. (sss-account-data-longpoll)
- Fixed local invites and invite acceptances not being reflected in sync promptly. Contributed by @eleboucher (wake-local-member-sync)
- Fixed the deeplink redirect for deleting devices. Contributed by @koen (#1965)
@@ -21,7 +30,8 @@ ## Bugfixes
- Exempt m.room.create from auth_events check. Contributed by @eleboucher (#1987)
- Fixed `create` being returned as a supported prompt value regardless of if registration is enabled or not. Contributed by @ginger (#1994)
- Fixed high CPU usage when multiple clients from the same account were connected at once. Each sync woke the account's other sync loops, causing them to wake each other in a loop. (#2006)
- Fixed MSC4190 appservice device creation registering a random device ID instead of the requested one (and dropping the requested display name), which prevented encrypted mautrix bridges from starting on OIDC-enabled servers and leaked an orphan device on the bridge bot per startup attempt. (#2015)
- Fixed MSC4190 appservice device creation registering a random device ID instead of the requested one (and dropping the requested display name), which prevented encrypted mautrix bridges from
starting on OIDC-enabled servers and leaked an orphan device on the bridge bot per startup attempt. (#2015)
- Deactivated users and appservice puppets are no longer counted by `/_continuwuity/local_user_count`. Contributed by @ginger. (#2040)
- Re-introduced admin room registration alerts that were accidentally removed in the OAuth2 update. (#2057)
- Appservices are now properly able to create devices for E2EE.
@@ -30,22 +40,22 @@ ## Bugfixes
- Fixed freshly left room failing to sync.
- Fixed newly created rooms failing to sync properly in clients using legacy sync.
- Fixed newly joined rooms failing to sync their full state (including the room name) to clients using legacy sync.
- Fixed requests returning `500 Internal Server Error` when the header selected by `request_ip_source` is absent, duplicated, or malformed (for example Envoy omitting `X-Envoy-External-Address` on internal requests). The client IP now falls back to the connection peer address instead of failing the request. Contributed by @eleboucher
- Fixed requests returning `500 Internal Server Error` when the header selected by `request_ip_source` is absent, duplicated, or malformed (for example Envoy omitting `X-Envoy-External-Address` on
internal requests). The client IP now falls back to the connection peer address instead of failing the request. Contributed by @eleboucher
- Resolve alias service by correct name for auto-join. Contributed by @eleboucher
## Improved Documentation
- Updated an out-of-date statement about Oracle Linux release cadences. (#1999)
# Continuwuity 26.6.2 (2026-07-12)
## Bugfixes
- Fixed the server returning 500 errors if `admin_console_automatic` is enabled and no TTY is available. Contributed by @s1lv3r. (#1975)
- Fixed `global.oauth.compatibility_mode` being required, despite being ignored, when the `[global.oauth.oidc]` config section is provided.
- Fixed an issue with a migration that could cause user accounts imported from an identity provider to be marked as deactivated when the server started. If you have accounts affected by this issue, use `!admin users reset-password --convert-to-local-account` to reactivate them.
- Fixed an issue with a migration that could cause user accounts imported from an identity provider to be marked as deactivated when the server started. If you have accounts affected by this issue,
use `!admin users reset-password --convert-to-local-account` to reactivate them.
# Continuwuity 26.6.1 (2026-07-12)
@@ -61,7 +71,6 @@ ## Bugfixes
- Stopped appservice users from being erroneously marked as deactivated during a 26.6 database migration.
- Whitespace will now automatically be trimmed from the start and end of the `global.oauth.oidc.client_secret_file`.
# Continuwuity 26.6.0 (2026-07-10)
## Features
@@ -71,19 +80,15 @@ ## Features
- Added config option for default room ACLs. Contributed by @eve. (#1691)
- Added support for fallback encryption keys. (#1710)
- Add `!admin users reject-all-invites` to clean invite spam (#1741)
- Implemented event rejection, which should resolve and prevent future netsplits of the kinds observed
within some Continuwuity rooms.
Also resolved several bugs related to both soft-failing events, and event backfilling, which should
improve state resolution stability.
The `!admin debug get-pdu` command was updated to disambiguate event acceptance status, and
`!admin debug show-auth-chain` was added to visually display event auth chains, which may assist
developers in debugging strangely complex events.
- Implemented event rejection, which should resolve and prevent future netsplits of the kinds observed within some Continuwuity rooms. Also resolved several bugs related to both soft-failing events,
and event backfilling, which should improve state resolution stability. The `!admin debug get-pdu` command was updated to disambiguate event acceptance status, and
`!admin debug show-auth-chain` was added to visually display event auth chains, which may assist developers in debugging strangely complex events.
Contributed by @nex. (#1747)
- Added full support for [MSC4168: Update `m.space.*` state on room upgrade](https://github.com/matrix-org/matrix-spec-proposals/pull/4168). Contributed by @nex. (#1807)
- Improved the performance and reliability of fetching missing events, improving network partition recovery. Contributed
by @nex. (#1818)
- Added static builds using Nix, allowing for Continuwuity on musl. During this, we also introduced a `max-perf-haswell` package, separating it from `max-perf`, so you may want to swap to this if you are on NixOS. Contributed by @Henry-Hiles (QuadRadical). (#1853)
- Improved the performance and reliability of fetching missing events, improving network partition recovery. Contributed by @nex. (#1818)
- Added static builds using Nix, allowing for Continuwuity on musl. During this, we also introduced a `max-perf-haswell` package, separating it from `max-perf`, so you may want to swap to this if you
are on NixOS. Contributed by @Henry-Hiles (QuadRadical). (#1853)
- Added support for MSC4380 invite blocking, which has become part of the Matrix specification in v1.18. Contributed by @nex. (#1875)
- Added `!admin debug get-state-at` command (#1877)
- Added a configuration option to allow choosing a client IP source that is not the TCP connecting IP. Contributed by @nex. (#1931)
@@ -95,10 +100,12 @@ ## Features
## Bugfixes
- Adjusted legacy sync logic to allow the `roomsynctoken_shortstatehash` database column to be dropped, massively reducing database sizes, especially for old deployments. Contributed by @ginger. (#917)
- Adjusted legacy sync logic to allow the `roomsynctoken_shortstatehash` database column to be dropped, massively reducing database sizes, especially for old deployments. Contributed by @ginger.
(#917)
- Fixed a bug that caused the server to drop events during processing if several events for the same room were sent in a singular transaction. Contributed by @nex. (#1711)
- fix `!admin query account-data account-data-get` not returning the content (#1742)
- Fixed an issue where Continuwuity would only advertise support for the unstable endpoint for Mutual Rooms (MSC2666), despite only supporting the stable endpoint. Contributed by @Henry-Hiles (QuadRadical) (#1752)
- Fixed an issue where Continuwuity would only advertise support for the unstable endpoint for Mutual Rooms (MSC2666), despite only supporting the stable endpoint. Contributed by @Henry-Hiles
(QuadRadical) (#1752)
- Fixed admin commands being ignored when they had leading whitespace before admin commands. Contributed by @kitvonsnookerz. (#1804)
- Fixed several bugs in the `POST /_matrix/client/v3/rooms/{roomId}/upgrade` endpoint. Contributed by @nex. (#1807)
- Devices which set their presence as "offline" will no longer be considered for presence updates. Contributed by @timedout.
@@ -125,8 +132,8 @@ ## Misc
- #1505, #1829, #1927, #1933, #1934
- Switched from Continuwuity's fork of Ruma back to upstream Ruma. Contributed by @ginger.
- The version of Debian that the Docker-based build process uses has been upgraded from Bookworm to Trixie, meaning that standalone binaries now have a minimum glibc of 2.41, and can no longer be used on distro versions from before 2025-01-30
- The version of Debian that the Docker-based build process uses has been upgraded from Bookworm to Trixie, meaning that standalone binaries now have a minimum glibc of 2.41, and can no longer be used
on distro versions from before 2025-01-30
# Continuwuity 0.5.8 (2026-04-24)
@@ -144,7 +151,6 @@ ## Improved Documentation
- Updated config docs to state we support room version 12, and set it as default. Contributed by @ezera. (#1622)
- Improve instructions for generic deployments, removing unnecessary parts and documenting the new initial registration token flow. Contributed by @stratself (#1677)
# Continuwuity v0.5.7 (2026-04-17)
## Features
@@ -186,7 +192,6 @@ ## Misc
- Fixed compiler warning in cf_opts.rs when building in release. Contributed by @ezera. (#1620)
# Continuwuity 0.5.6 (2026-03-03)
## Security
@@ -196,22 +201,33 @@ ## Security
## Features
- Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts. Contributed by @nex. ([#1399](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1399))
- Improved the concurrency handling of federation transactions, vastly improving performance and reliability by more accurately handling inbound transactions and reducing the amount of repeated wasted work. Contributed by @nex and @Jade. ([#1428](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1428))
- Added [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202) Device masquerading (not all of MSC3202). This should fix issues with enabling [MSC4190](https://github.com/matrix-org/matrix-spec-proposals/pull/4190) for some Mautrix bridges. Contributed by @Jade ([#1435](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1435))
- Added [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) Dehydrated Devices - you can now decrypt messages sent while all devices were logged out. ([#1436](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1436))
- Implement [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) MatrixRTC transport discovery endpoint. Move RTC foci configuration from `[global.well_known]` to a new `[global.matrix_rtc]` section with a `foci` field. Contributed by @0xnim ([#1442](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1442))
- Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts.
Contributed by @nex. ([#1399](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1399))
- Improved the concurrency handling of federation transactions, vastly improving performance and reliability by more accurately handling inbound transactions and reducing the amount of repeated wasted
work. Contributed by @nex and @Jade. ([#1428](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1428))
- Added [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202) Device masquerading (not all of MSC3202). This should fix issues with
enabling [MSC4190](https://github.com/matrix-org/matrix-spec-proposals/pull/4190) for some Mautrix bridges. Contributed by @Jade
([#1435](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1435))
- Added [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) Dehydrated Devices - you can now decrypt messages sent while all devices were logged out.
([#1436](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1436))
- Implement [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) MatrixRTC transport discovery endpoint. Move RTC foci configuration from `[global.well_known]` to a new
`[global.matrix_rtc]` section with a `foci` field. Contributed by @0xnim ([#1442](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1442))
- Updated `list-backups` admin command to output one backup per line. ([#1394](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1394))
- Improved URL preview fetching with a more compatible user agent for sites like YouTube Music. Added `!admin media delete-url-preview <url>` command to clear cached URL previews that were stuck and broken. ([#1434](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1434))
- Improved URL preview fetching with a more compatible user agent for sites like YouTube Music. Added `!admin media delete-url-preview <url>` command to clear cached URL previews that were stuck and
broken. ([#1434](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1434))
## Bugfixes
- Removed non-compliant nor functional room alias lookups over federation. Contributed by @nex ([#1393](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1393))
- Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160. ([#1418](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1418))
- Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers` config option is set correctly. Contributed by @katie. ([#1421](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1421))
- Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160.
([#1418](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1418))
- Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers` config option is set correctly. Contributed by @katie.
([#1421](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1421))
- Removed the `allow_public_room_directory_without_auth` config option. Contributed by @0xnim. ([#1441](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1441))
- Fixed sliding sync v5 list ranges always starting from 0, causing extra rooms to be unnecessarily processed and returned. Contributed by @0xnim ([#1445](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1445))
- Fixed a bug that (repairably) caused a room split between continuwuity and non-continuwuity servers when the room had both `m.room.policy` and `org.matrix.msc4284.policy` in its room state. Contributed by @nex ([#1481](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1481))
- Fixed sliding sync v5 list ranges always starting from 0, causing extra rooms to be unnecessarily processed and returned. Contributed by @0xnim
([#1445](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1445))
- Fixed a bug that (repairably) caused a room split between continuwuity and non-continuwuity servers when the room had both `m.room.policy` and `org.matrix.msc4284.policy` in its room state.
Contributed by @nex ([#1481](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1481))
- Fixed `!admin media delete --mxc <url>` responding with an error message when the media was deleted successfully. Contributed by @lynxize
- Fixed spurious 404 media errors in the logs. Contributed by @benbot.
- Fixed spurious warn about needed backfill via federation for non-federated rooms. Contributed by @kraem.
@@ -221,19 +237,15 @@ # Continuwuity v0.5.5 (2026-02-15)
## Features
- Added unstable support for [MSC4406:
`M_SENDER_IGNORED`](https://github.com/matrix-org/matrix-spec-proposals/pull/4406).
Contributed by @nex ([#1308](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1308))
- Introduce a resolver command to allow flushing a server from the cache or to flush the complete cache. Contributed by
@Omar007 ([#1349](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1349))
- Improved the handling of restricted join rules and improved the performance of local-first joins. Contributed by
@nex. ([#1368](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1368))
- You can now set a custom User Agent for URL previews; the default one has been modified to be less likely to be
rejected. Contributed by @trashpanda ([#1372](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1372))
`M_SENDER_IGNORED`](https://github.com/matrix-org/matrix-spec-proposals/pull/4406). Contributed by @nex ([#1308](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1308))
- Introduce a resolver command to allow flushing a server from the cache or to flush the complete cache. Contributed by @Omar007
([#1349](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1349))
- Improved the handling of restricted join rules and improved the performance of local-first joins. Contributed by @nex. ([#1368](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1368))
- You can now set a custom User Agent for URL previews; the default one has been modified to be less likely to be rejected. Contributed by @trashpanda
([#1372](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1372))
- Improved the first-time setup experience for new homeserver administrators:
- Account registration is disabled on the first run, except for with a new special registration token that is logged
to the console.
- Other helpful information is logged to the console as well, including a giant warning if open registration is
enabled.
- Account registration is disabled on the first run, except for with a new special registration token that is logged to the console.
- Other helpful information is logged to the console as well, including a giant warning if open registration is enabled.
- The default index page now says to check the console for setup instructions if no accounts have been created.
- Once the first admin account is created, an improved welcome message is sent to the admin room.
@@ -241,10 +253,10 @@ ## Features
## Bugfixes
- Fixed invites sent to other users in the same homeserver not being properly sent down sync. Users with missing or
broken invites should clear their client caches after updating to make them appear. ([#1249](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1249))
- LDAP-enabled servers will no longer have all admins demoted when LDAP-controlled admins are not configured.
Contributed by @Jade ([#1307](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1307))
- Fixed invites sent to other users in the same homeserver not being properly sent down sync. Users with missing or broken invites should clear their client caches after updating to make them appear.
([#1249](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1249))
- LDAP-enabled servers will no longer have all admins demoted when LDAP-controlled admins are not configured. Contributed by @Jade
([#1307](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1307))
- Fixed sliding sync not resolving wildcard state key requests, enabling Video/Audio calls in Element X. ([#1370](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1370))
## Misc
@@ -255,100 +267,78 @@ # Continuwuity v0.5.4 (2026-02-08)
## Features
- The announcement checker will now announce errors it encounters in the first run to the admin room, plus a few other
misc improvements. Contributed by @Jade ([#1288](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1288))
- Drastically improved the performance and reliability of account deactivations. Contributed by
@nex ([#1314](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1314))
- Refuse to process requests for and events in rooms that we no longer have any local users in (reduces state resets
and improves performance). Contributed by
@nex ([#1316](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1316))
- Added server-specific admin API routes to ban and unban rooms, for use with moderation bots. Contributed by @nex
([#1301](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1301))
- The announcement checker will now announce errors it encounters in the first run to the admin room, plus a few other misc improvements. Contributed by @Jade
([#1288](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1288))
- Drastically improved the performance and reliability of account deactivations. Contributed by @nex ([#1314](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1314))
- Refuse to process requests for and events in rooms that we no longer have any local users in (reduces state resets and improves performance). Contributed by @nex
([#1316](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1316))
- Added server-specific admin API routes to ban and unban rooms, for use with moderation bots. Contributed by @nex ([#1301](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1301))
## Bugfixes
- Fix the generated configuration containing uncommented optional sections. Contributed by
@Jade ([#1290](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1290))
- Fixed specification non-compliance when handling remote media errors. Contributed by
@nex ([#1298](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1298))
- UIAA requests which check for out-of-band success (sent by matrix-js-sdk) will no longer create unhelpful errors in
the logs. Contributed by @ginger ([#1305](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1305))
- Use exists instead of contains to save writing to a buffer in `src/service/users/mod.rs`: `is_login_disabled`.
Contributed
by @aprilgrimoire. ([#1340](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1340))
- Fixed backtraces being swallowed during panics. Contributed by
@jade ([#1337](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1337))
- Fixed a potential vulnerability that could allow an evil remote server to return malicious events during the room join
and knock process. Contributed by @nex, reported by violet & [mat](https://matdoes.dev).
- Fixed a race condition that could result in outlier PDUs being incorrectly marked as visible to a remote server.
Contributed by @nex, reported by violet & [mat](https://matdoes.dev).
- Fix the generated configuration containing uncommented optional sections. Contributed by @Jade ([#1290](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1290))
- Fixed specification non-compliance when handling remote media errors. Contributed by @nex ([#1298](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1298))
- UIAA requests which check for out-of-band success (sent by matrix-js-sdk) will no longer create unhelpful errors in the logs. Contributed by @ginger
([#1305](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1305))
- Use exists instead of contains to save writing to a buffer in `src/service/users/mod.rs`: `is_login_disabled`. Contributed by @aprilgrimoire.
([#1340](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1340))
- Fixed backtraces being swallowed during panics. Contributed by @jade ([#1337](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1337))
- Fixed a potential vulnerability that could allow an evil remote server to return malicious events during the room join and knock process. Contributed by @nex, reported by
violet & [mat](https://matdoes.dev).
- Fixed a race condition that could result in outlier PDUs being incorrectly marked as visible to a remote server. Contributed by @nex, reported by violet & [mat](https://matdoes.dev).
- ACLs are no longer case-sensitive. Contributed by @nex, reported by [vel](matrix:u/vel:nhjkl.com?action=chat).
## Docs
- Fixed Fedora install instructions. Contributed by
@julian45 ([#1342](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1342))
- Fixed Fedora install instructions. Contributed by @julian45 ([#1342](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1342))
# Continuwuity 0.5.3 (2026-01-12)
## Features
- Improve the display of nested configuration with the `!admin server show-config` command. Contributed by
@Jade ([#1279](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1279))
- Improve the display of nested configuration with the `!admin server show-config` command. Contributed by @Jade ([#1279](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1279))
## Bugfixes
- Fixed `M_BAD_JSON` error when sending invites to other servers or when providing joins. Contributed by
@nex ([#1286](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1286))
- Fixed `M_BAD_JSON` error when sending invites to other servers or when providing joins. Contributed by @nex ([#1286](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1286))
## Docs
- Improve admin command documentation generation. Contributed by
@ginger ([#1280](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1280))
- Improve admin command documentation generation. Contributed by @ginger ([#1280](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1280))
## Misc
- Improve timeout-related code for federation and URL previews. Contributed by
@Jade ([#1278](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1278))
- Improve timeout-related code for federation and URL previews. Contributed by @Jade ([#1278](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1278))
# Continuwuity 0.5.2 (2026-01-09)
## Features
- Added support for issuing additional registration tokens, stored in the database, which supplement the existing
registration token hardcoded in the config file. These tokens may optionally expire after a certain number of uses or
after a certain amount of time has passed. Additionally, the `registration_token_file` configuration option is
superseded by this feature and **has been removed**. Use the new `!admin token` command family to manage registration
tokens. Contributed by @ginger (#783).
- Implemented a configuration defined admin list independent of the admin room. Contributed by
@Terryiscool160. ([#1253](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1253))
- Added support for invite and join anti-spam via Draupnir and Meowlnir, similar to that of synapse-http-antispam.
Contributed by @nex. ([#1263](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1263))
- Implemented account locking functionality, to complement user suspension. Contributed by
@nex. ([#1266](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1266))
- Added admin command to forcefully log out all of a user's existing sessions. Contributed by
@nex. ([#1271](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1271))
- Added support for issuing additional registration tokens, stored in the database, which supplement the existing registration token hardcoded in the config file. These tokens may optionally expire
after a certain number of uses or after a certain amount of time has passed. Additionally, the `registration_token_file` configuration option is superseded by this feature and **has been removed**.
Use the new `!admin token` command family to manage registration tokens. Contributed by @ginger (#783).
- Implemented a configuration defined admin list independent of the admin room. Contributed by @Terryiscool160. ([#1253](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1253))
- Added support for invite and join anti-spam via Draupnir and Meowlnir, similar to that of synapse-http-antispam. Contributed by @nex.
([#1263](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1263))
- Implemented account locking functionality, to complement user suspension. Contributed by @nex. ([#1266](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1266))
- Added admin command to forcefully log out all of a user's existing sessions. Contributed by @nex. ([#1271](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1271))
- Implemented toggling the ability for an account to log in without mutating any of its data. Contributed by @nex. (
[#1272](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1272))
- Add support for custom room create event timestamps, to allow generating custom prefixes in hashed room IDs.
Contributed by @nex. ([#1277](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1277))
- Certain potentially dangerous admin commands are now restricted to only be usable in the admin room and server
console. Contributed by @ginger.
- Add support for custom room create event timestamps, to allow generating custom prefixes in hashed room IDs. Contributed by @nex.
([#1277](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1277))
- Certain potentially dangerous admin commands are now restricted to only be usable in the admin room and server console. Contributed by @ginger.
## Bugfixes
- Fixed unreliable room summary fetching and improved error messages. Contributed by
@nex. ([#1257](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1257))
- Client requested timeout parameter is now applied to e2ee key lookups and claims. Related federation requests are now
also concurrent. Contributed by @nex. ([#1261](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1261))
- Fixed the whoami endpoint returning HTTP 404 instead of HTTP 403, which confused some appservices. Contributed by
@nex. ([#1276](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1276))
- Fixed unreliable room summary fetching and improved error messages. Contributed by @nex. ([#1257](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1257))
- Client requested timeout parameter is now applied to e2ee key lookups and claims. Related federation requests are now also concurrent. Contributed by @nex.
([#1261](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1261))
- Fixed the whoami endpoint returning HTTP 404 instead of HTTP 403, which confused some appservices. Contributed by @nex. ([#1276](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1276))
## Misc
- The `console` feature is now enabled by default, allowing the server console to be used for running admin commands
directly. To automatically open the console on startup, set the `admin_console_automatic` config option to `true`.
Contributed by @ginger.
- The `console` feature is now enabled by default, allowing the server console to be used for running admin commands directly. To automatically open the console on startup, set the
`admin_console_automatic` config option to `true`. Contributed by @ginger.
- We now (finally) document our container image mirrors. Contributed by @Jade
# Continuwuity 0.5.0 (2025-12-30)
@@ -357,12 +347,9 @@ # Continuwuity 0.5.0 (2025-12-30)
## Features
- Enabled the OTLP exporter in default builds, and allow configuring the exporter protocol. (
@Jade). ([#1251](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1251))
- Enabled the OTLP exporter in default builds, and allow configuring the exporter protocol. (@Jade). ([#1251](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1251))
## Bug Fixes
- Don't allow admin room upgrades, as this can break the admin room (
@timedout) ([#1245](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1245))
- Fix invalid creators in power levels during upgrade to v12 (
@timedout) ([#1245](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1245))
- Don't allow admin room upgrades, as this can break the admin room (@timedout) ([#1245](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1245))
- Fix invalid creators in power levels during upgrade to v12 (@timedout) ([#1245](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1245))
Generated
+42 -43
View File
@@ -259,9 +259,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-lc-rs"
version = "1.17.3"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1"
checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e"
dependencies = [
"aws-lc-sys",
"zeroize",
@@ -269,9 +269,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.43.0"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c"
checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483"
dependencies = [
"cc",
"cmake",
@@ -571,9 +571,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "bytesize"
version = "2.4.2"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7c8918969267b2932ffd5655509bbbea0833823058c378876953217f5fc50e"
checksum = "7354288c522e7e980fafd2075d63d1285794c3a6a16cdd492f189ea406e5f18b"
[[package]]
name = "bzip2-sys"
@@ -626,7 +626,7 @@ checksum = "aa61aec073ec94791433ddf3df2323ff9d1711557c2a0eefb0f99cb4f8dca520"
dependencies = [
"semver",
"serde",
"toml 1.1.3+spec-1.1.0",
"toml 1.1.4+spec-1.1.0",
]
[[package]]
@@ -709,9 +709,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.6.4"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
dependencies = [
"clap_builder",
"clap_derive",
@@ -719,9 +719,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.6.2"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
dependencies = [
"anstyle",
"clap_lex",
@@ -985,7 +985,7 @@ dependencies = [
"tikv-jemallocator",
"tokio",
"tokio-metrics",
"toml 1.1.3+spec-1.1.0",
"toml 1.1.4+spec-1.1.0",
"tracing",
"tracing-core",
"tracing-subscriber",
@@ -1452,9 +1452,9 @@ dependencies = [
[[package]]
name = "ctor"
version = "1.0.11"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9bb72bb94fdc1bd619f4c18cc91ecf6302aeb333d31b3c6ec0bb841cd920209"
checksum = "2d83cb7e7a873830708d6b02a78cd36a592c6fa14bf267b68725103b85c0d77f"
dependencies = [
"link-section",
"linktime-proc-macro",
@@ -2402,9 +2402,9 @@ dependencies = [
[[package]]
name = "http"
version = "1.4.2"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
dependencies = [
"bytes",
"itoa",
@@ -3016,9 +3016,9 @@ dependencies = [
[[package]]
name = "link-section"
version = "0.19.1"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc98458dfe90986c5e2f6ddcf68360c7e5c4252600153e06aa4ee8176c0f8d1"
checksum = "5ee1a0d6e252afe82e7bc2db42fba60e02ddf3b1accaf8cb21d96e34ba61f3d4"
[[package]]
name = "linked-hash-map"
@@ -3028,9 +3028,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linktime-proc-macro"
version = "0.2.0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c7b0a3383c2a1002d11349c92c85a666a5fb679e96c79d782cf0dbe557fd6ee"
checksum = "348d0075b1fc163b26d72a7f75fc5141daf2fd1bdf128d873cbaf6785d495bdf"
[[package]]
name = "linux-raw-sys"
@@ -3447,7 +3447,7 @@ version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
dependencies = [
"base64 0.21.7",
"base64 0.22.1",
"chrono",
"getrandom 0.2.17",
"http",
@@ -4505,9 +4505,9 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
[[package]]
name = "resolvematrix"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68b9735d8c1096d8152d4dfb5a705c0135dd14d181542fd87301f8d3cb4ed03"
checksum = "f072176a8b0cbe7c3df2281991ef00a09889d8ec789f2878e7fee43a3b978a6f"
dependencies = [
"futures",
"hickory-resolver",
@@ -4567,7 +4567,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.16.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"assign",
"js_int",
@@ -4586,7 +4586,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.16.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"js_int",
"ruma-common",
@@ -4598,7 +4598,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.24.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"as_variant",
"assign",
@@ -4620,7 +4620,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.19.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"as_variant",
"base64 0.22.1",
@@ -4653,7 +4653,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.34.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"as_variant",
"indexmap 2.14.0",
@@ -4674,7 +4674,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.15.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"bytes",
"headers",
@@ -4697,7 +4697,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.12.1"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"js_int",
"thiserror 2.0.19",
@@ -4706,7 +4706,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.19.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"as_variant",
"cfg-if",
@@ -4716,13 +4716,13 @@ dependencies = [
"ruma-identifiers-validation",
"serde",
"syn 2.0.119",
"toml 1.1.3+spec-1.1.0",
"toml 1.1.4+spec-1.1.0",
]
[[package]]
name = "ruma-push-gateway-api"
version = "0.15.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"js_int",
"ruma-common",
@@ -4734,7 +4734,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.21.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@@ -4751,7 +4751,7 @@ dependencies = [
[[package]]
name = "ruma-state-res"
version = "0.17.0"
source = "git+https://github.com/ruma/ruma.git?rev=e7284c31da289f0a3b885191f5b2b6b307fa059f#e7284c31da289f0a3b885191f5b2b6b307fa059f"
source = "git+https://github.com/ruma/ruma.git?rev=c1100793d2fd464be34052773360f3f3773d0fc4#c1100793d2fd464be34052773360f3f3773d0fc4"
dependencies = [
"js_int",
"ruma-common",
@@ -5202,7 +5202,7 @@ checksum = "7bd22781911de0ca6debda95f073c8f18bec65d1a94f1fa9573f3102e514cea4"
dependencies = [
"ahash",
"annotate-snippets",
"base64 0.21.7",
"base64 0.22.1",
"encoding_rs_io",
"getrandom 0.3.4",
"granit-parser",
@@ -5943,9 +5943,9 @@ dependencies = [
[[package]]
name = "toml"
version = "1.1.3+spec-1.1.0"
version = "1.1.4+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c"
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
dependencies = [
"indexmap 2.14.0",
"serde_core",
@@ -6002,9 +6002,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.1.2+spec-1.1.0"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow 1.0.3",
]
@@ -6433,12 +6433,11 @@ dependencies = [
[[package]]
name = "validator"
version = "0.20.0"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa"
checksum = "c3d68c6633c483df6780cc5277a417c7c2d1bceee2649d06c8ab6b0fd2dd3c81"
dependencies = [
"idna",
"once_cell",
"regex",
"serde",
"serde_derive",
+4 -2
View File
@@ -343,7 +343,7 @@ version = "1.1.1"
# Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma]
git = "https://github.com/ruma/ruma.git"
rev = "e7284c31da289f0a3b885191f5b2b6b307fa059f"
rev = "c1100793d2fd464be34052773360f3f3773d0fc4"
features = [
"appservice-api-c",
"client-api",
@@ -377,6 +377,8 @@ features = [
"unstable-msc4293",
"unstable-msc4406",
"unstable-msc4439",
"unstable-msc4354",
"unstable-msc4480",
"unstable-msc4466",
"unstable-extensible-events",
]
@@ -562,7 +564,7 @@ features = ["std"]
version = "0.3.0"
[workspace.dependencies.resolvematrix]
version = "1.2.0"
version = "1.3.0"
[workspace.dependencies.serde_urlencoded]
version = "0.7.1"
+1
View File
@@ -0,0 +1 @@
Update backup instructions: the right paths for `.sst` files are now in `$DATABASE_BACKUP_PATH/private/<number>` path instead of `$DATABASE_BACKUP_PATH/<number>`. Contributed by @stratself
+1
View File
@@ -0,0 +1 @@
Added MSC4480 sticky events to simplified sliding sync. Contributed by @eleboucher.
+1
View File
@@ -0,0 +1 @@
Added scoped typing updates to simplified Sliding Sync (MSC4508). Contributed by @eleboucher.
+1
View File
@@ -0,0 +1 @@
Replace deprecated `docker-compose` commands with `docker compose` to reflect latest Docker changes.
+1
View File
@@ -0,0 +1 @@
Fixed typing notifications not interrupting a sync long poll. Contributed by @eleboucher.
+1
View File
@@ -0,0 +1 @@
Updated Ruma dependencies. Contributed by @eleboucher.
+1
View File
@@ -0,0 +1 @@
Fixed missed wake-ups and stale responses in legacy and sliding sync long polls. Contributed by @eleboucher.
+1
View File
@@ -0,0 +1 @@
Livekit docs: `room.auto_create: false` is now included in default `livekit.yaml`, following `lk-jwt-service` recommendation.
+1
View File
@@ -0,0 +1 @@
Add caveat and workaround for container resolution (e.g. for dockerized appservices) when using mounted `/etc/resolv.conf`. Contributed by @stratself
+1
View File
@@ -0,0 +1 @@
Added MSC4354 sticky events, behind the `allow_sticky_events` config option. Contributed by @eleboucher.
+1
View File
@@ -0,0 +1 @@
Added MSC4186 direct, encrypted, and room-type list filters, resolving invited rooms from their stripped invite state. Contributed by @eleboucher.
+1
View File
@@ -0,0 +1 @@
Preserve all users' read receipts when batching room receipt updates.
+1
View File
@@ -0,0 +1 @@
Refactor TURN docs and remove unsafe setups. Polish LiveKit docs. Add guidance for TURNS-over-443 multiplexing for both LiveKit and legacy calls. Contributed by @stratself
+1
View File
@@ -0,0 +1 @@
Updated admin command names (list-users -> list, list-rooms -> list, and reload-mods -> reload), old names are now aliases. Contributed by @PerformativeJade
+1
View File
@@ -0,0 +1 @@
Updated the docs to reflect the current default User-Agent for URL previews
+1
View File
@@ -0,0 +1 @@
Resolve membership service by correct name for auto-join. Contributed by @Aranjedeath
+1
View File
@@ -0,0 +1 @@
Plugged a logic gap that may have been allowing Continuwuity to process events with incorrect room versions under certain conditions. Contributed by @nex.
+1
View File
@@ -0,0 +1 @@
Added missing documentation to config parameters. Contributed by @renegadespork
+1
View File
@@ -0,0 +1 @@
The OAuth 2.0 device authorization endpoint now rejects clients which did not register the device code grant type, instead of issuing them a device code. The token endpoint now returns the `unauthorized_client` error code when a client requests a grant type it did not register, instead of `invalid_grant`. Contributed by @mmaudet.
+1
View File
@@ -0,0 +1 @@
Generic deployment documentation: Use `/etc/caddy/Caddyfile` instead of `/etc/caddy/conf.d/*` paths, which do not exist on a majority of Caddy installations.
-1
View File
@@ -1 +0,0 @@
**TODO - embargoed until next release** (maintainers see security issue 10 when writing rls notes). Contributed by @eleboucher.
+151 -18
View File
@@ -170,43 +170,136 @@
#
#db_write_buffer_capacity_mb = varies by system
# This item is undocumented. Please contribute documentation for it.
# The maximum number of Persisted Data Units (PDUs) to cache.
# Accepts any 32-bit integer.
#
# PDUs are events broadcast from one homeserver to any others that have
# joined the same room (identified by Room ID). They are persisted in
# long-term storage and record the history of messages and state
# for a room.
#
# An example of a PDU would be a message or joining a room.
#
# Setting this higher might be useful if the server has a large memory
# capacity and is noticeably I/O limited.
#
# This defaults to 100,000 + (10,000 * CPU core count)
#
#pdu_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# The maximum number of auth chains to cache.
# Accepts any 32-bit integer.
#
# When determining is a user is authorized to send an event,
# the server will first check the cache for an auth chain.
# If the auth chain is not present in the cache,
# The server will read the auth chain from the database
# and add it to the cache.
#
# This defaults to 100,000 + (10,000 * CPU core count)
#
#auth_chain_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# Determines the cache size for eventid data references.
# Accepts any 32-bit integer.
#
# Each matrix event can be referenced in the database via either
# an eventid (string) or a shorteventid (64-bit integer).
# This caps the capacity of cached eventids referenced
# by their shorteventid.
#
# This defaults to 100,000 + (50,000 * CPU core count)
#
#shorteventid_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# Determines the cache size for eventid data references.
# Accepts any 32-bit integer.
#
# Each matrix event can be referenced in the database via either
# an eventid (string) or a shorteventid (64-bit integer).
# This caps the capacity of cached shorteventids referenced
# by their eventid.
#
# This defaults to 100,000 + (25,000 * CPU core count)
#
#eventidshort_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# Determines the cache size for pdu data references.
# Accepts any 32-bit integer.
#
# This caps the capacity of cached pdus referenced
# by their eventid.
#
# This defaults to 100,000 + (25,000 * CPU core count)
#
#eventid_pdu_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# Determines the cache size for state key data references.
# Accepts any 32-bit integer.
#
# State keys are used by the server to verify that a token
# from a third-party invite event was actually generated
# by the server where the room resides.
#
# These state keys can be referenced in the database via either
# a statekey (string) or a shortstatekey (64-bit integer).
# This caps the capacity of cached statekeys referenced
# by shortstatekeys.
#
# This defaults to 100,000 + (10,000 * CPU core count)
#
#shortstatekey_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# Determines the cache size for state key data references.
# Accepts any 32-bit integer.
#
# State keys are used by the server to verify that a token
# from a third-party invite event was actually generated
# by the server where the room resides.
#
# These state keys can be referenced in the database via either
# a statekey (string) or a shortstatekey (64-bit integer).
# This caps the capacity of cached shortstatekeys referenced
# by statekeys.
#
# This defaults to 100,000 + (10,000 * CPU core count)
#
#statekeyshort_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# Determines the cache size for event_data references scoped
# by server_name.
# Accepts any 32-bit integer.
#
# Event data can be referenced in the database by server name.
# This is useful if the server needs to clear all events associated
# with a particular federated server, for example.
# This caps the capacity of cached event_data referenced
# by server_name.
#
# This defaults to 500,000 + (100,000 * CPU core count)
#
#servernameevent_data_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# Determines the cache size for room states.
# Accepts any 32-bit integer.
#
# To ensure data integrity of room events across asynchronous
# requests from federated servers, Matrix tracks the state
# of a room using a statehash and the changes since the
# previous (parent) statehash.
# This caps the capacity of cached room state data.
#
# This defaults to 100 * CPU core count.
#
#stateinfo_cache_capacity = varies by system
# This item is undocumented. Please contribute documentation for it.
# Determines the cache size for spacehierarchy data.
# Accepts any 32-bit integer.
#
# Each roomid contains info on its place in the space hierarchy.
# This caps the capacity of cached spacehierarchy data.
#
# This defaults to 1,000 * CPU core count.
#
#roomid_spacehierarchy_cache_capacity = varies by system
@@ -347,7 +440,13 @@
#
#max_request_size = 20971520
# This item is undocumented. Please contribute documentation for it.
# Maximum number of prev_events the server will request from
# other servers.
#
# When requesting room events from another server, this server will
# specify a maximum prev_events for the other server to return in its
# response. This can be used for things like backfilling room data and
# getting missing events.
#
#max_fetch_prev_events = 1024
@@ -732,11 +831,15 @@
#
#tracing_flame = false
# This item is undocumented. Please contribute documentation for it.
# Which log level tracing_flame will trace.
#
# See "tracing_flame" for more details.
#
#tracing_flame_filter = "info"
# This item is undocumented. Please contribute documentation for it.
# The output path for tracing_flame data.
#
# See "tracing_flame" for more details.
#
#tracing_flame_output_path = "./tracing.folded"
@@ -964,7 +1067,7 @@
#
#rocksdb_log_level = "error"
# This item is undocumented. Please contribute documentation for it.
# Whether to output RocksDB errors to stderr.
#
#rocksdb_log_stderr = false
@@ -1214,7 +1317,17 @@
#
#emergency_password =
# This item is undocumented. Please contribute documentation for it.
# Specifies the path where push notifications will be sent to a
# push gateway.
#
# When a Matrix client registers for push notifications, it will specify
# a push gateway url where notification events will be sent.
# According to the current spec for Matrix Push Gateways, the path should
# always be "/_matrix/push/v1/notify".
# You should only change this if you have configured your own push gateway
# that requires a different path.
#
# For more information on Push Gateways: https://spec.matrix.org/latest/push-gateway-api/
#
#notification_push_path = "/_matrix/push/v1/notify"
@@ -1306,6 +1419,12 @@
#
#allow_incoming_typing = true
# Allow the use of unstable persistent "sticky events" ([MSC4354])
#
# MSC4354: https://c10y.cc/MSC4354
#
#allow_sticky_events = false
# Maximum time federation user can indicate typing.
#
#typing_federation_timeout_s = 30
@@ -1365,7 +1484,10 @@
#
#allow_legacy_media = true
# This item is undocumented. Please contribute documentation for it.
# If set to true, prevents fetching new legacy remote media.
#
# Legacy media may still be accessible if it was already fetched
# previously and allow_legacy_media is true.
#
#freeze_legacy_media = true
@@ -1595,7 +1717,7 @@
# User agent that is used specifically when fetching url previews.
#
#url_preview_user_agent = "continuwuity/<version> (bot; +https://continuwuity.org)"
#url_preview_user_agent = "continuwuity/<version> (embedbot; facebookexternalhit/1.1; +https://continuwuity.org)"
# Determines whether audio and video files will be downloaded for URL
# previews.
@@ -1773,7 +1895,18 @@
#
#tokio_console = false
# This item is undocumented. Please contribute documentation for it.
# A list of flags that modify startup behavior.
# Accepts an array of strings.
#
# During startup, the program will check for the presence of certain
# strings in this array. Matching values will modify how the program runs
# those startup tasks.
#
# If this variable is empty or contains no matches, the server will start
# normally.
#
# Currently this only checks for "smoke" which enables "Smoketest mode."
# This mode exits the program after running startup tasks.
#
#test = false
+19 -11
View File
@@ -20,17 +20,19 @@ ## Set up the appservice - general instructions
registered on the homeserver automatically joins it. Then send a message into
the room like this:
!admin appservices register
```
paste
the
contents
of
the
yaml
registration
here
```
~~~txt
!admin appservices register
```
paste
the
contents
of
the
yaml
registration
here
```
~~~
You can confirm it worked by sending a message like this:
`!admin appservices list`
@@ -51,3 +53,9 @@ ### Remove an appservice
`!admin appservices unregister <name>`
where `<name>` one of the output of `appservices list`.
## Caveats
### Docker DNS
If you follow the [DNS tuning guide](./guides/dns.mdx#for-docker-users) and mount a custom `/etc/resolv.conf`, you may not be able to connect to appservices' containers using their hostnames. Follow the [workaround in the guide](./guides/dns.mdx#for-docker-users) to connect to your appservice using static IPs.
+3 -3
View File
@@ -3,11 +3,11 @@ # Calls
Matrix supports two types of calls:
- Element Call powered by [MatrixRTC](https://half-shot.github.io/msc-crafter/#msc/4143) and [LiveKit](https://github.com/livekit/livekit)
- Legacy calls, sometimes using Jitsi
- Legacy calls, supported by a STUN/TURN server.
Both types of calls are supported by different sets of clients, but most clients are moving towards MatrixRTC / Element Call.
For either one to work correctly, you have to do some additional setup.
For either one to work correctly, you have to do some additional setup:
- For legacy calls to work, you need to set up a TURN/STUN server. [Read the TURN guide for tips on how to set up coturn](./calls/turn.mdx)
- For MatrixRTC / Element Call to work, you have to set up the LiveKit backend (foci). LiveKit also uses TURN/STUN to increase reliability - you can set up its built-in TURN server, or integrate with an existing one. [Read the LiveKit guide](./calls/livekit.mdx)
- For MatrixRTC / Element Call to work, you have to set up the LiveKit backend. LiveKit also uses TURN/STUN to increase reliability - you can set up its built-in TURN server, or integrate with an existing one. [Read the LiveKit guide](./calls/livekit.mdx)
+193 -87
View File
@@ -1,8 +1,6 @@
# Matrix RTC/Element Call Setup
# MatrixRTC/Element Call Setup
:::info
This guide assumes that you are using docker compose for deployment. LiveKit only provides Docker images.
:::
This guide assumes that you are using docker compose for deployment.
:::tip
You can find help setting up MatrixRTC in our dedicated room - [#matrixrtc:continuwuity.org](https://matrix.to/#/%23matrixrtc%3Acontinuwuity.org)
@@ -10,15 +8,15 @@ # Matrix RTC/Element Call Setup
## Instructions
### 1. Domain
### 1. Set up your domain
LiveKit should live on its own domain or subdomain. In this guide we use `livekit.example.com` - this should be replaced with a domain you control.
Make sure the DNS record for the (sub)domain you plan to use is pointed to your server.
### 2. Services
### 2. Set up the LiveKit services
Using LiveKit with Matrix requires two services - LiveKit itself, and a service (`lk-jwt-service`) that grants Matrix users permission to connect to it.
Using LiveKit with Matrix requires two services - LiveKit itself, and a brokering service (`lk-jwt-service`) that grants Matrix users permission to connect to it.
You must generate a key and secret to allow the Matrix service to authenticate with LiveKit. `LK_MATRIX_KEY` should be around 20 random characters, and `LK_MATRIX_SECRET` should be around 64. Remember to replace these with the actual values!
@@ -81,15 +79,23 @@ # - "50100-50200:50100-50200/udp"
LK_MATRIX_KEY: LK_MATRIX_SECRET
# replace these with your key-secret pair. Example:
# APIUxUnMnSkuFWV: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
# do not create rooms by default
room:
auto_create: false
```
Consult [`config-sample.yaml`][livekit-config-sample-yaml] for all LiveKit options.
[livekit-config-sample-yaml]: https://github.com/livekit/livekit/blob/master/config-sample.yaml
#### Firewall hints
You will need to allow ports `7881/tcp` and `50100:50200/udp` through your firewall. If you use UFW, the commands are: `ufw allow 7881/tcp` and `ufw allow 50100:50200/udp`.
### 3. Telling clients where to find LiveKit
To tell clients where to find LiveKit, you need to add the address of your `lk-jwt-service` to the `[global.matrix_rtc]` config section using the `foci` option.
To tell clients where to find LiveKit, you need to add your `lk-jwt-service`'s address to the `[global.matrix_rtc] > foci` field of your Continuwuity config file.
The variable should be a list of servers serving as MatrixRTC endpoints. Replace the URL with the address you are deploying your instance of lk-jwt-service to:
@@ -100,6 +106,12 @@ ### 3. Telling clients where to find LiveKit
]
```
If you configure Continuwuity via environment variables, use the following:
```bash
CONTINUWUITY_MATRIX_RTC__FOCI=[{ type = "livekit", livekit_service_url = "https://livekit.example.com" }]
```
This will expose LiveKit information on the following endpoints for clients to discover:
- `/_matrix/client/unstable/org.matrix.msc4143/rtc/transports` (MSC4143 unstable, behind auth)
@@ -132,6 +144,11 @@ ### 4. Configure your Reverse Proxy
}
```
**Note**: if you run Caddy inside a container (e.g. by following the example [here](../deploying/docker.mdx#caddy-using-caddyfile)) instead of on the host, then:
- Put these containers on the same bridge network as caddy (by defining `networks: [ caddy ]` in each of the services), and
- Use appropriate container hostnames (`lk-jwt-service` and `livekit`) instead of `127.0.0.1` in the Caddyfile
</details>
<details>
@@ -187,6 +204,7 @@ ### 4. Configure your Reverse Proxy
```
</details>
<details>
<summary>Example docker compose file with caddy-docker-proxy labels</summary>
```yaml
@@ -262,76 +280,9 @@ ### 6. Start Everything
Start up the services using your usual method - for example `docker compose up -d`.
## Additional TURN configuration
### Using LiveKit's built-in TURN server
LiveKit includes a built-in TURN server which can be used in place of an external option. This TURN server will only work with LiveKit, so you can't use it for legacy Matrix calling or anything else.
If you don't want to set up a separate TURN server, you can enable this with the following changes:
```yaml
### add this to livekit.yaml ###
turn:
enabled: true
udp_port: 3478
relay_range_start: 50300
relay_range_end: 50400
domain: livekit.example.com
```
```yaml
### add these to livekit's docker-compose ###
ports:
- "3478:3478/udp"
- "50300-50400:50300-50400/udp"
### if you're using `network_mode: host`, you can skip this part
```
Recreate the LiveKit container (with `docker-compose up -d livekit`) to apply these changes. Remember to allow the new `3478/udp` and `50300:50400/udp` ports through your firewall.
### Integration with an external TURN server
If you've already [set up coturn](./turn), you can configure Livekit to use it.
:::tip Avoid port clashes between the two services
Before continuing, make sure coturn's `min-port` and `max-port` do not overlap with LiveKit's port range:
```ini
# in your coturn.conf
min-port=50201
max-port=65535
```
:::
Generate a long random secret for LiveKit, and add it to your coturn config under the `static-auth-secret` option. You can add as many secrets as you want, so set a different one for LiveKit to use.
Then configure LiveKit, making sure to replace `COTURN_SECRET` with the one you generated:
```yaml
# livekit.yaml
rtc:
turn_servers:
- host: coturn.example.com
port: 3478
protocol: udp
secret: "COTURN_SECRET"
- host: coturn.example.com
port: 3478
protocol: tcp
secret: "COTURN_SECRET"
- host: coturn.example.com
port: 5349
protocol: tls # Only if you have already set up TLS in your coturn
secret: "COTURN_SECRET"
```
Restart LiveKit and coturn to apply these changes.
## Testing
To test that LiveKit is successfully integrated with Continuwuity, you will need to replicate its [Token Exchange Flow](https://github.com/element-hq/lk-jwt-service#%EF%B8%8F-how-it-works--token-exchange-flow). Follow the steps below while checking Docker logs (`docker-compose logs --follow`), in order to help [troubleshooting](#troubleshooting) any issues.
To test that LiveKit is successfully integrated with Continuwuity, you will need to replicate its [Token Exchange Flow](https://github.com/element-hq/lk-jwt-service#%EF%B8%8F-how-it-works--token-exchange-flow). Follow the steps below while checking Docker logs (`docker compose logs --follow`), in order to help [troubleshooting](#troubleshooting) any issues.
First, you will need an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html).
@@ -396,16 +347,19 @@ ## Testing
{"url":"wss://livekit.example.com","jwt":"a_really_really_long_string"}
```
Use this token to test at the [LiveKit Connection Tester](https://livekit.io/connection-test). If everything works there, then you have set up LiveKit successfully!
Use this token to test at the [LiveKit Connection Tester][livekit-connection-test]. If everything works there, then you have set up LiveKit successfully!
[livekit-connection-test]: https://livekit.com/webrtc/connection-test
## Troubleshooting
To debug any issues, you can place a call or redo the Testing instructions, and check the container logs for any specific errors. Use `docker-compose logs --follow` to follow these logs in real-time.
To debug any issues, you can place a call or redo the Testing instructions, and check the container logs for any specific errors. Use `docker compose logs --follow` to follow these logs in real-time.
### Common errors in Element Call UI
- `MISSING_MATRIX_RTC_FOCUS`: LiveKit is missing from Continuwuity's config file
- `MISSING_MATRIX_RTC_FOCUS`/`MISSING_MATRIX_RTC_TRANSPORT`: LiveKit is missing from Continuwuity's config file
- "Waiting for media" popup always showing: a LiveKit URL has been configured in Continuwuity, but your client cannot connect to it for some reason
- `OPEN_ID_ERROR`: Your client can reach out to `lk-jwt-service`, but has problems authenticating with it. In this case, check `lk-jwt-service` logs for more details
For browser-based clients, you can also inspect connections using DevTools' Networking tab, to see which requests are erroring out.
@@ -440,6 +394,19 @@ # --- some errors ---
After implementing the changes and restarting your compose, `lk-jwt-service` should now connect to your other services. The sidecar container test above should now return an `OK` from LiveKit.
### Incorrect IP address for LiveKit
By default, LiveKit auto-discovers its public IP address(es), which is reflected in the "Establishing WebRTC connection" section of the connection test page. If these IPs are incorrect, you may want to hardcode your own IP by doing the following:
```diff
### in your livekit.yaml ###
rtc:
# ... other configs here ...
- use_external_ip: true
+ use_external_ip: false
+ node_ip: "1.2.3.4"
```
### Workaround for non-federating servers
When deploying on servers with federation disabled (`allow_federation = false`), LiveKit will fail as it can't fetch the required [OpenID endpoint](https://spec.matrix.org/v1.17/server-server-api/#get_matrixfederationv1openiduserinfo) via federation paths.
@@ -458,17 +425,156 @@ ## Related Documentation
Guides:
- [Element Call self-hosting documentation](https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md)
- [Community guide with overview of LiveKit's mechanisms](https://tomfos.tr/matrix/livekit/)
- [Community guide using systemd](https://blog.kimiblock.top/2024/12/24/hosting-element-call/)
- [Element Call self-hosting documentation from element-hq][element-call-selfhosting]
- [Community guide with overview of LiveKit's mechanisms][tom-livekit-guide]
- [Community guide using systemd][kimiblock-livekit-guide]
[element-call-selfhosting]: https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md
[tom-livekit-guide]: https://tomfos.tr/matrix/livekit/
[kimiblock-livekit-guide]: https://blog.kimiblock.top/2024/12/24/hosting-element-call/
Configurations:
- [Livekit's `config-sample.yaml`][livekit-config-sample-yaml] - LiveKit configuration file with full options
Specifications:
- [MSC4143 - MatrixRTC proposal](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)
- [MSC4195 - LiveKit proposal](https://github.com/matrix-org/matrix-spec-proposals/pull/4195)
- [MSC4143 - MatrixRTC proposal][MSC4143]
- [MSC4195 - LiveKit proposal][MSC4195]
[MSC4143]: https://github.com/matrix-org/matrix-spec-proposals/pull/4143
[MSC4195]: https://github.com/matrix-org/matrix-spec-proposals/pull/4195
Source code:
- [Element Call](https://github.com/element-hq/element-call)
- [lk-jwt-service](https://github.com/element-hq/lk-jwt-service)
- [LiveKit server](https://github.com/livekit/livekit)
- [Element Call][element-call-github]
- [lk-jwt-service][lk-jwt-service-github]
- [LiveKit server][livekit-server-github]
[element-call-github]: https://github.com/element-hq/element-call
[lk-jwt-service-github]: https://github.com/element-hq/lk-jwt-service
[livekit-server-github]: https://github.com/livekit/livekit
Other:
- [Matrix VOIP and LiveKit][sspaeth-matrix-voip] - Community member deep dive on current VOIP solutions on Matrix
[sspaeth-matrix-voip]: https://sspaeth.de/2026/04/matrix-voip-and-livekit/
## Appendix
### Additional TURNS-over-443 configuration
Most of the time, LiveKit [**does not need TURN**][sspaeth-matrix-voip-turn] to function. However, there are situations where clients are in very restrictive networks that disallows non-standard ports and UDP. In these cases, a TURN-over-TLS server on port :443 could be employed to relay traffic for them.
[sspaeth-matrix-voip-turn]: https://sspaeth.de/2026/04/matrix-voip-and-livekit/#turn
You can either use LiveKit's built-in TURN server, or integrate LiveKit with [coturn](./turn).
<details>
<summary>Using LiveKit's built-in TURN server</summary>
First, set up LiveKit's built-in TURN server with its own domain - we'll use `livekit-turn.example.com` in our example.
```yaml
## add this to `livekit.yaml` ##
turn:
enabled: true
# note: the TLS port will always be advertised as :443
tls_port: 5349
# optional: configure an extra UDP port on :3478
# udp_port: 3478
relay_range_start: 50300
relay_range_end: 50400
domain: livekit-turn.example.com
# replace these with your actual cert/key files
cert_file: /path/to/livekit-turn.example.com.crt
key_file: /path/to/livekit-turn.example.com.key
```
```yaml
### add these ports to livekit's docker-compose ###
### if you're using `network_mode: host`, you can skip this part
ports:
- "127.0.0.1:5349:5349/tcp"
- "50300-50400:50300-50400/udp"
# "3478:3478/udp" # (optional UDP port)
```
Recreate the LiveKit container (with `docker compose up -d livekit`) to apply these changes. Remember to allow the new `50300:50400/udp` ports through your firewall.
Then, we will configure a route from port 443 of the host back to our `livekit-turn.example.com` service on port 5349. To both **multiplex** this and LiveKit's websocket on the same port, we will use a layer-4 reverse proxy with **SNI routing** capabilities, such as [caddy-l4][caddy-l4] on the host system.
```
## in your Caddyfile ##
{
servers {
listener_wrappers {
# intercept packets meant for the TURN domain first
# before forwarding other packets to "normal" HTTP listeners
layer4 {
@turn tls sni livekit-turn.example.com
route @turn {
proxy 127.0.0.1:5349 # forward to normal TURNS port
}
}
}
tls
}
}
}
# livekit stuff
https://livekit.example.com {
@lk-jwt-service path /healthz /get_token /sfu/get
route @lk-jwt-service {
reverse_proxy 127.0.0.1:8081
}
reverse_proxy http://127.0.0.1:7880
}
```
[caddy-l4]: https://github.com/mholt/caddy-l4
</details>
<details>
<summary>Using an external TURN server (coturn)</summary>
Before continuing, make sure coturn's `min-port` and `max-port` do not overlap with LiveKit's port range:
```ini
# in your coturn.conf
min-port=50201
max-port=65535
```
Then, generate a long random secret for LiveKit, and add it to your coturn config under the `static-auth-secret` option. You can add as many secrets as you want, so set a different one for LiveKit to use.
After that, refer to the following [**TURN instructions**](./turn#turns-over-443) to set up coturn with TLS, as well as multiplexing with LiveKit's websocket on port 443.
Then configure LiveKit, making sure to replace `COTURN_SECRET` with the one you generated:
```yaml
### in your livekit.yaml ###
rtc:
# ... other configs here ...
turn_servers:
- host: coturn.example.com
port: 443
protocol: tls
secret: "COTURN_SECRET"
```
Restart LiveKit, coturn, and Caddy-l4 to apply these changes.
</details>
After finishing configuration, you can run the Testing steps again to check that TURN-over-TLS is working. In the LiveKit connection test page, there should be a green tick saying "Can connect to TURN".
+284 -120
View File
@@ -1,36 +1,95 @@
# Setting up TURN/STUN
[TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT) and [STUN](https://en.wikipedia.org/wiki/STUN) are used as a component in many calling systems. Matrix uses them directly for legacy calls and indirectly for MatrixRTC via Livekit.
[TURN][turn] and [STUN][stun] are used as a component in many calling systems. Matrix uses them directly for legacy calls and indirectly for MatrixRTC via Livekit.
Continuwuity recommends using [Coturn](https://github.com/coturn/coturn) as your TURN/STUN server, which is available as a Docker image or a distro package.
Continuwuity recommends using [Coturn][coturn] as your TURN/STUN server, which is available as a Docker image or a distro package. This guide assumes that you are using docker compose for deployment.
:::tip
You can find help setting up TURN/STUN in our MatrixRTC room - [#matrixrtc:continuwuity.org](https://matrix.to/#/%23matrixrtc%3Acontinuwuity.org)
:::
[turn]: https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT
[stun]: https://en.wikipedia.org/wiki/STUN
[coturn]: https://github.com/coturn/coturn
## Installing Coturn
### Configuration
### 1. Domain
Coturn should live on its own domain or subdomain. In this guide we use `coturn.example.com` - this should be replaced with a domain you control.
### 2. Configuration
Create a configuration file called `coturn.conf` containing:
```ini
use-auth-secret
static-auth-secret=<a secret key>
realm=<your server domain>
realm=coturn.example.com
```
:::tip Generating a secure secret
A common way to generate a suitable alphanumeric secret key is by using:
:::tip Tip: Generate a long, secure secret with the following command
```bash
pwgen -s 64 1
```
:::
#### Port Configuration
If you want to enable TURN-over-TLS (TURNS), add the appropriate cert/key paths to your `coturn.conf` by adding the following lines:
```ini
cert=/etc/letsencrypt/coturn.example.com.crt
pkey=/etc/letsencrypt/coturn.example.com.key
```
The cert and key must be encoded in PEM format and are readable by the coturn user.
Check out the [`turnserver.conf`][turnserver-conf] example for all coturn options.
[turnserver-conf]: https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf
### 3. Running the coturn container
Next, we will start the Coturn container with the [official image][coturn-image]. **Host networking mode** will be used, as it is better for performance and reduces configuration complexity (see [Coturn Docker docs][coturn-docker-docs] for rationale).
[coturn-image]: https://hub.docker.com/r/coturn/coturn
[coturn-docker-docs]: https://github.com/coturn/coturn/blob/master/docker/coturn/README.md#why-so-many-ports-opened
Create a `docker-compose.yml` file as follows and run `docker compose up -d`:
```yaml
version: '3'
services:
coturn:
container_name: coturn
image: docker.io/coturn/coturn
restart: unless-stopped
network_mode: "host"
volumes:
- ./coturn.conf:/etc/coturn/turnserver.conf
# replace this with actual paths to your certificates
- /path/to/certs:/etc/letsencrypt
```
### 4. Opening ports
By default, coturn uses the following ports:
- `3478` (UDP/TCP): Standard TURN/STUN port
- `5349` (UDP/TCP): TURN/STUN over TLS
- `49152-65535` (UDP): Media relay ports
If you're also running LiveKit, you'll need to avoid port conflicts. Configure non-overlapping port ranges:
You will need to allow them through your firewall. If you use UFW, the commands are:
```bash
ufw allow 3478/udp
ufw allow 3478/tcp
ufw allow 5349/tcp
ufw allow 5349/udp
ufw allow 49152-65535/udp
```
::: tip For LiveKit users
If you're also running LiveKit, you'll need configure non-overlapping port ranges to avoid port conflicts:
```ini
# In coturn.conf
@@ -38,57 +97,33 @@ # In coturn.conf
max-port=65535
```
```bash
# With ufw
ufw allow 50201:65535/udp
```
This leaves ports `50100-50200` available for LiveKit's default configuration.
### Running with Docker
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using:
```bash
docker run -d --network=host \
-v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf \
coturn/coturn
```
### Running with Docker Compose
Create a `docker-compose.yml` file and run `docker compose up -d`:
```yaml
version: '3'
services:
turn:
container_name: coturn-server
image: docker.io/coturn/coturn
restart: unless-stopped
network_mode: "host"
volumes:
- ./coturn.conf:/etc/coturn/turnserver.conf
```
:::info Why host networking?
Coturn uses host networking mode because it needs to bind to multiple ports and work with various network protocols. Using host networking is better for performance, and reduces configuration complexity. To understand alternative configuration options, visit [Coturn's Docker documentation](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md).
:::
### Security Recommendations
### 5. Security Recommendations
For security best practices, see Synapse's [Coturn documentation](https://element-hq.github.io/synapse/latest/turn-howto.html), which includes important firewall and access control recommendations.
For Coturn hardening and security best practices, see Synapse's [Coturn documentation][synapse-coturn-guide],
which includes important firewall and access control recommendations.
[synapse-coturn-guide]: https://element-hq.github.io/synapse/latest/setup/turn/coturn.html#configuration
## Configuring Continuwuity
Once your TURN server is running, configure Continuwuity to provide credentials to clients. Add the following to your Continuwuity configuration file:
### Shared Secret Authentication (Recommended)
This is the most secure method and generates time-limited credentials automatically:
```toml
# TURN URIs that clients should connect to
turn_uris = [
"turn:coturn.example.com?transport=udp",
"turn:coturn.example.com?transport=tcp",
"turns:coturn.example.com?transport=udp",
"turns:coturn.example.com?transport=tcp"
"turn:coturn.example.com:3478?transport=udp",
"turn:coturn.example.com:3478?transport=tcp",
# Add this if you're using TURN-over-TLS (note the `turns:` prefix)
"turns:coturn.example.com:5349?transport=tcp"
]
# Shared secret for generating credentials (must match coturn's static-auth-secret)
@@ -98,54 +133,18 @@ # Optional: Read secret from a file instead (takes priority over turn_secret)
# turn_secret_file = "/etc/continuwuity/.turn_secret"
# TTL for generated credentials in seconds (default: 86400 = 24 hours)
turn_ttl = 86400
turn_ttl = 10800
```
:::tip Using TLS
The `turns:` URI prefix instructs clients to connect to TURN over TLS, which is highly recommended for security. Make sure you've configured TLS in your coturn server first.
:::
### Static Credentials (Alternative)
If you prefer static username/password credentials instead of shared secrets:
```toml
turn_uris = [
"turn:coturn.example.com?transport=udp",
"turn:coturn.example.com?transport=tcp"
]
turn_username = "your_username"
turn_password = "your_password"
```
:::warning
Static credentials are less secure than shared secrets because they don't expire and must be configured in coturn separately. It is strongly advised you use shared secret authentication.
:::
### Guest Access
By default, TURN credentials require client authentication. To allow unauthenticated access:
```toml
turn_allow_guests = true
```
:::caution
This is not recommended as it allows unauthenticated users to access your TURN server, potentially enabling abuse by bots. All major Matrix clients that support legacy calls *also* support authenticated TURN access.
:::
### Important Notes
- Replace `coturn.example.com` with your actual TURN server domain (the `realm` from coturn.conf)
- The `turn_secret` must match the `static-auth-secret` in your coturn configuration
- Restart or reload Continuwuity after making configuration changes
Restart Continuwuity, and the new changes should now be applied.
## Testing Your TURN Server
### Testing Credentials
Verify that Continuwuity is correctly serving TURN credentials to clients:
Get an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html).
Then, using that token, verify that Continuwuity is correctly serving TURN credentials to clients:
```bash
curl "https://matrix.example.com/_matrix/client/r0/voip/turnServer" \
@@ -159,56 +158,221 @@ ### Testing Credentials
"username": "1752792167:@jade:example.com",
"password": "KjlDlawdPbU9mvP4bhdV/2c/h65=",
"uris": [
"turns:coturn.example.com?transport=udp",
"turns:coturn.example.com?transport=tcp",
"turn:coturn.example.com?transport=udp",
"turn:coturn.example.com?transport=tcp"
"turns:coturn.example.com:3478?transport=tcp",
"turn:coturn.example.com:3478?transport=udp",
"turn:coturn.example.com:5349?transport=tcp"
],
"ttl": 86400
}
```
:::note MSC4166 Compliance
If no TURN URIs are configured (`turn_uris` is empty), Continuwuity will return a 404 Not Found response, as specified in MSC4166.
:::
### Testing Connectivity
Use [Trickle ICE](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/) to verify that the TURN credentials actually work:
Open the [Trickle ICE][trickle-ice] testing page in a browser and then:
1. Copy the credentials from the response above
2. Paste them into the Trickle ICE testing tool
3. Click "Gather candidates"
4. Look for successful `relay` candidates in the results
1. Copy the URIs and credentials from the response above
2. Paste them into the Trickle ICE testing tool, and click on "Add server"
3. When finished adding all URIs and credentials, click "Gather candidates"
If you see relay candidates, your TURN server is working correctly!
If you see `relay` candidates in the results, your TURN/STUN server is working correctly! You should now be able to place and receive legacy calls.
[trickle-ice]: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
## Troubleshooting
### Clients can't connect to TURN server
To gather debug logs while troubleshooting Coturn, add `verbose` to your `coturn.conf`. You can then view these logs with `docker-compose logs --follow coturn`.
- Verify firewall rules allow the necessary ports (3478, 5349, and your media port range)
- Check that DNS resolves correctly for your TURN domain
- Ensure your `turn_secret` matches coturn's `static-auth-secret`
- Test with Trickle ICE to isolate the issue
### Errors with Trickle ICE
### Port conflicts with LiveKit
- `code=701` - the TURN server is not reachable
- Verify firewall rules allow the necessary ports (3478, 5349, and your media port range)
- Verify via logs that coturn is exposed on the correct addresses and interfaces
- Check that DNS resolves correctly for your TURN domain
- `code=401` - unauthorized credentials
- Ensure your `turn_secret` matches coturn's `static-auth-secret`
- Ensure the credentials you obtained from the Testing steps has not expired yet. You can adjust `turn_ttl` in your Continuwuity configuration to increase this, or simply re-request a new one
- Wrong IP address advertised
- This may be caused by coturn not recognizing its public-facing IP correctly, due to particular network setups. You can configure `external-ip=<desired-public-ip>` to fix this issue.
- Make sure coturn's `min-port` starts above LiveKit's `port_range_end` (default: 50200)
- Or adjust LiveKit's port range to avoid coturn's default range
### 404 when calling the turnServer endpoint
### 404 when calling turnServer endpoint
This is the correct response when no TURN servers are configured, as per [MSC4166][msc4166]. Verify that your `turn_uris` is not empty in your Continuwuity config and try again.
- Verify that `turn_uris` is not empty in your Continuwuity config
- This behavior is correct per MSC4166 if no TURN URIs are configured
[msc4166]: https://github.com/matrix-org/matrix-spec-proposals/pull/4166
### Credentials expire too quickly
- Adjust the `turn_ttl` value in your Continuwuity configuration
- Default is 86400 seconds (24 hours)
### Related Documentation
## Related Documentation
- [MatrixRTC/LiveKit Setup](./livekit.mdx) - Configure group calling with LiveKit
- [Coturn GitHub](https://github.com/coturn/coturn) - Official coturn repository
- [Synapse TURN Guide](https://element-hq.github.io/synapse/latest/turn-howto.html) - Additional security recommendations
- [Coturn GitHub][coturn] - Official coturn repository
- [`turnserver.conf`][turnserver-conf] - Coturn TURN SERVER configuration file with full options
- [Synapse TURN Guide][synapse-turn-guide] - TURN server guide for Synapse
- [Synapse Coturn Guide][synapse-coturn-guide] - Coturn-specific guide for Synapse, with important security recommendations
[synapse-turn-guide]: https://element-hq.github.io/synapse/latest/turn-howto.html
## Appendix
### Using Eturnal
If you instead prefer to use [eturnal][eturnal], you can refer to the guides below:
- [Community guidance][eturnal-continuwuity] on using eturnal with Legacy Calls, LiveKit calls, and more
- Synapse's [eturnal documentation][synapse-eturnal-guide]
[eturnal]: https://eturnal.net
[eturnal-continuwuity]: https://muoi.me/~stratself/articles/an-eturnal-to-rule-them-all/
[synapse-eturnal-guide]: https://element-hq.github.io/synapse/latest/setup/turn/eturnal.html
### TURNS-over-443
Normally, TURN would work on their default setups. However, there are situations where clients are in very restrictive networks that disallows non-standard ports and UDP. In these cases, a TURN-over-TLS server on port :443 could be employed to relay traffic for them.
However, port 443 is usually utilized by other HTTPS services. Therefore, one would need to **multiplex** both TURN HTTPS on these ports, and filter packets to them via **SNI routing**.
Below are examples to multiplex Coturn and [LiveKit](./livekit.mdx) on port 443, using [caddy-l4][caddy-l4] on the host system.
<details>
<summary>Caddyfile with TLS passthrough</summary>
This Caddyfile:
- Route `turn.example.com` to the TURNS port for Coturn without TLS termination, and
- Route `livekit.example.com` to the [LiveKit services](./livekit.mdx) with TLS termination by Caddy
Please note that all traffic from Coturn's perspective will be coming from caddy-l4's IP now.
```
{
servers {
listener_wrappers {
# intercept packets meant for the TURN domain first
# before forwarding other packets to "normal" HTTP listeners
layer4 {
@turn tls sni turn.example.com
route @turn {
proxy 127.0.0.1:5349 # forward to normal TURNS port
}
}
tls
}
}
}
# livekit stuff
https://livekit.example.com {
@lk-jwt-service path /healthz /get_token /sfu/get
route @lk-jwt-service {
reverse_proxy 127.0.0.1:8081
}
reverse_proxy http://127.0.0.1:7880
}
```
</details>
<details>
<summary>Caddyfile with TLS termination and PROXY protocol forwarding</summary>
This setup:
- Terminates TLS for `turn.example.com`,
- Tag the decrypted packets with PROXY protocol, and route it to coturn's `tcp-proxy-port`
- Route `livekit.example.com` to the [LiveKit services](./livekit.mdx) with TLS termination by Caddy
It allows coturn to see real client IPs, but the TLS handling is done on Caddy's side.
First, enable coturn's PROXY-protocol accepting port by adding this:
```ini
# in coturn.conf
tcp-proxy-port=5555
```
Then, in the Caddyfile:
```
{
servers {
listener_wrappers {
# intercept packets meant for the TURN domain first
# before forwarding other packets to "normal" HTTP listeners
layer4 {
@turn tlssni turn.example.com
route @turn {
tls # terminate TLS for the turn.example.com packets
proxy {
# then, proxy them to tcp-proxy-port and enable PROXY protocol version 2
upstream 127.0.0.1:5555
proxy_protocol v2
}
}
}
tls
}
}
}
# livekit stuff
https://livekit.example.com {
@lk-jwt-service path /healthz /get_token /sfu/get
route @lk-jwt-service {
reverse_proxy 127.0.0.1:8081
}
reverse_proxy http://127.0.0.1:7880
}
# placeholder block to obtain certs for turn.example.com
https://turn.example.com {
respond "OK" 200
}
```
**Note**: the setup will disable TURN-over-TLS functionality on port 5349/tcp.
</details>
After configuration and spin-up, the destination `turns:turn.example.com:443?transport=tcp` should work with Trickle ICE tests. You can now advertise it as an address in your `turn_uris` as well as [LiveKit](./livekit#additional-turns-over-443-configuration).
[caddy-l4]: https://github.com/mholt/caddy-l4
### Unsafe TURN setups (not recommended)
<details>
<summary>Using static credentials</summary>
:::caution
Static credentials are less secure than shared secrets because they don't expire and must be configured in coturn separately. It is strongly advised you use [shared secret authentication](#2-configuration).
:::
If you prefer static username/password credentials instead of shared secrets:
```ini
# In coturn.conf
# Comment out options to use a secret
# use-auth-secret
# static-auth-secret=<a secret key>
# Define a username-password pair
user=your_username:your_password
```
```toml
# In continuwuity.toml
turn_uris = [
"turn:coturn.example.com?transport=udp",
"turn:coturn.example.com?transport=tcp"
]
turn_username = "your_username"
turn_password = "your_password"
```
</details>
+29 -6
View File
@@ -9,7 +9,7 @@ ### Choose an image
| Image | Notes |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| [https://forgejo.ellis.link/continuwuation/continuwuity:**latest**][latest] | Latest tagged release. (recommended) |
| [https://forgejo.ellis.link/continuwuation/continuwuity:**main**][main] | Latest `main` branch commit. |
| [https://forgejo.ellis.link/continuwuation/continuwuity:**main**][main] | Latest `main` branch commit. Built with [CI][release-image] |
| [https://forgejo.ellis.link/continuwuation/continuwuity:**latest-maxperf**][latest-maxperf] | Latest tagged release, [performance optimised version](./generic.mdx#performance-optimised-builds). |
| [https://forgejo.ellis.link/continuwuation/continuwuity:**main-maxperf**][main-maxperf] | Latest `main` branch commit, [performance optimised version](./generic.mdx#performance-optimised-builds). |
@@ -17,6 +17,7 @@ ### Choose an image
[main]: https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main
[latest-maxperf]: https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest-maxperf
[main-maxperf]: https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main-maxperf
[release-image]: https://forgejo.ellis.link/continuwuation/continuwuity/actions/?workflow=release-image.yml
If you want a specific version or commit hash, you can browse for them [here][oci-all-versions].
@@ -25,13 +26,11 @@ ### Choose an image
- `ghcr.io/continuwuity/continuwuity` ([Github Registry][ghcr-io])
- `docker.io/jadedblueeyes/continuwuity` ([Docker Hub][docker-hub])
- `registry.gitlab.com/continuwuity/continuwuity` ([Gitlab Registry][gitlab-registry])
- `git.nexy7574.co.uk/mirrored/continuwuity` ([Nexy's forge][nexy-forge]. Releases only, no `main` tags)
[oci-all-versions]: https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/versions
[ghcr-io]: https://github.com/continuwuity/continuwuity/pkgs/container/continuwuity/versions?filters%5Bversion_type%5D=tagged
[docker-hub]: https://hub.docker.com/r/jadedblueeyes/continuwuity/
[gitlab-registry]: https://gitlab.com/continuwuity/continuwuity/container_registry/8871720
[nexy-forge]: https://git.nexy7574.co.uk/mirrored/-/packages/container/continuwuity/versions
### Prerequisites
@@ -63,8 +62,8 @@ ### Choose Your Reverse Proxy
These examples include reverse proxy configurations for Matrix federation, which will route your Matrix domain (and optionally .well-known paths) to Continuwuity.
:::note Docker DNS Performance
Docker's default DNS resolver are known to [cause timeout issues](../troubleshooting#dns-issues) for Matrix federation. To bypass it and use a more performant resolver, mount a custom `/etc/resolv.conf` config file into the Continuwuity container.
:::important Docker DNS Performance
Docker's default DNS resolver is known to [**cause timeout issues**](../troubleshooting#dns-issues) for Matrix federation. To bypass it and use a more performant resolver, mount a custom `/etc/resolv.conf` config file into the Continuwuity container:
```yaml title='docker-compose.yml'
services:
@@ -85,8 +84,19 @@ ### Choose Your Reverse Proxy
#### Caddy (using Caddyfile)
<details>
<summary>docker-compose.with-caddy.yml ([view raw](/deploying/docker-compose.with-caddy.yml))</summary>
Create a Caddyfile with the following content:
```
https://example.com:443, https://example.com:8448 {
reverse_proxy http://homeserver:8008
}
```
Then, for the docker-compose:
```yaml file="../public/deploying/docker-compose.with-caddy.yml"
```
@@ -164,7 +174,7 @@ ### Starting Your Server
4. Check your server logs for a registration token:
```bash
docker-compose logs continuwuity 2>&1
docker compose logs continuwuity 2>&1
```
You'll see output as below.
@@ -187,6 +197,19 @@ ## Testing
Check your container logs using `docker-compose logs --follow` to debug any issues. See the [Troubleshooting](../troubleshooting.mdx) page for common errors and how to fix them.
## Updating
To update to a new version of Continuwuity, use the following commands:
```bash
# stop the services in docker-compose.yaml
docker compose down
# pull latest images for the services in docker-compose.yaml
docker compose pull
# spin up the containers again
docker compose up -d
```
## Other deployment methods
### Docker - Quick Run
+33 -37
View File
@@ -29,19 +29,12 @@ ### Prebuilt binary
#### Performance-optimised builds
For x86_64 systems with CPUs from the last ~15 years, use the
`-haswell-` optimised binaries for best performance. These
binaries enable hardware-accelerated CRC32 checksumming in
RocksDB, which significantly improves database performance.
The haswell instruction set provides an excellent balance of
compatibility and speed.
Continuwuity provides `*-maxperf` tagged binaries, which uses the `release-max-perf` build profile with [link-time optimisation (LTO)][lto-rust-docs]. For the x86_64 architecture, these binaries specifically target the Haswell architecture (hence the `-haswell-` name extension), and enables hardware-accelerated CRC32 checksumming in
RocksDB which significantly improves database performance. If you're using an x86_64 system with CPUs from the last ~15 years, consider using these images for best performance.
If you're using Docker instead, equivalent performance-optimised
images are available with the `-maxperf` suffix (e.g.
`forgejo.ellis.link/continuwuation/continuwuity:latest-maxperf`).
These images use the `release-max-perf` build profile with
[link-time optimisation (LTO)][lto-rust-docs]
and, for amd64, target the haswell CPU architecture.
[lto-rust-docs]: https://doc.rust-lang.org/cargo/reference/profiles.html#lto
@@ -59,28 +52,7 @@ ### Nix
### Compiling
Alternatively, you may compile the binary yourself.
#### Using Docker
See the [Building Docker Images](../development/index.mdx#building-docker-images)
section in the development documentation.
#### Manual
##### Dependencies
- Run `nix develop` to get a devshell with everything you need
- Or, install the following:
- (On linux) `liburing-dev` on the compiling machine, and `liburing` on the target host
- (On linux) `pkg-config` on the compiling machine to allow finding `liburing`
- A C++ compiler and (on linux) `libclang` for RocksDB
##### Build
You can now build Continuwuity using `cargo build --release`.
Continuwuity supports various optional features that can be enabled during compilation. Please see the Cargo.toml file for a comprehensive list, or ask in our rooms.
Alternatively, you may compile the binary yourself. See the [Appendix subsection](#compiling-continuwuity) for more details.
## Adding a Continuwuity user
@@ -179,12 +151,11 @@ ### Caddy
Caddy is the recommended reverse proxy as it is easy to use, has good defaults,
and handle TLS certificates automatically. After installing Caddy via your preferred
method, create `/etc/caddy/conf.d/conduwuit_caddyfile` and enter the following
method, add the following content to your `/etc/caddy/Caddyfile` file
(substitute `example.com` with your actual server name):
```
example.com, example.com:8448 {
# TCP reverse_proxy
reverse_proxy 127.0.0.1:8008
}
```
@@ -207,7 +178,7 @@ ### Other Reverse Proxies
- `/_matrix/client` - core Client-Server APIs. These should be available on port :443
- `/_continuwuity/` - Continuwuity's integrated account management interface for OAuth-compatible clients
- `/_continuwuity/` - Continuwuity's integrated account management interface and authentication path for OAuth-compatible clients
You can optionally reverse proxy the following individual routes:
@@ -215,7 +186,7 @@ ### Other Reverse Proxies
Continuwuity to perform delegation (see the `[global.well_known]` config section)
- `/.well-known/matrix/support` if using Continuwuity to send the homeserver admin
[contact and support page][well-known-support]
- `/` and `/_continuwuity/logo.svg` if you would like to see the Continuwuity landing page
- `/` and `/_continuwuity/resources` if you would like to see the Continuwuity landing page
Refer to the respective software's documentation and online guides on how to do so.
@@ -286,10 +257,10 @@ ## How do I know it works?
As a quick health check, you can also use these cURL commands:
```bash
curl https://example.com/_conduwuit/server_version
curl https://example.com/_continuwuity/server_version
# If using port 8448
curl https://example.com:8448/_conduwuit/server_version
curl https://example.com:8448/_continuwuity/server_version
# If federation is enabled
curl https://example.com:8448/_matrix/federation/v1/version
@@ -305,3 +276,28 @@ ## What's next?
- For Audio/Video call functionality see the [**Calls**](../calls.md) page.
- Consult the [Maintenance](../maintenance.mdx) page for guidance on maintaining your homeserver.
- If you want to set up an appservice, take a look at the [**Appservice Guide**](../appservices.md).
## Appendix
### Compiling Continuwuity
#### Using Docker
See the [Building Docker Images](../development/index.mdx#building-docker-images)
section in the development documentation.
#### Manual
##### DEPENDENCIES
- Run `nix develop` to get a devshell with everything you need
- Or, install the following:
- (On linux) `liburing-dev` on the compiling machine, and `liburing` on the target host
- (On linux) `pkg-config` on the compiling machine to allow finding `liburing`
- A C++ compiler and (on linux) `libclang` for RocksDB
##### BUILD
You can now build Continuwuity using `cargo build --release`.
Continuwuity supports various optional features that can be enabled during compilation. Please see the Cargo.toml file for a comprehensive list, or ask in our rooms.
+82 -64
View File
@@ -53,12 +53,19 @@ # Defaults to members of the admin room if unset
After doing the steps above, Continuwuity will serve these 3 JSON files:
- `/.well-known/matrix/client`: for Client-Server discovery
- `/.well-known/matrix/server`: for Server-Server (federation) discovery
- `/.well-known/matrix/support`: admin contact details (strongly recommended to have)
- `/.well-known/matrix/support`: admin contact details (strongly recommended).
- By default, this include members of the admin room.
To enable full discovery, you will need to reverse proxy these paths from the base domain back to Continuwuity.
## Reverse proxying well-known files to Continuwuity
## Serving well-known files
You can either reverse proxy the well-known paths to Continuwuity, or serve static JSON files yourself.
### Option 1: Reverse proxying well-known files to Continuwuity
<details>
@@ -96,7 +103,7 @@ ## Reverse proxying well-known files to Continuwuity
For **Docker** users, consult the compose files in the [Appendix section](#docker-compose-examples).
After applying these changes, restart Continuwuity and your reverse proxy.Visit these routes and check that the responses match the examples below:
After applying these changes, restart Continuwuity and your reverse proxy. Visit these routes and check that the responses match the examples below:
<details open>
@@ -122,12 +129,12 @@ ## Reverse proxying well-known files to Continuwuity
</details>
### Serving well-known files manually
### Option 2: Manually serving static files for well-known
Instead of reverse proxying well-known URIs, you can serve these files directly as static JSON that match the ones above. This is useful if your base domain points to a different physical server, and reverse proxying isn't feasible.
:::warning
Even if you choose to serve the well-known files manually, if you are using delegation at all, you **must** still set the `global.well_known.client` configuration option to the domain you're delegating to. Continuwuity needs to know the domain it runs on for OAuth-compatible clients to work correctly.
:::important Important: always configure [global.well_known]
Even if you choose to serve the well-known files manually, you **must** still set the `global.well_known.client` configuration option to the domain you're delegating to. Continuwuity needs to know the domain it runs on in order to redirect clients properly to email, OAuth, and account management endpoints.
:::
<details>
@@ -190,15 +197,78 @@ ### Issues with alternative setups
In these scenarios, further configurations would be needed. Refer to the [Related Documentation](#related-documentation) section for resolution steps and see how they could apply to your use case.
---
## Related Documentation
## Using SRV records (not recommended)
### Matrix Specification
See the following sections of the Matrix Specification for more details on client and server resolution mechanisms:
- [Server-to-Server resolution](https://spec.matrix.org/latest/server-server-api/#resolving-server-names)
- [Client-to-Server resolution](https://spec.matrix.org/latest/client-server-api/#server-discovery)
- [MSC1929: Homeserver Admin Contact and Support page](https://github.com/matrix-org/matrix-spec-proposals/pull/1929)
### Community guides
- [Federation Setup guide by MTRNord](https://connectivity-tester.mtrnord.blog/docs/getting-started/federation-setup)
## Appendix
### Docker Compose examples
The following Compose files are taken from [Docker deployment instructions](../deploying/docker.mdx) and reconfigured to support split-domain delegation. Note the updated `CONTINUWUITY_WELL_KNOWN` variable and relevant changes in reverse proxy rules.
<details>
<summary>Caddy (using Caddyfile) - delegated.docker-compose.with-caddy.yml ([view raw](/advanced/delegated.docker-compose.with-caddy.yml))</summary>
```yaml file="../public/advanced/delegated.docker-compose.with-caddy.yml"
```
```txt title="Caddyfile"
https://matrix.example.com:443 {
reverse_proxy http://homeserver:8008
}
https://example.com:443 {
reverse_proxy /.well-known/matrix* http://homeserver:8008
}
```
</details>
<details>
<summary>Caddy (using labels) - delegated.docker-compose.with-caddy-labels.yml ([view raw](/advanced/delegated.docker-compose.with-caddy-labels.yml))</summary>
```yaml file="../public/advanced/delegated.docker-compose.with-caddy-labels.yml"
```
</details>
<details>
<summary>Traefik (for existing setup) - delegated.docker-compose.for-traefik.yml ([view raw](/advanced/delegated.docker-compose.for-traefik.yml))</summary>
```yaml file="../public/advanced/delegated.docker-compose.for-traefik.yml"
```
</details>
<details>
<summary>Traefik included - delegated.docker-compose.with-traefik.yml ([view raw](/advanced/delegated.docker-compose.with-traefik.yml))</summary>
```yaml file="../public/advanced/delegated.docker-compose.with-traefik.yml"
```
</details>
### Using SRV records (not recommended)
:::warning
The following methods are **not recommended** due to increased complexity with little benefits. If you have already set up `.well-known` delegation as above, you can safely skip this part.
:::
The following methods use DNS SRV records, and are **not recommended** due to increased complexity with few benefits. Additionally, they only work for federation traffic.
The following methods uses SRV DNS records and only work with federation traffic. They are only included for completeness.
If you have already set up well-known delegation as above, you can safely skip this part.
:::
<details>
@@ -241,7 +311,7 @@ ## Using SRV records (not recommended)
<details>
<summary>Using SRV records as a fallback for .well-known delegation</summary>
<summary>Using SRV records as a fallback for well-known delegation</summary>
Assume your delegation is as below:
@@ -263,55 +333,3 @@ ## Using SRV records (not recommended)
On the target's IP at port 443, you'll need to provide a valid route and cert for `example.com`.
</details>
---
## Related Documentation
See the following Matrix Specs for full details on client/server resolution mechanisms:
- [Server-to-Server resolution](https://spec.matrix.org/v1.17/server-server-api/#resolving-server-names) (see this for more information on SRV records)
- [Client-to-Server resolution](https://spec.matrix.org/v1.17/client-server-api/#server-discovery)
- [MSC1929: Homeserver Admin Contact and Support page](https://github.com/matrix-org/matrix-spec-proposals/pull/1929)
## Appendix
### Docker Compose examples
The following Compose files are taken from [Docker instructions](../deploying/docker.mdx) and reconfigured to support split-domain delegation. Note the updated `CONTINUWUITY_WELL_KNOWN` variable and relevant changes in reverse proxy rules.
<details>
<summary>Caddy (using Caddyfile) - delegated.docker-compose.with-caddy.yml ([view raw](/advanced/delegated.docker-compose.with-caddy.yml))</summary>
```yaml file="../public/advanced/delegated.docker-compose.with-caddy.yml"
```
</details>
<details>
<summary>Caddy (using labels) - delegated.docker-compose.with-caddy-labels.yml ([view raw](/advanced/delegated.docker-compose.with-caddy-labels.yml))</summary>
```yaml file="../public/advanced/delegated.docker-compose.with-caddy-labels.yml"
```
</details>
<details>
<summary>Traefik (for existing setup) - delegated.docker-compose.for-traefik.yml ([view raw](/advanced/delegated.docker-compose.for-traefik.yml))</summary>
```yaml file="../public/advanced/delegated.docker-compose.for-traefik.yml"
```
</details>
<details>
<summary>Traefik included - delegated.docker-compose.with-traefik.yml ([view raw](/advanced/delegated.docker-compose.with-traefik.yml))</summary>
```yaml file="../public/advanced/delegated.docker-compose.with-traefik.yml"
```
</details>
+55 -18
View File
@@ -8,7 +8,7 @@ # DNS Tuning (recommended)
## Overview
For generic deployments, install your resolver of choice and configure `/etc/resolv.conf` to point to it. The resolver should ideally reside on the same host as Continuwuity.
For generic deployments, install your resolver of choice and configure your host system's `/etc/resolv.conf` to point to it. The resolver should ideally reside on the same host as Continuwuity.
```txt title="/etc/resolv.conf"
nameserver 127.0.0.1
@@ -18,7 +18,7 @@ ## Overview
### For Docker users
Docker bridge networks uses a non-performant resolver to intercept and respond to container hostnames, and **this should also be avoided**. Instead, mount a custom `/etc/resolv.conf` file into the container, and hardcode a resolver address to bypass Docker's.
By default, Docker bridge networks uses a poorly performing resolver to intercept and respond to container hostnames. **This resolver should also be avoided**. Instead, mount a custom `/etc/resolv.conf` file into the container, and hardcode a resolver address to bypass Docker's.
It is recommended to run a dedicated resolver container for Continuwuity, as to separate from the host's resolver setup. To do this, create a custom bridge network and IP range, and explicitly define an IP address for the resolver container.
@@ -27,23 +27,28 @@ ### For Docker users
```yaml title="docker-compose.yml"
networks:
matrix_net:
# re-use the `proxy` network, as shown in the Docker deployment docs
# (https://continuwuity.org/deploying/docker#choose-your-reverse-proxy)
# and configure it with a custom subnet
proxy:
ipam:
driver: default
config:
- subnet: "10.10.10.0/24"
services:
homeserver:
# ...
volume:
- ./continuwuity-resolv.conf:/etc/resolv.conf:ro
homeserver:
# ...
volume:
- ./continuwuity-resolv.conf:/etc/resolv.conf:ro
networks:
- proxy
unbound:
# ...
networks:
matrix_net:
ipv4_address: 10.10.10.20
unbound:
# ...
networks:
proxy:
ipv4_address: 10.10.10.20
```
```txt title="continuwuity-resolv.conf"
@@ -52,9 +57,41 @@ ### For Docker users
</details>
:::warning Breaking resolution with container hostnames
After you mount a custom `/etc/resolv.conf`, you won't be able to connect to other containers using their hostnames, such as for [appservices](../appservices.mdx) in the same Docker network. To work around this, force your appservice to use a static IP address, and refer to its static IP during registration.
<details>
<summary>Example Docker deployment with an appservice</summary>
```yaml title="docker-compose.yml"
# ... other parts of compose file ...
appservice:
# ...
networks:
proxy:
ipv4_address: 10.10.10.30
```
~~~txt
!admin appservices register
```
id: appservice-name
url: http://10.10.10.30:29318
# rest of the registration file
```
~~~
</details>
:::
### For IPv4-only users
If you don't have IPv6 connectivity, changing `ip_lookup_strategy` to only resolve for IPv4 will reduce unnecessary AAAA queries.
If you don't have IPv6 connectivity, change `ip_lookup_strategy` to `1` in Continuwuity's configuration in order to onlyresolve IPv4 addresses and remove unnecessary IPv6 queries.
```toml title="continuwuity.toml"
[global]
@@ -72,13 +109,13 @@ ## Unbound
- Increase `rrset-cache-size` and `msg-cache-size` to something much higher than the default `4M`, such as `64M`.
- Increase `discard-timeout` to something like `4800` to wait longer for upstream resolvers, as recursion can take a long time to respond to some domains. Continuwuity default to `dns_timeout = 10` seconds, so dropping requests early would lead to unnecessary retries and/or failures.
- Increase `discard-timeout` to something like `4800` to wait longer for upstream resolvers, as recursion can take a long time to respond to some domains. Continuwuity defaults to `dns_timeout = 10` seconds, so dropping requests early would lead to unnecessary retries and/or failures.
### Recursion versus forwarding
### Using a forwarder (optional)
Unbound by default employs **recursive resolution** and contacts many servers around the world. While this allows updated and authoritative answers and are generally viable for most users, sometimes these recursive queries can be too slow to fully resolve. As an alternative, you can consider **forwarding** your queries to public resolvers, and benefit from faster responses from their CDNs.
Unbound employs **recursive resolution** by default, contacting many nameservers around the world in the process. While this is generally viable for most users, sometimes these recursive queries can be too slow to fully resolve. As an alternative, you may consider **forwarding** your queries to public resolvers, and benefit from faster responses from their caches.
Do note that most popular upstreams (such as Google DNS or Quad9) employ IP ratelimiting, so a generous cache is still needed to avoid making too many queries.
However, do note that most popular upstreams (such as Google DNS or Quad9) employ IP ratelimiting, so a generous Unbound cache is still needed to avoid making too many queries.
If you want to use forwarders, configure it as follows:
@@ -144,7 +181,7 @@ ### Technitium DNS
## Testing
As a rough stress test, you can run `!admin query resolver flush-cache -a` or `!admin server clear-caches` to trigger a netburst of DNS queries. If your resolver can handle these loads without problem, then it should be ready for regular Continuwuity activity.
As a rough stress test, you can run `!admin query resolver flush-cache -a` or `!admin server clear-caches` to clear your destinations cache - this will then trigger a netburst of DNS queries. If your resolver can handle this load without issue, then it should be ready for regular Continuwuity activity.
To test connectivity against a specific server, use `!admin debug ping <SERVER_NAME>` and `!admin debug resolve-true-destination <SERVER_NAME>`.
+3 -1
View File
@@ -6,7 +6,7 @@ # Performance tuning
## DNS tuning (recommended)
Please see the dedicated [DNS tuning guide](./dns.mdx).
DNS tuning is important to ensure a smooth experience for federated servers. Please see the dedicated [DNS tuning guide](./dns.mdx).
## Cache capacities
@@ -115,6 +115,8 @@ ### Tuning your trusted servers
:::info Vet your trusted servers!
Trusted servers are your first point of contact when obtaining public keys from other servers, and they could theoretically impersonate other servers and cause significant harm to your deployment. Please thoroughly verify your trusted servers' credibility before adding them to your configuration.
If you need suggestions for trusted servers, ask in the Continuwuity main room.
:::
Trusted servers are queried sequentially in the order they are listed. If you have multiple trusted servers configured, put the faster ones first:
+48 -35
View File
@@ -42,12 +42,16 @@ ## Database (RocksDB)
creating unnecessary I/O amplification. RocksDB is built with io_uring support
via liburing for improved read performance.
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](troubleshooting.md).
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](./troubleshooting.mdx#rocksdb--database-issues).
### Compression
Some RocksDB settings can be adjusted such as the compression method chosen. See
the RocksDB section in the [example config](./reference/config.mdx).
Some RocksDB settings can be adjusted, such as the chosen compression method and level.
See the RocksDB section in the [example config](./reference/config.mdx), and the
[database compression section](./guides/performance.mdx#tuning-database-compression)
in the performance tuning documentation for more.
#### Caveats for btrfs users
btrfs users have reported that database compression does not need to be disabled
on Continuwuity as the filesystem already does not attempt to compress. This can be
@@ -57,10 +61,12 @@ ### Compression
unbuffered Direct IO inoperable, significantly slowing down read and write
performance. See [the Btrfs docs](https://btrfs.readthedocs.io/en/latest/Compression.html#compatibility).
> Compression is done using the COW mechanism so its incompatible with
> nodatacow. Direct IO read works on compressed files but will fall back to
> buffered writes and leads to no compression even if force compression is set.
> Currently nodatasum and compression dont work together.
:::important
Compression is done using the COW mechanism so its incompatible with
`nodatacow`. Direct IO read works on compressed files but will fall back to
buffered writes and leads to no compression even if force compression is set.
Currently `nodatasum` and compression dont work together.
:::
### Files in database
@@ -78,37 +84,47 @@ ### Files in database
## Backups
Currently only RocksDB supports online backups. If you'd like to backup your
database online without any downtime, see the `!admin server` command for the
backup commands and the `database_backup_path` config options in the example
config. Please note that the format of the database backup is not the exact
same. This is unfortunately a bad design choice by Facebook as we are using the
database backup engine API from RocksDB, however the data is still there and can
still be joined together.
### Database online backup
f you'd like to run an online backup of your database - that is, a backup with
no downtime - check the [`!admin server` command](./reference/admin/server.md)
for the required commands and the `database_backup_path` config options in
the example config.
Please note that the format of the database backup is not the exact same as the
format of offline backups. This is unfortunately a bad design choice by Facebook
as we are using the database backup engine API from RocksDB, however the data
is still there and can still be joined together.
To restore a backup from an online RocksDB backup:
- shutdown Continuwuity
- create a new directory for merging together the data
- in the online backup created, copy all `.sst` files in
- Shutdown Continuwuity
- Create a new directory for merging together the data
- In the online backup created, copy all `.sst` files in
`$DATABASE_BACKUP_PATH/shared_checksum` to your new directory
- trim all the strings so instead of `######_sxxxxxxxxx.sst`, it reads
`######.sst`. A way of doing this with sed and bash is `for file in *.sst; do mv
"$file" "$(echo "$file" | sed 's/_s.*/.sst/')"; done`
- copy all the files in `$DATABASE_BACKUP_PATH/1` (or the latest backup number
- Copy all the files in `$DATABASE_BACKUP_PATH/private/1` (or the latest backup number
if you have multiple) to your new directory
- set your `database_path` config option to your new directory, or replace your
- Set your `database_path` config option to your new directory, or replace your
old one with the new one you crafted
- start up Continuwuity again and it should open as normal
- Start up Continuwuity again and it should open as normal
Note: You can verify the necessary files to copy by comparing them with contents in the `$DATABASE_BACKUP_PATH/meta/1` (or latest backup number) file.
### Database offline backup
If you'd like to do an offline backup, shutdown Continuwuity and copy your
`database_path` directory elsewhere. This can be restored with no modifications
needed.
Backing up media is also just copying the `media/` directory from your database
directory.
### Media backup
## Media
Media is stored in the `media/` subdirectory from your database directory.
Backing up media is also just copying that subdirectory.
## Media management
Media still needs various work, however Continuwuity implements media deletion via:
@@ -117,19 +133,16 @@ ## Media
- Delete list of MXC URIs
- Delete remote media in the past `N` seconds/minutes via filesystem metadata on
the file created time (`btime`) or file modified time (`mtime`)
- Delete all media from a local user
- Delete all media from a remote server
- Delete cached URL previews
See the `!admin media` command for further information. All media in Continuwuity
is stored at `$DATABASE_DIR/media`. This will be configurable soon.
See the [`!admin media` commands](./reference/admin/media.md) for further information.
All media in Continuwuity is stored at `$DATABASE_DIR/media`.
If you are finding yourself needing extensive granular control over media, we
recommend looking into [Matrix Media
Repo](https://github.com/t2bot/matrix-media-repo). Continuwuity intends to
implement various utilities for media, but MMR is dedicated to extensive media
management.
Built-in S3 support is also planned, but for now using a "S3 filesystem" on
`media/` works. Continuwuity also sends a `Cache-Control` header of 1 year and
immutable for all media requests (download and thumbnail) to reduce unnecessary
media requests from browsers, reduce bandwidth usage, and reduce load.
While Continuwuity does not implement built-in S3 support, using an S3 filesystem
mount on the `media/` path will work. Continuwuity also sends a `Cache-Control`
header of 1 year with the `immutable` directive for all media requests
(download and thumbnail) to reduce unnecessary bandwidth and load.
[rocksdb-compaction]: https://github.com/facebook/rocksdb/wiki/Compaction
@@ -6,10 +6,10 @@
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
},
{
"id": 16,
"id": 17,
"mention_room": true,
"date": "2026-07-13",
"message": "[Continuwuity 26.6.2](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v26.6.1) has just been released! This release fixes a severe bug with OIDC that could cause users' accounts to be flagged as deactivated. If you use OIDC, please update as soon as possible."
"date": "2026-07-30",
"message": "[Continuwuity 26.7.2](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v26.7.2) (and [v26.7.1](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v26.7.1)) have been released! v26.7.1 includes several new bug fixes and features, such as the OAuth2 device authorization flow, and a fix for the \"empty room\" bug (check the release notes!). v26.7.2 is a hotfix that fixes a low severity vulnerability in simplified sliding sync (particularly relevant to multi-user homeservers) and two regressions. It is recommended every deployment upgrades as soon as possible. Join the [announcements room](https://matrix.to/#/#announcements:continuwuity.org) for more prompt announcements regarding updates!"
}
]
}
@@ -1,54 +1,55 @@
# Continuwuity - With Caddy Labels
services:
caddy:
# This compose file uses caddy-docker-proxy as the reverse proxy for Continuwuity!
# For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
image: "docker.io/lucaslorentz/caddy-docker-proxy:ci-alpine"
ports:
- 80:80
- 443:443
environment:
- CADDY_INGRESS_NETWORKS=caddy
networks:
- caddy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
restart: unless-stopped
labels:
caddy: example.com
caddy.reverse_proxy: /.well-known/matrix/* homeserver:8008
caddy:
# This compose file uses caddy-docker-proxy as the reverse proxy for Continuwuity!
# For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
image: "docker.io/lucaslorentz/caddy-docker-proxy:ci-alpine"
ports:
- 80:80
- 443:443
environment:
- CADDY_INGRESS_NETWORKS=caddy
networks:
- proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
restart: unless-stopped
labels:
caddy: example.com
caddy.reverse_proxy: /.well-known/matrix/* homeserver:8008
homeserver:
image: "forgejo.ellis.link/continuwuation/continuwuity:latest"
restart: unless-stopped
command: /sbin/conduwuit
volumes:
- db:/var/lib/continuwuity
- ./continuwuity-resolv.conf:/etc/resolv.conf # use custom resolvers rather than Docker's
#- ./continuwuity.toml:/etc/continuwuity.toml
environment:
CONTINUWUITY_SERVER_NAME: example.com # EDIT THIS
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 8008
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
homeserver:
image: "forgejo.ellis.link/continuwuation/continuwuity:latest"
restart: unless-stopped
command: /sbin/conduwuit
volumes:
- db:/var/lib/continuwuity
- ./continuwuity-resolv.conf:/etc/resolv.conf # use custom resolvers rather than Docker's
# - ./continuwuity.toml:/etc/continuwuity.toml
environment:
CONTINUWUITY_SERVER_NAME: example.com # EDIT THIS
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 8008
# CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
# Serve .well-known files to tell others to reach Continuwuity on port :443
CONTINUWUITY_WELL_KNOWN: |
{
client=https://matrix.example.com,
server=matrix.example.com:443
}
# Serve .well-known files to tell others to reach Continuwuity on port :443
CONTINUWUITY_WELL_KNOWN: |
{
client=https://matrix.example.com,
server=matrix.example.com:443
}
networks:
- proxy
labels:
caddy: matrix.example.com
caddy.reverse_proxy: "{{upstreams 8008}}"
networks:
- caddy
labels:
caddy: matrix.example.com
caddy.reverse_proxy: "{{upstreams 8008}}"
volumes:
db:
networks:
caddy:
proxy:
@@ -1,57 +1,46 @@
# Continuwuity - Using Caddy Docker Image
services:
caddy:
image: "docker.io/caddy:latest"
ports:
- 80:80
- 443:443
networks:
- caddy
volumes:
- ./data:/data
restart: unless-stopped
configs:
- source: Caddyfile
target: /etc/caddy/Caddyfile
caddy:
image: "docker.io/caddy:latest"
ports:
- 80:80
- 443:443
networks:
- caddy
volumes:
- ./data:/data
# mount your created Caddyfile into the container
- ./Caddyfile:/etc/caddy/Caddyfile
restart: unless-stopped
homeserver:
image: "forgejo.ellis.link/continuwuation/continuwuity:latest"
restart: unless-stopped
command: /sbin/conduwuit
volumes:
- db:/var/lib/continuwuity
- ./continuwuity-resolv.conf:/etc/resolv.conf # use custom resolvers rather than Docker's
#- ./continuwuity.toml:/etc/continuwuity.toml
environment:
CONTINUWUITY_SERVER_NAME: example.com
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 8008
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
homeserver:
image: "forgejo.ellis.link/continuwuation/continuwuity:latest"
restart: unless-stopped
command: /sbin/conduwuit
volumes:
- db:/var/lib/continuwuity
- ./continuwuity-resolv.conf:/etc/resolv.conf # use custom resolvers rather than Docker's
# - ./continuwuity.toml:/etc/continuwuity.toml
environment:
CONTINUWUITY_SERVER_NAME: example.com
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 8008
# CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
## Serve .well-known files to tell others to reach Continuwuity on port :443
CONTINUWUITY_WELL_KNOWN: |
{
client=https://matrix.example.com,
server=matrix.example.com:443
}
## Serve .well-known files to tell others to reach Continuwuity on port :443
CONTINUWUITY_WELL_KNOWN: |
{
client=https://example.com,
server=example.com:443
}
networks:
- caddy
networks:
- proxy
networks:
caddy:
proxy:
volumes:
db:
configs:
Caddyfile:
content: |
https://matrix.example.com:443 {
reverse_proxy http://homeserver:8008
}
https://example.com:443 {
reverse_proxy /.well-known/matrix* http://homeserver:8008
}
@@ -1,51 +1,52 @@
# Continuwuity - With Caddy Labels
services:
caddy:
# This compose file uses caddy-docker-proxy as the reverse proxy for Continuwuity!
# For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
image: "docker.io/lucaslorentz/caddy-docker-proxy:ci-alpine"
ports:
- 80:80
- 443:443
environment:
- CADDY_INGRESS_NETWORKS=caddy
networks:
- caddy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
restart: unless-stopped
caddy:
# This compose file uses caddy-docker-proxy as the reverse proxy for Continuwuity!
# For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
image: "docker.io/lucaslorentz/caddy-docker-proxy:ci-alpine"
ports:
- 80:80
- 443:443
environment:
- CADDY_INGRESS_NETWORKS=caddy
networks:
- proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
restart: unless-stopped
homeserver:
image: "forgejo.ellis.link/continuwuation/continuwuity:latest"
restart: unless-stopped
command: /sbin/conduwuit
volumes:
- db:/var/lib/continuwuity
- ./continuwuity-resolv.conf:/etc/resolv.conf # use custom resolvers rather than Docker's
#- ./continuwuity.toml:/etc/continuwuity.toml
environment:
CONTINUWUITY_SERVER_NAME: example.com
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 8008
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
homeserver:
image: "forgejo.ellis.link/continuwuation/continuwuity:latest"
restart: unless-stopped
command: /sbin/conduwuit
volumes:
- db:/var/lib/continuwuity
- ./continuwuity-resolv.conf:/etc/resolv.conf # use custom resolvers rather than Docker's
# - ./continuwuity.toml:/etc/continuwuity.toml
environment:
CONTINUWUITY_SERVER_NAME: example.com
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 8008
# CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
# Serve .well-known files to tell others to reach Continuwuity on port :443
CONTINUWUITY_WELL_KNOWN: |
{
client=https://example.com,
server=example.com:443
}
# Serve .well-known files to tell others to reach Continuwuity on port :443
CONTINUWUITY_WELL_KNOWN: |
{
client=https://example.com,
server=example.com:443
}
networks:
- proxy
labels:
caddy: example.com
caddy.reverse_proxy: "{{upstreams 8008}}"
networks:
- caddy
labels:
caddy: example.com
caddy.reverse_proxy: "{{upstreams 8008}}"
volumes:
db:
db:
networks:
caddy:
proxy:
@@ -1,56 +1,48 @@
# Continuwuity - Using Caddy Docker Image
services:
caddy:
image: "docker.io/caddy:latest"
ports:
- 80:80
- 443:443
- 8448:8448
networks:
- caddy
volumes:
- ./data:/data
restart: unless-stopped
configs:
- source: Caddyfile
target: /etc/caddy/Caddyfile
caddy:
image: "docker.io/caddy:latest"
ports:
- 80:80
- 443:443
- 8448:8448
networks:
- proxy
volumes:
- ./data:/data
# mount your created Caddyfile into the container
- ./Caddyfile:/etc/caddy/Caddyfile
restart: unless-stopped
homeserver:
image: "forgejo.ellis.link/continuwuation/continuwuity:latest"
restart: unless-stopped
command: /sbin/conduwuit
volumes:
- db:/var/lib/continuwuity
- ./continuwuity-resolv.conf:/etc/resolv.conf # use custom resolvers rather than Docker's
#- ./continuwuity.toml:/etc/continuwuity.toml
environment:
CONTINUWUITY_SERVER_NAME: example.com
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 8008
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
homeserver:
image: "forgejo.ellis.link/continuwuation/continuwuity:latest"
restart: unless-stopped
command: /sbin/conduwuit
volumes:
- db:/var/lib/continuwuity
- ./continuwuity-resolv.conf:/etc/resolv.conf # use custom resolvers rather than Docker's
# - ./continuwuity.toml:/etc/continuwuity.toml
environment:
CONTINUWUITY_SERVER_NAME: example.com
CONTINUWUITY_DATABASE_PATH: /var/lib/continuwuity
CONTINUWUITY_ADDRESS: 0.0.0.0
CONTINUWUITY_PORT: 8008
# CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
## (Optional) Serve .well-known files to tell others to reach Continuwuity on port :443
## If you do this, remove all routes to port :8448 from the compose and Caddyfile
# CONTINUWUITY_WELL_KNOWN: |
# {
# client=https://example.com,
# server=example.com:443
# }
## (Optional) Serve .well-known files to tell others to reach Continuwuity on port :443
## If you do this, remove all routes to port :8448 from the compose and Caddyfile
# CONTINUWUITY_WELL_KNOWN: |
# {
# client=https://example.com,
# server=example.com:443
# }
networks:
- caddy
networks:
- proxy
networks:
caddy:
proxy:
volumes:
db:
configs:
Caddyfile:
content: |
https://example.com:443, https://example.com:8448 {
reverse_proxy http://homeserver:8008
}
db:
+1 -1
View File
@@ -4,7 +4,7 @@ # `!admin rooms`
Commands for managing rooms
## `!admin rooms list-rooms`
## `!admin rooms list`
List all rooms the server knows about
+1 -1
View File
@@ -36,7 +36,7 @@ ## `!admin server admin-notice`
Send a message to the admin room
## `!admin server reload-mods`
## `!admin server reload`
Hot-reload the server
+2 -2
View File
@@ -4,7 +4,7 @@ # `!admin users`
Commands for managing local users
## `!admin users create-user`
## `!admin users create`
Create a new user
@@ -92,7 +92,7 @@ ## `!admin users disable-login`
Disables login for the specified user without deactivating or locking their account. This prevents the user from obtaining new access tokens, but does not invalidate existing sessions.
## `!admin users list-users`
## `!admin users list`
List local users in the database
+5
View File
@@ -1,3 +1,8 @@
## Configuration file reference
This configuration file reflect the latest developments on the `main` branch. To check the configuration from a specific Continuwuity version, browse the [tags](https://forgejo.ellis.link/continuwuation/continuwuity/tags) and navigate to the `conduwuit-example.toml` file for that version.
---
```toml file="../../conduwuit-example.toml"
+5 -3
View File
@@ -13,7 +13,7 @@ ### Slow joins to rooms
Some slowness is to be expected if you're the first person on your homeserver to join a room (which will
always be the case for single-user homeservers). In this situation, your homeserver has to verify the signatures of
all of the state events sent by other servers before your join. To make this process as fast as possible, make sure you have
multiple fast, trusted servers listed in `trusted_servers` in your configuration, and ensure
[multiple fast, trusted servers](./guides/performance.mdx#tuning-your-trusted-servers) listed in `trusted_servers` in your configuration, and ensure
`query_trusted_key_servers_first_on_join` is set to true (the default).
If you need suggestions for trusted servers, ask in the Continuwuity main room.
@@ -23,6 +23,8 @@ ### Slow joins to rooms
the bug caused your homeserver to forget to tell your client. **To fix this, clear your client's cache.** Both Element and Cinny
have a button to clear their cache in the "About" section of their settings.
[Tuning your DNS](./guides/dns.mdx) can also help make initial joins faster.
### Configuration not working as expected
Sometimes you can make a mistake in your configuration that
@@ -64,7 +66,7 @@ ### DNS server overload
### Intermittent federation failures to a specific server
There may be circumstances where servers fail to connect to each other, probably due to a bad DNS cache. In such cases, issuing `!admin debug ping <SERVER_NAME>` would return some errors.
There may be circumstances where servers fail to connect to each other, probably due to a bad DNS cache. In such cases, issuing `!admin debug ping <SERVER_NAME>` would return some errors, and `!admin query resolver cache <SERVER_NAME>` would return a wrong destination endpoint.
To fix this, you can run `!admin query resolver flush-cache <SERVER_NAME>` to clear the bad cache for that domain, and outbound requests should work again.
@@ -121,7 +123,7 @@ ## Debugging
Note that users should not really need to debug things. If you find yourself
debugging and find the issue, please let us know and/or how we can fix it.
Various debug commands can be found in `!admin debug`.
Various debug commands can be [found in `!admin debug`](./reference/admin/debug).
### Debug/Trace log level
+110 -126
View File
@@ -16,21 +16,21 @@
}
},
"node_modules/@emnapi/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"@emnapi/wasi-threads": "1.2.3",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
"integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -39,9 +39,9 @@
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
"integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
"integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -125,13 +125,13 @@
}
},
"node_modules/@rsbuild/core": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.1.5.tgz",
"integrity": "sha512-7TW4U1SH7VxQZzSTIOzvwj5lo9uNTmGpsmTXFr4axQAE4giLDKP3kVUA1ZW4P3/Mz4QQJJyvZP29mVcb8kZCfg==",
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.1.10.tgz",
"integrity": "sha512-lwxC5w88U2AMv6aNwG3VH7+AV33N4JJQjD//egVWFsMbV+OE/bnfCsurSpX8s3lGyRYkJMwUVN+YXMbmmYZFfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/core": "~2.1.3",
"@rspack/core": "~2.1.8",
"@swc/helpers": "^0.5.23"
},
"bin": {
@@ -169,30 +169,30 @@
}
},
"node_modules/@rspack/binding": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.1.3.tgz",
"integrity": "sha512-4UGXJqUHmm36tWG1GgFZz3p8sQ5JuSgWI+gq1xPPoihS41uYJL3cXuJnirTeWsmVrusmYZTQhgU3tl3VYGcJYg==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.1.8.tgz",
"integrity": "sha512-tmAyHzDbPiy8V7HvQqtuPsbs6dPgwV0YjzW5XrPRV9gzf+Hdm7pvsZJKE1QKO9WV5RuvGYav98xIX6O+abZxzQ==",
"dev": true,
"license": "MIT",
"optionalDependencies": {
"@rspack/binding-darwin-arm64": "2.1.3",
"@rspack/binding-darwin-x64": "2.1.3",
"@rspack/binding-linux-arm64-gnu": "2.1.3",
"@rspack/binding-linux-arm64-musl": "2.1.3",
"@rspack/binding-linux-riscv64-gnu": "2.1.3",
"@rspack/binding-linux-riscv64-musl": "2.1.3",
"@rspack/binding-linux-x64-gnu": "2.1.3",
"@rspack/binding-linux-x64-musl": "2.1.3",
"@rspack/binding-wasm32-wasi": "2.1.3",
"@rspack/binding-win32-arm64-msvc": "2.1.3",
"@rspack/binding-win32-ia32-msvc": "2.1.3",
"@rspack/binding-win32-x64-msvc": "2.1.3"
"@rspack/binding-darwin-arm64": "2.1.8",
"@rspack/binding-darwin-x64": "2.1.8",
"@rspack/binding-linux-arm64-gnu": "2.1.8",
"@rspack/binding-linux-arm64-musl": "2.1.8",
"@rspack/binding-linux-riscv64-gnu": "2.1.8",
"@rspack/binding-linux-riscv64-musl": "2.1.8",
"@rspack/binding-linux-x64-gnu": "2.1.8",
"@rspack/binding-linux-x64-musl": "2.1.8",
"@rspack/binding-wasm32-wasi": "2.1.8",
"@rspack/binding-win32-arm64-msvc": "2.1.8",
"@rspack/binding-win32-ia32-msvc": "2.1.8",
"@rspack/binding-win32-x64-msvc": "2.1.8"
}
},
"node_modules/@rspack/binding-darwin-arm64": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.1.3.tgz",
"integrity": "sha512-oOGI0RSL89Ehu9T22rugmfUY9OC2eBqLMeWRYsu7bhlUrjoXeVfGBBSEXCse666BQ1sAiM8hD/k7nqVria/okQ==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.1.8.tgz",
"integrity": "sha512-kia+eWtyWPvR4ntg1bWYoVU8nLPbUg2fG3zgBEocsTcsh5ZENSiEPxEKymDgMyIMONUqj611E0775cdUBoNmqw==",
"cpu": [
"arm64"
],
@@ -204,9 +204,9 @@
]
},
"node_modules/@rspack/binding-darwin-x64": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.1.3.tgz",
"integrity": "sha512-sVqWXNiFTMXAyN362y6IA+eJc8LXZKfHdhEJ/zDuMmRp+u2IvhgaF8tk3vX/OmeB9jydVjySijuiqk8No/FoCA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.1.8.tgz",
"integrity": "sha512-08pBkFhlD3Y3Qzh94w/Fc3skaIE3e96kl2P14m8+tnYTcglpOfpA2OwS3iHt9fOqy0HjoAVe6/MW3cBgs5iabA==",
"cpu": [
"x64"
],
@@ -218,9 +218,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-gnu": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.1.3.tgz",
"integrity": "sha512-aCy9Zli/2Qf+Ee5otXfFQ6mhv5fEyn0wIoBVmouqtJoqOO21et6UTtJ+LHLsMDolwGLyHERAljeSFSmYX3/O5A==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.1.8.tgz",
"integrity": "sha512-KLniMc9GzhKpVqhPzaJo3KJwzdAllXVVqZIk/uL1QipXOxs57fgM4u7IexKPFVla0o/u1PQG/Ah2YLDmda24Ow==",
"cpu": [
"arm64"
],
@@ -235,9 +235,9 @@
]
},
"node_modules/@rspack/binding-linux-arm64-musl": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.1.3.tgz",
"integrity": "sha512-flIE7eluz0d21Fn28EVm3vPwoJooOSqtmjLFVSuOMcoCbwV9clfor195oIrAppp/W7dL/3XquFuVfrsa01Jy8Q==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.1.8.tgz",
"integrity": "sha512-yUKAxHNGnICtw5RnxFWu4dHtsz/tdt7rbeFcsINNVre9HcrRxf5XP+FbOGL/SMxd9oM9XCo10paU2WckTKwbEA==",
"cpu": [
"arm64"
],
@@ -252,9 +252,9 @@
]
},
"node_modules/@rspack/binding-linux-riscv64-gnu": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-2.1.3.tgz",
"integrity": "sha512-yojg8elye1nhsNeGncw0NrZ4pMGF7NVebR80CLg72TXyzfYwFJlFTdU5yUYb1Gy+JXIvrSCwzQt2QkyiEvmkfg==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-2.1.8.tgz",
"integrity": "sha512-gg4S1jaitwYPHR9HZ3zNGH1EK2GXINm66p4kEpOP1gbc+akyOouVF/dMcu9NGPlRg58FbEhVRZYKu7Z/zcpKHg==",
"cpu": [
"riscv64"
],
@@ -269,9 +269,9 @@
]
},
"node_modules/@rspack/binding-linux-riscv64-musl": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-2.1.3.tgz",
"integrity": "sha512-6PvbXb3FOK4X3S5QGvoSW/sqExmsvAoPnQ/YSFrXvTphkXFezA7wnobmGBHT8JQP31hcFRzJHIZSIOKktzSzzg==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-2.1.8.tgz",
"integrity": "sha512-b/aU5j1h368SLNyz5u+flqpZVhzSZ1UIslaj9sZJuAvqkGWv3xsjc/28/PTo/RYXCxd0FNVAxTxWHKvRiAAS8w==",
"cpu": [
"riscv64"
],
@@ -286,9 +286,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-gnu": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.1.3.tgz",
"integrity": "sha512-lMXjoGKf0SnviH596fmTszgtnXLHmWOoE90G8grG9MvKVa3pelRmfps5ewZL9s8ENf3NXRfOxIhIf/M9as6MqA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.1.8.tgz",
"integrity": "sha512-EyegohSx0BJRqieCg9f/caCqFARRWkqI5hwJt6k530MoOTLeq8I3vsbeg24/2MktwIC1dmJi8bl0+WhPKQs4eQ==",
"cpu": [
"x64"
],
@@ -303,9 +303,9 @@
]
},
"node_modules/@rspack/binding-linux-x64-musl": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.1.3.tgz",
"integrity": "sha512-0esT35v7pW2ZsJTMc/zDUHwpNXlPqyUCyuiLJvrAAUcdENrgOVe4DmFrgVJ2hwqI4GjeN1VBnGRJ8c+edAHH5w==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.1.8.tgz",
"integrity": "sha512-I6E+goN+UQ297q4r1qdbiAyNCI3t0+a5Y0xDIAPOZfRDRxDTnH/LF8/y65gjsJoKRKyn7zxRC0T/NURTkRNQ9A==",
"cpu": [
"x64"
],
@@ -320,9 +320,9 @@
]
},
"node_modules/@rspack/binding-wasm32-wasi": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.1.3.tgz",
"integrity": "sha512-UsrDjD59UEP0mhfN/Z+uTc3vLgiUvIr+mn92WC1sbQi9gtZohTYvaQYFhWuMkBqsACGcmZp704JAbbSrVrVYCA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.1.8.tgz",
"integrity": "sha512-om7GAKWAU3lcSvbCon2m7mzw8v9OTrO2LW2MZ1lGe/uVJJmwGGkl9HVoXFyWFLrN6YVFyx8iP+AkN4owDWB9Cw==",
"cpu": [
"wasm32"
],
@@ -330,15 +330,15 @@
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "1.11.1",
"@emnapi/runtime": "1.11.1",
"@emnapi/core": "1.11.3",
"@emnapi/runtime": "1.11.3",
"@napi-rs/wasm-runtime": "1.1.6"
}
},
"node_modules/@rspack/binding-win32-arm64-msvc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.1.3.tgz",
"integrity": "sha512-42RS/SwKBTkNvXIPZXqTn0yEFN8zwGRfRe/ly0GDvPp/KxpLMFWxJmLxgtoLompU+0UCGvV4KpcBENt3oWs6BA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.1.8.tgz",
"integrity": "sha512-WDnsP/SUb9zbxyGX9XjPw5AXrX86u5oidn0MDdfJduOOqdCSpHwmRjlQ8NUJhbBq9WqVJMFlcab7NwZVWX/yyg==",
"cpu": [
"arm64"
],
@@ -350,9 +350,9 @@
]
},
"node_modules/@rspack/binding-win32-ia32-msvc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.1.3.tgz",
"integrity": "sha512-thk43H1JHHbetNF3txcsGXeOwZi2m6Luf/uVUqNORXjxr5VV8woHAgaAx4QXVZRg70z1VDjd8mBWnHBnKI0FGA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.1.8.tgz",
"integrity": "sha512-QiMQMPNDiY3dhhaIdaFPzcPDC06cEYkNY89ea+EmDvNVgZq6V+2mFS/WnzZVMeEbGAYJCjsv/ABhhLT1hlYMvg==",
"cpu": [
"ia32"
],
@@ -364,9 +364,9 @@
]
},
"node_modules/@rspack/binding-win32-x64-msvc": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.1.3.tgz",
"integrity": "sha512-ObaUcj+BHo/aBL1weyM3orApaHyAR5Phr3YNEpQEifxjZbNKM1iO5X5prN4OqEv3H+7o5e/Wh9Fy3N7Vvd+psA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.1.8.tgz",
"integrity": "sha512-b7sA5eB64vo2mbsuc//MOYzVLeCKHPn0dfP/GmNEoHdWbhRgZ/orZLWurYMQj04ELTLW6YCJEy59g5KRzNYHfw==",
"cpu": [
"x64"
],
@@ -378,13 +378,13 @@
]
},
"node_modules/@rspack/core": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.1.3.tgz",
"integrity": "sha512-1iGnxLrP+iyY0ZSjLZeQTaxrISpQz4yLyqJPmaF/l4uw27/dBcrloszAeLOJQ9jiv7EJtU0T5zB+LOFPpivIxA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.1.8.tgz",
"integrity": "sha512-na1kyA6Mj8/LWw9O3A8NsrG9rNKN3Iq2WiXrEuIwsU5r/Nl/evm3hO7bWKHxgsRyydI6W7okwx3MXgf8rzel6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rspack/binding": "2.1.3"
"@rspack/binding": "2.1.8"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
@@ -419,20 +419,21 @@
}
},
"node_modules/@rspress/core": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.18.tgz",
"integrity": "sha512-DBpsr/6XAItQkZPL5FSjthLGuzdG9ks/7EOxkqaaXp93uAT54LO0PD5Dkoy6ydup1PnvkpHyiHpUyDcnHHTVhg==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.19.tgz",
"integrity": "sha512-m0KfQNNXxjP975652nIOwUTqrvbJapRp4NpnoQMc5heawGhkC4+F7zTHvxY+huvzGYnKtL2fMMTrgWhEjKkrug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mdx-js/mdx": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@rsbuild/core": "^2.1.5",
"@rsbuild/core": "^2.1.8",
"@rsbuild/plugin-react": "~2.1.0",
"@rspress/shared": "2.0.18",
"@rspress/shared": "2.0.19",
"@shikijs/rehype": "^4.2.0",
"@types/unist": "^3.0.3",
"@unhead/react": "^2.1.15",
"@types/mdast": "^4.0.4",
"@types/react": "^19.2.17",
"@unhead/react": "^2.1.16",
"body-scroll-lock": "4.0.0-beta.0",
"clsx": "2.1.1",
"copy-to-clipboard": "^3.3.3",
@@ -443,10 +444,9 @@
"mdast-util-mdxjs-esm": "^2.0.1",
"medium-zoom": "1.1.0",
"nprogress": "^0.2.0",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-lazy-with-preload": "^2.2.1",
"react-reconciler": "0.33.0",
"react-render-to-markdown": "19.1.0",
"react-router-dom": "^7.18.1",
"rehype-external-links": "^3.0.0",
@@ -460,9 +460,9 @@
"scroll-into-view-if-needed": "^3.1.0",
"shiki": "^4.2.0",
"unified": "^11.0.5",
"unist-util-remove": "^4.0.0",
"unist-util-visit": "^5.1.0",
"unist-util-visit-children": "^3.0.0"
"unist-util-visit-children": "^3.0.0",
"vfile": "^6.0.3"
},
"bin": {
"rspress": "bin/rspress.js"
@@ -472,9 +472,9 @@
}
},
"node_modules/@rspress/plugin-client-redirects": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.18.tgz",
"integrity": "sha512-q70ufN0S125kl0QlucO7JuiZ0WJaxFOtPPoGu7eWwam9fb+i4YsWnOWtAKSKGH4JP5+897mgpDPP+dmfl9DPCw==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.19.tgz",
"integrity": "sha512-BUwgEyj8vCkU58cV3Gp97aTSnfGJm7aXh40K0Dot1BqsCQ8ekdsMuaLXrICROdP9/ctd4oALtLBkOE+PKTa4Sg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -485,9 +485,9 @@
}
},
"node_modules/@rspress/plugin-sitemap": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.18.tgz",
"integrity": "sha512-XfaXzZx+ASN9OMTc0fOvW8QmbmyCyDWkO8xJj4XF4E0YJtsu7j/65+v8/KXXdwpywOfUIQ6M94/meRTy2urNmQ==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.19.tgz",
"integrity": "sha512-9ydrpj9hmmXWKWKIXUqJ1UrfxSvfxhHZRBZLb5OkTlOuQBNAg6BpsrW1WHoJqJr7p+3zqlz7S/9HTzSA3+EYcA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -498,14 +498,16 @@
}
},
"node_modules/@rspress/shared": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.18.tgz",
"integrity": "sha512-GJswqJQCPSxvBt5r+gJzz8Em8EEK/sOmCQjTpemTvldvxr1Lva85BGVnSQZOgofnM0nrjt18kn4mmnuuSstbpA==",
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.19.tgz",
"integrity": "sha512-INrETllWuR49lksqCz+xeLSO6rKtHA+5Ix2YQWcP9nerCuTSyGYwH8eBC0HrIacJkGHxB8wkkbV99tyzGtY8Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rsbuild/core": "^2.1.5",
"@rsbuild/core": "^2.1.8",
"@shikijs/rehype": "^4.2.0",
"@types/react": "^19.2.17",
"mdast-util-mdx-jsx": "^3.2.0",
"unified": "^11.0.5"
}
},
@@ -723,7 +725,6 @@
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1083,13 +1084,13 @@
"license": "ISC"
},
"node_modules/@unhead/react": {
"version": "2.1.15",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.15.tgz",
"integrity": "sha512-5hfAaZ3XJq9JkspRzZdSPsMrXXA8v/SKiEOxZcN9L40o44byF/50bcQuOLgSSCAx8802mI5VG32KZXWTtsLu9Q==",
"version": "2.1.17",
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.17.tgz",
"integrity": "sha512-KmcYksDjlLozL0fxUjIDwH/0k6+Lg2HWHLUPlkRW8Gl08hdB1AbLB5iU9QxFvkaT8p0Q4dFEqZWnz3g6RMeiRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"unhead": "2.1.15"
"unhead": "2.1.17"
},
"funding": {
"url": "https://github.com/sponsors/harlan-zw"
@@ -1272,8 +1273,7 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
@@ -3176,9 +3176,9 @@
}
},
"node_modules/react": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3186,16 +3186,16 @@
}
},
"node_modules/react-dom": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.7"
"react": "^19.2.8"
}
},
"node_modules/react-lazy-with-preload": {
@@ -3736,9 +3736,9 @@
}
},
"node_modules/unhead": {
"version": "2.1.15",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.15.tgz",
"integrity": "sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA==",
"version": "2.1.17",
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.17.tgz",
"integrity": "sha512-HLMKXOszRhAPBrr6VlqCeVeJq2kbC4kXwzGLEZvvojPLWNYTJw22xG7Bfwhsvs31+IBet3Wl8ADg9dwYdyphfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3810,22 +3810,6 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-remove": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-4.0.0.tgz",
"integrity": "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-is": "^6.0.0",
"unist-util-visit-parents": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/unist-util-stringify-position": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+1 -1
View File
@@ -18,7 +18,7 @@
#[derive(Debug, Subcommand)]
pub enum RoomCommand {
/// List all rooms the server knows about
#[clap(alias = "list")]
#[command(name = "list", alias = "list-rooms")]
ListRooms {
page: Option<usize>,
+1 -1
View File
@@ -40,7 +40,7 @@ pub enum ServerCommand {
},
/// Hot-reload the server
#[clap(alias = "reload")]
#[command(name = "reload", alias = "reload-mods")]
ReloadMods,
#[cfg(unix)]
+2 -2
View File
@@ -10,7 +10,7 @@
#[derive(Debug, Subcommand)]
pub enum UserCommand {
/// Create a new user
#[clap(alias = "create")]
#[command(name = "create", alias = "create-user")]
CreateUser {
/// Username of the new user
username: String,
@@ -163,7 +163,7 @@ pub enum UserCommand {
},
/// List local users in the database
#[clap(alias = "list")]
#[command(name = "list", alias = "list-users")]
ListUsers,
/// Lists all the rooms (local and remote) that the specified user is
+1
View File
@@ -541,6 +541,7 @@ pub(crate) async fn create_room_route(
&content,
&state_key,
None,
None,
)
.boxed()
.await
+11 -1
View File
@@ -1,7 +1,11 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduwuit::{Err, Result, err, matrix::pdu::PartialPdu, utils};
use conduwuit::{
Err, Result, err,
matrix::pdu::{PartialPdu, sticky},
utils,
};
use ruma::{api::client::message::send_message_event, events::MessageLikeEventType};
use serde_json::from_str;
@@ -81,6 +85,12 @@ pub(crate) async fn send_message_event_route(
PartialPdu {
event_type: body.event_type.clone().into(),
content,
sticky: services
.config
.allow_sticky_events
.then_some(body.sticky_duration_ms)
.flatten()
.map(sticky::object),
unsigned: Some(unsigned),
timestamp: if body.identity.is_appservice() {
body.timestamp
+1
View File
@@ -49,6 +49,7 @@ pub(crate) async fn send_state_event_for_key_route(
&body.event_type,
&body.body.body,
&body.state_key,
body.sticky_duration_ms,
if body.identity.is_appservice() {
body.timestamp
} else {
+81 -4
View File
@@ -4,7 +4,7 @@
Result, at, debug_warn, err, extract_variant,
matrix::{
Event,
pdu::{PduCount, PduEvent},
pdu::{PduCount, PduEvent, sticky},
},
trace,
utils::{
@@ -20,12 +20,12 @@
future::{OptionFuture, join, join3, join4, try_join, try_join3},
};
use ruma::{
OwnedRoomId, OwnedUserId, RoomId, UserId,
MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedUserId, RoomId, UserId,
api::client::sync::sync_events::{
UnreadNotificationsCount,
v3::{
Ephemeral, JoinedRoom, RoomAccountData, RoomSummary, State as RoomState, StateEvents,
Timeline,
Sticky, Timeline,
},
},
assign,
@@ -42,7 +42,7 @@
use super::{load_timeline, share_encrypted_room};
use crate::client::{
TimelinePdus, ignored_filter,
TimelinePdus, ignored_filter, is_ignored_pdu,
sync::v3::{
DEFAULT_TIMELINE_LIMIT, DeviceListUpdates, SyncContext, prepare_lazily_loaded_members,
state::{build_state_incremental, build_state_initial},
@@ -76,6 +76,7 @@ pub(super) async fn load_joined_room(
StateAndTimeline {
state_events,
timeline,
sticky,
summary,
notification_counts,
device_list_updates,
@@ -111,6 +112,7 @@ pub(super) async fn load_joined_room(
RoomState::Before(state_events)
},
ephemeral,
sticky,
unread_thread_notifications: BTreeMap::new(),
});
@@ -251,11 +253,82 @@ async fn build_ephemeral(
struct StateAndTimeline {
state_events: Vec<PduEvent>,
timeline: Timeline,
sticky: Sticky,
summary: Option<RoomSummary>,
notification_counts: Option<UnreadNotificationsCount>,
device_list_updates: DeviceListUpdates,
}
async fn build_sticky_events(
services: &Services,
sync_context: SyncContext<'_>,
room_id: &RoomId,
timeline: &TimelinePdus,
joined_since_last_sync: bool,
) -> Sticky {
if !services.config.allow_sticky_events {
return Sticky::new();
}
let SyncContext {
syncing_user,
last_sync_end_count,
current_count,
..
} = sync_context;
let timeline_event_ids: HashSet<_> =
timeline.pdus.iter().map(|(_, pdu)| &pdu.event_id).collect();
let now = u64::from(MilliSecondsSinceUnixEpoch::now().get());
// on join the client needs every unexpired sticky event, not just the ones
// since its last sync
let since = (!joined_since_last_sync)
.then_some(last_sync_end_count)
.flatten()
.map(PduCount::Normal);
// nothing older than this can still be sticky, so the walk back never goes far
let oldest_sticky_ts = now.saturating_sub(sticky::MAX_DURATION_MS);
let mut events = Vec::new();
let pdus = services
.rooms
.timeline
.pdus_rev(room_id, Some(PduCount::Normal(current_count).saturating_add(1)))
.ignore_err();
pin!(pdus);
while let Some((count, mut pdu)) = pdus.next().await {
if since.is_some_and(|since| count <= since)
|| u64::from(pdu.origin_server_ts) < oldest_sticky_ts
{
break;
}
let is_sticky = pdu
.sticky
.as_deref()
.is_some_and(|sticky| sticky::is_sticky(pdu.origin_server_ts, sticky, now));
if !is_sticky
|| timeline_event_ids.contains(&pdu.event_id)
|| is_ignored_pdu(services, &pdu, syncing_user)
.await
.unwrap_or(true)
{
continue;
}
pdu.set_unsigned(Some(syncing_user));
events.push(Event::into_format(pdu));
}
events.reverse();
assign!(Sticky::new(), { events })
}
/// Compute changes to the room's state and timeline.
#[tracing::instrument(level = "debug", skip_all)]
async fn build_state_and_timeline(
@@ -274,6 +347,9 @@ async fn build_state_and_timeline(
check_joined_since_last_sync(services, shortstatehashes, sync_context),
)
.await?;
let sticky =
build_sticky_events(services, sync_context, room_id, &timeline, joined_since_last_sync)
.await;
let state_events = build_state_events(
services,
@@ -342,6 +418,7 @@ async fn build_state_and_timeline(
prev_batch: prev_batch.as_ref().map(ToString::to_string),
events: filtered_timeline,
}),
sticky,
summary,
notification_counts,
device_list_updates,
+1
View File
@@ -328,6 +328,7 @@ fn create_dummy_leave_event(
.expect("Timestamp is valid js_int value"),
kind: TimelineEventType::RoomMember,
content: RawValue::from_string(r#"{"membership": "leave"}"#.to_owned()).unwrap(),
sticky: None,
state_key: Some(syncing_user.as_str().into()),
unsigned: None,
// The following keys are dropped on conversion
+2 -1
View File
@@ -201,6 +201,7 @@ pub(crate) async fn sync_events_route(
.update_device_last_seen(sender_user, Some(sender_device), client_ip)
.await;
let mut wake_receiver = services.sync.subscribe_to_wake(sender_user).await;
let response = build_sync_events(&services, &body).await?;
if body.body.full_state
|| !(response.rooms.is_empty()
@@ -216,7 +217,7 @@ pub(crate) async fn sync_events_route(
// Stop hanging if new info arrives
let default = Duration::from_secs(30);
let duration = cmp::min(body.body.timeout.unwrap_or(default), default);
_ = tokio::time::timeout(duration, services.sync.wait_for_wake(sender_user)).await;
_ = tokio::time::timeout(duration, wake_receiver.changed()).await;
// Retry returning data
build_sync_events(&services, &body).await
+707 -121
View File
File diff suppressed because it is too large Load Diff
+11 -4
View File
@@ -21,12 +21,19 @@
/// Note: Unstable features are used while developing new features. Clients
/// should avoid using unstable features in their stable releases
pub(crate) async fn get_supported_versions_route(
State(services): State<crate::State>,
_body: Ruma<get_supported_versions::Request>,
) -> Result<get_supported_versions::Response> {
Ok(assign!(
get_supported_versions::Response::new(versions()),
{ unstable_features: unstable_features() }
))
let mut unstable_features = unstable_features();
if services.config.allow_sticky_events {
// sticky events (https://github.com/matrix-org/matrix-spec-proposals/pull/4354)
unstable_features.insert("org.matrix.msc4354".to_owned(), true);
}
Ok(assign!(get_supported_versions::Response::new(versions()), {
unstable_features
}))
}
/// # `GET /_continuwuity/server_version`
+177 -1
View File
@@ -238,42 +238,155 @@ pub struct Config {
#[serde(default = "default_db_write_buffer_capacity_mb")]
pub db_write_buffer_capacity_mb: f64,
/// The maximum number of Persisted Data Units (PDUs) to cache.
/// Accepts any 32-bit integer.
///
/// PDUs are events broadcast from one homeserver to any others that have
/// joined the same room (identified by Room ID). They are persisted in
/// long-term storage and record the history of messages and state
/// for a room.
///
/// An example of a PDU would be a message or joining a room.
///
/// Setting this higher might be useful if the server has a large memory
/// capacity and is noticeably I/O limited.
///
/// This defaults to 100,000 + (10,000 * CPU core count)
///
/// default: varies by system
#[serde(default = "default_pdu_cache_capacity")]
pub pdu_cache_capacity: u32,
/// The maximum number of auth chains to cache.
/// Accepts any 32-bit integer.
///
/// When determining is a user is authorized to send an event,
/// the server will first check the cache for an auth chain.
/// If the auth chain is not present in the cache,
/// The server will read the auth chain from the database
/// and add it to the cache.
///
/// This defaults to 100,000 + (10,000 * CPU core count)
///
/// default: varies by system
#[serde(default = "default_auth_chain_cache_capacity")]
pub auth_chain_cache_capacity: u32,
/// Determines the cache size for eventid data references.
/// Accepts any 32-bit integer.
///
/// Each matrix event can be referenced in the database via either
/// an eventid (string) or a shorteventid (64-bit integer).
/// This caps the capacity of cached eventids referenced
/// by their shorteventid.
///
/// This defaults to 100,000 + (50,000 * CPU core count)
///
/// default: varies by system
#[serde(default = "default_shorteventid_cache_capacity")]
pub shorteventid_cache_capacity: u32,
/// Determines the cache size for eventid data references.
/// Accepts any 32-bit integer.
///
/// Each matrix event can be referenced in the database via either
/// an eventid (string) or a shorteventid (64-bit integer).
/// This caps the capacity of cached shorteventids referenced
/// by their eventid.
///
/// This defaults to 100,000 + (25,000 * CPU core count)
///
/// default: varies by system
#[serde(default = "default_eventidshort_cache_capacity")]
pub eventidshort_cache_capacity: u32,
/// Determines the cache size for pdu data references.
/// Accepts any 32-bit integer.
///
/// This caps the capacity of cached pdus referenced
/// by their eventid.
///
/// This defaults to 100,000 + (25,000 * CPU core count)
///
/// default: varies by system
#[serde(default = "default_eventid_pdu_cache_capacity")]
pub eventid_pdu_cache_capacity: u32,
/// Determines the cache size for state key data references.
/// Accepts any 32-bit integer.
///
/// State keys are used by the server to verify that a token
/// from a third-party invite event was actually generated
/// by the server where the room resides.
///
/// These state keys can be referenced in the database via either
/// a statekey (string) or a shortstatekey (64-bit integer).
/// This caps the capacity of cached statekeys referenced
/// by shortstatekeys.
///
/// This defaults to 100,000 + (10,000 * CPU core count)
///
/// default: varies by system
#[serde(default = "default_shortstatekey_cache_capacity")]
pub shortstatekey_cache_capacity: u32,
/// Determines the cache size for state key data references.
/// Accepts any 32-bit integer.
///
/// State keys are used by the server to verify that a token
/// from a third-party invite event was actually generated
/// by the server where the room resides.
///
/// These state keys can be referenced in the database via either
/// a statekey (string) or a shortstatekey (64-bit integer).
/// This caps the capacity of cached shortstatekeys referenced
/// by statekeys.
///
/// This defaults to 100,000 + (10,000 * CPU core count)
///
/// default: varies by system
#[serde(default = "default_statekeyshort_cache_capacity")]
pub statekeyshort_cache_capacity: u32,
/// Determines the cache size for event_data references scoped
/// by server_name.
/// Accepts any 32-bit integer.
///
/// Event data can be referenced in the database by server name.
/// This is useful if the server needs to clear all events associated
/// with a particular federated server, for example.
/// This caps the capacity of cached event_data referenced
/// by server_name.
///
/// This defaults to 500,000 + (100,000 * CPU core count)
///
/// default: varies by system
#[serde(default = "default_servernameevent_data_cache_capacity")]
pub servernameevent_data_cache_capacity: u32,
/// Determines the cache size for room states.
/// Accepts any 32-bit integer.
///
/// To ensure data integrity of room events across asynchronous
/// requests from federated servers, Matrix tracks the state
/// of a room using a statehash and the changes since the
/// previous (parent) statehash.
/// This caps the capacity of cached room state data.
///
/// This defaults to 100 * CPU core count.
///
/// default: varies by system
#[serde(default = "default_stateinfo_cache_capacity")]
pub stateinfo_cache_capacity: u32,
/// Determines the cache size for spacehierarchy data.
/// Accepts any 32-bit integer.
///
/// Each roomid contains info on its place in the space hierarchy.
/// This caps the capacity of cached spacehierarchy data.
///
/// This defaults to 1,000 * CPU core count.
///
/// default: varies by system
#[serde(default = "default_roomid_spacehierarchy_cache_capacity")]
pub roomid_spacehierarchy_cache_capacity: u32,
@@ -431,6 +544,14 @@ pub struct Config {
#[serde(default = "default_max_request_size")]
pub max_request_size: usize,
/// Maximum number of prev_events the server will request from
/// other servers.
///
/// When requesting room events from another server, this server will
/// specify a maximum prev_events for the other server to return in its
/// response. This can be used for things like backfilling room data and
/// getting missing events.
///
/// default: 1024
#[serde(default = "default_max_fetch_prev_events")]
pub max_fetch_prev_events: u16,
@@ -890,13 +1011,23 @@ pub struct Config {
///
/// [1]: https://github.com/jonhoo/inferno
/// [2]: www.speedscope.app
///
/// default: false
#[serde(default)]
pub tracing_flame: bool,
/// Which log level tracing_flame will trace.
///
/// See "tracing_flame" for more details.
///
/// default: "info"
#[serde(default = "default_tracing_flame_filter")]
pub tracing_flame_filter: String,
/// The output path for tracing_flame data.
///
/// See "tracing_flame" for more details.
///
/// default: "./tracing.folded"
#[serde(default = "default_tracing_flame_output_path")]
pub tracing_flame_output_path: String,
@@ -1158,6 +1289,9 @@ pub struct Config {
#[serde(default = "default_rocksdb_log_level")]
pub rocksdb_log_level: String,
/// Whether to output RocksDB errors to stderr.
///
/// default: false
#[serde(default)]
pub rocksdb_log_stderr: bool,
@@ -1430,6 +1564,18 @@ pub struct Config {
/// display: sensitive
pub emergency_password: Option<String>,
/// Specifies the path where push notifications will be sent to a
/// push gateway.
///
/// When a Matrix client registers for push notifications, it will specify
/// a push gateway url where notification events will be sent.
/// According to the current spec for Matrix Push Gateways, the path should
/// always be "/_matrix/push/v1/notify".
/// You should only change this if you have configured your own push gateway
/// that requires a different path.
///
/// For more information on Push Gateways: https://spec.matrix.org/latest/push-gateway-api/
///
/// default: "/_matrix/push/v1/notify"
#[serde(default = "default_notification_push_path")]
pub notification_push_path: String,
@@ -1526,6 +1672,14 @@ pub struct Config {
#[serde(default = "true_fn")]
pub allow_incoming_typing: bool,
/// Allow the use of unstable persistent "sticky events" ([MSC4354])
///
/// MSC4354: https://c10y.cc/MSC4354
///
/// default: false
#[serde(default)]
pub allow_sticky_events: bool,
/// Maximum time federation user can indicate typing.
///
/// default: 30
@@ -1588,9 +1742,17 @@ pub struct Config {
///
/// Defaults to true for now, but this is highly subject to change, likely
/// in the next release.
///
/// default: true
#[serde(default = "true_fn")]
pub allow_legacy_media: bool,
/// If set to true, prevents fetching new legacy remote media.
///
/// Legacy media may still be accessible if it was already fetched
/// previously and allow_legacy_media is true.
///
/// default: true
#[serde(default = "true_fn")]
pub freeze_legacy_media: bool,
@@ -1846,7 +2008,7 @@ pub struct Config {
/// User agent that is used specifically when fetching url previews.
///
/// default: "continuwuity/<version> (bot; +https://continuwuity.org)"
/// default: "continuwuity/<version> (embedbot; facebookexternalhit/1.1; +https://continuwuity.org)"
pub url_preview_user_agent: Option<String>,
/// Determines whether audio and video files will be downloaded for URL
@@ -2050,6 +2212,20 @@ pub struct Config {
#[serde(default)]
pub tokio_console: bool,
/// A list of flags that modify startup behavior.
/// Accepts an array of strings.
///
/// During startup, the program will check for the presence of certain
/// strings in this array. Matching values will modify how the program runs
/// those startup tasks.
///
/// If this variable is empty or contains no matches, the server will start
/// normally.
///
/// Currently this only checks for "smoke" which enables "Smoketest mode."
/// This mode exits the program after running startup tasks.
///
/// default: false
#[serde(default)]
pub test: BTreeSet<String>,
+3 -1
View File
@@ -88,7 +88,9 @@ pub enum Error {
#[error(transparent)]
YamlSer(#[from] serde_saphyr::ser_error::Error),
#[error(transparent)]
ResolveServer(#[from] resolvematrix::error::ResolveServerError),
ResolveServer(#[from] resolvematrix::error::ServerResolverBuilderError),
#[error(transparent)]
ServerResolutionError(#[from] resolvematrix::error::ServerResolutionError),
// ruma/conduwuit
#[error("Arithmetic operation failed: {0}")]
+4
View File
@@ -181,6 +181,10 @@ fn as_mut_pdu(&mut self) -> &mut Pdu { unimplemented!("not a mutable Pdu") }
/// The state key for this event.
fn state_key(&self) -> Option<&str>;
/// The MSC4354 sticky object, verbatim, if the event has one.
#[inline]
fn sticky(&self) -> Option<&RawJsonValue> { None }
/// The event type.
fn kind(&self) -> &TimelineEventType;
+10
View File
@@ -9,6 +9,7 @@
use serde_json::json;
use super::{Event, redact};
use crate::matrix::pdu::sticky;
pub struct Owned<E: Event>(pub(super) E);
@@ -36,6 +37,9 @@ fn from(event: Ref<'a, E>) -> Self {
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(sticky_object) = event.sticky() {
json[sticky::PDU_KEY] = json!(sticky_object);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
@@ -67,6 +71,9 @@ fn from(event: Ref<'a, E>) -> Self {
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(sticky_object) = event.sticky() {
json[sticky::PDU_KEY] = json!(sticky_object);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
@@ -98,6 +105,9 @@ fn from(event: Ref<'a, E>) -> Self {
if let Some(state_key) = event.state_key() {
json["state_key"] = json!(state_key);
}
if let Some(sticky_object) = event.sticky() {
json[sticky::PDU_KEY] = json!(sticky_object);
}
if let Some(unsigned) = event.unsigned() {
json["unsigned"] = json!(unsigned);
}
+10 -4
View File
@@ -1,4 +1,7 @@
use ruma::{CanonicalJsonObject, OwnedEventId, room_version_rules::RoomVersionRules};
use ruma::{
CanonicalJsonObject, EventId, OwnedEventId,
room_version_rules::{EventIdFormatVersion, RoomVersionRules},
};
use serde_json::value::RawValue as RawJsonValue;
use crate::{Result, err};
@@ -24,8 +27,11 @@ pub fn gen_event_id(
value: &CanonicalJsonObject,
room_version_rules: &RoomVersionRules,
) -> Result<OwnedEventId> {
assert_ne!(
room_version_rules.event_id_format,
EventIdFormatVersion::V1,
"Continuwuity does not support PDU v1"
);
let reference_hash = ruma::signatures::reference_hash(value, room_version_rules)?;
let event_id: OwnedEventId = format!("${reference_hash}").try_into()?;
Ok(event_id)
Ok(EventId::new_v2_or_v3(&reference_hash)?)
}
+12
View File
@@ -3,6 +3,7 @@
mod partial;
mod raw_id;
mod redact;
pub mod sticky;
#[cfg(test)]
mod tests;
mod unsigned;
@@ -46,6 +47,11 @@ pub struct Pdu {
pub content: Box<RawJsonValue>,
/// MSC4354 sticky object, kept verbatim because it is signed. Interpret it
/// with [`sticky`], never by rewriting it.
#[serde(rename = "msc4354_sticky", default, skip_serializing_if = "Option::is_none")]
pub sticky: Option<Box<RawJsonValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<StateKey>,
@@ -137,6 +143,9 @@ fn sender(&self) -> &UserId { &self.sender }
#[inline]
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
#[inline]
fn sticky(&self) -> Option<&RawJsonValue> { self.sticky.as_deref() }
#[inline]
fn kind(&self) -> &TimelineEventType { &self.kind }
@@ -208,6 +217,9 @@ fn sender(&self) -> &UserId { &self.sender }
#[inline]
fn state_key(&self) -> Option<&str> { self.state_key.as_deref() }
#[inline]
fn sticky(&self) -> Option<&RawJsonValue> { self.sticky.as_deref() }
#[inline]
fn kind(&self) -> &TimelineEventType { &self.kind }
+3
View File
@@ -20,6 +20,8 @@ pub struct PartialPdu {
pub content: Box<RawJsonValue>,
pub sticky: Option<Box<RawJsonValue>>,
pub unsigned: Option<Unsigned>,
pub state_key: Option<StateKey>,
@@ -66,6 +68,7 @@ fn default() -> Self {
Self {
event_type: "m.room.message".into(),
content: to_raw_value(&json!({})).unwrap(),
sticky: None,
unsigned: None,
state_key: None,
redacts: None,
+3
View File
@@ -11,6 +11,9 @@ pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) ->
self.unsigned = None;
// a redacted sticky event is just a normal event (MSC4354)
self.sticky = None;
let mut content = serde_json::from_str(self.content.get()).map_err(|e| {
err!(Request(BadJson("Failed to deserialize content into type: {e}")))
})?;
+155
View File
@@ -0,0 +1,155 @@
//! Sticky event helpers, as defined by [MSC4354].
//!
//! [MSC4354]: https://github.com/matrix-org/matrix-spec-proposals/pull/4354
use ruma::{UInt, events::sticky::StickyDurationMs};
use serde::Deserialize;
use serde_json::{
json,
value::{RawValue as RawJsonValue, to_raw_value},
};
/// Name of the sticky object at the top level of a PDU. Unstable prefix.
pub const PDU_KEY: &str = "msc4354_sticky";
/// Name of the expiry hint injected into `unsigned`. Unstable prefix.
pub const TTL_UNSIGNED_KEY: &str = "msc4354_sticky_duration_ttl_ms";
/// The longest an event may remain sticky (one hour).
#[allow(clippy::as_conversions)]
pub const MAX_DURATION_MS: u64 = StickyDurationMs::MAX as u64;
#[derive(Deserialize)]
struct Object {
duration_ms: u64,
}
/// Build the sticky object for an event we are creating locally.
#[must_use]
pub fn object(duration_ms: StickyDurationMs) -> Box<RawJsonValue> {
to_raw_value(&json!({ "duration_ms": duration_ms }))
.expect("a StickyDurationMs always produces a valid sticky object")
}
/// The sticky duration of `sticky`, clamped to [`MAX_DURATION_MS`], or `None`
/// if this is not a valid sticky object.
///
/// A malformed object leaves the event unsticky rather than invalidating it, so
/// a peer more lenient than us cannot split the room DAG.
#[must_use]
pub fn duration_ms(sticky: &RawJsonValue) -> Option<u64> {
serde_json::from_str::<Object>(sticky.get())
.ok()
.map(|sticky| sticky.duration_ms.min(MAX_DURATION_MS))
}
/// The time at which an event stops being sticky, in milliseconds since the
/// unix epoch, or `None` if the event was never sticky.
///
/// MSC4354 starts the clock at `min(received_ts, origin_server_ts)` so a server
/// cannot extend stickiness by dating its events forward. We keep no receive
/// time per PDU, so instead we deny stickiness to events dated further ahead
/// than any event could still be sticky for.
#[must_use]
pub fn expires_at(origin_server_ts: UInt, sticky: &RawJsonValue, now: u64) -> Option<u64> {
let origin_server_ts = u64::from(origin_server_ts);
if origin_server_ts.saturating_sub(now) > MAX_DURATION_MS {
return None;
}
let start = origin_server_ts.min(now);
duration_ms(sticky).map(|duration_ms| start.saturating_add(duration_ms))
}
#[must_use]
pub fn is_sticky(origin_server_ts: UInt, sticky: &RawJsonValue, now: u64) -> bool {
expires_at(origin_server_ts, sticky, now).is_some_and(|expires_at| expires_at > now)
}
#[cfg(test)]
mod tests {
use ruma::{UInt, events::sticky::StickyDurationMs};
use serde_json::value::{RawValue as RawJsonValue, to_raw_value};
use super::{MAX_DURATION_MS, duration_ms, expires_at, is_sticky, object};
fn raw(json: &serde_json::Value) -> Box<RawJsonValue> { to_raw_value(json).unwrap() }
fn ts(millis: u64) -> UInt { UInt::try_from(millis).unwrap() }
#[test]
fn duration_of_valid_object() {
let sticky = raw(&serde_json::json!({ "duration_ms": 300_000 }));
assert_eq!(duration_ms(&sticky), Some(300_000));
}
#[test]
fn duration_is_clamped_to_one_hour() {
let sticky = raw(&serde_json::json!({ "duration_ms": 9_999_999_999_u64 }));
assert_eq!(duration_ms(&sticky), Some(MAX_DURATION_MS));
}
#[test]
fn malformed_objects_are_not_sticky() {
for malformed in [
serde_json::json!({}),
serde_json::json!({ "duration_ms": -1 }),
serde_json::json!({ "duration_ms": "300000" }),
serde_json::json!({ "duration_ms": 3000.5 }),
serde_json::json!("nonsense"),
serde_json::json!(null),
] {
let sticky = raw(&malformed);
assert_eq!(duration_ms(&sticky), None, "{malformed} should not be sticky");
}
}
#[test]
fn object_round_trips_through_duration_ms() {
let sticky = object(StickyDurationMs::new_clamped(600_000_u32));
assert_eq!(duration_ms(&sticky), Some(600_000));
}
#[test]
fn expiry_is_origin_server_ts_plus_duration() {
let sticky = raw(&serde_json::json!({ "duration_ms": 300_000 }));
assert_eq!(expires_at(ts(1_000_000), &sticky, 1_100_000), Some(1_300_000));
}
#[test]
fn expiry_uses_the_clamped_duration() {
let sticky = raw(&serde_json::json!({ "duration_ms": 9_999_999_999_u64 }));
assert_eq!(
expires_at(ts(1_000_000), &sticky, 1_000_000),
Some(1_000_000 + MAX_DURATION_MS)
);
}
#[test]
fn timestamps_far_in_the_future_are_not_sticky() {
let sticky = raw(&serde_json::json!({ "duration_ms": 300_000 }));
let now = 1_000_000;
// a little ahead of us is tolerated, and starts the clock at `now`
let slightly_ahead = ts(now + MAX_DURATION_MS);
assert_eq!(expires_at(slightly_ahead, &sticky, now), Some(now + 300_000));
// beyond the point any honest event could still be sticky, it is not
let far_ahead = ts(now + MAX_DURATION_MS + 1);
assert_eq!(expires_at(far_ahead, &sticky, now), None);
}
#[test]
fn stickiness_ends_at_the_expiry() {
let sticky = raw(&serde_json::json!({ "duration_ms": 300_000 }));
assert!(is_sticky(ts(1_000_000), &sticky, 1_299_999));
assert!(!is_sticky(ts(1_000_000), &sticky, 1_300_000));
assert!(!is_sticky(ts(1_000_000), &sticky, 1_300_001));
}
#[test]
fn zero_duration_is_never_sticky() {
let sticky = raw(&serde_json::json!({ "duration_ms": 0 }));
assert!(!is_sticky(ts(1_000_000), &sticky, 1_000_000));
}
}
+93
View File
@@ -15,3 +15,96 @@ fn normal_parse() {
assert!(!backfilled, "backfilled variant");
}
fn sticky_pdu(sticky: &serde_json::Value) -> super::Pdu {
serde_json::from_value(serde_json::json!({
"event_id": "$test:example.com",
"room_id": "!test:example.com",
"sender": "@test:example.com",
"origin_server_ts": 1_000_000,
"type": "m.room.message",
"content": { "msgtype": "m.text", "body": "test" },
"msc4354_sticky": sticky,
"prev_events": [],
"depth": 1,
"auth_events": [],
"hashes": { "sha256": "test_hash" },
}))
.expect("PDU is valid")
}
#[test]
fn sticky_object_round_trips_verbatim() {
// the object is signed, so serialization must not touch it
let pdu = sticky_pdu(&serde_json::json!({ "duration_ms": 300_000, "unknown": "key" }));
let value = serde_json::to_value(&pdu).expect("PDU serializes");
assert_eq!(
value["msc4354_sticky"],
serde_json::json!({ "duration_ms": 300_000, "unknown": "key" })
);
}
#[test]
fn out_of_range_sticky_object_is_clamped_not_rejected() {
let pdu = sticky_pdu(&serde_json::json!({ "duration_ms": 9_999_999_999_u64 }));
let sticky = pdu.sticky.as_deref().expect("sticky object is kept");
assert_eq!(super::sticky::duration_ms(sticky), Some(super::sticky::MAX_DURATION_MS));
}
#[test]
fn nonsense_sticky_object_does_not_reject_the_pdu() {
let pdu = sticky_pdu(&serde_json::json!("nonsense"));
let sticky = pdu.sticky.as_deref().expect("sticky object is kept");
assert_eq!(super::sticky::duration_ms(sticky), None);
}
#[test]
fn redaction_removes_stickiness() {
let mut pdu = sticky_pdu(&serde_json::json!({ "duration_ms": 300_000 }));
pdu.redact(&ruma::RoomVersionId::V11, serde_json::json!({}))
.expect("redaction succeeds");
assert!(pdu.sticky.is_none());
}
#[test]
fn sticky_ttl_is_added_to_unsigned() {
let mut pdu = sticky_pdu(&serde_json::json!({ "duration_ms": 300_000 }));
pdu.origin_server_ts = ruma::MilliSecondsSinceUnixEpoch::now().get();
pdu.add_sticky_duration_ttl().expect("ttl is added");
let unsigned: serde_json::Value =
serde_json::from_str(pdu.unsigned.as_deref().expect("unsigned is set").get())
.expect("unsigned is valid");
let ttl = unsigned["msc4354_sticky_duration_ttl_ms"]
.as_u64()
.expect("ttl is a number");
assert!(ttl > 0 && ttl <= 300_000, "ttl {ttl} out of range");
}
#[test]
fn no_ttl_for_events_that_are_not_sticky() {
let mut pdu = sticky_pdu(&serde_json::json!({ "duration_ms": "nonsense" }));
pdu.add_sticky_duration_ttl().expect("ttl is skipped");
assert!(pdu.unsigned.is_none());
}
#[test]
fn set_unsigned_adds_the_sticky_ttl() {
// sticky events in the timeline get the hint through here, not the sticky
// section
let mut pdu = sticky_pdu(&serde_json::json!({ "duration_ms": 300_000 }));
pdu.origin_server_ts = ruma::MilliSecondsSinceUnixEpoch::now().get();
pdu.set_unsigned(None);
let unsigned: serde_json::Value =
serde_json::from_str(pdu.unsigned.as_deref().expect("unsigned is set").get())
.expect("unsigned is valid");
assert!(unsigned["msc4354_sticky_duration_ttl_ms"].is_number());
}
+27 -1
View File
@@ -3,7 +3,7 @@
use ruma::MilliSecondsSinceUnixEpoch;
use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue, to_raw_value};
use super::Pdu;
use super::{Pdu, sticky};
use crate::{Result, err, result::LogErr};
impl Pdu {
@@ -17,6 +17,7 @@ pub fn set_unsigned(&mut self, user_id: Option<&ruma::UserId>) {
self.remove_transaction_id().log_err().ok();
}
self.add_age().log_err().ok();
self.add_sticky_duration_ttl().log_err().ok();
}
pub fn remove_transaction_id(&mut self) -> Result {
@@ -58,6 +59,31 @@ pub fn add_age(&mut self) -> Result {
Ok(())
}
pub fn add_sticky_duration_ttl(&mut self) -> Result {
use BTreeMap as Map;
let now = u64::from(MilliSecondsSinceUnixEpoch::now().get());
let Some(expires_at) = self
.sticky
.as_deref()
.and_then(|sticky| sticky::expires_at(self.origin_server_ts, sticky, now))
else {
return Ok(());
};
let mut unsigned: Map<&str, Box<RawJsonValue>> = self
.unsigned
.as_deref()
.map(RawJsonValue::get)
.map_or_else(|| Ok(Map::new()), serde_json::from_str)
.map_err(|e| err!(Database("Invalid unsigned in pdu event: {e}")))?;
unsigned.insert(sticky::TTL_UNSIGNED_KEY, to_raw_value(&expires_at.saturating_sub(now))?);
self.unsigned = Some(to_raw_value(&unsigned)?);
Ok(())
}
pub fn add_relation(&mut self, name: &str, pdu: Option<&Self>) -> Result {
use serde_json::Map;
+1
View File
@@ -414,6 +414,7 @@ fn to_pdu_event<S>(
content,
origin: None,
redacts: None,
sticky: None,
unsigned: None,
auth_events,
prev_events,
+2
View File
@@ -414,6 +414,7 @@ pub(crate) fn to_init_pdu_event(
content,
origin: None,
redacts: None,
sticky: None,
unsigned: None,
auth_events: vec![],
prev_events: vec![],
@@ -462,6 +463,7 @@ pub(crate) fn to_pdu_event<S>(
content,
origin: None,
redacts: None,
sticky: None,
unsigned: None,
auth_events,
prev_events,
+12 -13
View File
@@ -1,6 +1,5 @@
use std::{borrow::Cow, fmt::Debug, mem, time::Instant};
use bytes::Bytes;
use conduwuit::{
Err, Error, Result, debug, debug_error, debug_info, debug_warn, err, trace,
utils::response::LimitReadExt,
@@ -11,7 +10,8 @@
use ruma::{
ServerName,
api::{
EndpointError, IncomingResponse, OutgoingRequest, OutgoingRequestExt, SupportedVersions,
EndpointError, IncomingResponseExt, OutgoingRequest, OutgoingRequestExt,
SupportedVersions,
auth_scheme::{AuthScheme, NoAuthentication},
error::Error as RumaError,
federation::authentication::{ServerSignatures, ServerSignaturesInput},
@@ -232,8 +232,12 @@ async fn handle_response<T>(
let response =
into_http_response(dest, actual, method, url, response, size_limit).await?;
T::IncomingResponse::try_from_http_response(response)
.map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
let (parts, body) = response.into_parts();
T::IncomingResponse::try_from_http_response(http::Response::from_parts(
parts,
body.as_ref(),
))
.map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
}
}
@@ -244,7 +248,7 @@ async fn into_http_response(
url: &Url,
mut response: Response,
max_size: u64,
) -> Result<http::Response<Bytes>> {
) -> Result<http::Response<Vec<u8>>> {
let status = response.status();
trace!(
%status, %method,
@@ -267,20 +271,15 @@ async fn into_http_response(
trace!("Waiting for response body...");
let http_response = http_response_builder
.body(
response
.limit_read(max_size)
.await
.unwrap_or_default()
.into(),
)
.body(response.limit_read(max_size).await.unwrap_or_default())
.expect("reqwest body is valid http body");
debug!("Got {status:?} for {method} {url}");
if !status.is_success() {
let (parts, body) = http_response.into_parts();
return Err(Error::Federation(
dest.to_owned(),
RumaError::from_http_response(http_response),
RumaError::from_http_response(http::Response::from_parts(parts, body.as_ref())),
));
}
+6
View File
@@ -178,6 +178,11 @@ pub const fn invalid_request(error_description: &'static str) -> Self {
pub const fn invalid_grant(error_description: &'static str) -> Self {
Self::new_static(ErrorCode::InvalidGrant, error_description)
}
#[must_use]
pub const fn unauthorized_client(error_description: &'static str) -> Self {
Self::new_static(ErrorCode::UnauthorizedClient, error_description)
}
}
impl Display for OAuthError {
@@ -197,6 +202,7 @@ pub enum ErrorCode {
InvalidGrant,
InvalidClient,
InvalidClientMetadata,
UnauthorizedClient,
AuthorizationPending,
ExpiredToken,
}
+6 -2
View File
@@ -20,7 +20,7 @@
use crate::{
Dep, config,
oauth::{
client_metadata::{ApplicationType, ClientMetadata, ResponseType},
client_metadata::{ApplicationType, ClientMetadata, GrantType, ResponseType},
grant::{
AuthorizationCodeQuery, AuthorizationCodeResponse, CodeChallengeMethod,
DeviceCodeRequest, DeviceCodeResponse, ErrorCode, OAuthError, ResponseMode, Scope,
@@ -329,6 +329,10 @@ pub async fn request_device_code(
return Err(OAuthError::new_static(ErrorCode::InvalidClient, "Invalid client ID"));
};
if !client_metadata.grant_types.contains(&GrantType::DeviceCode) {
return Err(OAuthError::unauthorized_client("Client cannot request this grant type"));
}
let requested_scopes = query
.scope
.to_scopes()
@@ -438,7 +442,7 @@ pub async fn issue_token(&self, request: TokenRequest) -> Result<TokenResponse,
};
if !client_metadata.grant_types.contains(&request.grant_type()) {
return Err(OAuthError::invalid_grant("Client cannot request this grant type"));
return Err(OAuthError::unauthorized_client("Client cannot request this grant type"));
}
match request {
+6 -4
View File
@@ -13,7 +13,7 @@
use ruma::{
DeviceId, OwnedDeviceId, RoomId, UInt, UserId,
api::{
IncomingResponse, OutgoingRequest, OutgoingRequestExt,
IncomingResponseExt, OutgoingRequest, OutgoingRequestExt,
auth_scheme::NoAuthentication,
client::push::{Pusher, PusherKind, set_pusher},
path_builder::SinglePath,
@@ -266,10 +266,12 @@ pub async fn send_request<T>(&self, dest: &str, request: T) -> Result<T::Incomin
)));
}
let (parts, body) = http_response_builder
.body(body)
.expect("reqwest body is valid http body")
.into_parts();
let response = T::IncomingResponse::try_from_http_response(
http_response_builder
.body(body)
.expect("reqwest body is valid http body"),
http::Response::from_parts(parts, body.as_ref()),
);
response.map_err(|e| {
err!(BadServerResponse(warn!(
@@ -1,13 +1,15 @@
use std::str::FromStr;
use conduwuit::{
Err, Result, err,
Err, Error, Result, err,
matrix::event::{gen_event_id, gen_event_id_canonical_json},
};
use itertools::Itertools;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, RoomId,
RoomVersionId, room_version_rules::RoomVersionRules,
RoomVersionId,
api::error::{ErrorKind, IncompatibleRoomVersionErrorData},
room_version_rules::RoomVersionRules,
};
use serde_json::value::RawValue as RawJsonValue;
@@ -102,14 +104,25 @@ pub async fn parse_incoming_pdu(
let room_version_rules = match room_version_rules {
| Some(r) => r,
| None => &self
.services
.state
.get_room_version(&room_id)
.await
.unwrap_or(RoomVersionId::V1)
.rules()
.expect("room version must be supported"),
| None => {
let room_version = self
.services
.state
.get_room_version(&room_id)
.await
.map_err(|_| err!(Request(NotFound("no version associated with room ID"))))?;
if matches!(room_version, RoomVersionId::V1 | RoomVersionId::V2) {
return Err(Error::BadRequest(
ErrorKind::IncompatibleRoomVersion(
IncompatibleRoomVersionErrorData::new(room_version),
),
"Room version is too old.",
));
}
&room_version
.rules()
.expect("room version must be supported")
},
};
let (event_id, value) =
+127 -2
View File
@@ -1,5 +1,6 @@
use std::collections::HashMap;
use base64::Engine;
use conduwuit::{
Err, Event, EventTypeExt, PduEvent, Result, debug, debug::DebugInspect, debug_error,
debug_info, err, info, matrix::StateKey, state_res, trace,
@@ -7,14 +8,43 @@
use futures::future::ready;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, ServerName,
api::error::ErrorKind, canonical_json::redact, events::StateEventType,
room_version_rules::RoomVersionRules,
api::error::ErrorKind,
canonical_json::redact,
events::StateEventType,
room_version_rules::{EventIdFormatVersion, RoomVersionRules},
};
use crate::rooms::{
event_handler::parse_incoming_pdu::expect_event_id_array, timeline::pdu_fits,
};
/// Checks that the given event ID matches the expected format by attempting to
/// decode the base64 string.
fn check_event_id_format(
event_id: &EventId,
room_version_rules: &RoomVersionRules,
) -> Result<Vec<u8>> {
let event_id_without_sigil = event_id
.as_str()
.strip_prefix("$")
.expect("event ID must start with a $ sigil");
let b64_alphabet = match room_version_rules.event_id_format {
| EventIdFormatVersion::V2 => base64::alphabet::STANDARD,
| EventIdFormatVersion::V3 => base64::alphabet::URL_SAFE,
| _ => return Err!("Unsupported event ID Format"),
};
let b64_engine = base64::engine::GeneralPurpose::new(
&b64_alphabet,
base64::engine::general_purpose::NO_PAD,
);
b64_engine.decode(event_id_without_sigil).map_err(|e| {
err!(Request(InvalidParam(debug_error!(
error=?e,
"PDU references an invalid event ID: {event_id}"
))))
})
}
impl super::Service {
/// Checks that the PDU conforms to the PDU format (check 1). This is
/// already mostly done during deserialisation, so this function just checks
@@ -42,6 +72,9 @@ pub fn pdu_format_check_1(
if auth_events.len() > 10 {
return Err!(Request(BadJson("PDU has too many auth events")));
}
for auth_event_id in &auth_events {
check_event_id_format(auth_event_id, room_version_rules)?;
}
// The m.room.create event is the genesis event and has empty auth_events
// by definition, so it is exempt from the checks below requiring or
@@ -70,6 +103,9 @@ pub fn pdu_format_check_1(
if prev_events.len() > 20 {
return Err!(Request(BadJson("PDU has too many prev events")));
}
for prev_event_id in &prev_events {
check_event_id_format(prev_event_id, room_version_rules)?;
}
Ok(())
}
@@ -306,3 +342,92 @@ pub(super) async fn policy_server_check_7(
Ok(true)
}
}
#[cfg(test)]
mod tests {
use ruma::server_name;
use super::*;
#[test]
fn v1_event_id_always_errors() {
let v1_event_id = EventId::new_v1(server_name!("example.com"));
assert!(
check_event_id_format(&v1_event_id, &RoomVersionRules::V3).is_err(),
"V1 event ID should not be valid in room V3"
);
assert!(
check_event_id_format(&v1_event_id, &RoomVersionRules::V4).is_err(),
"V1 event ID should not be valid in room V4"
);
}
#[test]
fn v2_event_id_ok_in_room_v3_only() {
let v2_event_id = EventId::new_v2_or_v3("KtY/RFXNXYxprwSOypvTlZsbohReRw19qcPATZDda4E")
.expect("fixture event hash must be valid");
let decoded_bytes = check_event_id_format(&v2_event_id, &RoomVersionRules::V3)
.expect("V2 event ID should be valid in room V3");
assert_eq!(
decoded_bytes,
vec![
42, 214, 63, 68, 85, 205, 93, 140, 105, 175, 4, 142, 202, 155, 211, 149, 155, 27,
162, 20, 94, 71, 13, 125, 169, 195, 192, 77, 144, 221, 107, 129
],
"V2 event reference hash did not decode to expected bytes"
);
assert!(
check_event_id_format(&v2_event_id, &RoomVersionRules::V4).is_err(),
"V2 event ID should not be valid in room V4"
);
}
#[test]
fn v3_event_id_errors_in_room_v3() {
let v3_event_id = EventId::new_v2_or_v3("zsj67_pqjr5qqh5GMTXqxLM0FqjP5OLrvXO0PjwWe88")
.expect("fixture event hash must be valid");
// Since the urlsafe replacements aren't in the standard base64 alphabet, this
// simply errors instead of decoding to potentially incorrect bytes.
assert!(
check_event_id_format(&v3_event_id, &RoomVersionRules::V3).is_err(),
"V3 event ID should not be valid in room V3"
);
}
#[test]
fn v3_event_id_ok_in_room_v4_onward() {
let v3_event_id = EventId::new_v2_or_v3("zsj67_pqjr5qqh5GMTXqxLM0FqjP5OLrvXO0PjwWe88")
.expect("fixture event hash must be valid");
// Since the urlsafe replacements aren't in the standard base64 alphabet, this
// simply errors instead of decoding to potentially incorrect bytes.
let expected_bytes = vec![
206, 200, 250, 239, 250, 106, 142, 190, 106, 170, 30, 70, 49, 53, 234, 196, 179, 52,
22, 168, 207, 228, 226, 235, 189, 115, 180, 62, 60, 22, 123, 207,
];
assert_eq!(
check_event_id_format(&v3_event_id, &RoomVersionRules::V4)
.expect("V3 event should be valid room V4"),
expected_bytes,
"V3 event ID in room V4 did not decode to expected bytes"
);
// These versions didn't change the algorithm, but might as well test them
// anyway
assert_eq!(
check_event_id_format(&v3_event_id, &RoomVersionRules::V6)
.expect("V3 event should be valid room V6"),
expected_bytes,
"V3 event ID in room V6 did not decode to expected bytes"
);
assert_eq!(
check_event_id_format(&v3_event_id, &RoomVersionRules::V10)
.expect("V3 event should be valid room V10"),
expected_bytes,
"V3 event ID in room V10 did not decode to expected bytes"
);
assert_eq!(
check_event_id_format(&v3_event_id, &RoomVersionRules::V12)
.expect("V3 event should be valid room V12"),
expected_bytes,
"V3 event ID in room V12 did not decode to expected bytes"
);
}
}
@@ -281,6 +281,7 @@ fn create_test_pdu(unsigned_content: Option<JsonValue>) -> PduEvent {
origin_server_ts: UInt::try_from(1_234_567_890_u64).unwrap(),
kind: TimelineEventType::RoomMessage,
content: to_raw_value(&json!({"msgtype": "m.text", "body": "test"})).unwrap(),
sticky: None,
state_key: None,
prev_events: vec![],
depth: UInt::from(1_u32),
@@ -445,6 +446,7 @@ fn create_test_event(
origin_server_ts: UInt::try_from(1_234_567_890_u64).unwrap(),
kind: event_type,
content: to_raw_value(&content).unwrap(),
sticky: None,
state_key: state_key.map(Into::into),
prev_events: vec![],
depth: UInt::from(1_u32),
+52 -3
View File
@@ -152,15 +152,21 @@ pub fn pack_receipts<I>(receipts: I) -> Raw<SyncEphemeralRoomEvent<ReceiptEventC
where
I: Iterator<Item = Raw<AnySyncEphemeralRoomEvent>>,
{
let mut json = BTreeMap::new();
let mut json: BTreeMap<OwnedEventId, Receipts> = BTreeMap::new();
for value in receipts {
let receipt = serde_json::from_str::<SyncEphemeralRoomEvent<ReceiptEventContent>>(
value.json().get(),
);
match receipt {
| Ok(value) =>
for (event, receipt) in value.content {
json.insert(event, receipt);
for (event, receipt_types) in value.content {
let event_receipts = json.entry(event).or_default();
for (receipt_type, users) in receipt_types {
event_receipts
.entry(receipt_type)
.or_default()
.extend(users);
}
},
| _ => {
debug!("failed to parse receipt: {:?}", receipt);
@@ -175,3 +181,46 @@ pub fn pack_receipts<I>(receipts: I) -> Raw<SyncEphemeralRoomEvent<ReceiptEventC
.expect("received valid json"),
)
}
#[cfg(test)]
mod tests {
use ruma::{
events::receipt::{Receipt, ReceiptType},
user_id,
};
use super::*;
fn receipt(event_id: OwnedEventId, user_id: OwnedUserId) -> Raw<AnySyncEphemeralRoomEvent> {
let content = ReceiptEventContent::from_iter([(
event_id,
BTreeMap::from([(
ReceiptType::Read,
BTreeMap::from([(user_id, Receipt::default())]),
)]),
)]);
Raw::from_json(
serde_json::value::to_raw_value(&SyncEphemeralRoomEvent::new(content))
.expect("receipt event serializes"),
)
}
#[test]
fn pack_receipts_keeps_users_reading_the_same_event() {
let event_id: OwnedEventId = "$event:example.org".try_into().expect("valid event ID");
let packed = pack_receipts(
[
receipt(event_id.clone(), user_id!("@alice:example.org").to_owned()),
receipt(event_id.clone(), user_id!("@bob:example.org").to_owned()),
]
.into_iter(),
);
let packed: SyncEphemeralRoomEvent<ReceiptEventContent> =
serde_json::from_str(packed.json().get()).expect("packed receipt is valid");
let users = &packed.content[&event_id][&ReceiptType::Read];
assert_eq!(users.len(), 2);
assert!(users.contains_key(user_id!("@alice:example.org")));
assert!(users.contains_key(user_id!("@bob:example.org")));
}
}
+95 -6
View File
@@ -1,6 +1,7 @@
use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap, HashSet},
iter::once,
sync::Arc,
};
@@ -18,17 +19,22 @@
};
use conduwuit_core::{
err, error,
matrix::pdu::{PduCount, PduId, RawPduId},
utils::{self},
matrix::pdu::{PduCount, PduId, RawPduId, sticky},
result::LogErr,
utils::{self, stream::TryIgnore},
};
use futures::{FutureExt, StreamExt, TryFutureExt};
use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, RoomId,
RoomVersionId, UserId,
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName,
RoomId, RoomVersionId, ServerName, UserId,
events::{
AnySyncTimelineEvent, GlobalAccountDataEventType, TimelineEventType,
push_rules::PushRulesEvent,
room::{encrypted::Relation, redaction::RoomRedactionEventContent},
room::{
encrypted::Relation,
member::{MembershipState, RoomMemberEventContent},
redaction::RoomRedactionEventContent,
},
},
push::{Action, Ruleset, Tweak},
room_version_rules::RoomVersionRules,
@@ -542,6 +548,10 @@ async fn handle_pdu_effects(
let target_user_id = UserId::parse(state_key)
.expect("This state_key was previously validated");
// must be checked before the membership update, which is what
// marks the server as being in the room
let joining_server = self.joining_server(pdu, &target_user_id, room_id).await;
// Update our membership info, we do this here incase a user is invited or
// knocked and immediately leaves we need the DB to record the invite or
// knock event for auth
@@ -549,6 +559,11 @@ async fn handle_pdu_effects(
.state_cache
.update_membership(room_id, &target_user_id, pdu, true)
.await?;
if let Some(server) = joining_server {
self.send_sticky_events_to_server(room_id, short_room_id, &server)
.await;
}
}
},
| TimelineEventType::RoomMessage => {
@@ -563,6 +578,80 @@ async fn handle_pdu_effects(
Ok(())
}
/// The server of `target_user_id`, if this membership event brings it into
/// the room for the first time.
async fn joining_server(
&self,
pdu: &PduEvent,
target_user_id: &UserId,
room_id: &RoomId,
) -> Option<OwnedServerName> {
let server = target_user_id.server_name();
if !self.services.config.allow_sticky_events
|| self.services.globals.server_is_ours(server)
{
return None;
}
let content: RoomMemberEventContent = pdu.get_content().ok()?;
if content.membership != MembershipState::Join {
return None;
}
let already_joined = self
.services
.state_cache
.server_in_room(server, room_id)
.await;
(!already_joined).then(|| server.to_owned())
}
/// MSC4354: a server which has just joined will not backfill far enough to
/// see our unexpired sticky events, so push them to it.
async fn send_sticky_events_to_server(
&self,
room_id: &RoomId,
short_room_id: ShortRoomId,
server: &ServerName,
) {
let now = utils::millis_since_unix_epoch();
let oldest_sticky_ts = now.saturating_sub(sticky::MAX_DURATION_MS);
let mut pdu_ids = Vec::new();
let pdus = self.pdus_rev(room_id, None).ignore_err();
pin_mut!(pdus);
while let Some((count, pdu)) = pdus.next().await {
if u64::from(pdu.origin_server_ts) < oldest_sticky_ts {
break;
}
let is_ours = self.services.globals.user_is_local(pdu.sender());
let is_sticky = pdu
.sticky
.as_deref()
.is_some_and(|sticky| sticky::is_sticky(pdu.origin_server_ts, sticky, now));
if is_ours && is_sticky {
pdu_ids.push(RawPduId::from(PduId {
shortroomid: short_room_id,
shorteventid: count,
}));
}
}
// oldest first, as MSC4354 asks for creation order
for pdu_id in pdu_ids.iter().rev() {
self.services
.sending
.send_pdu_servers(once(server.to_owned()).stream(), pdu_id)
.await
.log_err()
.ok();
}
}
/// Adds relation data to the incoming event and events it relates to.
async fn aggregate_relations(&self, pdu: &PduEvent, count2: Count) {
// CONCERN: If we receive events with a relation out-of-order, we never write
+2
View File
@@ -88,6 +88,7 @@ pub async fn create_event(
let PartialPdu {
event_type,
content,
sticky,
unsigned,
state_key,
redacts,
@@ -209,6 +210,7 @@ pub async fn create_event(
),
kind: event_type,
content,
sticky,
state_key,
prev_events,
depth,
+13 -1
View File
@@ -1,6 +1,9 @@
//! Helpers for submitting events with the right checks performed
use conduwuit::{Err, Result, err, matrix::pdu::PartialPdu};
use conduwuit::{
Err, Result, err,
matrix::pdu::{PartialPdu, sticky},
};
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, UserId,
events::{
@@ -12,6 +15,7 @@
member::{MembershipState, RoomMemberEventContent},
server_acl::RoomServerAclEventContent,
},
sticky::StickyDurationMs,
},
serde::Raw,
};
@@ -28,6 +32,7 @@ pub async fn send_state_event_for_key(
event_type: &StateEventType,
content: &Raw<AnyStateEventContent>,
state_key: &str,
sticky_duration_ms: Option<StickyDurationMs>,
timestamp: Option<MilliSecondsSinceUnixEpoch>,
) -> Result<OwnedEventId> {
let mut content: Raw<AnyStateEventContent> = content.clone();
@@ -42,6 +47,13 @@ pub async fn send_state_event_for_key(
PartialPdu {
event_type: event_type.to_string().into(),
content,
sticky: self
.services
.config
.allow_sticky_events
.then_some(sticky_duration_ms)
.flatten()
.map(sticky::object),
state_key: Some(state_key.into()),
timestamp,
..Default::default()
+8 -23
View File
@@ -1,7 +1,7 @@
use std::{collections::BTreeMap, sync::Arc};
use conduwuit::{
Result, Server, debug_info, trace,
Result, Server, debug_info,
utils::{self, IterStream},
};
use futures::StreamExt;
@@ -10,9 +10,9 @@
api::federation::transactions::edu::{Edu, TypingContent},
events::{SyncEphemeralRoomEvent, typing::TypingEventContent},
};
use tokio::sync::{RwLock, broadcast};
use tokio::sync::RwLock;
use crate::{Dep, globals, sending, sending::EduBuf, users};
use crate::{Dep, globals, sending, sending::EduBuf, sync, users};
pub struct Service {
server: Arc<Server>,
@@ -21,12 +21,12 @@ pub struct Service {
pub typing: RwLock<BTreeMap<OwnedRoomId, BTreeMap<OwnedUserId, u64>>>,
/// timestamp of the last change to typing users
pub last_typing_update: RwLock<BTreeMap<OwnedRoomId, u64>>,
pub typing_update_sender: broadcast::Sender<OwnedRoomId>,
}
struct Services {
globals: Dep<globals::Service>,
sending: Dep<sending::Service>,
sync: Dep<sync::Service>,
users: Dep<users::Service>,
}
@@ -37,11 +37,11 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
services: Services {
globals: args.depend::<globals::Service>("globals"),
sending: args.depend::<sending::Service>("sending"),
sync: args.depend::<sync::Service>("sync"),
users: args.depend::<users::Service>("users"),
},
typing: RwLock::new(BTreeMap::new()),
last_typing_update: RwLock::new(BTreeMap::new()),
typing_update_sender: broadcast::channel(100).0,
}))
}
@@ -71,9 +71,7 @@ pub async fn typing_add(
.await
.insert(room_id.to_owned(), self.services.globals.next_count()?);
if self.typing_update_sender.send(room_id.to_owned()).is_err() {
trace!("receiver found what it was looking for and is no longer interested");
}
self.services.sync.wake_all_joined(room_id).await;
// update federation
if self.services.globals.user_is_local(user_id) {
@@ -99,9 +97,7 @@ pub async fn typing_remove(&self, user_id: &UserId, room_id: &RoomId) -> Result<
.await
.insert(room_id.to_owned(), self.services.globals.next_count()?);
if self.typing_update_sender.send(room_id.to_owned()).is_err() {
trace!("receiver found what it was looking for and is no longer interested");
}
self.services.sync.wake_all_joined(room_id).await;
// update federation
if self.services.globals.user_is_local(user_id) {
@@ -111,15 +107,6 @@ pub async fn typing_remove(&self, user_id: &UserId, room_id: &RoomId) -> Result<
Ok(())
}
pub async fn wait_for_update(&self, room_id: &RoomId) {
let mut receiver = self.typing_update_sender.subscribe();
while let Ok(next) = receiver.recv().await {
if next == room_id {
break;
}
}
}
/// Makes sure that typing events with old timestamps get removed.
async fn typings_maintain(&self, room_id: &RoomId) -> Result<()> {
let current_timestamp = utils::millis_since_unix_epoch();
@@ -152,9 +139,7 @@ async fn typings_maintain(&self, room_id: &RoomId) -> Result<()> {
.await
.insert(room_id.to_owned(), self.services.globals.next_count()?);
if self.typing_update_sender.send(room_id.to_owned()).is_err() {
trace!("receiver found what it was looking for and is no longer interested");
}
self.services.sync.wake_all_joined(room_id).await;
// update federation
for user in &removable {
+9 -6
View File
@@ -4,7 +4,7 @@
use conduwuit::{Err, Result, debug_info, err, utils::response::LimitReadExt};
use reqwest::Client;
use ruma::api::{
IncomingResponse, OutgoingRequest, OutgoingRequestExt,
IncomingResponseExt, OutgoingRequest, OutgoingRequestExt,
auth_scheme::{AppserviceToken, SendAccessToken},
path_builder::VersionHistory,
};
@@ -76,11 +76,14 @@ pub(crate) async fn send_antispam_request<T>(
};
}
let response = T::IncomingResponse::try_from_http_response(
http_response_builder
.body(body)
.expect("reqwest body is valid http body"),
);
let (parts, body) = http_response_builder
.body(body)
.expect("reqwest body is valid http body")
.into_parts();
let response = T::IncomingResponse::try_from_http_response(http::Response::from_parts(
parts,
body.as_ref(),
));
response.map_err(|e| {
err!(BadServerResponse(warn!(
+9 -6
View File
@@ -5,7 +5,7 @@
Err, Result, debug_error, err, trace, utils, utils::response::LimitReadExt, warn,
};
use ruma::api::{
IncomingResponse, OutgoingRequest, OutgoingRequestExt,
IncomingResponseExt, OutgoingRequest, OutgoingRequestExt,
appservice::Registration,
auth_scheme::{AccessToken, SendAccessToken},
path_builder::SinglePath,
@@ -97,11 +97,14 @@ pub async fn send_appservice_request<T>(
)));
}
let response = T::IncomingResponse::try_from_http_response(
http_response_builder
.body(body)
.expect("reqwest body is valid http body"),
);
let (parts, body) = http_response_builder
.body(body)
.expect("reqwest body is valid http body")
.into_parts();
let response = T::IncomingResponse::try_from_http_response(http::Response::from_parts(
parts,
body.as_ref(),
));
response.map(Some).map_err(|e| {
err!(BadServerResponse(warn!(
+6 -6
View File
@@ -441,10 +441,10 @@ fn num_senders(args: &crate::Args<'_>) -> usize {
max_senders = max_senders.min(num_cores);
}
// If the user doesn't override the default 0, this is intended to then default
// to 1 for now as multiple senders is experimental.
args.server
.config
.sender_workers
.clamp(MIN_SENDERS, max_senders)
let worker_count = args.server.config.sender_workers;
if worker_count == 0 {
max_senders
} else {
worker_count.clamp(MIN_SENDERS, max_senders)
}
}
+28 -1
View File
@@ -14,6 +14,7 @@
};
use conduwuit_core::{
Error, Event, Result, at, debug, err, error,
matrix::pdu::sticky,
result::LogErr,
utils::{
ReadyExt, calculate_hash, continue_exponential_backoff_secs,
@@ -268,7 +269,10 @@ async fn startup_netburst<'a>(
}
let entry = txns.entry(dest.clone()).or_default();
if self.server.config.startup_netburst_keep >= 0 && entry.len() >= keep {
let over_budget =
self.server.config.startup_netburst_keep >= 0 && entry.len() >= keep;
if over_budget && !self.survives_netburst_trim(&event).await {
warn!(
startup_netburst_keep = self.server.config.startup_netburst_keep,
queue_size = entry.len(),
@@ -289,6 +293,29 @@ async fn startup_netburst<'a>(
}
}
/// Whether this event skips the netburst budget, which MSC4354 asks of
/// still-sticky events. Only netburst delivers this queue, so with it
/// disabled there is nothing to keep the event for.
async fn survives_netburst_trim(&self, event: &SendingEvent) -> bool {
if !self.server.config.allow_sticky_events || !self.server.config.startup_netburst {
return false;
}
let SendingEvent::Pdu(pdu_id) = event else {
return false;
};
let Ok(pdu) = self.services.timeline.get_pdu_from_id(pdu_id).await else {
return false;
};
let now = MilliSecondsSinceUnixEpoch::now().get().into();
pdu.sticky
.as_deref()
.is_some_and(|sticky| sticky::is_sticky(pdu.origin_server_ts, sticky, now))
}
/// Selects any new events to send to the given destination.
#[tracing::instrument(
name = "select",
+117 -20
View File
@@ -1,6 +1,5 @@
use std::{
collections::{BTreeMap, BTreeSet, HashMap},
pin::pin,
sync::Arc,
};
@@ -9,13 +8,13 @@
use ruma::{
OwnedDeviceId, OwnedRoomId, OwnedUserId, RoomId, UserId, api::client::sync::sync_events::v5,
};
use tokio::sync::{Mutex, Notify};
use tokio::sync::{Mutex, watch};
use crate::{Dep, rooms};
pub struct Service {
services: Services,
wakers: Mutex<HashMap<OwnedUserId, Arc<Notify>>>,
wakers: Wakers,
snake_connections: DbConnections<SnakeConnectionsKey, SnakeConnectionsVal>,
}
@@ -42,16 +41,60 @@ struct SnakeSyncCache {
type DbConnections<K, V> = SyncMutex<BTreeMap<K, V>>;
type DbConnectionsKey = (OwnedUserId, OwnedDeviceId, String);
type SnakeConnectionsKey = (OwnedUserId, OwnedDeviceId, Option<String>);
pub type SnakeConnectionsKey = (OwnedUserId, OwnedDeviceId, Option<String>);
type SnakeConnectionsVal = Arc<SyncMutex<SnakeSyncCache>>;
#[derive(Default)]
struct Wakers(Mutex<HashMap<OwnedUserId, watch::Sender<u64>>>);
impl Wakers {
async fn subscribe(&self, user: &UserId) -> watch::Receiver<u64> {
self.0
.lock()
.await
.entry(user.to_owned())
.or_insert_with(|| watch::channel(0).0)
.subscribe()
}
async fn wake(&self, user: &UserId) {
let mut wakers = self.0.lock().await;
wake_waker(&mut wakers, user);
}
async fn wake_all<I>(&self, users: I)
where
I: IntoIterator<Item = OwnedUserId> + Send,
{
let mut wakers = self.0.lock().await;
for user in users {
wake_waker(&mut wakers, &user);
}
}
}
/// Bumps the generation observed by anyone long-polling for this user, reaping
/// the entry once its last subscriber is gone.
fn wake_waker(wakers: &mut HashMap<OwnedUserId, watch::Sender<u64>>, user: &UserId) {
let Some(waker) = wakers.get(user) else {
return;
};
if waker.receiver_count() == 0 {
wakers.remove(user);
return;
}
waker.send_modify(|generation| *generation = generation.wrapping_add(1));
}
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
services: Services {
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
},
wakers: Mutex::default(),
wakers: Wakers::default(),
snake_connections: SyncMutex::new(BTreeMap::new()),
}))
}
@@ -60,8 +103,8 @@ fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}
impl Service {
pub async fn wait_for_wake(&self, user: &UserId) {
self.waker_for(user).await.notified().await;
pub async fn subscribe_to_wake(&self, user: &UserId) -> watch::Receiver<u64> {
self.wakers.subscribe(user).await
}
/// Wake the target user's sync loop. Call this when something
@@ -74,25 +117,20 @@ pub async fn wait_for_wake(&self, user: &UserId) {
pub async fn wake(&self, user: &UserId) {
trace!(?user, "Waking user's sync loops");
self.waker_for(user).await.notify_waiters();
self.wakers.wake(user).await;
}
/// Wake all of our users who are joined to the specified room.
pub async fn wake_all_joined(&self, room: &RoomId) {
trace!(?room, "Waking all joined users' sync loops");
let mut wakers = self.wakers.lock().await;
let users: Vec<_> = self
.services
.state_cache
.active_local_users_in_room(room)
.collect()
.await;
let mut users_in_room = pin!(self.services.state_cache.active_local_users_in_room(room));
while let Some(user) = users_in_room.next().await {
wakers.entry(user).or_default().notify_waiters();
}
}
async fn waker_for(&self, user: &UserId) -> Arc<Notify> {
let mut wakers = self.wakers.lock().await;
wakers.entry(user.to_owned()).or_default().clone()
self.wakers.wake_all(users).await;
}
pub fn snake_connection_cached(&self, key: &SnakeConnectionsKey) -> bool {
@@ -290,3 +328,62 @@ fn some_or_sticky<T>(target: &mut Option<T>, cached: Option<T>) {
*target = cached;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn wake_after_subscription_is_observable() {
let wakers = Wakers::default();
let user = UserId::parse("@alice:example.com").unwrap();
let mut receiver = wakers.subscribe(&user).await;
let generation = *receiver.borrow_and_update();
wakers.wake(&user).await;
wakers.wake(&user).await;
receiver.changed().await.unwrap();
assert_eq!(*receiver.borrow_and_update(), generation.wrapping_add(2));
}
#[tokio::test]
async fn wake_notifies_all_subscribers() {
let wakers = Wakers::default();
let user = UserId::parse("@alice:example.com").unwrap();
let mut first = wakers.subscribe(&user).await;
let mut second = wakers.subscribe(&user).await;
wakers.wake(&user).await;
first.changed().await.unwrap();
second.changed().await.unwrap();
}
#[tokio::test]
async fn waker_is_not_retained_without_subscribers() {
let wakers = Wakers::default();
let user = UserId::parse("@alice:example.com").unwrap();
wakers.wake(&user).await;
assert!(wakers.0.lock().await.is_empty());
drop(wakers.subscribe(&user).await);
assert_eq!(wakers.0.lock().await.len(), 1);
wakers.wake(&user).await;
assert!(wakers.0.lock().await.is_empty());
}
#[tokio::test]
async fn subscriber_still_woken_after_a_sibling_disconnects() {
let wakers = Wakers::default();
let user = UserId::parse("@alice:example.com").unwrap();
let mut receiver = wakers.subscribe(&user).await;
drop(wakers.subscribe(&user).await);
wakers.wake(&user).await;
receiver.changed().await.unwrap();
}
}
+1 -1
View File
@@ -114,7 +114,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
config: args.depend::<config::Service>("config"),
firstrun: args.depend::<firstrun::Service>("firstrun"),
globals: args.depend::<globals::Service>("globals"),
membership: args.depend::<membership::Service>("membership"),
membership: args.depend::<membership::Service>("rooms::membership"),
oauth: args.depend::<oauth::Service>("oauth"),
presence: args.depend::<presence::Service>("presence"),
state: args.depend::<rooms::state::Service>("rooms::state"),
+1 -1
View File
@@ -40,7 +40,7 @@ serde.workspace = true
serde_json.workspace = true
lettre.workspace = true
memory-serve = "2.1.0"
validator = { version = "0.20.0", features = ["derive"] }
validator = { version = "0.21.0", features = ["derive"] }
tower-sec-fetch = { version = "0.1.2", features = ["tracing"] }
tower-sessions = { version = "0.15.0", default-features = false, features = ["axum-core"] }
tower-sessions-core = { version = "0.15.0", features = ["deletion-task"] }
+1 -1
View File
@@ -9,7 +9,7 @@ About server
<h1>About {{ server_name }}</h1>
{% if let Some(support_page) = support_page %}
<p>
Visit this server's website: <a href="{{ support_page }} target="_blank">{{ support_page }}</a>
Visit this server's website: <a href="{{ support_page }}" target="_blank">{{ support_page }}</a>
</p>
{% endif %}
{% if !contacts.is_empty() %}