mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-08-08 21:59:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18139ea891 | ||
|
|
9080494464 | ||
|
|
e13c6990d2 | ||
|
|
26d5c03a0d | ||
|
|
9c8c38496d | ||
|
|
534ff1fe07 | ||
|
|
f0493a7ba8 | ||
|
|
8c1bbf3d1a | ||
|
|
02bee2e898 | ||
|
|
1c23cf3a93 | ||
|
|
30be07fe8f | ||
|
|
a530eaa3a8 | ||
|
|
97d37a9bb3 | ||
|
|
4ff7a88df0 | ||
|
|
1b6bc83b9f | ||
|
|
cdf07a8811 | ||
|
|
69b6431213 | ||
|
|
21ea9d3ea7 | ||
|
|
d79ffe6ef0 | ||
|
|
8f0c7a99d7 | ||
|
|
f1755dd36f | ||
|
|
3b4665a171 | ||
|
|
9c9d15d601 | ||
|
|
5402c4f208 | ||
|
|
c7160edc13 | ||
|
|
2144317933 | ||
|
|
e19f634ffa | ||
|
|
7b020e8563 | ||
|
|
d24245a68e | ||
|
|
3c3e3b5316 | ||
|
|
d0ca302347 | ||
|
|
37ddb8f37f | ||
|
|
a5fe5b531d | ||
|
|
144ff89447 | ||
|
|
6915bca11c | ||
|
|
519ebc96cc | ||
|
|
39ca63afe5 | ||
|
|
77c22ca82d | ||
|
|
f463cbab4f | ||
|
|
80e98a2299 | ||
|
|
20cdcee68c | ||
|
|
6647d405f4 | ||
|
|
4e9ed55c6b | ||
|
|
679b75eb66 | ||
|
|
c3bd6d697a | ||
|
|
4fea19b0e3 | ||
|
|
ecb52af225 | ||
|
|
61be6aa7c6 | ||
|
|
95dfa2c389 | ||
|
|
5ca21de3ca | ||
|
|
c11a30bb0d | ||
|
|
a73cdbeff2 | ||
|
|
e14a86026a | ||
|
|
e62d74197e | ||
|
|
7b4729e48c | ||
|
|
3b5aa44229 | ||
|
|
da547a7b89 | ||
|
|
bb2d93e42e | ||
|
|
0d7c82cf5f | ||
|
|
486b50f328 | ||
|
|
c2641488bf | ||
|
|
04ae2cb7fc | ||
|
|
a7b5fa4edc | ||
|
|
52293ad55a | ||
|
|
3bdfaa55dc | ||
|
|
e6ae6b83e6 | ||
|
|
668a9c9ada | ||
|
|
9f20633457 | ||
|
|
f0ff4dbcc9 | ||
|
|
aa884fad6b | ||
|
|
98bc859023 | ||
|
|
618b1c1a11 | ||
|
|
43197f695c | ||
|
|
7789399ba5 | ||
|
|
27a559a751 | ||
|
|
74591d24e5 | ||
|
|
4146e0f1da | ||
|
|
8481efb482 | ||
|
|
3f69334f9a |
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
Added MSC4480 sticky events to simplified sliding sync. Contributed by @eleboucher.
|
||||
@@ -0,0 +1 @@
|
||||
Added scoped typing updates to simplified Sliding Sync (MSC4508). Contributed by @eleboucher.
|
||||
@@ -0,0 +1 @@
|
||||
Replace deprecated `docker-compose` commands with `docker compose` to reflect latest Docker changes.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed typing notifications not interrupting a sync long poll. Contributed by @eleboucher.
|
||||
@@ -0,0 +1 @@
|
||||
Updated Ruma dependencies. Contributed by @eleboucher.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed missed wake-ups and stale responses in legacy and sliding sync long polls. Contributed by @eleboucher.
|
||||
@@ -0,0 +1 @@
|
||||
Livekit docs: `room.auto_create: false` is now included in default `livekit.yaml`, following `lk-jwt-service` recommendation.
|
||||
@@ -0,0 +1 @@
|
||||
Add caveat and workaround for container resolution (e.g. for dockerized appservices) when using mounted `/etc/resolv.conf`. Contributed by @stratself
|
||||
@@ -0,0 +1 @@
|
||||
Added MSC4354 sticky events, behind the `allow_sticky_events` config option. Contributed by @eleboucher.
|
||||
@@ -0,0 +1 @@
|
||||
Added MSC4186 direct, encrypted, and room-type list filters, resolving invited rooms from their stripped invite state. Contributed by @eleboucher.
|
||||
@@ -0,0 +1 @@
|
||||
Preserve all users' read receipts when batching room receipt updates.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
Updated the docs to reflect the current default User-Agent for URL previews
|
||||
@@ -0,0 +1 @@
|
||||
Resolve membership service by correct name for auto-join. Contributed by @Aranjedeath
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
Added missing documentation to config parameters. Contributed by @renegadespork
|
||||
@@ -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.
|
||||
@@ -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 +0,0 @@
|
||||
**TODO - embargoed until next release** (maintainers see security issue 10 when writing rls notes). Contributed by @eleboucher.
|
||||
+151
-18
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>`.
|
||||
|
||||
|
||||
@@ -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
@@ -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 it’s 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 don’t work together.
|
||||
:::important
|
||||
Compression is done using the COW mechanism so it’s 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 don’t 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:
|
||||
|
||||
@@ -4,7 +4,7 @@ # `!admin rooms`
|
||||
Commands for managing rooms
|
||||
|
||||
|
||||
## `!admin rooms list-rooms`
|
||||
## `!admin rooms list`
|
||||
|
||||
List all rooms the server knows about
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Generated
+110
-126
@@ -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",
|
||||
|
||||
@@ -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>,
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ pub enum ServerCommand {
|
||||
},
|
||||
|
||||
/// Hot-reload the server
|
||||
#[clap(alias = "reload")]
|
||||
#[command(name = "reload", alias = "reload-mods")]
|
||||
ReloadMods,
|
||||
|
||||
#[cfg(unix)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -541,6 +541,7 @@ pub(crate) async fn create_room_route(
|
||||
&content,
|
||||
&state_key,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
|
||||
+11
-1
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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>,
|
||||
|
||||
|
||||
@@ -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}")]
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)?)
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}")))
|
||||
})?;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -414,6 +414,7 @@ fn to_pdu_event<S>(
|
||||
content,
|
||||
origin: None,
|
||||
redacts: None,
|
||||
sticky: None,
|
||||
unsigned: None,
|
||||
auth_events,
|
||||
prev_events,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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"] }
|
||||
|
||||
@@ -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() %}
|
||||
|
||||
Reference in New Issue
Block a user