mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 38e93cde3e | |||
| 7e501cdb09 | |||
| da182c162d | |||
| 9a3f7f4af7 | |||
| 5ce1f682f6 | |||
| 5feb08dff2 | |||
| 1e527c1075 | |||
| c6943ae683 | |||
| 8932dacdc4 | |||
| 0be3d850ac | |||
| 57e7cf7057 | |||
| 1005585ccb | |||
| 1188566dbd | |||
| 0058212757 | |||
| dbf8fd3320 | |||
| ce295b079e | |||
| 5eb74bc1dd | |||
| da561ab792 | |||
| 80c9bb4796 | |||
| 22a47d1e59 | |||
| 83883a002c | |||
| 8dd4b71e0e | |||
| 6fe3b1563c | |||
| 44d3825c8e | |||
| d6c5484c3a | |||
| 1fd6056f3f | |||
| 525a0ae52b | |||
| 60210754d9 | |||
| 08dd787083 | |||
| 2c7233812b | |||
| d725e98220 | |||
| 0226ca1e83 | |||
| 1695b6d19e | |||
| c40cc3b236 | |||
| 754959e80d | |||
| 37888fb670 | |||
| 7207398a9e | |||
| 1a7bda209b | |||
| 7e1950b3d2 | |||
| b507898c62 | |||
| f4af67575e | |||
| 6adb99397e | |||
| 8ce83a8a14 | |||
| 052c4dfa21 | |||
| a43dee1728 | |||
| 763d9b3de8 | |||
| 1e6d95583c | |||
| 8a254a33cc | |||
| c97dd54766 | |||
| 8ddb7c70c0 | |||
| cb9786466b | |||
| 18d2662b01 | |||
| 558262dd1f | |||
| d311b87579 | |||
| 8702f55cf5 | |||
| d4481b07ac | |||
| 92351df925 | |||
| 47e2733ea1 | |||
| 6637e4c6a7 | |||
| 35e441452f | |||
| 66bbb655bf | |||
| 81b202ce51 | |||
| 4657844d46 | |||
| 9016cd11a6 | |||
| dd70094719 | |||
| fcd49b7ab3 | |||
| 470c9b52dd | |||
| 0d8cafc329 | |||
| 2f9956ddca | |||
| 21a97cdd0b | |||
| e986cd4536 | |||
| 526d862296 | |||
| fbeb5bf186 | |||
| a336f2df44 | |||
| 19b78ec73e | |||
| 27ff2d9363 | |||
| 50fa8c3abf | |||
| 18c4be869f | |||
| fc00b96d8b | |||
| fa4156d8a6 | |||
| 23638cd714 | |||
| 9f1a483e76 | |||
| 688ef727e5 | |||
| 3de026160e | |||
| 9fe761513d | |||
| abf1e1195a | |||
| d9537e9b55 | |||
| 0d1de70d8f | |||
| 4aa03a71eb | |||
| f847918575 | |||
| 7569a0545b | |||
| b6c5991e1f | |||
| efd879fcd8 | |||
| 92a848f74d | |||
| 776b5865ba | |||
| 722bacbe89 | |||
| 46907e3dce | |||
| 31e2195e56 | |||
| 7ecac93ddc | |||
| 6a0b103722 | |||
| 23d77b614f | |||
| e01aa44b16 | |||
| a08739c246 | |||
| c14864b881 | |||
| 1773e72e68 | |||
| 0f94d55689 | |||
| abfb6377c2 | |||
| 91d64f5b24 | |||
| 9a3f3f6e78 | |||
| b3e31a4aad | |||
| 8cda431cc6 | |||
| 02b9a3f713 | |||
| d40893730c | |||
| 28fae58cf6 | |||
| f458f6ab76 | |||
| fdf9cea533 | |||
| ecb1b73c84 | |||
| e03082480a | |||
| f9e7f019ad | |||
| 12069e7c86 | |||
| 77928a62b4 | |||
| c73cb5c1bf | |||
| a140eacb04 |
@@ -30,22 +30,22 @@ jobs:
|
|||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT
|
echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT
|
||||||
echo "Debian distribution: $DISTRIBUTION ($VERSION)"
|
echo "Debian distribution: $DISTRIBUTION ($VERSION)"
|
||||||
- name: Work around llvm-project#153385
|
#- name: Work around llvm-project#153385
|
||||||
id: llvm-workaround
|
# id: llvm-workaround
|
||||||
run: |
|
# run: |
|
||||||
if [ -f /usr/share/apt/default-sequoia.config ]; then
|
# if [ -f /usr/share/apt/default-sequoia.config ]; then
|
||||||
echo "Applying workaround for llvm-project#153385"
|
# echo "Applying workaround for llvm-project#153385"
|
||||||
mkdir -p /etc/crypto-policies/back-ends/
|
# mkdir -p /etc/crypto-policies/back-ends/
|
||||||
cp /usr/share/apt/default-sequoia.config /etc/crypto-policies/back-ends/apt-sequoia.config
|
# cp /usr/share/apt/default-sequoia.config /etc/crypto-policies/back-ends/apt-sequoia.config
|
||||||
sed -i 's/\(sha1\.second_preimage_resistance = \)2026-02-01/\12026-06-01/' /etc/crypto-policies/back-ends/apt-sequoia.config
|
# sed -i 's/\(sha1\.second_preimage_resistance = \)2026-02-01/\12026-06-01/' /etc/crypto-policies/back-ends/apt-sequoia.config
|
||||||
else
|
# else
|
||||||
echo "No workaround needed for llvm-project#153385"
|
# echo "No workaround needed for llvm-project#153385"
|
||||||
fi
|
# fi
|
||||||
- name: Pick compatible clang version
|
- name: Pick compatible clang version
|
||||||
id: clang-version
|
id: clang-version
|
||||||
run: |
|
run: |
|
||||||
# both latest need to use clang-23, but oldstable and previous can just use clang
|
# both latest need to use clang-23, but oldstable and previous can just use clang
|
||||||
if [[ "${{ matrix.container }}" == "ubuntu-latest" || "${{ matrix.container }}" == "debian-latest" ]]; then
|
if [[ "${{ matrix.container }}" == "ubuntu-latest" ]]; then
|
||||||
echo "Using clang-23 package for ${{ matrix.container }}"
|
echo "Using clang-23 package for ${{ matrix.container }}"
|
||||||
echo "version=clang-23" >> $GITHUB_OUTPUT
|
echo "version=clang-23" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
|
|||||||
+14
-2
@@ -1,5 +1,6 @@
|
|||||||
default_install_hook_types:
|
default_install_hook_types:
|
||||||
- pre-commit
|
- pre-commit
|
||||||
|
- pre-push
|
||||||
- commit-msg
|
- commit-msg
|
||||||
default_stages:
|
default_stages:
|
||||||
- pre-commit
|
- pre-commit
|
||||||
@@ -23,7 +24,7 @@ repos:
|
|||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
|
|
||||||
- repo: https://github.com/crate-ci/typos
|
- repo: https://github.com/crate-ci/typos
|
||||||
rev: v1.43.4
|
rev: v1.44.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: typos
|
- id: typos
|
||||||
- id: typos
|
- id: typos
|
||||||
@@ -31,7 +32,7 @@ repos:
|
|||||||
stages: [commit-msg]
|
stages: [commit-msg]
|
||||||
|
|
||||||
- repo: https://github.com/crate-ci/committed
|
- repo: https://github.com/crate-ci/committed
|
||||||
rev: v1.1.10
|
rev: v1.1.11
|
||||||
hooks:
|
hooks:
|
||||||
- id: committed
|
- id: committed
|
||||||
|
|
||||||
@@ -45,3 +46,14 @@ repos:
|
|||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
stages:
|
stages:
|
||||||
- pre-commit
|
- pre-commit
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: cargo-clippy
|
||||||
|
name: cargo clippy
|
||||||
|
entry: cargo clippy -- -D warnings
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
types: [rust]
|
||||||
|
stages:
|
||||||
|
- pre-push
|
||||||
|
|||||||
@@ -1,3 +1,32 @@
|
|||||||
|
# Continuwuity 0.5.6 (2026-03-03)
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Admin escape commands received over federation will never be executed, as this is never valid in a genuine situation. Contributed by @Jade.
|
||||||
|
- Fixed data amplification vulnerability (CWE-409) that affected configurations with server-side compression enabled (non-default). Contributed by @nex.
|
||||||
|
|
||||||
|
## 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))
|
||||||
|
- 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))
|
||||||
|
|
||||||
|
## 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 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 `!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.
|
||||||
|
|
||||||
# Continuwuity v0.5.5 (2026-02-15)
|
# Continuwuity v0.5.5 (2026-02-15)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
+16
-8
@@ -85,24 +85,31 @@ If your changes are done to fix Matrix tests, please note that in your pull requ
|
|||||||
|
|
||||||
### Writing documentation
|
### Writing documentation
|
||||||
|
|
||||||
Continuwuity's website uses [`mdbook`][mdbook] and is deployed via CI using Cloudflare Pages
|
Continuwuity's website uses [`rspress`][rspress] and is deployed via CI using Cloudflare Pages
|
||||||
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/`
|
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/`
|
||||||
directory at the top level.
|
directory at the top level.
|
||||||
|
|
||||||
To build the documentation locally:
|
To load the documentation locally:
|
||||||
|
|
||||||
|
1. Install NodeJS and npm from their [official website][nodejs-download] or via your package manager of choice
|
||||||
|
|
||||||
|
2. From the project's root directory, install the relevant npm modules
|
||||||
|
|
||||||
1. Install mdbook if you don't have it already:
|
|
||||||
```bash
|
```bash
|
||||||
cargo install mdbook # or cargo binstall, or another method
|
npm ci
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Build the documentation:
|
3. Make changes to the document pages as you see fit
|
||||||
|
|
||||||
|
4. Generate a live preview of the documentation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mdbook build
|
npm run docs:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The output of the mdbook generation is in `public/`. You can open the HTML files directly in your browser without needing a web server.
|
A webserver for the docs will be spun up for you (e.g. at `http://localhost:3000`). Any changes you make to the documentation will be live-reloaded on the webpage.
|
||||||
|
|
||||||
|
Alternatively, you can build the documentation using `npm run docs:build` - the output of this will be in the `/doc_build` directory. Once you're happy with your documentation updates, you can commit the changes.
|
||||||
|
|
||||||
### Commit Messages
|
### Commit Messages
|
||||||
|
|
||||||
@@ -169,5 +176,6 @@ continuwuity Matrix rooms for Code of Conduct violations.
|
|||||||
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
|
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
|
||||||
[complement]: https://github.com/matrix-org/complement/
|
[complement]: https://github.com/matrix-org/complement/
|
||||||
[sytest]: https://github.com/matrix-org/sytest/
|
[sytest]: https://github.com/matrix-org/sytest/
|
||||||
[mdbook]: https://rust-lang.github.io/mdBook/
|
[nodejs-download]: https://nodejs.org/en/download
|
||||||
|
[rspress]: https://rspress.rs/
|
||||||
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml
|
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml
|
||||||
|
|||||||
Generated
+512
-538
File diff suppressed because it is too large
Load Diff
+18
-14
@@ -12,7 +12,7 @@ license = "Apache-2.0"
|
|||||||
# See also `rust-toolchain.toml`
|
# See also `rust-toolchain.toml`
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||||
version = "0.5.5"
|
version = "0.5.6"
|
||||||
|
|
||||||
[workspace.metadata.crane]
|
[workspace.metadata.crane]
|
||||||
name = "conduwuit"
|
name = "conduwuit"
|
||||||
@@ -68,7 +68,7 @@ default-features = false
|
|||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
|
||||||
[workspace.dependencies.rand]
|
[workspace.dependencies.rand]
|
||||||
version = "0.8.5"
|
version = "0.10.0"
|
||||||
|
|
||||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||||
[workspace.dependencies.bytes]
|
[workspace.dependencies.bytes]
|
||||||
@@ -84,7 +84,7 @@ version = "1.3.1"
|
|||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
|
|
||||||
[workspace.dependencies.axum]
|
[workspace.dependencies.axum]
|
||||||
version = "0.7.9"
|
version = "0.8.8"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"form",
|
"form",
|
||||||
@@ -97,7 +97,7 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.axum-extra]
|
[workspace.dependencies.axum-extra]
|
||||||
version = "0.9.6"
|
version = "0.12.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["typed-header", "tracing"]
|
features = ["typed-header", "tracing"]
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ default-features = false
|
|||||||
version = "0.7"
|
version = "0.7"
|
||||||
|
|
||||||
[workspace.dependencies.axum-client-ip]
|
[workspace.dependencies.axum-client-ip]
|
||||||
version = "0.6.1"
|
version = "0.7"
|
||||||
|
|
||||||
[workspace.dependencies.tower]
|
[workspace.dependencies.tower]
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -118,7 +118,7 @@ default-features = false
|
|||||||
features = ["util"]
|
features = ["util"]
|
||||||
|
|
||||||
[workspace.dependencies.tower-http]
|
[workspace.dependencies.tower-http]
|
||||||
version = "0.6.2"
|
version = "0.6.8"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"add-extension",
|
"add-extension",
|
||||||
@@ -144,6 +144,7 @@ features = [
|
|||||||
"socks",
|
"socks",
|
||||||
"hickory-dns",
|
"hickory-dns",
|
||||||
"http2",
|
"http2",
|
||||||
|
"stream",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.serde]
|
[workspace.dependencies.serde]
|
||||||
@@ -253,7 +254,7 @@ features = [
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
||||||
[workspace.dependencies.libloading]
|
[workspace.dependencies.libloading]
|
||||||
version = "0.8.6"
|
version = "0.9.0"
|
||||||
|
|
||||||
# Validating urls in config, was already a transitive dependency
|
# Validating urls in config, was already a transitive dependency
|
||||||
[workspace.dependencies.url]
|
[workspace.dependencies.url]
|
||||||
@@ -298,7 +299,7 @@ default-features = false
|
|||||||
features = ["env", "toml"]
|
features = ["env", "toml"]
|
||||||
|
|
||||||
[workspace.dependencies.hickory-resolver]
|
[workspace.dependencies.hickory-resolver]
|
||||||
version = "0.25.1"
|
version = "0.25.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"serde",
|
"serde",
|
||||||
@@ -342,7 +343,8 @@ version = "0.1.2"
|
|||||||
# Used for matrix spec type definitions and helpers
|
# Used for matrix spec type definitions and helpers
|
||||||
[workspace.dependencies.ruma]
|
[workspace.dependencies.ruma]
|
||||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||||
rev = "b496b7f38d517149361a882e75d3fd4faf210441"
|
#branch = "conduwuit-changes"
|
||||||
|
rev = "bb12ed288a31a23aa11b10ba0fad22b7f985eb88"
|
||||||
features = [
|
features = [
|
||||||
"compat",
|
"compat",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -362,6 +364,7 @@ features = [
|
|||||||
"unstable-msc2870",
|
"unstable-msc2870",
|
||||||
"unstable-msc3026",
|
"unstable-msc3026",
|
||||||
"unstable-msc3061",
|
"unstable-msc3061",
|
||||||
|
"unstable-msc3814",
|
||||||
"unstable-msc3245",
|
"unstable-msc3245",
|
||||||
"unstable-msc3266",
|
"unstable-msc3266",
|
||||||
"unstable-msc3381", # polls
|
"unstable-msc3381", # polls
|
||||||
@@ -380,6 +383,7 @@ features = [
|
|||||||
"unstable-pdu",
|
"unstable-pdu",
|
||||||
"unstable-msc4155",
|
"unstable-msc4155",
|
||||||
"unstable-msc4143", # livekit well_known response
|
"unstable-msc4143", # livekit well_known response
|
||||||
|
"unstable-msc4284"
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.rust-rocksdb]
|
[workspace.dependencies.rust-rocksdb]
|
||||||
@@ -424,7 +428,7 @@ features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
|
|||||||
|
|
||||||
# optional sentry metrics for crash/panic reporting
|
# optional sentry metrics for crash/panic reporting
|
||||||
[workspace.dependencies.sentry]
|
[workspace.dependencies.sentry]
|
||||||
version = "0.45.0"
|
version = "0.46.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
@@ -440,9 +444,9 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.sentry-tracing]
|
[workspace.dependencies.sentry-tracing]
|
||||||
version = "0.45.0"
|
version = "0.46.0"
|
||||||
[workspace.dependencies.sentry-tower]
|
[workspace.dependencies.sentry-tower]
|
||||||
version = "0.45.0"
|
version = "0.46.0"
|
||||||
|
|
||||||
# jemalloc usage
|
# jemalloc usage
|
||||||
[workspace.dependencies.tikv-jemalloc-sys]
|
[workspace.dependencies.tikv-jemalloc-sys]
|
||||||
@@ -471,7 +475,7 @@ features = ["use_std"]
|
|||||||
version = "0.5"
|
version = "0.5"
|
||||||
|
|
||||||
[workspace.dependencies.nix]
|
[workspace.dependencies.nix]
|
||||||
version = "0.30.1"
|
version = "0.31.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["resource"]
|
features = ["resource"]
|
||||||
|
|
||||||
@@ -553,7 +557,7 @@ version = "0.7.5"
|
|||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
||||||
[workspace.dependencies.askama]
|
[workspace.dependencies.askama]
|
||||||
version = "0.14.0"
|
version = "0.15.0"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Patches
|
# Patches
|
||||||
|
|||||||
@@ -57,10 +57,15 @@ Continuwuity aims to:
|
|||||||
|
|
||||||
### Can I try it out?
|
### Can I try it out?
|
||||||
|
|
||||||
Check out the [documentation](https://continuwuity.org) for installation instructions, or join one of these vetted public homeservers running Continuwuity to get a feel for things!
|
Check out the [documentation](https://continuwuity.org) for installation instructions.
|
||||||
|
|
||||||
- https://continuwuity.rocks -- A public demo server operated by the Continuwuity Team.
|
If you want to try it out as a user, we have some partnered homeservers you can use:
|
||||||
- https://federated.nexus -- Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo.
|
* You can head over to [https://federated.nexus](https://federated.nexus/) in your browser.
|
||||||
|
* Hit the `Apply to Join` button. Once your request has been accepted, you will receive an email with your username and password.
|
||||||
|
* Head over to [https://app.federated.nexus](https://app.federated.nexus/) and you can sign in there, or use any other matrix chat client you wish elsewhere.
|
||||||
|
* Your username for matrix will be in the form of `@username:federated.nexus`, however you can simply use the `username` part to log in. Your password is your password.
|
||||||
|
|
||||||
|
* There's also [https://continuwuity.rocks/](https://continuwuity.rocks/). You can register a new account using Cinny via [this convenient link](https://app.cinny.in/register/continuwuity.rocks), or you can use Element or another matrix client *that supports registration*.
|
||||||
|
|
||||||
### What are we working on?
|
### What are we working on?
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -6,10 +6,10 @@ set -euo pipefail
|
|||||||
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
|
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
|
||||||
|
|
||||||
# A `.jsonl` file to write test logs to
|
# A `.jsonl` file to write test logs to
|
||||||
LOG_FILE="${2:-complement_test_logs.jsonl}"
|
LOG_FILE="${2:-tests/test_results/complement/test_logs.jsonl}"
|
||||||
|
|
||||||
# A `.jsonl` file to write test results to
|
# A `.jsonl` file to write test results to
|
||||||
RESULTS_FILE="${3:-complement_test_results.jsonl}"
|
RESULTS_FILE="${3:-tests/test_results/complement/test_results.jsonl}"
|
||||||
|
|
||||||
# The base docker image to use for complement tests
|
# The base docker image to use for complement tests
|
||||||
# You can build the default with `docker build -t continuwuity:complement -f ./docker/complement.Dockerfile .`
|
# You can build the default with `docker build -t continuwuity:complement -f ./docker/complement.Dockerfile .`
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Stopped left rooms from being unconditionally sent on initial sync, hopefully fixing spurious appearances of left rooms in some clients (and making sync faster as a bonus). Contributed by @ginger
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Re-added support for reading registration tokens from a file. Contributed by @ginger and @benbot.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Prevent removing the admin room alias (`#admins`) to avoid accidentally breaking admin room functionality. Contributed by @0xnim
|
||||||
+15
-3
@@ -15,6 +15,18 @@ disallowed-macros = [
|
|||||||
{ path = "log::trace", reason = "use conduwuit_core::trace" },
|
{ path = "log::trace", reason = "use conduwuit_core::trace" },
|
||||||
]
|
]
|
||||||
|
|
||||||
disallowed-methods = [
|
[[disallowed-methods]]
|
||||||
{ path = "tokio::spawn", reason = "use and pass conduuwit_core::server::Server::runtime() to spawn from" },
|
path = "tokio::spawn"
|
||||||
]
|
reason = "use and pass conduwuit_core::server::Server::runtime() to spawn from"
|
||||||
|
|
||||||
|
[[disallowed-methods]]
|
||||||
|
path = "reqwest::Response::bytes"
|
||||||
|
reason = "bytes is unsafe, use limit_read via the conduwuit_core::utils::LimitReadExt trait instead"
|
||||||
|
|
||||||
|
[[disallowed-methods]]
|
||||||
|
path = "reqwest::Response::text"
|
||||||
|
reason = "text is unsafe, use limit_read_text via the conduwuit_core::utils::LimitReadExt trait instead"
|
||||||
|
|
||||||
|
[[disallowed-methods]]
|
||||||
|
path = "reqwest::Response::json"
|
||||||
|
reason = "json is unsafe, use limit_read_text via the conduwuit_core::utils::LimitReadExt trait instead"
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ address = "0.0.0.0"
|
|||||||
allow_device_name_federation = true
|
allow_device_name_federation = true
|
||||||
allow_guest_registration = true
|
allow_guest_registration = true
|
||||||
allow_public_room_directory_over_federation = true
|
allow_public_room_directory_over_federation = true
|
||||||
allow_public_room_directory_without_auth = true
|
|
||||||
allow_registration = true
|
allow_registration = true
|
||||||
database_path = "/database"
|
database_path = "/database"
|
||||||
log = "trace,h2=debug,hyper=debug"
|
log = "trace,h2=debug,hyper=debug,conduwuit_database=warn,conduwuit_service::manager=info,conduwuit_api::router=error,conduwuit_router=error,tower_http=error"
|
||||||
port = [8008, 8448]
|
port = [8008, 8448]
|
||||||
trusted_servers = []
|
trusted_servers = []
|
||||||
only_query_trusted_key_servers = false
|
only_query_trusted_key_servers = false
|
||||||
@@ -25,7 +24,7 @@ url_preview_domain_explicit_denylist = ["*"]
|
|||||||
media_compat_file_link = false
|
media_compat_file_link = false
|
||||||
media_startup_check = true
|
media_startup_check = true
|
||||||
prune_missing_media = true
|
prune_missing_media = true
|
||||||
log_colors = true
|
log_colors = false
|
||||||
admin_room_notices = false
|
admin_room_notices = false
|
||||||
allow_check_for_updates = false
|
allow_check_for_updates = false
|
||||||
intentionally_unknown_config_option_for_testing = true
|
intentionally_unknown_config_option_for_testing = true
|
||||||
@@ -48,6 +47,7 @@ federation_idle_timeout = 300
|
|||||||
sender_timeout = 300
|
sender_timeout = 300
|
||||||
sender_idle_timeout = 300
|
sender_idle_timeout = 300
|
||||||
sender_retry_backoff_limit = 300
|
sender_retry_backoff_limit = 300
|
||||||
|
force_disable_first_run_mode = true
|
||||||
|
|
||||||
[global.tls]
|
[global.tls]
|
||||||
dual_protocol = true
|
dual_protocol = true
|
||||||
|
|||||||
+83
-40
@@ -290,6 +290,25 @@
|
|||||||
#
|
#
|
||||||
#max_fetch_prev_events = 192
|
#max_fetch_prev_events = 192
|
||||||
|
|
||||||
|
# How many incoming federation transactions the server is willing to be
|
||||||
|
# processing at any given time before it becomes overloaded and starts
|
||||||
|
# rejecting further transactions until some slots become available.
|
||||||
|
#
|
||||||
|
# Setting this value too low or too high may result in unstable
|
||||||
|
# federation, and setting it too high may cause runaway resource usage.
|
||||||
|
#
|
||||||
|
#max_concurrent_inbound_transactions = 150
|
||||||
|
|
||||||
|
# Maximum age (in seconds) for cached federation transaction responses.
|
||||||
|
# Entries older than this will be removed during cleanup.
|
||||||
|
#
|
||||||
|
#transaction_id_cache_max_age_secs = 7200 (2 hours)
|
||||||
|
|
||||||
|
# Maximum number of cached federation transaction responses.
|
||||||
|
# When the cache exceeds this limit, older entries will be removed.
|
||||||
|
#
|
||||||
|
#transaction_id_cache_max_entries = 8192
|
||||||
|
|
||||||
# Default/base connection timeout (seconds). This is used only by URL
|
# Default/base connection timeout (seconds). This is used only by URL
|
||||||
# previews and update/news endpoint checks.
|
# previews and update/news endpoint checks.
|
||||||
#
|
#
|
||||||
@@ -457,18 +476,25 @@
|
|||||||
#yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false
|
#yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false
|
||||||
|
|
||||||
# A static registration token that new users will have to provide when
|
# A static registration token that new users will have to provide when
|
||||||
# creating an account. If unset and `allow_registration` is true,
|
# creating an account. This token does not supersede tokens from other
|
||||||
# you must set
|
# sources, such as the `!admin token` command or the
|
||||||
# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
|
# `registration_token_file` configuration option.
|
||||||
# to true to allow open registration without any conditions.
|
|
||||||
#
|
|
||||||
# If you do not want to set a static token, the `!admin token` commands
|
|
||||||
# may also be used to manage registration tokens.
|
|
||||||
#
|
#
|
||||||
# example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
# example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
||||||
#
|
#
|
||||||
#registration_token =
|
#registration_token =
|
||||||
|
|
||||||
|
# A path to a file containing static registration tokens, one per line.
|
||||||
|
# Tokens in this file do not supersede tokens from other sources, such as
|
||||||
|
# the `!admin token` command or the `registration_token` configuration
|
||||||
|
# option.
|
||||||
|
#
|
||||||
|
# The file will be read once, when Continuwuity starts. It is not
|
||||||
|
# currently reread when the server configuration is reloaded. If the file
|
||||||
|
# cannot be read, Continuwuity will fail to start.
|
||||||
|
#
|
||||||
|
#registration_token_file =
|
||||||
|
|
||||||
# The public site key for reCaptcha. If this is provided, reCaptcha
|
# The public site key for reCaptcha. If this is provided, reCaptcha
|
||||||
# becomes required during registration. If both captcha *and*
|
# becomes required during registration. If both captcha *and*
|
||||||
# registration token are enabled, both will be required during
|
# registration token are enabled, both will be required during
|
||||||
@@ -527,12 +553,6 @@
|
|||||||
#
|
#
|
||||||
#allow_public_room_directory_over_federation = false
|
#allow_public_room_directory_over_federation = false
|
||||||
|
|
||||||
# Set this to true to allow your server's public room directory to be
|
|
||||||
# queried without client authentication (access token) through the Client
|
|
||||||
# APIs. Set this to false to protect against /publicRooms spiders.
|
|
||||||
#
|
|
||||||
#allow_public_room_directory_without_auth = false
|
|
||||||
|
|
||||||
# Allow guests/unauthenticated users to access TURN credentials.
|
# Allow guests/unauthenticated users to access TURN credentials.
|
||||||
#
|
#
|
||||||
# This is the equivalent of Synapse's `turn_allow_guests` config option.
|
# This is the equivalent of Synapse's `turn_allow_guests` config option.
|
||||||
@@ -1056,14 +1076,6 @@
|
|||||||
#
|
#
|
||||||
#rocksdb_repair = false
|
#rocksdb_repair = false
|
||||||
|
|
||||||
# This item is undocumented. Please contribute documentation for it.
|
|
||||||
#
|
|
||||||
#rocksdb_read_only = false
|
|
||||||
|
|
||||||
# This item is undocumented. Please contribute documentation for it.
|
|
||||||
#
|
|
||||||
#rocksdb_secondary = false
|
|
||||||
|
|
||||||
# Enables idle CPU priority for compaction thread. This is not enabled by
|
# Enables idle CPU priority for compaction thread. This is not enabled by
|
||||||
# default to prevent compaction from falling too far behind on busy
|
# default to prevent compaction from falling too far behind on busy
|
||||||
# systems.
|
# systems.
|
||||||
@@ -1120,27 +1132,34 @@
|
|||||||
|
|
||||||
# Allow local (your server only) presence updates/requests.
|
# Allow local (your server only) presence updates/requests.
|
||||||
#
|
#
|
||||||
# Note that presence on continuwuity is very fast unlike Synapse's. If
|
# Local presence must be enabled for outgoing presence to function.
|
||||||
# using outgoing presence, this MUST be enabled.
|
#
|
||||||
|
# Note that local presence is not as heavy on the CPU as federated
|
||||||
|
# presence, but will still become more expensive the more local users you
|
||||||
|
# have.
|
||||||
#
|
#
|
||||||
#allow_local_presence = true
|
#allow_local_presence = true
|
||||||
|
|
||||||
# Allow incoming federated presence updates/requests.
|
# Allow incoming federated presence updates.
|
||||||
#
|
#
|
||||||
# This option receives presence updates from other servers, but does not
|
# This option enables processing inbound presence updates from other
|
||||||
# send any unless `allow_outgoing_presence` is true. Note that presence on
|
# servers. Without it, remote users will appear as if they are always
|
||||||
# continuwuity is very fast unlike Synapse's.
|
# offline to your local users. This does not affect typing indicators or
|
||||||
|
# read receipts.
|
||||||
#
|
#
|
||||||
#allow_incoming_presence = true
|
#allow_incoming_presence = true
|
||||||
|
|
||||||
# Allow outgoing presence updates/requests.
|
# Allow outgoing presence updates/requests.
|
||||||
#
|
#
|
||||||
# This option sends presence updates to other servers, but does not
|
# This option sends presence updates to other servers, and requires that
|
||||||
# receive any unless `allow_incoming_presence` is true. Note that presence
|
# `allow_local_presence` is also enabled.
|
||||||
# on continuwuity is very fast unlike Synapse's. If using outgoing
|
|
||||||
# presence, you MUST enable `allow_local_presence` as well.
|
|
||||||
#
|
#
|
||||||
#allow_outgoing_presence = true
|
# Note that outgoing presence is very heavy on the CPU and network, and
|
||||||
|
# will typically cause extreme strain and slowdowns for no real benefit.
|
||||||
|
# There are only a few clients that even implement presence, so you
|
||||||
|
# probably don't want to enable this.
|
||||||
|
#
|
||||||
|
#allow_outgoing_presence = false
|
||||||
|
|
||||||
# How many seconds without presence updates before you become idle.
|
# How many seconds without presence updates before you become idle.
|
||||||
# Defaults to 5 minutes.
|
# Defaults to 5 minutes.
|
||||||
@@ -1174,6 +1193,10 @@
|
|||||||
|
|
||||||
# Allow sending read receipts to remote servers.
|
# Allow sending read receipts to remote servers.
|
||||||
#
|
#
|
||||||
|
# Note that sending read receipts to remote servers in large rooms with
|
||||||
|
# lots of other homeservers may cause additional strain on the CPU and
|
||||||
|
# network.
|
||||||
|
#
|
||||||
#allow_outgoing_read_receipts = true
|
#allow_outgoing_read_receipts = true
|
||||||
|
|
||||||
# Allow local typing updates.
|
# Allow local typing updates.
|
||||||
@@ -1185,6 +1208,10 @@
|
|||||||
|
|
||||||
# Allow outgoing typing updates to federation.
|
# Allow outgoing typing updates to federation.
|
||||||
#
|
#
|
||||||
|
# Note that sending typing indicators to remote servers in large rooms
|
||||||
|
# with lots of other homeservers may cause additional strain on the CPU
|
||||||
|
# and network.
|
||||||
|
#
|
||||||
#allow_outgoing_typing = true
|
#allow_outgoing_typing = true
|
||||||
|
|
||||||
# Allow incoming typing updates from federation.
|
# Allow incoming typing updates from federation.
|
||||||
@@ -1318,7 +1345,7 @@
|
|||||||
# sender user's server name, inbound federation X-Matrix origin, and
|
# sender user's server name, inbound federation X-Matrix origin, and
|
||||||
# outbound federation handler.
|
# outbound federation handler.
|
||||||
#
|
#
|
||||||
# You can set this to ["*"] to block all servers by default, and then
|
# You can set this to [".*"] to block all servers by default, and then
|
||||||
# use `allowed_remote_server_names` to allow only specific servers.
|
# use `allowed_remote_server_names` to allow only specific servers.
|
||||||
#
|
#
|
||||||
# example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
|
# example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
|
||||||
@@ -1824,14 +1851,13 @@
|
|||||||
#
|
#
|
||||||
#support_mxid =
|
#support_mxid =
|
||||||
|
|
||||||
# A list of MatrixRTC foci URLs which will be served as part of the
|
# **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
|
||||||
# MSC4143 client endpoint at /.well-known/matrix/client. If you're
|
|
||||||
# setting up livekit, you'd want something like:
|
|
||||||
# rtc_focus_server_urls = [
|
|
||||||
# { type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
|
||||||
# ]
|
|
||||||
#
|
#
|
||||||
# To disable, set this to be an empty vector (`[]`).
|
# A list of MatrixRTC foci URLs which will be served as part of the
|
||||||
|
# MSC4143 client endpoint at /.well-known/matrix/client.
|
||||||
|
#
|
||||||
|
# This option is deprecated and will be removed in a future release.
|
||||||
|
# Please migrate to the new `[global.matrix_rtc]` config section.
|
||||||
#
|
#
|
||||||
#rtc_focus_server_urls = []
|
#rtc_focus_server_urls = []
|
||||||
|
|
||||||
@@ -1853,6 +1879,23 @@
|
|||||||
#
|
#
|
||||||
#blurhash_max_raw_size = 33554432
|
#blurhash_max_raw_size = 33554432
|
||||||
|
|
||||||
|
[global.matrix_rtc]
|
||||||
|
|
||||||
|
# A list of MatrixRTC foci (transports) which will be served via the
|
||||||
|
# MSC4143 RTC transports endpoint at
|
||||||
|
# `/_matrix/client/v1/rtc/transports`. If you're setting up livekit,
|
||||||
|
# you'd want something like:
|
||||||
|
# ```toml
|
||||||
|
# [global.matrix_rtc]
|
||||||
|
# foci = [
|
||||||
|
# { type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
||||||
|
# ]
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# To disable, set this to an empty list (`[]`).
|
||||||
|
#
|
||||||
|
#foci = []
|
||||||
|
|
||||||
[global.ldap]
|
[global.ldap]
|
||||||
|
|
||||||
# Whether to enable LDAP login.
|
# Whether to enable LDAP login.
|
||||||
|
|||||||
+9
-4
@@ -48,11 +48,11 @@ EOF
|
|||||||
|
|
||||||
# Developer tool versions
|
# Developer tool versions
|
||||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||||
ENV BINSTALL_VERSION=1.17.5
|
ENV BINSTALL_VERSION=1.17.6
|
||||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||||
ENV CARGO_SBOM_VERSION=0.9.1
|
ENV CARGO_SBOM_VERSION=0.9.1
|
||||||
# renovate: datasource=crate depName=lddtree
|
# renovate: datasource=crate depName=lddtree
|
||||||
ENV LDDTREE_VERSION=0.4.0
|
ENV LDDTREE_VERSION=0.5.0
|
||||||
# renovate: datasource=crate depName=timelord-cli
|
# renovate: datasource=crate depName=timelord-cli
|
||||||
ENV TIMELORD_VERSION=3.0.1
|
ENV TIMELORD_VERSION=3.0.1
|
||||||
|
|
||||||
@@ -180,6 +180,11 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|||||||
export RUSTFLAGS="${RUSTFLAGS}"
|
export RUSTFLAGS="${RUSTFLAGS}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
RUST_PROFILE_DIR="${RUST_PROFILE}"
|
||||||
|
if [[ "${RUST_PROFILE}" == "dev" ]]; then
|
||||||
|
RUST_PROFILE_DIR="debug"
|
||||||
|
fi
|
||||||
|
|
||||||
TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \
|
TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \
|
||||||
jq -r ".target_directory"))
|
jq -r ".target_directory"))
|
||||||
mkdir /out/sbin
|
mkdir /out/sbin
|
||||||
@@ -191,8 +196,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|||||||
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
|
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
|
||||||
for BINARY in "${BINARIES[@]}"; do
|
for BINARY in "${BINARIES[@]}"; do
|
||||||
echo $BINARY
|
echo $BINARY
|
||||||
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY
|
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE_DIR}/$BINARY
|
||||||
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY /out/sbin/$BINARY
|
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE_DIR}/$BINARY /out/sbin/$BINARY
|
||||||
done
|
done
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
|
|||||||
|
|
||||||
# Developer tool versions
|
# Developer tool versions
|
||||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||||
ENV BINSTALL_VERSION=1.17.5
|
ENV BINSTALL_VERSION=1.17.6
|
||||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||||
ENV CARGO_SBOM_VERSION=0.9.1
|
ENV CARGO_SBOM_VERSION=0.9.1
|
||||||
# renovate: datasource=crate depName=lddtree
|
# renovate: datasource=crate depName=lddtree
|
||||||
ENV LDDTREE_VERSION=0.4.0
|
ENV LDDTREE_VERSION=0.5.0
|
||||||
|
|
||||||
# Install unpackaged tools
|
# Install unpackaged tools
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
|
|||||||
@@ -34,6 +34,11 @@
|
|||||||
"name": "troubleshooting",
|
"name": "troubleshooting",
|
||||||
"label": "Troubleshooting"
|
"label": "Troubleshooting"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "dir",
|
||||||
|
"name": "advanced",
|
||||||
|
"label": "Advanced"
|
||||||
|
},
|
||||||
"security",
|
"security",
|
||||||
{
|
{
|
||||||
"type": "dir-section-header",
|
"type": "dir-section-header",
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"text": "Guide",
|
"text": "Guide",
|
||||||
"link": "/introduction",
|
"link": "/introduction",
|
||||||
"activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting)"
|
"activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting|advanced)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "Development",
|
"text": "Development",
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "file",
|
||||||
|
"name": "delegation",
|
||||||
|
"label": "Delegation / split-domain"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
# Delegation/split-domain deployment
|
||||||
|
|
||||||
|
Matrix allows clients and servers to discover a homeserver's "true" destination via **`.well-known` delegation**. This is especially useful if you would like to:
|
||||||
|
|
||||||
|
- Serve Continuwuity on a subdomain while having only the base domain for your usernames
|
||||||
|
- Use a port other than `:8448` for server-to-server connections
|
||||||
|
|
||||||
|
This guide will show you how to have `@user:example.com` usernames while serving Continuwuity on `https://matrix.example.com`. It assumes you are using port 443 for both client-to-server connections and server-to-server federation.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
First, ensure you have set up A/AAAA records for `matrix.example.com` and `example.com` pointing to your IP.
|
||||||
|
|
||||||
|
Then, ensure that the `server_name` field matches your intended username suffix. If this is not the case, you **MUST** wipe the database directory and reinstall Continuwuity with your desired `server_name`.
|
||||||
|
|
||||||
|
Then, in the `[global.well_known]` section of your config file, add the following fields:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[global.well_known]
|
||||||
|
|
||||||
|
client = "https://matrix.example.com"
|
||||||
|
|
||||||
|
# port number MUST be specified
|
||||||
|
server = "matrix.example.com:443"
|
||||||
|
|
||||||
|
# (optional) customize your support contacts
|
||||||
|
#support_page =
|
||||||
|
#support_role = "m.role.admin"
|
||||||
|
#support_email =
|
||||||
|
#support_mxid = "@user:example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively if you are using Docker, you can set the `CONTINUWUITY_WELL_KNOWN` environment variable as below:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
continuwuity:
|
||||||
|
...
|
||||||
|
environment:
|
||||||
|
CONTINUWUITY_WELL_KNOWN: |
|
||||||
|
{
|
||||||
|
client=https://matrix.example.com,
|
||||||
|
server=matrix.example.com:443
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Serving with a reverse proxy
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
To enable full discovery, you will need to reverse proxy these paths from the base domain back to Continuwuity.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>For Caddy</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
matrix.example.com:443 {
|
||||||
|
reverse_proxy 127.0.0.1:8008
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com:443 {
|
||||||
|
reverse_proxy /.well-known/matrix* 127.0.0.1:8008
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>For Traefik (via Docker labels)</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
services:
|
||||||
|
continuwuity:
|
||||||
|
...
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.continuwuity.rule=(Host(`matrix.example.com`) || (Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))"
|
||||||
|
- "traefik.http.routers.continuwuity.service=continuwuity"
|
||||||
|
- "traefik.http.services.continuwuity.loadbalancer.server.port=8008"
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Restart Continuwuity and your reverse proxy. Once that's done, visit these routes and check that the responses match the examples below:
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
|
||||||
|
<summary>`https://example.com/.well-known/matrix/server`</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"m.server": "matrix.example.com:443"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
|
||||||
|
<summary>`https://example.com/.well-known/matrix/client`</summary>
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"m.homeserver": {
|
||||||
|
"base_url": "https://matrix.example.com/"
|
||||||
|
},
|
||||||
|
"org.matrix.msc3575.proxy": {
|
||||||
|
"url": "https://matrix.example.com/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Cannot log in with web clients
|
||||||
|
|
||||||
|
Make sure there is an `Access-Control-Allow-Origin: *` header in your `/.well-known/matrix/client` path. While Continuwuity serves this header by default, it may be dropped by reverse proxies or other middlewares.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 uses SRV DNS records and only work with federation traffic. They are only included for completeness.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Using only SRV records</summary>
|
||||||
|
|
||||||
|
If you can't set up `/.well-known/matrix/server` on :443 for some reason, you can set up a SRV record (via your DNS provider) as below:
|
||||||
|
|
||||||
|
- Service and name: `_matrix-fed._tcp.example.com.`
|
||||||
|
- Priority: `10` (can be any number)
|
||||||
|
- Weight: `10` (can be any number)
|
||||||
|
- Port: `443`
|
||||||
|
- Target: `matrix.example.com.`
|
||||||
|
|
||||||
|
On the target's IP at port 443, you must configure a valid route and cert for your server name, `example.com`. Therefore, this method only works to redirect traffic into the right IP/port combo, and can not delegate your federation to a different domain.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Using SRV records + .well-known</summary>
|
||||||
|
|
||||||
|
You can also set up `/.well-known/matrix/server` with a delegated domain but no ports:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[global.well_known]
|
||||||
|
server = "matrix.example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, set up a SRV record (via your DNS provider) to announce the port number as below:
|
||||||
|
|
||||||
|
- Service and name: `_matrix-fed._tcp.matrix.example.com.`
|
||||||
|
- Priority: `10` (can be any number)
|
||||||
|
- Weight: `10` (can be any number)
|
||||||
|
- Port: `443`
|
||||||
|
- Target: `matrix.example.com.`
|
||||||
|
|
||||||
|
On the target's IP at port 443, you'll need to provide a valid route and cert for `matrix.example.com`. It provides the same feature as pure `.well-known` delegation, albeit with more parts to handle.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Using SRV records as a fallback for .well-known delegation</summary>
|
||||||
|
|
||||||
|
Assume your delegation is as below:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[global.well_known]
|
||||||
|
server = "example.com:443"
|
||||||
|
```
|
||||||
|
|
||||||
|
If your Continuwuity instance becomes temporarily unreachable, other servers will not be able to find your `/.well-known/matrix/server` file, and defaults to using `server_name:8448`. This incorrect cache can persist for a long time, and would hinder re-federation when your server eventually comes back online.
|
||||||
|
|
||||||
|
If you want other servers to default to using port :443 even when it is offline, you could set up a SRV record (via your DNS provider) as follows:
|
||||||
|
|
||||||
|
- Service and name: `_matrix-fed._tcp.example.com.`
|
||||||
|
- Priority: `10` (can be any number)
|
||||||
|
- Weight: `10` (can be any number)
|
||||||
|
- Port: `443`
|
||||||
|
- Target: `example.com.`
|
||||||
|
|
||||||
|
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)
|
||||||
+45
-34
@@ -78,47 +78,19 @@ You will need to allow ports `7881/tcp` and `50100:50200/udp` through your firew
|
|||||||
|
|
||||||
### 3. Telling clients where to find LiveKit
|
### 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 your client .well-known file. To do so, in the config section `global.well-known`, add (or modify) the option `rtc_focus_server_urls`.
|
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.
|
||||||
|
|
||||||
The variable should be a list of servers serving as MatrixRTC endpoints to serve in the well-known file to the client.
|
The variable should be a list of servers serving as MatrixRTC endpoints. Clients discover these via the `/_matrix/client/v1/rtc/transports` endpoint (MSC4143).
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
rtc_focus_server_urls = [
|
[global.matrix_rtc]
|
||||||
|
foci = [
|
||||||
{ type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
{ type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Remember to replace the URL with the address you are deploying your instance of lk-jwt-service to.
|
Remember to replace the URL with the address you are deploying your instance of lk-jwt-service to.
|
||||||
|
|
||||||
#### Serving .well-known manually
|
|
||||||
|
|
||||||
If you don't let Continuwuity serve your `.well-known` files, you need to add the following lines to your `.well-known/matrix/client` file, remembering to replace the URL with your own `lk-jwt-service` deployment:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{
|
|
||||||
"type": "livekit",
|
|
||||||
"livekit_service_url": "https://livekit.example.com"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
The final file should look something like this:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"m.homeserver": {
|
|
||||||
"base_url":"https://matrix.example.com"
|
|
||||||
},
|
|
||||||
"org.matrix.msc4143.rtc_foci": [
|
|
||||||
{
|
|
||||||
"type": "livekit",
|
|
||||||
"livekit_service_url": "https://livekit.example.com"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Configure your Reverse Proxy
|
### 4. Configure your Reverse Proxy
|
||||||
|
|
||||||
Reverse proxies can be configured in many different ways - so we can't provide a step by step for this.
|
Reverse proxies can be configured in many different ways - so we can't provide a step by step for this.
|
||||||
@@ -137,7 +109,7 @@ By default, all routes should be forwarded to Livekit with the exception of the
|
|||||||
# for lk-jwt-service
|
# for lk-jwt-service
|
||||||
@lk-jwt-service path /sfu/get* /healthz* /get_token*
|
@lk-jwt-service path /sfu/get* /healthz* /get_token*
|
||||||
route @lk-jwt-service {
|
route @lk-jwt-service {
|
||||||
reverse_proxy 127.0.0.1:8080
|
reverse_proxy 127.0.0.1:8081
|
||||||
}
|
}
|
||||||
|
|
||||||
# for livekit
|
# for livekit
|
||||||
@@ -146,6 +118,46 @@ By default, all routes should be forwarded to Livekit with the exception of the
|
|||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Example nginx config</summary>
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
server_name matrix-rtc.example.com;
|
||||||
|
|
||||||
|
# for lk-jwt-service
|
||||||
|
location ~ ^/(sfu/get|healthz|get_token) {
|
||||||
|
proxy_pass http://127.0.0.1:8081$request_uri;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_buffering off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# for livekit
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:7880$request_uri;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
# websocket
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that for websockets to work, you need to have this somewhere outside your server block:
|
||||||
|
```
|
||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Example traefik router</summary>
|
<summary>Example traefik router</summary>
|
||||||
```
|
```
|
||||||
@@ -226,4 +238,3 @@ turn:
|
|||||||
- [Synapse documentation](https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md)
|
- [Synapse documentation](https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md)
|
||||||
- [Community guide](https://tomfos.tr/matrix/livekit/)
|
- [Community guide](https://tomfos.tr/matrix/livekit/)
|
||||||
- [Community guide](https://blog.kimiblock.top/2024/12/24/hosting-element-call/)
|
- [Community guide](https://blog.kimiblock.top/2024/12/24/hosting-element-call/)
|
||||||
-
|
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ settings.
|
|||||||
|
|
||||||
The config file to use can be specified on the commandline when running
|
The config file to use can be specified on the commandline when running
|
||||||
Continuwuity by specifying the `-c`, `--config` flag. Alternatively, you can use
|
Continuwuity by specifying the `-c`, `--config` flag. Alternatively, you can use
|
||||||
the environment variable `CONDUWUIT_CONFIG` to specify the config file to used.
|
the environment variable `CONTINUWUITY_CONFIG` to specify the config file to be
|
||||||
Conduit's environment variables are supported for backwards compatibility.
|
used; see [the section on environment variables](#environment-variables) for
|
||||||
|
more information.
|
||||||
|
|
||||||
## Option commandline flag
|
## Option commandline flag
|
||||||
|
|
||||||
@@ -52,13 +53,15 @@ This commandline argument can be paired with the `--option` flag.
|
|||||||
|
|
||||||
All of the settings that are found in the config file can be specified by using
|
All of the settings that are found in the config file can be specified by using
|
||||||
environment variables. The environment variable names should be all caps and
|
environment variables. The environment variable names should be all caps and
|
||||||
prefixed with `CONDUWUIT_`.
|
prefixed with `CONTINUWUITY_`.
|
||||||
|
|
||||||
For example, if the setting you are changing is `max_request_size`, then the
|
For example, if the setting you are changing is `max_request_size`, then the
|
||||||
environment variable to set is `CONDUWUIT_MAX_REQUEST_SIZE`.
|
environment variable to set is `CONTINUWUITY_MAX_REQUEST_SIZE`.
|
||||||
|
|
||||||
To modify config options not in the `[global]` context such as
|
To modify config options not in the `[global]` context such as
|
||||||
`[global.well_known]`, use the `__` suffix split: `CONDUWUIT_WELL_KNOWN__SERVER`
|
`[global.well_known]`, use the `__` suffix split:
|
||||||
|
`CONTINUWUITY_WELL_KNOWN__SERVER`
|
||||||
|
|
||||||
Conduit's environment variables are supported for backwards compatibility (e.g.
|
Conduit and conduwuit's environment variables are also supported for backwards
|
||||||
|
compatibility, via the `CONDUIT_` and `CONDUWUIT_` prefixes respectively (e.g.
|
||||||
`CONDUIT_SERVER_NAME`).
|
`CONDUIT_SERVER_NAME`).
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
labels:
|
labels:
|
||||||
caddy: example.com
|
caddy: example.com
|
||||||
caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
|
caddy.reverse_proxy: /.well-known/matrix/* homeserver:6167
|
||||||
caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}
|
|
||||||
|
|
||||||
homeserver:
|
homeserver:
|
||||||
### If you already built the Continuwuity image with 'docker build' or want to use a registry image,
|
### If you already built the Continuwuity image with 'docker build' or want to use a registry image,
|
||||||
@@ -42,6 +41,10 @@ services:
|
|||||||
#CONTINUWUITY_LOG: warn,state_res=warn
|
#CONTINUWUITY_LOG: warn,state_res=warn
|
||||||
CONTINUWUITY_ADDRESS: 0.0.0.0
|
CONTINUWUITY_ADDRESS: 0.0.0.0
|
||||||
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
|
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
|
||||||
|
|
||||||
|
# Required for .well-known delegation - edit these according to your chosen domain
|
||||||
|
CONTINUWUITY_WELL_KNOWN__CLIENT: https://matrix.example.com
|
||||||
|
CONTINUWUITY_WELL_KNOWN__SERVER: matrix.example.com:443
|
||||||
networks:
|
networks:
|
||||||
- caddy
|
- caddy
|
||||||
labels:
|
labels:
|
||||||
|
|||||||
@@ -3,3 +3,5 @@
|
|||||||
Continuwuity currently does not provide FreeBSD builds or FreeBSD packaging. However, Continuwuity does build and work on FreeBSD using the system-provided RocksDB.
|
Continuwuity currently does not provide FreeBSD builds or FreeBSD packaging. However, Continuwuity does build and work on FreeBSD using the system-provided RocksDB.
|
||||||
|
|
||||||
Contributions to get Continuwuity packaged for FreeBSD are welcome.
|
Contributions to get Continuwuity packaged for FreeBSD are welcome.
|
||||||
|
|
||||||
|
Please join our [Continuwuity BSD](https://matrix.to/#/%23bsd:continuwuity.org) community room.
|
||||||
|
|||||||
@@ -1,7 +1,109 @@
|
|||||||
# Continuwuity for Kubernetes
|
# Continuwuity for Kubernetes
|
||||||
|
|
||||||
Continuwuity doesn't support horizontal scalability or distributed loading
|
Continuwuity doesn't support horizontal scalability or distributed loading
|
||||||
natively. However, [a community-maintained Helm Chart is available here to run
|
natively. However, a deployment in Kubernetes is very similar to the docker
|
||||||
|
setup. This is because Continuwuity can be fully configured using environment
|
||||||
|
variables. A sample StatefulSet is shared below. The only thing missing is
|
||||||
|
a PVC definition (named `continuwuity-data`) for the volume mounted to
|
||||||
|
the StatefulSet, an Ingress resources to point your webserver to the
|
||||||
|
Continuwuity Pods, and a Service resource (targeting `app.kubernetes.io/name: continuwuity`)
|
||||||
|
to glue the Ingress and Pod together.
|
||||||
|
|
||||||
|
Carefully go through the `env` section and add, change, and remove any env vars you like using the [Configuration reference](https://continuwuity.org/reference/config.html)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: continuwuity
|
||||||
|
namespace: matrix
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: continuwuity
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
serviceName: continuwuity
|
||||||
|
podManagementPolicy: Parallel
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: continuwuity
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: continuwuity
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
sysctls:
|
||||||
|
- name: net.ipv4.ip_unprivileged_port_start
|
||||||
|
value: "0"
|
||||||
|
containers:
|
||||||
|
- name: continuwuity
|
||||||
|
# use a sha hash <3
|
||||||
|
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /data
|
||||||
|
name: data
|
||||||
|
subPath: data
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- NET_BIND_SERVICE
|
||||||
|
env:
|
||||||
|
- name: TOKIO_WORKER_THREADS
|
||||||
|
value: "2"
|
||||||
|
- name: CONTINUWUITY_SERVER_NAME
|
||||||
|
value: "example.com"
|
||||||
|
- name: CONTINUWUITY_DATABASE_PATH
|
||||||
|
value: "/data/db"
|
||||||
|
- name: CONTINUWUITY_DATABASE_BACKEND
|
||||||
|
value: "rocksdb"
|
||||||
|
- name: CONTINUWUITY_PORT
|
||||||
|
value: "80"
|
||||||
|
- name: CONTINUWUITY_MAX_REQUEST_SIZE
|
||||||
|
value: "20000000"
|
||||||
|
- name: CONTINUWUITY_ALLOW_FEDERATION
|
||||||
|
value: "true"
|
||||||
|
- name: CONTINUWUITY_TRUSTED_SERVERS
|
||||||
|
value: '["matrix.org"]'
|
||||||
|
- name: CONTINUWUITY_ADDRESS
|
||||||
|
value: "0.0.0.0"
|
||||||
|
- name: CONTINUWUITY_ROCKSDB_PARALLELISM_THREADS
|
||||||
|
value: "1"
|
||||||
|
- name: CONTINUWUITY_WELL_KNOWN__SERVER
|
||||||
|
value: "matrix.example.com:443"
|
||||||
|
- name: CONTINUWUITY_WELL_KNOWN__CLIENT
|
||||||
|
value: "https://matrix.example.com"
|
||||||
|
- name: CONTINUWUITY_ALLOW_REGISTRATION
|
||||||
|
value: "false"
|
||||||
|
- name: RUST_LOG
|
||||||
|
value: info
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /_matrix/federation/v1/version
|
||||||
|
port: http
|
||||||
|
periodSeconds: 4
|
||||||
|
failureThreshold: 5
|
||||||
|
resources:
|
||||||
|
# Continuwuity might use quite some RAM :3
|
||||||
|
requests:
|
||||||
|
cpu: "2"
|
||||||
|
memory: "512Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "4"
|
||||||
|
memory: "2048Mi"
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: continuwuity-data
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Apart from manually configuring the containers,
|
||||||
|
[a community-maintained Helm Chart is available here to run
|
||||||
conduwuit on Kubernetes](https://gitlab.cronce.io/charts/conduwuit)
|
conduwuit on Kubernetes](https://gitlab.cronce.io/charts/conduwuit)
|
||||||
|
|
||||||
This should be compatible with Continuwuity, but you will need to change the image reference.
|
This should be compatible with Continuwuity, but you will need to change the image reference.
|
||||||
|
|||||||
@@ -51,7 +51,13 @@ continuwuity aims to:
|
|||||||
|
|
||||||
Check out the [documentation](https://continuwuity.org) for installation instructions.
|
Check out the [documentation](https://continuwuity.org) for installation instructions.
|
||||||
|
|
||||||
There are currently no open registration continuwuity instances available.
|
If you want to try it out as a user, we have some partnered homeservers you can use:
|
||||||
|
* You can head over to [https://federated.nexus](https://federated.nexus/) in your browser.
|
||||||
|
* Hit the `Apply to Join` button. Once your request has been accepted, you will receive an email with your username and password.
|
||||||
|
* Head over to [https://app.federated.nexus](https://app.federated.nexus/) and you can sign in there, or use any other matrix chat client you wish elsewhere.
|
||||||
|
* Your username for matrix will be in the form of `@username:federated.nexus`, however you can simply use the `username` part to log in. Your password is your password.
|
||||||
|
|
||||||
|
* There's also [https://continuwuity.rocks/](https://continuwuity.rocks/). You can register a new account using Cinny via [this convenient link](https://app.cinny.in/register/continuwuity.rocks), or you can use Element or another matrix client *that supports registration*.
|
||||||
|
|
||||||
## What are we working on?
|
## What are we working on?
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 9,
|
"id": 10,
|
||||||
"mention_room": false,
|
"mention_room": false,
|
||||||
"date": "2026-02-09",
|
"date": "2026-03-03",
|
||||||
"message": "Yesterday we released [v0.5.4](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.4). Bugfixes, performance improvements and more moderation features! There's also a security fix, so please update as soon as possible. Don't forget to join [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM/%2489TY9CqRg4-ff1MGo3Ulc5r5X4pakfdzT-99RD8Docc?via=ellis.link&via=explodie.org&via=matrix.org) to get important information sooner <3 "
|
"message": "We've just released [v0.5.6](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.6), which contains a few security improvements - plus significant reliability and performance improvements. Please update as soon as possible. \n\nWe released [v0.5.5](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.5) two weeks ago, but it skipped your admin room straight to [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM?via=ellis.link&via=gingershaped.computer&via=matrix.org). Make sure you're there to get important information as soon as we announce it! [Our space](https://matrix.to/#/!8cR4g-i9ucof69E4JHNg9LbPVkGprHb3SzcrGBDDJgk?via=continuwuity.org&via=ellis.link&via=matrix.org) has also gained a bunch of new and interesting rooms - be there or be square."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ default.
|
|||||||
* Delete all remote and local media from 3 days ago, up until now:
|
* Delete all remote and local media from 3 days ago, up until now:
|
||||||
|
|
||||||
`!admin media delete-past-remote-media -a 3d
|
`!admin media delete-past-remote-media -a 3d
|
||||||
-yes-i-want-to-delete-local-media`
|
--yes-i-want-to-delete-local-media`
|
||||||
|
|
||||||
## `!admin media delete-all-from-user`
|
## `!admin media delete-all-from-user`
|
||||||
|
|
||||||
@@ -36,3 +36,7 @@ Deletes all the local media from a local user on our server. This will always ig
|
|||||||
## `!admin media delete-all-from-server`
|
## `!admin media delete-all-from-server`
|
||||||
|
|
||||||
Deletes all remote media from the specified remote server. This will always ignore errors by default
|
Deletes all remote media from the specified remote server. This will always ignore errors by default
|
||||||
|
|
||||||
|
## `!admin media delete-url-preview`
|
||||||
|
|
||||||
|
Deletes a cached URL preview, forcing it to be re-fetched. Use --all to purge all cached URL previews
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
# Troubleshooting Continuwuity
|
# Troubleshooting Continuwuity
|
||||||
|
|
||||||
> **Docker users ⚠️**
|
:::warning{title="Docker users:"}
|
||||||
>
|
Docker can be difficult to use and debug. It's common for Docker
|
||||||
> Docker can be difficult to use and debug. It's common for Docker
|
misconfigurations to cause issues, particularly with networking and permissions.
|
||||||
> misconfigurations to cause issues, particularly with networking and permissions.
|
Please check that your issues are not due to problems with your Docker setup.
|
||||||
> Please check that your issues are not due to problems with your Docker setup.
|
:::
|
||||||
|
|
||||||
## Continuwuity and Matrix issues
|
## Continuwuity and Matrix issues
|
||||||
|
|
||||||
|
### Slow joins to rooms
|
||||||
|
|
||||||
|
Some slowness is to be expected if you're the first person on your homserver 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
|
||||||
|
`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.
|
||||||
|
|
||||||
|
However, _very_ slow joins, especially to rooms with only a few users in them or rooms created by another user
|
||||||
|
on your homeserver, may be caused by [issue !779](https://forgejo.ellis.link/continuwuation/continuwuity/issues/779),
|
||||||
|
which is a longstanding bug with synchronizing room joins to clients. In this situation, you did succeed in joining the room, but
|
||||||
|
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.
|
||||||
|
|
||||||
### Lost access to admin room
|
### Lost access to admin room
|
||||||
|
|
||||||
You can reinvite yourself to the admin room through the following methods:
|
You can reinvite yourself to the admin room through the following methods:
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
rocksdbAllFeatures = self'.packages.rocksdb.override {
|
rocksdbAllFeatures = self'.packages.rocksdb.override {
|
||||||
enableJemalloc = true;
|
enableJemalloc = true;
|
||||||
enableLiburing = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
commonAttrs = (uwulib.build.commonAttrs { }) // {
|
commonAttrs = (uwulib.build.commonAttrs { }) // {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
commonAttrsArgs.profile = "release";
|
commonAttrsArgs.profile = "release";
|
||||||
rocksdb = self'.packages.rocksdb.override {
|
rocksdb = self'.packages.rocksdb.override {
|
||||||
enableJemalloc = true;
|
enableJemalloc = true;
|
||||||
enableLiburing = true;
|
|
||||||
};
|
};
|
||||||
features = {
|
features = {
|
||||||
enabledFeatures = "all";
|
enabledFeatures = "all";
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
rust-jemalloc-sys-unprefixed,
|
rust-jemalloc-sys-unprefixed,
|
||||||
|
|
||||||
enableJemalloc ? false,
|
enableJemalloc ? false,
|
||||||
enableLiburing ? false,
|
|
||||||
|
|
||||||
fetchFromGitea,
|
fetchFromGitea,
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ in
|
|||||||
|
|
||||||
# for some reason enableLiburing in nixpkgs rocksdb is default true
|
# for some reason enableLiburing in nixpkgs rocksdb is default true
|
||||||
# which breaks Darwin entirely
|
# which breaks Darwin entirely
|
||||||
enableLiburing = enableLiburing && notDarwin;
|
enableLiburing = notDarwin;
|
||||||
}).overrideAttrs
|
}).overrideAttrs
|
||||||
(old: {
|
(old: {
|
||||||
src = fetchFromGitea {
|
src = fetchFromGitea {
|
||||||
@@ -74,7 +73,7 @@ in
|
|||||||
"USE_RTTI"
|
"USE_RTTI"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
enableLiburing = enableLiburing && notDarwin;
|
enableLiburing = notDarwin;
|
||||||
|
|
||||||
# outputs has "tools" which we don't need or use
|
# outputs has "tools" which we don't need or use
|
||||||
outputs = [ "out" ];
|
outputs = [ "out" ];
|
||||||
|
|||||||
@@ -77,7 +77,12 @@ rec {
|
|||||||
craneLib.buildDepsOnly (
|
craneLib.buildDepsOnly (
|
||||||
(commonAttrs commonAttrsArgs)
|
(commonAttrs commonAttrsArgs)
|
||||||
// {
|
// {
|
||||||
env = uwuenv.buildDepsOnlyEnv // (makeRocksDBEnv { inherit rocksdb; });
|
env = uwuenv.buildDepsOnlyEnv
|
||||||
|
// (makeRocksDBEnv { inherit rocksdb; })
|
||||||
|
// {
|
||||||
|
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
|
||||||
|
RUSTFLAGS = "--cfg reqwest_unstable";
|
||||||
|
};
|
||||||
inherit (features) cargoExtraArgs;
|
inherit (features) cargoExtraArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +107,13 @@ rec {
|
|||||||
'';
|
'';
|
||||||
cargoArtifacts = deps;
|
cargoArtifacts = deps;
|
||||||
doCheck = true;
|
doCheck = true;
|
||||||
env = uwuenv.buildPackageEnv // rocksdbEnv;
|
env =
|
||||||
|
uwuenv.buildPackageEnv
|
||||||
|
// rocksdbEnv
|
||||||
|
// {
|
||||||
|
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
|
||||||
|
RUSTFLAGS = "--cfg reqwest_unstable";
|
||||||
|
};
|
||||||
passthru.env = uwuenv.buildPackageEnv // rocksdbEnv;
|
passthru.env = uwuenv.buildPackageEnv // rocksdbEnv;
|
||||||
meta.mainProgram = crateInfo.pname;
|
meta.mainProgram = crateInfo.pname;
|
||||||
inherit (features) cargoExtraArgs;
|
inherit (features) cargoExtraArgs;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
uwulib = inputs.self.uwulib.init pkgs;
|
uwulib = inputs.self.uwulib.init pkgs;
|
||||||
rocksdbAllFeatures = self'.packages.rocksdb.override {
|
rocksdbAllFeatures = self'.packages.rocksdb.override {
|
||||||
enableJemalloc = true;
|
enableJemalloc = true;
|
||||||
enableLiburing = true;
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+188
-153
@@ -119,15 +119,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rsbuild/core": {
|
"node_modules/@rsbuild/core": {
|
||||||
"version": "2.0.0-beta.3",
|
"version": "2.0.0-beta.6",
|
||||||
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-2.0.0-beta.6.tgz",
|
||||||
"integrity": "sha512-dfH+Pt2GuF3rWOWGsf5XOhn3Zarvr4DoHwoI1arAsCGvpzoeud3DNGmWPy13tngj0r/YvQRcPTRBCRV4RP5CMw==",
|
"integrity": "sha512-DUBhUzvzj6xlGUAHTTipFskSuZmVEuTX7lGU+ToPuo8n3bsQrWn/UBOEQAd45g66k7QfXadoZ/v7eodQErpvGQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rspack/core": "2.0.0-beta.0",
|
"@rspack/core": "2.0.0-beta.3",
|
||||||
"@swc/helpers": "^0.5.18",
|
"@swc/helpers": "^0.5.19"
|
||||||
"jiti": "^2.6.1"
|
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"rsbuild": "bin/rsbuild.js"
|
"rsbuild": "bin/rsbuild.js"
|
||||||
@@ -159,28 +158,28 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding": {
|
"node_modules/@rspack/binding": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-L6PPqhwZWC2vzwdhBItNPXw+7V4sq+MBDRXLdd8NMqaJSCB5iKdJIbpbEQucST9Nn7V28IYoQTXs6+ol5vWUBA==",
|
"integrity": "sha512-GSj+d8AlLs1oElhYq32vIN/eAsxWG9jy0EiNgSxWTt5Gdamv87kcvsV4jwfWIjlltdnBIJgey2RnU+hDZlTAvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rspack/binding-darwin-arm64": "2.0.0-beta.0",
|
"@rspack/binding-darwin-arm64": "2.0.0-beta.3",
|
||||||
"@rspack/binding-darwin-x64": "2.0.0-beta.0",
|
"@rspack/binding-darwin-x64": "2.0.0-beta.3",
|
||||||
"@rspack/binding-linux-arm64-gnu": "2.0.0-beta.0",
|
"@rspack/binding-linux-arm64-gnu": "2.0.0-beta.3",
|
||||||
"@rspack/binding-linux-arm64-musl": "2.0.0-beta.0",
|
"@rspack/binding-linux-arm64-musl": "2.0.0-beta.3",
|
||||||
"@rspack/binding-linux-x64-gnu": "2.0.0-beta.0",
|
"@rspack/binding-linux-x64-gnu": "2.0.0-beta.3",
|
||||||
"@rspack/binding-linux-x64-musl": "2.0.0-beta.0",
|
"@rspack/binding-linux-x64-musl": "2.0.0-beta.3",
|
||||||
"@rspack/binding-wasm32-wasi": "2.0.0-beta.0",
|
"@rspack/binding-wasm32-wasi": "2.0.0-beta.3",
|
||||||
"@rspack/binding-win32-arm64-msvc": "2.0.0-beta.0",
|
"@rspack/binding-win32-arm64-msvc": "2.0.0-beta.3",
|
||||||
"@rspack/binding-win32-ia32-msvc": "2.0.0-beta.0",
|
"@rspack/binding-win32-ia32-msvc": "2.0.0-beta.3",
|
||||||
"@rspack/binding-win32-x64-msvc": "2.0.0-beta.0"
|
"@rspack/binding-win32-x64-msvc": "2.0.0-beta.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-darwin-arm64": {
|
"node_modules/@rspack/binding-darwin-arm64": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-PPx1+SPEROSvDKmBuCbsE7W9tk07ajPosyvyuafv2wbBI6PW2rNcz62uzpIFS+FTgwwZ5u/06WXRtlD2xW9bKg==",
|
"integrity": "sha512-QebSomLWlCbFsC0sfDuGqLJtkgyrnr38vrCepWukaAXIY4ANy5QB49LDKdLpVv6bKlC95MpnW37NvSNWY5GMYA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -192,9 +191,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-darwin-x64": {
|
"node_modules/@rspack/binding-darwin-x64": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-GucsfjrSKBZ9cuOTXmHWxeY2wPmaNyvGNxTyzttjRcfwqOWz8r+ku6PCsMSXUqxZRYWW1L9mvtTdlDrzTYJZ0w==",
|
"integrity": "sha512-EysmBq+sz+Ph0bu0gXpU1uuZG9gXgjqY+w3MJel+ieTFyQO3L/R56V32McgssMbheJbYcviDDn7Tz4D+lTvdJA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -206,9 +205,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-linux-arm64-gnu": {
|
"node_modules/@rspack/binding-linux-arm64-gnu": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-nTtYtklRZD4sb2RIFCF9YS8tZ/MjpqIBKVS3YIvdXcfHUdVfmQHTZGtwEuZGg6AxTC5L1hcvkYmTXCG0ok7auw==",
|
"integrity": "sha512-iFPj4TQZKewnqWPfTbyk3F8QCBI/Edv7TVSRIPBHRnCM0lvYZl/8IZlUzXSamLvrtDpouF0nUzht/fktoWOhAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -220,9 +219,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-linux-arm64-musl": {
|
"node_modules/@rspack/binding-linux-arm64-musl": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-S2fshx0Rf7/XYwoMLaqFsVg4y+VAfHzubrczy8AW5xIs6UNC3eRLVTgShLerUPtF6SG+v6NQxQ9JI3vOo2qPOA==",
|
"integrity": "sha512-355mygfCNb0eF/y4HgtJcd0i9csNTG4Z15PCCplIkSAKJpFpkORM2xJb50BqsbhVafYl6AHoBlGWAo9iIzUb/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -234,9 +233,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-linux-x64-gnu": {
|
"node_modules/@rspack/binding-linux-x64-gnu": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-yx5Fk1gl7lfkvqcjolNLCNeduIs6C2alMsQ/kZ1pLeP5MPquVOYNqs6EcDPIp+fUjo3lZYtnJBiZKK+QosbzYg==",
|
"integrity": "sha512-U8a+bcP/tkMyiwiO9XfeRYYO20YPGiZNxWWt7FEsdmRuRAl6M+EmWaJllJFQtKH+GG8IN93pNoVPMvARjLoJOQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -248,9 +247,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-linux-x64-musl": {
|
"node_modules/@rspack/binding-linux-x64-musl": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-sBX4b2W0PgehlAVT224k0Q6GaH6t9HP+hBNDrbX/g6d0hfxZN56gm5NfOTOD1Rien4v7OBEejJ3/uFbm1WjwYQ==",
|
"integrity": "sha512-g81rqkaqDFRTID2VrHBYeM+xZe8yWov7IcryTrl9RGXXr61s+6Tu/mWyM378PuHOCyMNu7G3blVaSjLvKauG6Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -262,9 +261,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-wasm32-wasi": {
|
"node_modules/@rspack/binding-wasm32-wasi": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-o6OatnNvb4kCzXbCaomhENGaCsO3naIyAqqErew90HeAwa1lfY3NhRfDLeIyuANQ+xqFl34/R7n8q3ZDx3nd4Q==",
|
"integrity": "sha512-tzGd8H2oj5F3oR/Hxp+J68zVU/nG+9ndH2KK3/RieVjNAiVNHCR0/ZU9D47s6fnmvWOqAQ1qO8gnVoVLopC4YA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"wasm32"
|
"wasm32"
|
||||||
],
|
],
|
||||||
@@ -276,9 +275,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-win32-arm64-msvc": {
|
"node_modules/@rspack/binding-win32-arm64-msvc": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-neCzVllXzIqM8p8qKb89qV7wyk233gC/V9VrHIKbGeQjAEzpBsk5GOWlFbq5DDL6tivQ+uzYaTrZWm9tb2qxXg==",
|
"integrity": "sha512-TZZRSWa34sm5WyoQHwnyBjLJ4w3fcWRYA9ybYjSVWjUU6tVGdMiHiZp+WexUpIETvChLXU1JENNmBg/U7wvZEA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -290,9 +289,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-win32-ia32-msvc": {
|
"node_modules/@rspack/binding-win32-ia32-msvc": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-/f0n2eO+DxMKQm9IebeMQJITx8M/+RvY/i8d3sAQZBgR53izn8y7EcDlidXpr24/2DvkLbiub8IyCKPlhLB+1A==",
|
"integrity": "sha512-VFnfdbJhyl6gNW1VzTyd1ZrHCboHPR7vrOalEsulQRqVNbtDkjm1sqLHtDcLmhTEv0a9r4lli8uubWDwmel8KQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -304,9 +303,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/binding-win32-x64-msvc": {
|
"node_modules/@rspack/binding-win32-x64-msvc": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-dx4zgiAT88EQE7kEUpr7Z9EZAwLnO5FhzWzvd/cDK4bkqYsx+rTklgf/c0EYPBeroXCxlGiMsuC9wHAFNK7sFw==",
|
"integrity": "sha512-rwZ6Y3b3oqPj+ZDPPRxr3136HUPKDSlPQa4v7bBOPLDlrFDFOynMIEqDUUi5+8lPaUQ8WWR0aJK4cgcTTT0Siw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -318,20 +317,19 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/core": {
|
"node_modules/@rspack/core": {
|
||||||
"version": "2.0.0-beta.0",
|
"version": "2.0.0-beta.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/core/-/core-2.0.0-beta.3.tgz",
|
||||||
"integrity": "sha512-aEqlQQjiXixT5i9S4DFtiAap8ZjF6pOgfY2ALHOizins/QqWyB8dyLxSoXdzt7JixmKcFmHkbL9XahO28BlVUA==",
|
"integrity": "sha512-VuLteRIesuyFFTXZaciUY0lwDZiwMc7JcpE8guvjArztDhtpVvlaOcLlVBp/Yza8c/Tk8Dxwe1ARzFL7xG1/0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rspack/binding": "2.0.0-beta.0",
|
"@rspack/binding": "2.0.0-beta.3"
|
||||||
"@rspack/lite-tapable": "1.1.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@module-federation/runtime-tools": ">=0.22.0",
|
"@module-federation/runtime-tools": "^0.24.1 || ^2.0.0",
|
||||||
"@swc/helpers": ">=0.5.1"
|
"@swc/helpers": ">=0.5.1"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
@@ -343,13 +341,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspack/lite-tapable": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@rspack/plugin-react-refresh": {
|
"node_modules/@rspack/plugin-react-refresh": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.0.tgz",
|
||||||
@@ -371,22 +362,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspress/core": {
|
"node_modules/@rspress/core": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rspress/core/-/core-2.0.4.tgz",
|
||||||
"integrity": "sha512-a+JJFiALqMxGJBqR38/lkN6tas42UF4jRIhu6RilC/3DdqpfqR8j6jjQFOmqoNKo6ZGXW2W+i1Pscn6drvoG3w==",
|
"integrity": "sha512-OdeGMY75OFzyRZvXuBEMre3q8Y4/OjYJa4vVBDp4Z2E65LSt8+hYkzzkarEl6sFWqbp8c1o9qfSUf4xMctmKvw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdx-js/mdx": "^3.1.1",
|
"@mdx-js/mdx": "^3.1.1",
|
||||||
"@mdx-js/react": "^3.1.1",
|
"@mdx-js/react": "^3.1.1",
|
||||||
"@rsbuild/core": "2.0.0-beta.3",
|
"@rsbuild/core": "2.0.0-beta.6",
|
||||||
"@rsbuild/plugin-react": "~1.4.5",
|
"@rsbuild/plugin-react": "~1.4.5",
|
||||||
"@rspress/shared": "2.0.3",
|
"@rspress/shared": "2.0.4",
|
||||||
"@shikijs/rehype": "^3.21.0",
|
"@shikijs/rehype": "^4.0.1",
|
||||||
"@types/unist": "^3.0.3",
|
"@types/unist": "^3.0.3",
|
||||||
"@unhead/react": "^2.1.4",
|
"@unhead/react": "^2.1.9",
|
||||||
"body-scroll-lock": "4.0.0-beta.0",
|
"body-scroll-lock": "4.0.0-beta.0",
|
||||||
"cac": "^6.7.14",
|
"cac": "^7.0.0",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
@@ -404,7 +395,8 @@
|
|||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"react-lazy-with-preload": "^2.2.1",
|
"react-lazy-with-preload": "^2.2.1",
|
||||||
"react-reconciler": "0.33.0",
|
"react-reconciler": "0.33.0",
|
||||||
"react-router-dom": "^7.13.0",
|
"react-render-to-markdown": "19.0.1",
|
||||||
|
"react-router-dom": "^7.13.1",
|
||||||
"rehype-external-links": "^3.0.0",
|
"rehype-external-links": "^3.0.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
@@ -412,7 +404,7 @@
|
|||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-stringify": "^11.0.0",
|
"remark-stringify": "^11.0.0",
|
||||||
"scroll-into-view-if-needed": "^3.1.0",
|
"scroll-into-view-if-needed": "^3.1.0",
|
||||||
"shiki": "^3.21.0",
|
"shiki": "^4.0.1",
|
||||||
"tinyglobby": "^0.2.15",
|
"tinyglobby": "^0.2.15",
|
||||||
"tinypool": "^1.1.1",
|
"tinypool": "^1.1.1",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
@@ -428,125 +420,162 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspress/plugin-client-redirects": {
|
"node_modules/@rspress/plugin-client-redirects": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rspress/plugin-client-redirects/-/plugin-client-redirects-2.0.4.tgz",
|
||||||
"integrity": "sha512-9+SoAbfoxM6OCRWx8jWHHi2zwJDcNaej/URx0CWZk8tvQ618yJW5mXJydknlac62399eYh/F7C3w8TZM3ORGVA==",
|
"integrity": "sha512-cm7VNfisVCHe+YHNjd9YrWt6/WtJ5I/oNRyjt+tqCeOcC1IJSX2LhNXpNN5h9az3wxYn37kVctBUjzqkj2FQ+A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@rspress/core": "^2.0.3"
|
"@rspress/core": "^2.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspress/plugin-sitemap": {
|
"node_modules/@rspress/plugin-sitemap": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rspress/plugin-sitemap/-/plugin-sitemap-2.0.4.tgz",
|
||||||
"integrity": "sha512-SKa7YEAdkUqya2YjMKbakg3kcYMkXgXhTQdDsHd+QlJWN8j8cDPiCcctMZu8iIPeKZlb+hTJkTWvh27LSIKdOA==",
|
"integrity": "sha512-TKaj3/8+P1fP3sD5NOaWVMXvRvJFQmuJQlUBxhRM0oiUHhzNNkVy/2YXkjYJuXuMhFPLnOWCjrYjTG3xcZE7Wg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@rspress/core": "^2.0.3"
|
"@rspress/core": "^2.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rspress/shared": {
|
"node_modules/@rspress/shared": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rspress/shared/-/shared-2.0.4.tgz",
|
||||||
"integrity": "sha512-yI9G4P165fSsmm6QoYTUrdgUis1aFnDh04GcM4SQIpL3itvEZhGtItgoeGkX9EWbnEjhriwI8mTqDDJIp+vrGA==",
|
"integrity": "sha512-os2nzsPgHKVFXjDoW7N53rmhLChCw/y2O2TGilT4w2A4HNJa2oJwRk0UryXbxxWD5C85HErTjovs2uBdhdOTtA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rsbuild/core": "2.0.0-beta.3",
|
"@rsbuild/core": "2.0.0-beta.6",
|
||||||
"@shikijs/rehype": "^3.21.0",
|
"@shikijs/rehype": "^4.0.1",
|
||||||
"gray-matter": "4.0.3",
|
"gray-matter": "4.0.3",
|
||||||
"lodash-es": "^4.17.23",
|
"lodash-es": "^4.17.23",
|
||||||
"unified": "^11.0.5"
|
"unified": "^11.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/core": {
|
"node_modules/@shikijs/core": {
|
||||||
"version": "3.22.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.1.tgz",
|
||||||
"integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==",
|
"integrity": "sha512-vWvqi9JNgz1dRL9Nvog5wtx7RuNkf7MEPl2mU/cyUUxJeH1CAr3t+81h8zO8zs7DK6cKLMoU9TvukWIDjP4Lzg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "3.22.0",
|
"@shikijs/primitive": "4.0.1",
|
||||||
|
"@shikijs/types": "4.0.1",
|
||||||
"@shikijs/vscode-textmate": "^10.0.2",
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"hast-util-to-html": "^9.0.5"
|
"hast-util-to-html": "^9.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/engine-javascript": {
|
"node_modules/@shikijs/engine-javascript": {
|
||||||
"version": "3.22.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.1.tgz",
|
||||||
"integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==",
|
"integrity": "sha512-DJK9NiwtGYqMuKCRO4Ip0FKNDQpmaiS+K5bFjJ7DWFn4zHueDWgaUG8kAofkrnXF6zPPYYQY7J5FYVW9MbZyBg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "3.22.0",
|
"@shikijs/types": "4.0.1",
|
||||||
"@shikijs/vscode-textmate": "^10.0.2",
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
"oniguruma-to-es": "^4.3.4"
|
"oniguruma-to-es": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/engine-oniguruma": {
|
"node_modules/@shikijs/engine-oniguruma": {
|
||||||
"version": "3.22.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.1.tgz",
|
||||||
"integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==",
|
"integrity": "sha512-oCWdCTDch3J8Kc0OZJ98KuUPC02O1VqIE3W/e2uvrHqTxYRR21RGEJMtchrgrxhsoJJCzmIciKsqG+q/yD+Cxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "3.22.0",
|
"@shikijs/types": "4.0.1",
|
||||||
"@shikijs/vscode-textmate": "^10.0.2"
|
"@shikijs/vscode-textmate": "^10.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/langs": {
|
"node_modules/@shikijs/langs": {
|
||||||
"version": "3.22.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.1.tgz",
|
||||||
"integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==",
|
"integrity": "sha512-v/mluaybWdnGJR4GqAR6zh8qAZohW9k+cGYT28Y7M8+jLbC0l4yG085O1A+WkseHTn+awd+P3UBymb2+MXFc8w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "3.22.0"
|
"@shikijs/types": "4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/primitive": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-ns0hHZc5eWZuvuIEJz2pTx3Qecz0aRVYumVQJ8JgWY2tq/dH8WxdcVM49Fc2NsHEILNIT6vfdW9MF26RANWiTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/types": "4.0.1",
|
||||||
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
|
"@types/hast": "^3.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/rehype": {
|
"node_modules/@shikijs/rehype": {
|
||||||
"version": "3.22.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.0.1.tgz",
|
||||||
"integrity": "sha512-69b2VPc6XBy/VmAJlpBU5By+bJSBdE2nvgRCZXav7zujbrjXuT0F60DIrjKuutjPqNufuizE+E8tIZr2Yn8Z+g==",
|
"integrity": "sha512-bx7bYA0/p/pgeEICaPO0jT6TXrXHmr9tGRUDhOMy1cAUN2YA0iANfXX7seBnImy8DGu/rxm1ij9/ZofYrAaUjQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "3.22.0",
|
"@shikijs/types": "4.0.1",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"hast-util-to-string": "^3.0.1",
|
"hast-util-to-string": "^3.0.1",
|
||||||
"shiki": "3.22.0",
|
"shiki": "4.0.1",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"unist-util-visit": "^5.1.0"
|
"unist-util-visit": "^5.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/themes": {
|
"node_modules/@shikijs/themes": {
|
||||||
"version": "3.22.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.1.tgz",
|
||||||
"integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==",
|
"integrity": "sha512-FW41C/D6j/yKQkzVdjrRPiJCtgeDaYRJFEyCKFCINuRJRj9WcmubhP4KQHPZ4+9eT87jruSrYPyoblNRyDFzvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "3.22.0"
|
"@shikijs/types": "4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/types": {
|
"node_modules/@shikijs/types": {
|
||||||
"version": "3.22.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.1.tgz",
|
||||||
"integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==",
|
"integrity": "sha512-EaygPEn57+jJ76mw+nTLvIpJMAcMPokFbrF8lufsZP7Ukk+ToJYEcswN1G0e49nUZAq7aCQtoeW219A8HK1ZOw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/vscode-textmate": "^10.0.2",
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
"@types/hast": "^3.0.4"
|
"@types/hast": "^3.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/vscode-textmate": {
|
"node_modules/@shikijs/vscode-textmate": {
|
||||||
@@ -557,9 +586,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.18",
|
"version": "0.5.19",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz",
|
||||||
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
|
"integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -664,13 +693,13 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/@unhead/react": {
|
"node_modules/@unhead/react": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@unhead/react/-/react-2.1.10.tgz",
|
||||||
"integrity": "sha512-3DzMi5nJkUyLVfQF/q78smCvcSy84TTYgTwXVz5s3AjUcLyHro5Z7bLWriwk1dn5+YRfEsec8aPkLCMi5VjMZg==",
|
"integrity": "sha512-z9IzzkaCI1GyiBwVRMt4dGc2mOvsj9drbAdXGMy6DWpu9FwTR37ZTmAi7UeCVyIkpVdIaNalz7vkbvGG8afFng==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"unhead": "2.1.4"
|
"unhead": "2.1.10"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/harlan-zw"
|
"url": "https://github.com/sponsors/harlan-zw"
|
||||||
@@ -781,13 +810,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cac": {
|
"node_modules/cac": {
|
||||||
"version": "6.7.14",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz",
|
||||||
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
|
"integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=20.19.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ccount": {
|
"node_modules/ccount": {
|
||||||
@@ -1698,16 +1727,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jiti": {
|
|
||||||
"version": "2.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
|
||||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"jiti": "lib/jiti-cli.mjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "3.14.2",
|
"version": "3.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||||
@@ -3019,10 +3038,23 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-render-to-markdown": {
|
||||||
|
"version": "19.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-render-to-markdown/-/react-render-to-markdown-19.0.1.tgz",
|
||||||
|
"integrity": "sha512-BPv48o+ubcu2JyUDIktvJXFqLIZqR7hA4mvGu1eFIofz9fogT2me9UvXwRvqvGs9jEtNaJkxZIUKUX0oiK4hDA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-reconciler": "0.33.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.13.0",
|
"version": "7.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
|
||||||
"integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
|
"integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -3043,13 +3075,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "7.13.0",
|
"version": "7.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
|
||||||
"integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
|
"integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-router": "7.13.0"
|
"react-router": "7.13.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
@@ -3345,20 +3377,23 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/shiki": {
|
"node_modules/shiki": {
|
||||||
"version": "3.22.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.1.tgz",
|
||||||
"integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==",
|
"integrity": "sha512-EkAEhDTN5WhpoQFXFw79OHIrSAfHhlImeCdSyg4u4XvrpxKEmdo/9x/HWSowujAnUrFsGOwWiE58a6GVentMnQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/core": "3.22.0",
|
"@shikijs/core": "4.0.1",
|
||||||
"@shikijs/engine-javascript": "3.22.0",
|
"@shikijs/engine-javascript": "4.0.1",
|
||||||
"@shikijs/engine-oniguruma": "3.22.0",
|
"@shikijs/engine-oniguruma": "4.0.1",
|
||||||
"@shikijs/langs": "3.22.0",
|
"@shikijs/langs": "4.0.1",
|
||||||
"@shikijs/themes": "3.22.0",
|
"@shikijs/themes": "4.0.1",
|
||||||
"@shikijs/types": "3.22.0",
|
"@shikijs/types": "4.0.1",
|
||||||
"@shikijs/vscode-textmate": "^10.0.2",
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
"@types/hast": "^3.0.4"
|
"@types/hast": "^3.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
@@ -3563,9 +3598,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unhead": {
|
"node_modules/unhead": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.10.tgz",
|
||||||
"integrity": "sha512-+5091sJqtNNmgfQ07zJOgUnMIMKzVKAWjeMlSrTdSGPB6JSozhpjUKuMfWEoLxlMAfhIvgOU8Me0XJvmMA/0fA==",
|
"integrity": "sha512-We8l9uNF8zz6U8lfQaVG70+R/QBfQx1oPIgXin4BtZnK2IQpz6yazQ0qjMNVBDw2ADgF2ea58BtvSK+XX5AS7g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["config:recommended", "replacements:all"],
|
"extends": ["config:recommended", "replacements:all"],
|
||||||
|
"dependencyDashboard": true,
|
||||||
"osvVulnerabilityAlerts": true,
|
"osvVulnerabilityAlerts": true,
|
||||||
"lockFileMaintenance": {
|
"lockFileMaintenance": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
@@ -57,12 +58,25 @@
|
|||||||
"matchUpdateTypes": ["minor", "patch"],
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
"groupName": "github-actions-non-major"
|
"groupName": "github-actions-non-major"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Batch patch-level Node.js dependency updates",
|
||||||
|
"matchManagers": ["npm"],
|
||||||
|
"matchUpdateTypes": ["patch"],
|
||||||
|
"groupName": "node-patch-updates"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Pin forgejo artifact actions to prevent breaking changes",
|
"description": "Pin forgejo artifact actions to prevent breaking changes",
|
||||||
"matchManagers": ["github-actions"],
|
"matchManagers": ["github-actions"],
|
||||||
"matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"],
|
"matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"],
|
||||||
"enabled": false
|
"enabled": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Auto-merge crate-ci/typos minor updates",
|
||||||
|
"matchPackageNames": ["crate-ci/typos"],
|
||||||
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
|
"automerge": true,
|
||||||
|
"automergeStrategy": "fast-forward"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Auto-merge renovatebot docker image updates",
|
"description": "Auto-merge renovatebot docker image updates",
|
||||||
"matchDatasources": ["docker"],
|
"matchDatasources": ["docker"],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use conduwuit::{Err, Result};
|
use conduwuit::{Err, Result, utils::response::LimitReadExt};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
|
use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
|
||||||
|
|
||||||
@@ -30,12 +30,15 @@ pub(super) async fn incoming_federation(&self) -> Result {
|
|||||||
.federation_handletime
|
.federation_handletime
|
||||||
.read();
|
.read();
|
||||||
|
|
||||||
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
|
let mut msg = format!(
|
||||||
|
"Handling {} incoming PDUs across {} active transactions:\n",
|
||||||
|
map.len(),
|
||||||
|
self.services.transactions.txn_active_handle_count()
|
||||||
|
);
|
||||||
for (r, (e, i)) in map.iter() {
|
for (r, (e, i)) in map.iter() {
|
||||||
let elapsed = i.elapsed();
|
let elapsed = i.elapsed();
|
||||||
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
|
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg
|
msg
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,7 +55,15 @@ pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName
|
|||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let text = response.text().await?;
|
let text = response
|
||||||
|
.limit_read_text(
|
||||||
|
self.services
|
||||||
|
.config
|
||||||
|
.max_request_size
|
||||||
|
.try_into()
|
||||||
|
.expect("u64 fits into usize"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
return Err!("Response text/body is empty.");
|
return Err!("Response text/body is empty.");
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ pub(super) async fn delete(
|
|||||||
.delete(&mxc.as_str().try_into()?)
|
.delete(&mxc.as_str().try_into()?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
return Err!("Deleted the MXC from our database and on our filesystem.",);
|
return self
|
||||||
|
.write_str("Deleted the MXC from our database and on our filesystem.")
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(event_id) = event_id {
|
if let Some(event_id) = event_id {
|
||||||
@@ -388,3 +390,19 @@ pub(super) async fn get_remote_thumbnail(
|
|||||||
self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```"))
|
self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```"))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn delete_url_preview(&self, url: Option<String>, all: bool) -> Result {
|
||||||
|
if all {
|
||||||
|
self.services.media.clear_url_previews().await;
|
||||||
|
|
||||||
|
return self.write_str("Deleted all cached URL previews.").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = url.expect("clap enforces url is required unless --all");
|
||||||
|
|
||||||
|
self.services.media.remove_url_preview(&url).await?;
|
||||||
|
|
||||||
|
self.write_str(&format!("Deleted cached URL preview for: {url}"))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|||||||
+13
-1
@@ -40,7 +40,7 @@ pub enum MediaCommand {
|
|||||||
/// * Delete all remote and local media from 3 days ago, up until now:
|
/// * Delete all remote and local media from 3 days ago, up until now:
|
||||||
///
|
///
|
||||||
/// `!admin media delete-past-remote-media -a 3d
|
/// `!admin media delete-past-remote-media -a 3d
|
||||||
///-yes-i-want-to-delete-local-media`
|
///--yes-i-want-to-delete-local-media`
|
||||||
#[command(verbatim_doc_comment)]
|
#[command(verbatim_doc_comment)]
|
||||||
DeletePastRemoteMedia {
|
DeletePastRemoteMedia {
|
||||||
/// The relative time (e.g. 30s, 5m, 7d) from now within which to
|
/// The relative time (e.g. 30s, 5m, 7d) from now within which to
|
||||||
@@ -108,4 +108,16 @@ pub enum MediaCommand {
|
|||||||
#[arg(long, default_value("800"))]
|
#[arg(long, default_value("800"))]
|
||||||
height: u32,
|
height: u32,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Deletes a cached URL preview, forcing it to be re-fetched.
|
||||||
|
/// Use --all to purge all cached URL previews.
|
||||||
|
DeleteUrlPreview {
|
||||||
|
/// The URL to clear from the saved preview data
|
||||||
|
#[arg(required_unless_present = "all")]
|
||||||
|
url: Option<String>,
|
||||||
|
|
||||||
|
/// Purge all cached URL previews
|
||||||
|
#[arg(long, conflicts_with = "url")]
|
||||||
|
all: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ pub(super) async fn compact(
|
|||||||
let parallelism = parallelism.unwrap_or(1);
|
let parallelism = parallelism.unwrap_or(1);
|
||||||
let results = maps
|
let results = maps
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.try_stream()
|
.try_stream::<conduwuit::Error>()
|
||||||
.paralleln_and_then(runtime, parallelism, move |map| {
|
.paralleln_and_then(runtime, parallelism, move |map| {
|
||||||
map.compact_blocking(options.clone())?;
|
map.compact_blocking(options.clone())?;
|
||||||
Ok(map.name().to_owned())
|
Ok(map.name().to_owned())
|
||||||
|
|||||||
@@ -20,7 +20,17 @@ pub enum ResolverCommand {
|
|||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Flush a specific server from the resolver caches or everything
|
/// Flush a given server from the resolver caches or flush them completely
|
||||||
|
///
|
||||||
|
/// * Examples:
|
||||||
|
/// * Flush a specific server:
|
||||||
|
///
|
||||||
|
/// `!admin query resolver flush-cache matrix.example.com`
|
||||||
|
///
|
||||||
|
/// * Flush all resolver caches completely:
|
||||||
|
///
|
||||||
|
/// `!admin query resolver flush-cache --all`
|
||||||
|
#[command(verbatim_doc_comment)]
|
||||||
FlushCache {
|
FlushCache {
|
||||||
name: Option<OwnedServerName>,
|
name: Option<OwnedServerName>,
|
||||||
|
|
||||||
|
|||||||
@@ -89,13 +89,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
|||||||
locally, if not using get_alias_helper to fetch room ID remotely"
|
locally, if not using get_alias_helper to fetch room ID remotely"
|
||||||
);
|
);
|
||||||
|
|
||||||
match self
|
match self.services.rooms.alias.resolve_alias(room_alias).await {
|
||||||
.services
|
|
||||||
.rooms
|
|
||||||
.alias
|
|
||||||
.resolve_alias(room_alias, None)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
| Ok((room_id, servers)) => {
|
| Ok((room_id, servers)) => {
|
||||||
debug!(
|
debug!(
|
||||||
%room_id,
|
%room_id,
|
||||||
@@ -235,7 +229,7 @@ async fn ban_list_of_rooms(&self) -> Result {
|
|||||||
.services
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.resolve_alias(room_alias, None)
|
.resolve_alias(room_alias)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
| Ok((room_id, servers)) => {
|
| Ok((room_id, servers)) => {
|
||||||
@@ -388,13 +382,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
|||||||
room ID over federation"
|
room ID over federation"
|
||||||
);
|
);
|
||||||
|
|
||||||
match self
|
match self.services.rooms.alias.resolve_alias(room_alias).await {
|
||||||
.services
|
|
||||||
.rooms
|
|
||||||
.alias
|
|
||||||
.resolve_alias(room_alias, None)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
| Ok((room_id, servers)) => {
|
| Ok((room_id, servers)) => {
|
||||||
debug!(
|
debug!(
|
||||||
%room_id,
|
%room_id,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ pub(super) async fn list_backups(&self) -> Result {
|
|||||||
.db
|
.db
|
||||||
.backup_list()?
|
.backup_list()?
|
||||||
.try_stream()
|
.try_stream()
|
||||||
.try_for_each(|result| write!(self, "{result}"))
|
.try_for_each(|result| writeln!(self, "{result}"))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -252,6 +252,13 @@ pub(crate) async fn register_route(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't allow registration with user IDs that aren't local
|
||||||
|
if !services.globals.user_is_local(&user_id) {
|
||||||
|
return Err!(Request(InvalidUsername(
|
||||||
|
"Username {body_username} is not local to this server"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
user_id
|
user_id
|
||||||
},
|
},
|
||||||
| Err(e) => {
|
| Err(e) => {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use ruma::{
|
|||||||
},
|
},
|
||||||
events::{
|
events::{
|
||||||
AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent,
|
AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent,
|
||||||
GlobalAccountDataEventType, RoomAccountDataEventType,
|
RoomAccountDataEventType,
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
};
|
};
|
||||||
@@ -126,12 +126,6 @@ async fn set_account_data(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if event_type_s == GlobalAccountDataEventType::PushRules.to_cow_str() {
|
|
||||||
return Err!(Request(BadJson(
|
|
||||||
"This endpoint cannot be used for setting/configuring push rules."
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: serde_json::Value = serde_json::from_str(data.get())
|
let data: serde_json::Value = serde_json::from_str(data.get())
|
||||||
.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
|
.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
|
||||||
|
|
||||||
|
|||||||
+3
-65
@@ -1,12 +1,6 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Err, Result, debug};
|
use conduwuit::{Err, Result};
|
||||||
use conduwuit_service::Services;
|
use ruma::api::client::alias::{create_alias, delete_alias, get_alias};
|
||||||
use futures::StreamExt;
|
|
||||||
use rand::seq::SliceRandom;
|
|
||||||
use ruma::{
|
|
||||||
OwnedServerName, RoomAliasId, RoomId,
|
|
||||||
api::client::alias::{create_alias, delete_alias, get_alias},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::Ruma;
|
use crate::Ruma;
|
||||||
|
|
||||||
@@ -96,65 +90,9 @@ pub(crate) async fn get_alias_route(
|
|||||||
) -> Result<get_alias::v3::Response> {
|
) -> Result<get_alias::v3::Response> {
|
||||||
let room_alias = body.body.room_alias;
|
let room_alias = body.body.room_alias;
|
||||||
|
|
||||||
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias, None).await
|
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias).await else {
|
||||||
else {
|
|
||||||
return Err!(Request(NotFound("Room with alias not found.")));
|
return Err!(Request(NotFound("Room with alias not found.")));
|
||||||
};
|
};
|
||||||
|
|
||||||
let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
|
|
||||||
debug!(%room_alias, %room_id, "available servers: {servers:?}");
|
|
||||||
|
|
||||||
Ok(get_alias::v3::Response::new(room_id, servers))
|
Ok(get_alias::v3::Response::new(room_id, servers))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn room_available_servers(
|
|
||||||
services: &Services,
|
|
||||||
room_id: &RoomId,
|
|
||||||
room_alias: &RoomAliasId,
|
|
||||||
pre_servers: Vec<OwnedServerName>,
|
|
||||||
) -> Vec<OwnedServerName> {
|
|
||||||
// find active servers in room state cache to suggest
|
|
||||||
let mut servers: Vec<OwnedServerName> = services
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_servers(room_id)
|
|
||||||
.map(ToOwned::to_owned)
|
|
||||||
.collect()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// push any servers we want in the list already (e.g. responded remote alias
|
|
||||||
// servers, room alias server itself)
|
|
||||||
servers.extend(pre_servers);
|
|
||||||
|
|
||||||
servers.sort_unstable();
|
|
||||||
servers.dedup();
|
|
||||||
|
|
||||||
// shuffle list of servers randomly after sort and dedupe
|
|
||||||
servers.shuffle(&mut rand::thread_rng());
|
|
||||||
|
|
||||||
// insert our server as the very first choice if in list, else check if we can
|
|
||||||
// prefer the room alias server first
|
|
||||||
match servers
|
|
||||||
.iter()
|
|
||||||
.position(|server_name| services.globals.server_is_ours(server_name))
|
|
||||||
{
|
|
||||||
| Some(server_index) => {
|
|
||||||
servers.swap_remove(server_index);
|
|
||||||
servers.insert(0, services.globals.server_name().to_owned());
|
|
||||||
},
|
|
||||||
| _ => {
|
|
||||||
match servers
|
|
||||||
.iter()
|
|
||||||
.position(|server| server == room_alias.server_name())
|
|
||||||
{
|
|
||||||
| Some(alias_server_index) => {
|
|
||||||
servers.swap_remove(alias_server_index);
|
|
||||||
servers.insert(0, room_alias.server_name().into());
|
|
||||||
},
|
|
||||||
| _ => {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
servers
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
use axum::extract::State;
|
||||||
|
use axum_client_ip::InsecureClientIp;
|
||||||
|
use conduwuit::{Err, Result, at};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use ruma::api::client::dehydrated_device::{
|
||||||
|
delete_dehydrated_device::unstable as delete_dehydrated_device,
|
||||||
|
get_dehydrated_device::unstable as get_dehydrated_device, get_events::unstable as get_events,
|
||||||
|
put_dehydrated_device::unstable as put_dehydrated_device,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::Ruma;
|
||||||
|
|
||||||
|
const MAX_BATCH_EVENTS: usize = 50;
|
||||||
|
|
||||||
|
/// # `PUT /_matrix/client/../dehydrated_device`
|
||||||
|
///
|
||||||
|
/// Creates or overwrites the user's dehydrated device.
|
||||||
|
#[tracing::instrument(skip_all, fields(%client))]
|
||||||
|
pub(crate) async fn put_dehydrated_device_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<put_dehydrated_device::Request>,
|
||||||
|
) -> Result<put_dehydrated_device::Response> {
|
||||||
|
let sender_user = body
|
||||||
|
.sender_user
|
||||||
|
.as_deref()
|
||||||
|
.expect("AccessToken authentication required");
|
||||||
|
|
||||||
|
let device_id = body.body.device_id.clone();
|
||||||
|
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_dehydrated_device(sender_user, body.body)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(put_dehydrated_device::Response { device_id })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `DELETE /_matrix/client/../dehydrated_device`
|
||||||
|
///
|
||||||
|
/// Deletes the user's dehydrated device without replacement.
|
||||||
|
#[tracing::instrument(skip_all, fields(%client))]
|
||||||
|
pub(crate) async fn delete_dehydrated_device_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<delete_dehydrated_device::Request>,
|
||||||
|
) -> Result<delete_dehydrated_device::Response> {
|
||||||
|
let sender_user = body.sender_user();
|
||||||
|
|
||||||
|
let device_id = services.users.get_dehydrated_device_id(sender_user).await?;
|
||||||
|
|
||||||
|
services.users.remove_device(sender_user, &device_id).await;
|
||||||
|
|
||||||
|
Ok(delete_dehydrated_device::Response { device_id })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/../dehydrated_device`
|
||||||
|
///
|
||||||
|
/// Gets the user's dehydrated device
|
||||||
|
#[tracing::instrument(skip_all, fields(%client))]
|
||||||
|
pub(crate) async fn get_dehydrated_device_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<get_dehydrated_device::Request>,
|
||||||
|
) -> Result<get_dehydrated_device::Response> {
|
||||||
|
let sender_user = body.sender_user();
|
||||||
|
|
||||||
|
let device = services.users.get_dehydrated_device(sender_user).await?;
|
||||||
|
|
||||||
|
Ok(get_dehydrated_device::Response {
|
||||||
|
device_id: device.device_id,
|
||||||
|
device_data: device.device_data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/../dehydrated_device/{device_id}/events`
|
||||||
|
///
|
||||||
|
/// Paginates the events of the dehydrated device.
|
||||||
|
#[tracing::instrument(skip_all, fields(%client))]
|
||||||
|
pub(crate) async fn get_dehydrated_events_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<get_events::Request>,
|
||||||
|
) -> Result<get_events::Response> {
|
||||||
|
let sender_user = body.sender_user();
|
||||||
|
|
||||||
|
let device_id = &body.body.device_id;
|
||||||
|
let existing_id = services.users.get_dehydrated_device_id(sender_user).await;
|
||||||
|
|
||||||
|
if existing_id.as_ref().is_err()
|
||||||
|
|| existing_id
|
||||||
|
.as_ref()
|
||||||
|
.is_ok_and(|existing_id| existing_id != device_id)
|
||||||
|
{
|
||||||
|
return Err!(Request(Forbidden("Not the dehydrated device_id.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let since: Option<u64> = body
|
||||||
|
.body
|
||||||
|
.next_batch
|
||||||
|
.as_deref()
|
||||||
|
.map(str::parse)
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let mut next_batch: Option<u64> = None;
|
||||||
|
let events = services
|
||||||
|
.users
|
||||||
|
.get_to_device_events(sender_user, device_id, since, None)
|
||||||
|
.take(MAX_BATCH_EVENTS)
|
||||||
|
.inspect(|&(count, _)| {
|
||||||
|
next_batch.replace(count);
|
||||||
|
})
|
||||||
|
.map(at!(1))
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(get_events::Response {
|
||||||
|
events,
|
||||||
|
next_batch: next_batch.as_ref().map(ToString::to_string),
|
||||||
|
})
|
||||||
|
}
|
||||||
+25
-3
@@ -6,6 +6,7 @@ use conduwuit::{
|
|||||||
Err, Result, err,
|
Err, Result, err,
|
||||||
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
|
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
|
||||||
};
|
};
|
||||||
|
use conduwuit_core::error;
|
||||||
use conduwuit_service::{
|
use conduwuit_service::{
|
||||||
Services,
|
Services,
|
||||||
media::{CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, Dim, FileMeta, MXC_LENGTH},
|
media::{CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, Dim, FileMeta, MXC_LENGTH},
|
||||||
@@ -144,12 +145,22 @@ pub(crate) async fn get_content_route(
|
|||||||
server_name: &body.server_name,
|
server_name: &body.server_name,
|
||||||
media_id: &body.media_id,
|
media_id: &body.media_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
let FileMeta {
|
let FileMeta {
|
||||||
content,
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
content_disposition,
|
content_disposition,
|
||||||
} = fetch_file(&services, &mxc, user, body.timeout_ms, None).await?;
|
} = match fetch_file(&services, &mxc, user, body.timeout_ms, None).await {
|
||||||
|
| Ok(meta) => meta,
|
||||||
|
| Err(conduwuit::Error::Io(e)) => match e.kind() {
|
||||||
|
| std::io::ErrorKind::NotFound => return Err!(Request(NotFound("Media not found."))),
|
||||||
|
| std::io::ErrorKind::PermissionDenied => {
|
||||||
|
error!("Permission denied when trying to read file: {e:?}");
|
||||||
|
return Err!(Request(Unknown("Unknown error when fetching file.")));
|
||||||
|
},
|
||||||
|
| _ => return Err!(Request(Unknown("Unknown error when fetching file."))),
|
||||||
|
},
|
||||||
|
| Err(_) => return Err!(Request(Unknown("Unknown error when fetching file."))),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(get_content::v1::Response {
|
Ok(get_content::v1::Response {
|
||||||
file: content.expect("entire file contents"),
|
file: content.expect("entire file contents"),
|
||||||
@@ -185,7 +196,18 @@ pub(crate) async fn get_content_as_filename_route(
|
|||||||
content,
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
content_disposition,
|
content_disposition,
|
||||||
} = fetch_file(&services, &mxc, user, body.timeout_ms, Some(&body.filename)).await?;
|
} = match fetch_file(&services, &mxc, user, body.timeout_ms, None).await {
|
||||||
|
| Ok(meta) => meta,
|
||||||
|
| Err(conduwuit::Error::Io(e)) => match e.kind() {
|
||||||
|
| std::io::ErrorKind::NotFound => return Err!(Request(NotFound("Media not found."))),
|
||||||
|
| std::io::ErrorKind::PermissionDenied => {
|
||||||
|
error!("Permission denied when trying to read file: {e:?}");
|
||||||
|
return Err!(Request(Unknown("Unknown error when fetching file.")));
|
||||||
|
},
|
||||||
|
| _ => return Err!(Request(Unknown("Unknown error when fetching file."))),
|
||||||
|
},
|
||||||
|
| Err(_) => return Err!(Request(Unknown("Unknown error when fetching file."))),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(get_content_as_filename::v1::Response {
|
Ok(get_content_as_filename::v1::Response {
|
||||||
file: content.expect("entire file contents"),
|
file: content.expect("entire file contents"),
|
||||||
|
|||||||
@@ -198,11 +198,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
|||||||
(servers, room_id)
|
(servers, room_id)
|
||||||
},
|
},
|
||||||
| Err(room_alias) => {
|
| Err(room_alias) => {
|
||||||
let (room_id, mut servers) = services
|
let (room_id, mut servers) = services.rooms.alias.resolve_alias(&room_alias).await?;
|
||||||
.rooms
|
|
||||||
.alias
|
|
||||||
.resolve_alias(&room_alias, Some(body.via.clone()))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
banned_room_check(
|
banned_room_check(
|
||||||
&services,
|
&services,
|
||||||
|
|||||||
@@ -102,11 +102,7 @@ pub(crate) async fn knock_room_route(
|
|||||||
(servers, room_id)
|
(servers, room_id)
|
||||||
},
|
},
|
||||||
| Err(room_alias) => {
|
| Err(room_alias) => {
|
||||||
let (room_id, mut servers) = services
|
let (room_id, mut servers) = services.rooms.alias.resolve_alias(&room_alias).await?;
|
||||||
.rooms
|
|
||||||
.alias
|
|
||||||
.resolve_alias(&room_alias, Some(body.via.clone()))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
banned_room_check(
|
banned_room_check(
|
||||||
&services,
|
&services,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub(super) mod appservice;
|
|||||||
pub(super) mod backup;
|
pub(super) mod backup;
|
||||||
pub(super) mod capabilities;
|
pub(super) mod capabilities;
|
||||||
pub(super) mod context;
|
pub(super) mod context;
|
||||||
|
pub(super) mod dehydrated_device;
|
||||||
pub(super) mod device;
|
pub(super) mod device;
|
||||||
pub(super) mod directory;
|
pub(super) mod directory;
|
||||||
pub(super) mod filter;
|
pub(super) mod filter;
|
||||||
@@ -49,6 +50,7 @@ pub(super) use appservice::*;
|
|||||||
pub(super) use backup::*;
|
pub(super) use backup::*;
|
||||||
pub(super) use capabilities::*;
|
pub(super) use capabilities::*;
|
||||||
pub(super) use context::*;
|
pub(super) use context::*;
|
||||||
|
pub(super) use dehydrated_device::*;
|
||||||
pub(super) use device::*;
|
pub(super) use device::*;
|
||||||
pub(super) use directory::*;
|
pub(super) use directory::*;
|
||||||
pub(super) use filter::*;
|
pub(super) use filter::*;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use axum::extract::State;
|
|||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduwuit::{Err, Event, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
|
use conduwuit::{Err, Event, Result, debug_info, info, matrix::pdu::PduEvent, utils::ReadyExt};
|
||||||
use conduwuit_service::Services;
|
use conduwuit_service::Services;
|
||||||
use rand::Rng;
|
|
||||||
use ruma::{
|
use ruma::{
|
||||||
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||||
api::client::{
|
api::client::{
|
||||||
@@ -244,7 +243,7 @@ fn build_report(report: Report) -> RoomMessageEventContent {
|
|||||||
/// random delay sending a response per spec suggestion regarding
|
/// random delay sending a response per spec suggestion regarding
|
||||||
/// enumerating for potential events existing in our server.
|
/// enumerating for potential events existing in our server.
|
||||||
async fn delay_response() {
|
async fn delay_response() {
|
||||||
let time_to_wait = rand::thread_rng().gen_range(2..5);
|
let time_to_wait = rand::random_range(2..5);
|
||||||
debug_info!(
|
debug_info!(
|
||||||
"Got successful /report request, waiting {time_to_wait} seconds before sending \
|
"Got successful /report request, waiting {time_to_wait} seconds before sending \
|
||||||
successful response."
|
successful response."
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ pub(crate) async fn send_message_event_route(
|
|||||||
|
|
||||||
// Check if this is a new transaction id
|
// Check if this is a new transaction id
|
||||||
if let Ok(response) = services
|
if let Ok(response) = services
|
||||||
.transaction_ids
|
.transactions
|
||||||
.existing_txnid(sender_user, sender_device, &body.txn_id)
|
.get_client_txn(sender_user, sender_device, &body.txn_id)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// The client might have sent a txnid of the /sendToDevice endpoint
|
// The client might have sent a txnid of the /sendToDevice endpoint
|
||||||
@@ -92,7 +92,7 @@ pub(crate) async fn send_message_event_route(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
services.transaction_ids.add_txnid(
|
services.transactions.add_client_txnid(
|
||||||
sender_user,
|
sender_user,
|
||||||
sender_device,
|
sender_device,
|
||||||
&body.txn_id,
|
&body.txn_id,
|
||||||
|
|||||||
@@ -342,10 +342,10 @@ async fn allowed_to_send_state_event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for alias in aliases {
|
for alias in aliases {
|
||||||
let (alias_room_id, _servers) = services
|
let (alias_room_id, _) = services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.resolve_alias(&alias, None)
|
.resolve_alias(&alias)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
err!(Request(Unknown("Failed resolving alias \"{alias}\": {e}")))
|
err!(Request(Unknown("Failed resolving alias \"{alias}\": {e}")))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Event, PduCount, PduEvent, Result, at, debug_warn,
|
Event, PduEvent, Result, at, debug_warn,
|
||||||
pdu::EventHash,
|
pdu::EventHash,
|
||||||
trace,
|
trace,
|
||||||
utils::{self, IterStream, future::ReadyEqExt, stream::WidebandExt as _},
|
utils::{self, IterStream, future::ReadyEqExt, stream::WidebandExt as _},
|
||||||
@@ -68,9 +68,13 @@ pub(super) async fn load_left_room(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return early if this is an incremental sync, and we've already synced this
|
// return early if:
|
||||||
// leave to the user, and `include_leave` isn't set on the filter.
|
// - this is an initial sync and the room filter doesn't include leaves, or
|
||||||
if !filter.room.include_leave && last_sync_end_count >= Some(left_count) {
|
// - this is an incremental sync, and we've already synced the leave, and the
|
||||||
|
// room filter doesn't include leaves
|
||||||
|
if last_sync_end_count.is_none_or(|last_sync_end_count| last_sync_end_count >= left_count)
|
||||||
|
&& !filter.room.include_leave
|
||||||
|
{
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,27 +199,13 @@ async fn build_left_state_and_timeline(
|
|||||||
leave_shortstatehash: ShortStateHash,
|
leave_shortstatehash: ShortStateHash,
|
||||||
prev_membership_event: PduEvent,
|
prev_membership_event: PduEvent,
|
||||||
) -> Result<(TimelinePdus, Vec<PduEvent>)> {
|
) -> Result<(TimelinePdus, Vec<PduEvent>)> {
|
||||||
let SyncContext {
|
let SyncContext { syncing_user, filter, .. } = sync_context;
|
||||||
syncing_user,
|
|
||||||
last_sync_end_count,
|
|
||||||
filter,
|
|
||||||
..
|
|
||||||
} = sync_context;
|
|
||||||
|
|
||||||
let timeline_start_count = if let Some(last_sync_end_count) = last_sync_end_count {
|
let timeline_start_count = services
|
||||||
// for incremental syncs, start the timeline after `since`
|
.rooms
|
||||||
PduCount::Normal(last_sync_end_count)
|
.timeline
|
||||||
} else {
|
.get_pdu_count(&prev_membership_event.event_id)
|
||||||
// for initial syncs, start the timeline after the previous membership
|
.await?;
|
||||||
// event. we don't want to include the membership event itself
|
|
||||||
// because clients get confused when they see a `join`
|
|
||||||
// membership event in a `leave` room.
|
|
||||||
services
|
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.get_pdu_count(&prev_membership_event.event_id)
|
|
||||||
.await?
|
|
||||||
};
|
|
||||||
|
|
||||||
// end the timeline at the user's leave event
|
// end the timeline at the user's leave event
|
||||||
let timeline_end_count = services
|
let timeline_end_count = services
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use std::{
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Result, extract_variant,
|
Result, at, extract_variant,
|
||||||
utils::{
|
utils::{
|
||||||
ReadyExt, TryFutureExtExt,
|
ReadyExt, TryFutureExtExt,
|
||||||
stream::{BroadbandExt, Tools, WidebandExt},
|
stream::{BroadbandExt, Tools, WidebandExt},
|
||||||
@@ -297,12 +297,18 @@ pub(crate) async fn build_sync_events(
|
|||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_left(syncing_user)
|
.rooms_left(syncing_user)
|
||||||
.broad_filter_map(|(room_id, leave_pdu)| {
|
.broad_filter_map(|(room_id, leave_pdu)| async {
|
||||||
load_left_room(services, context, room_id.clone(), leave_pdu)
|
let left_room = load_left_room(services, context, room_id.clone(), leave_pdu).await;
|
||||||
.map_ok(move |left_room| (room_id, left_room))
|
|
||||||
.ok()
|
match left_room {
|
||||||
|
| Ok(Some(left_room)) => Some((room_id, left_room)),
|
||||||
|
| Ok(None) => None,
|
||||||
|
| Err(err) => {
|
||||||
|
warn!(?err, %room_id, "error loading joined room");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.ready_filter_map(|(room_id, left_room)| left_room.map(|left_room| (room_id, left_room)))
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let invited_rooms = services
|
let invited_rooms = services
|
||||||
@@ -385,6 +391,7 @@ pub(crate) async fn build_sync_events(
|
|||||||
last_sync_end_count,
|
last_sync_end_count,
|
||||||
Some(current_count),
|
Some(current_count),
|
||||||
)
|
)
|
||||||
|
.map(at!(1))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let device_one_time_keys_count = services
|
let device_one_time_keys_count = services
|
||||||
|
|||||||
@@ -336,7 +336,9 @@ where
|
|||||||
let ranges = list.ranges.clone();
|
let ranges = list.ranges.clone();
|
||||||
|
|
||||||
for mut range in ranges {
|
for mut range in ranges {
|
||||||
range.0 = uint!(0);
|
range.0 = range
|
||||||
|
.0
|
||||||
|
.min(UInt::try_from(active_rooms.len()).unwrap_or(UInt::MAX));
|
||||||
range.1 = range.1.checked_add(uint!(1)).unwrap_or(range.1);
|
range.1 = range.1.checked_add(uint!(1)).unwrap_or(range.1);
|
||||||
range.1 = range
|
range.1 = range
|
||||||
.1
|
.1
|
||||||
@@ -1027,6 +1029,7 @@ async fn collect_to_device(
|
|||||||
events: services
|
events: services
|
||||||
.users
|
.users
|
||||||
.get_to_device_events(sender_user, sender_device, None, Some(next_batch))
|
.get_to_device_events(sender_user, sender_device, None, Some(next_batch))
|
||||||
|
.map(at!(1))
|
||||||
.collect()
|
.collect()
|
||||||
.await,
|
.await,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ pub(crate) async fn send_event_to_device_route(
|
|||||||
|
|
||||||
// Check if this is a new transaction id
|
// Check if this is a new transaction id
|
||||||
if services
|
if services
|
||||||
.transaction_ids
|
.transactions
|
||||||
.existing_txnid(sender_user, sender_device, &body.txn_id)
|
.get_client_txn(sender_user, sender_device, &body.txn_id)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
@@ -104,8 +104,8 @@ pub(crate) async fn send_event_to_device_route(
|
|||||||
|
|
||||||
// Save transaction id with empty data
|
// Save transaction id with empty data
|
||||||
services
|
services
|
||||||
.transaction_ids
|
.transactions
|
||||||
.add_txnid(sender_user, sender_device, &body.txn_id, &[]);
|
.add_client_txnid(sender_user, sender_device, &body.txn_id, &[]);
|
||||||
|
|
||||||
Ok(send_event_to_device::v3::Response {})
|
Ok(send_event_to_device::v3::Response {})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ pub(crate) async fn get_supported_versions_route(
|
|||||||
("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */
|
("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */
|
||||||
("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */
|
("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */
|
||||||
("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */
|
("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */
|
||||||
|
("org.matrix.msc3814".to_owned(), true), /* dehydrated devices */
|
||||||
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
|
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
|
||||||
("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */
|
("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */
|
||||||
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
|
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
|
||||||
|
|||||||
@@ -27,10 +27,32 @@ pub(crate) async fn well_known_client(
|
|||||||
identity_server: None,
|
identity_server: None,
|
||||||
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
|
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
|
||||||
tile_server: None,
|
tile_server: None,
|
||||||
rtc_foci: services.config.well_known.rtc_focus_server_urls.clone(),
|
rtc_foci: services
|
||||||
|
.config
|
||||||
|
.matrix_rtc
|
||||||
|
.effective_foci(&services.config.well_known.rtc_focus_server_urls)
|
||||||
|
.to_vec(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/v1/rtc/transports`
|
||||||
|
/// # `GET /_matrix/client/unstable/org.matrix.msc4143/rtc/transports`
|
||||||
|
///
|
||||||
|
/// Returns the list of MatrixRTC foci (transports) configured for this
|
||||||
|
/// homeserver, implementing MSC4143.
|
||||||
|
pub(crate) async fn get_rtc_transports(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
_body: Ruma<ruma::api::client::discovery::get_rtc_transports::Request>,
|
||||||
|
) -> Result<ruma::api::client::discovery::get_rtc_transports::Response> {
|
||||||
|
Ok(ruma::api::client::discovery::get_rtc_transports::Response::new(
|
||||||
|
services
|
||||||
|
.config
|
||||||
|
.matrix_rtc
|
||||||
|
.effective_foci(&services.config.well_known.rtc_focus_server_urls)
|
||||||
|
.to_vec(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// # `GET /.well-known/matrix/support`
|
/// # `GET /.well-known/matrix/support`
|
||||||
///
|
///
|
||||||
/// Server support contact and support page of a homeserver's domain.
|
/// Server support contact and support page of a homeserver's domain.
|
||||||
|
|||||||
+21
-16
@@ -122,23 +122,23 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
// Ruma doesn't have support for multiple paths for a single endpoint yet, and these routes
|
// Ruma doesn't have support for multiple paths for a single endpoint yet, and these routes
|
||||||
// share one Ruma request / response type pair with {get,send}_state_event_for_key_route
|
// share one Ruma request / response type pair with {get,send}_state_event_for_key_route
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/client/r0/rooms/:room_id/state/:event_type",
|
"/_matrix/client/r0/rooms/{room_id}/state/{event_type}",
|
||||||
get(client::get_state_events_for_empty_key_route)
|
get(client::get_state_events_for_empty_key_route)
|
||||||
.put(client::send_state_event_for_empty_key_route),
|
.put(client::send_state_event_for_empty_key_route),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/client/v3/rooms/:room_id/state/:event_type",
|
"/_matrix/client/v3/rooms/{room_id}/state/{event_type}",
|
||||||
get(client::get_state_events_for_empty_key_route)
|
get(client::get_state_events_for_empty_key_route)
|
||||||
.put(client::send_state_event_for_empty_key_route),
|
.put(client::send_state_event_for_empty_key_route),
|
||||||
)
|
)
|
||||||
// These two endpoints allow trailing slashes
|
// These two endpoints allow trailing slashes
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/client/r0/rooms/:room_id/state/:event_type/",
|
"/_matrix/client/r0/rooms/{room_id}/state/{event_type}/",
|
||||||
get(client::get_state_events_for_empty_key_route)
|
get(client::get_state_events_for_empty_key_route)
|
||||||
.put(client::send_state_event_for_empty_key_route),
|
.put(client::send_state_event_for_empty_key_route),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/client/v3/rooms/:room_id/state/:event_type/",
|
"/_matrix/client/v3/rooms/{room_id}/state/{event_type}/",
|
||||||
get(client::get_state_events_for_empty_key_route)
|
get(client::get_state_events_for_empty_key_route)
|
||||||
.put(client::send_state_event_for_empty_key_route),
|
.put(client::send_state_event_for_empty_key_route),
|
||||||
)
|
)
|
||||||
@@ -160,6 +160,10 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
.ruma_route(&client::update_device_route)
|
.ruma_route(&client::update_device_route)
|
||||||
.ruma_route(&client::delete_device_route)
|
.ruma_route(&client::delete_device_route)
|
||||||
.ruma_route(&client::delete_devices_route)
|
.ruma_route(&client::delete_devices_route)
|
||||||
|
.ruma_route(&client::put_dehydrated_device_route)
|
||||||
|
.ruma_route(&client::delete_dehydrated_device_route)
|
||||||
|
.ruma_route(&client::get_dehydrated_device_route)
|
||||||
|
.ruma_route(&client::get_dehydrated_events_route)
|
||||||
.ruma_route(&client::get_tags_route)
|
.ruma_route(&client::get_tags_route)
|
||||||
.ruma_route(&client::update_tag_route)
|
.ruma_route(&client::update_tag_route)
|
||||||
.ruma_route(&client::delete_tag_route)
|
.ruma_route(&client::delete_tag_route)
|
||||||
@@ -177,13 +181,14 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
.ruma_route(&client::get_mutual_rooms_route)
|
.ruma_route(&client::get_mutual_rooms_route)
|
||||||
.ruma_route(&client::get_room_summary)
|
.ruma_route(&client::get_room_summary)
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary",
|
"/_matrix/client/unstable/im.nheko.summary/rooms/{room_id_or_alias}/summary",
|
||||||
get(client::get_room_summary_legacy)
|
get(client::get_room_summary_legacy)
|
||||||
)
|
)
|
||||||
.ruma_route(&client::get_suspended_status)
|
.ruma_route(&client::get_suspended_status)
|
||||||
.ruma_route(&client::put_suspended_status)
|
.ruma_route(&client::put_suspended_status)
|
||||||
.ruma_route(&client::well_known_support)
|
.ruma_route(&client::well_known_support)
|
||||||
.ruma_route(&client::well_known_client)
|
.ruma_route(&client::well_known_client)
|
||||||
|
.ruma_route(&client::get_rtc_transports)
|
||||||
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
|
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
|
||||||
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
|
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
|
||||||
.ruma_route(&client::room_initial_sync_route)
|
.ruma_route(&client::room_initial_sync_route)
|
||||||
@@ -196,7 +201,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
.ruma_route(&server::get_server_version_route)
|
.ruma_route(&server::get_server_version_route)
|
||||||
.route("/_matrix/key/v2/server", get(server::get_server_keys_route))
|
.route("/_matrix/key/v2/server", get(server::get_server_keys_route))
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/key/v2/server/:key_id",
|
"/_matrix/key/v2/server/{key_id}",
|
||||||
get(server::get_server_keys_deprecated_route),
|
get(server::get_server_keys_deprecated_route),
|
||||||
)
|
)
|
||||||
.ruma_route(&server::get_public_rooms_route)
|
.ruma_route(&server::get_public_rooms_route)
|
||||||
@@ -232,9 +237,9 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
.route("/_continuwuity/local_user_count", get(client::conduwuit_local_user_count));
|
.route("/_continuwuity/local_user_count", get(client::conduwuit_local_user_count));
|
||||||
} else {
|
} else {
|
||||||
router = router
|
router = router
|
||||||
.route("/_matrix/federation/*path", any(federation_disabled))
|
.route("/_matrix/federation/{*path}", any(federation_disabled))
|
||||||
.route("/.well-known/matrix/server", any(federation_disabled))
|
.route("/.well-known/matrix/server", any(federation_disabled))
|
||||||
.route("/_matrix/key/*path", any(federation_disabled))
|
.route("/_matrix/key/{*path}", any(federation_disabled))
|
||||||
.route("/_conduwuit/local_user_count", any(federation_disabled))
|
.route("/_conduwuit/local_user_count", any(federation_disabled))
|
||||||
.route("/_continuwuity/local_user_count", any(federation_disabled));
|
.route("/_continuwuity/local_user_count", any(federation_disabled));
|
||||||
}
|
}
|
||||||
@@ -253,27 +258,27 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
get(client::get_media_preview_legacy_legacy_route),
|
get(client::get_media_preview_legacy_legacy_route),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/media/v1/download/:server_name/:media_id",
|
"/_matrix/media/v1/download/{server_name}/{media_id}",
|
||||||
get(client::get_content_legacy_legacy_route),
|
get(client::get_content_legacy_legacy_route),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/media/v1/download/:server_name/:media_id/:file_name",
|
"/_matrix/media/v1/download/{server_name}/{media_id}/{file_name}",
|
||||||
get(client::get_content_as_filename_legacy_legacy_route),
|
get(client::get_content_as_filename_legacy_legacy_route),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/_matrix/media/v1/thumbnail/:server_name/:media_id",
|
"/_matrix/media/v1/thumbnail/{server_name}/{media_id}",
|
||||||
get(client::get_content_thumbnail_legacy_legacy_route),
|
get(client::get_content_thumbnail_legacy_legacy_route),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
router = router
|
router = router
|
||||||
.route("/_matrix/media/v1/*path", any(legacy_media_disabled))
|
.route("/_matrix/media/v1/{*path}", any(legacy_media_disabled))
|
||||||
.route("/_matrix/media/v3/config", any(legacy_media_disabled))
|
.route("/_matrix/media/v3/config", any(legacy_media_disabled))
|
||||||
.route("/_matrix/media/v3/download/*path", any(legacy_media_disabled))
|
.route("/_matrix/media/v3/download/{*path}", any(legacy_media_disabled))
|
||||||
.route("/_matrix/media/v3/thumbnail/*path", any(legacy_media_disabled))
|
.route("/_matrix/media/v3/thumbnail/{*path}", any(legacy_media_disabled))
|
||||||
.route("/_matrix/media/v3/preview_url", any(redirect_legacy_preview))
|
.route("/_matrix/media/v3/preview_url", any(redirect_legacy_preview))
|
||||||
.route("/_matrix/media/r0/config", any(legacy_media_disabled))
|
.route("/_matrix/media/r0/config", any(legacy_media_disabled))
|
||||||
.route("/_matrix/media/r0/download/*path", any(legacy_media_disabled))
|
.route("/_matrix/media/r0/download/{*path}", any(legacy_media_disabled))
|
||||||
.route("/_matrix/media/r0/thumbnail/*path", any(legacy_media_disabled))
|
.route("/_matrix/media/r0/thumbnail/{*path}", any(legacy_media_disabled))
|
||||||
.route("/_matrix/media/r0/preview_url", any(redirect_legacy_preview));
|
.route("/_matrix/media/r0/preview_url", any(redirect_legacy_preview));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::{mem, ops::Deref};
|
use std::{mem, ops::Deref};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use axum::{body::Body, extract::FromRequest};
|
use axum::{body::Body, extract::FromRequest};
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use conduwuit::{Error, Result, debug, debug_warn, err, trace, utils::string::EMPTY};
|
use conduwuit::{Error, Result, debug, debug_warn, err, trace, utils::string::EMPTY};
|
||||||
@@ -79,7 +78,6 @@ where
|
|||||||
fn deref(&self) -> &Self::Target { &self.body }
|
fn deref(&self) -> &Self::Target { &self.body }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<T> FromRequest<State, Body> for Args<T>
|
impl<T> FromRequest<State, Body> for Args<T>
|
||||||
where
|
where
|
||||||
T: IncomingRequest + Send + Sync + 'static,
|
T: IncomingRequest + Send + Sync + 'static,
|
||||||
|
|||||||
+39
-20
@@ -14,7 +14,8 @@ use futures::{
|
|||||||
pin_mut,
|
pin_mut,
|
||||||
};
|
};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
|
CanonicalJsonObject, CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedServerName,
|
||||||
|
OwnedUserId, UserId,
|
||||||
api::{
|
api::{
|
||||||
AuthScheme, IncomingRequest, Metadata,
|
AuthScheme, IncomingRequest, Metadata,
|
||||||
client::{
|
client::{
|
||||||
@@ -54,7 +55,8 @@ pub(super) async fn auth(
|
|||||||
json_body: Option<&CanonicalJsonValue>,
|
json_body: Option<&CanonicalJsonValue>,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
) -> Result<Auth> {
|
) -> Result<Auth> {
|
||||||
let bearer: Option<TypedHeader<Authorization<Bearer>>> = request.parts.extract().await?;
|
let bearer: Option<TypedHeader<Authorization<Bearer>>> =
|
||||||
|
request.parts.extract().await.unwrap_or(None);
|
||||||
let token = match &bearer {
|
let token = match &bearer {
|
||||||
| Some(TypedHeader(Authorization(bearer))) => Some(bearer.token()),
|
| Some(TypedHeader(Authorization(bearer))) => Some(bearer.token()),
|
||||||
| None => request.query.access_token.as_deref(),
|
| None => request.query.access_token.as_deref(),
|
||||||
@@ -65,23 +67,17 @@ pub(super) async fn auth(
|
|||||||
if metadata.authentication == AuthScheme::None {
|
if metadata.authentication == AuthScheme::None {
|
||||||
match metadata {
|
match metadata {
|
||||||
| &get_public_rooms::v3::Request::METADATA => {
|
| &get_public_rooms::v3::Request::METADATA => {
|
||||||
if !services
|
match token {
|
||||||
.server
|
| Token::Appservice(_) | Token::User(_) => {
|
||||||
.config
|
// we should have validated the token above
|
||||||
.allow_public_room_directory_without_auth
|
// already
|
||||||
{
|
},
|
||||||
match token {
|
| Token::None | Token::Invalid => {
|
||||||
| Token::Appservice(_) | Token::User(_) => {
|
return Err(Error::BadRequest(
|
||||||
// we should have validated the token above
|
ErrorKind::MissingToken,
|
||||||
// already
|
"Missing or invalid access token.",
|
||||||
},
|
));
|
||||||
| Token::None | Token::Invalid => {
|
},
|
||||||
return Err(Error::BadRequest(
|
|
||||||
ErrorKind::MissingToken,
|
|
||||||
"Missing or invalid access token.",
|
|
||||||
));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
| &get_profile::v3::Request::METADATA
|
| &get_profile::v3::Request::METADATA
|
||||||
@@ -233,10 +229,33 @@ async fn auth_appservice(
|
|||||||
return Err!(Request(Exclusive("User is not in namespace.")));
|
return Err!(Request(Exclusive("User is not in namespace.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MSC3202/MSC4190: Handle device_id masquerading for appservices.
|
||||||
|
// The device_id can be provided via `device_id` or
|
||||||
|
// `org.matrix.msc3202.device_id` query parameter.
|
||||||
|
let sender_device = if let Some(ref device_id_str) = request.query.device_id {
|
||||||
|
let device_id: &DeviceId = device_id_str.as_str().into();
|
||||||
|
|
||||||
|
// Verify the device exists for this user
|
||||||
|
if services
|
||||||
|
.users
|
||||||
|
.get_device_metadata(&user_id, device_id)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Err!(Request(Forbidden(
|
||||||
|
"Device does not exist for user or appservice cannot masquerade as this device."
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(device_id.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Auth {
|
Ok(Auth {
|
||||||
origin: None,
|
origin: None,
|
||||||
sender_user: Some(user_id),
|
sender_user: Some(user_id),
|
||||||
sender_device: None,
|
sender_device,
|
||||||
appservice_info: Some(*info),
|
appservice_info: Some(*info),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ use service::Services;
|
|||||||
pub(super) struct QueryParams {
|
pub(super) struct QueryParams {
|
||||||
pub(super) access_token: Option<String>,
|
pub(super) access_token: Option<String>,
|
||||||
pub(super) user_id: Option<String>,
|
pub(super) user_id: Option<String>,
|
||||||
|
/// Device ID for appservice device masquerading (MSC3202/MSC4190).
|
||||||
|
/// Can be provided as `device_id` or `org.matrix.msc3202.device_id`.
|
||||||
|
#[serde(alias = "org.matrix.msc3202.device_id")]
|
||||||
|
pub(super) device_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct Request {
|
pub(super) struct Request {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub(crate) async fn get_room_information_route(
|
|||||||
servers.sort_unstable();
|
servers.sort_unstable();
|
||||||
servers.dedup();
|
servers.dedup();
|
||||||
|
|
||||||
servers.shuffle(&mut rand::thread_rng());
|
servers.shuffle(&mut rand::rng());
|
||||||
|
|
||||||
// insert our server as the very first choice if in list
|
// insert our server as the very first choice if in list
|
||||||
if let Some(server_index) = servers
|
if let Some(server_index) = servers
|
||||||
|
|||||||
+214
-64
@@ -1,27 +1,33 @@
|
|||||||
use std::{collections::BTreeMap, net::IpAddr, time::Instant};
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
|
net::IpAddr,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Error, Result, debug, debug_warn, err, error,
|
Err, Error, Result, debug, debug_warn, err, error,
|
||||||
result::LogErr,
|
result::LogErr,
|
||||||
|
state_res::lexicographical_topological_sort,
|
||||||
trace,
|
trace,
|
||||||
utils::{
|
utils::{
|
||||||
IterStream, ReadyExt, millis_since_unix_epoch,
|
IterStream, ReadyExt, millis_since_unix_epoch,
|
||||||
stream::{BroadbandExt, TryBroadbandExt, automatic_width},
|
stream::{BroadbandExt, TryBroadbandExt, automatic_width},
|
||||||
},
|
},
|
||||||
warn,
|
|
||||||
};
|
};
|
||||||
use conduwuit_service::{
|
use conduwuit_service::{
|
||||||
Services,
|
Services,
|
||||||
sending::{EDU_LIMIT, PDU_LIMIT},
|
sending::{EDU_LIMIT, PDU_LIMIT},
|
||||||
};
|
};
|
||||||
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
|
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
|
||||||
|
use http::StatusCode;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
CanonicalJsonObject, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
|
CanonicalJsonObject, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId,
|
||||||
|
RoomId, ServerName, UserId,
|
||||||
api::{
|
api::{
|
||||||
client::error::ErrorKind,
|
client::error::{ErrorKind, ErrorKind::LimitExceeded},
|
||||||
federation::transactions::{
|
federation::transactions::{
|
||||||
edu::{
|
edu::{
|
||||||
DeviceListUpdateContent, DirectDeviceContent, Edu, PresenceContent,
|
DeviceListUpdateContent, DirectDeviceContent, Edu, PresenceContent,
|
||||||
@@ -32,9 +38,16 @@ use ruma::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
events::receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType},
|
events::receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType},
|
||||||
|
int,
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
to_device::DeviceIdOrAllDevices,
|
to_device::DeviceIdOrAllDevices,
|
||||||
|
uint,
|
||||||
};
|
};
|
||||||
|
use service::transactions::{
|
||||||
|
FederationTxnState, TransactionError, TxnKey, WrappedTransactionResponse,
|
||||||
|
};
|
||||||
|
use tokio::sync::watch::{Receiver, Sender};
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::Ruma;
|
use crate::Ruma;
|
||||||
|
|
||||||
@@ -44,15 +57,6 @@ type Pdu = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
|
|||||||
/// # `PUT /_matrix/federation/v1/send/{txnId}`
|
/// # `PUT /_matrix/federation/v1/send/{txnId}`
|
||||||
///
|
///
|
||||||
/// Push EDUs and PDUs to this server.
|
/// Push EDUs and PDUs to this server.
|
||||||
#[tracing::instrument(
|
|
||||||
name = "txn",
|
|
||||||
level = "debug",
|
|
||||||
skip_all,
|
|
||||||
fields(
|
|
||||||
%client,
|
|
||||||
origin = body.origin().as_str()
|
|
||||||
),
|
|
||||||
)]
|
|
||||||
pub(crate) async fn send_transaction_message_route(
|
pub(crate) async fn send_transaction_message_route(
|
||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
InsecureClientIp(client): InsecureClientIp,
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
@@ -76,16 +80,73 @@ pub(crate) async fn send_transaction_message_route(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let txn_start_time = Instant::now();
|
let txn_key = (body.origin().to_owned(), body.transaction_id.clone());
|
||||||
trace!(
|
|
||||||
pdus = body.pdus.len(),
|
|
||||||
edus = body.edus.len(),
|
|
||||||
elapsed = ?txn_start_time.elapsed(),
|
|
||||||
id = %body.transaction_id,
|
|
||||||
origin = %body.origin(),
|
|
||||||
"Starting txn",
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Atomically check cache, join active, or start new transaction
|
||||||
|
match services
|
||||||
|
.transactions
|
||||||
|
.get_or_start_federation_txn(txn_key.clone())?
|
||||||
|
{
|
||||||
|
| FederationTxnState::Cached(response) => {
|
||||||
|
// Already responded
|
||||||
|
Ok(response)
|
||||||
|
},
|
||||||
|
| FederationTxnState::Active(receiver) => {
|
||||||
|
// Another thread is processing
|
||||||
|
wait_for_result(receiver).await
|
||||||
|
},
|
||||||
|
| FederationTxnState::Started { receiver, sender } => {
|
||||||
|
// We're the first, spawn the processing task
|
||||||
|
services
|
||||||
|
.server
|
||||||
|
.runtime()
|
||||||
|
.spawn(process_inbound_transaction(services, body, client, txn_key, sender));
|
||||||
|
// and wait for it
|
||||||
|
wait_for_result(receiver).await
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_result(
|
||||||
|
mut recv: Receiver<WrappedTransactionResponse>,
|
||||||
|
) -> Result<send_transaction_message::v1::Response> {
|
||||||
|
if tokio::time::timeout(Duration::from_secs(50), recv.changed())
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
// Took too long, return 429 to encourage the sender to try again
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
LimitExceeded { retry_after: None },
|
||||||
|
"Transaction is being still being processed. Please try again later.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let value = recv.borrow_and_update();
|
||||||
|
match value.clone() {
|
||||||
|
| Some(Ok(response)) => Ok(response),
|
||||||
|
| Some(Err(err)) => Err(transaction_error_to_response(&err)),
|
||||||
|
| None => Err(Error::Request(
|
||||||
|
ErrorKind::Unknown,
|
||||||
|
"Transaction processing failed unexpectedly".into(),
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(
|
||||||
|
skip_all,
|
||||||
|
fields(
|
||||||
|
id = ?body.transaction_id.as_str(),
|
||||||
|
origin = ?body.origin()
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
async fn process_inbound_transaction(
|
||||||
|
services: crate::State,
|
||||||
|
body: Ruma<send_transaction_message::v1::Request>,
|
||||||
|
client: IpAddr,
|
||||||
|
txn_key: TxnKey,
|
||||||
|
sender: Sender<WrappedTransactionResponse>,
|
||||||
|
) {
|
||||||
|
let txn_start_time = Instant::now();
|
||||||
let pdus = body
|
let pdus = body
|
||||||
.pdus
|
.pdus
|
||||||
.iter()
|
.iter()
|
||||||
@@ -102,40 +163,79 @@ pub(crate) async fn send_transaction_message_route(
|
|||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.stream();
|
.stream();
|
||||||
|
|
||||||
let results = handle(&services, &client, body.origin(), txn_start_time, pdus, edus).await?;
|
debug!(pdus = body.pdus.len(), edus = body.edus.len(), "Processing transaction",);
|
||||||
|
let results = match handle(&services, &client, body.origin(), pdus, edus).await {
|
||||||
|
| Ok(results) => results,
|
||||||
|
| Err(err) => {
|
||||||
|
fail_federation_txn(services, &txn_key, &sender, err);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (id, result) in &results {
|
||||||
|
if let Err(e) = result {
|
||||||
|
if matches!(e, Error::BadRequest(ErrorKind::NotFound, _)) {
|
||||||
|
debug_warn!("Incoming PDU failed {id}: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
pdus = body.pdus.len(),
|
pdus = body.pdus.len(),
|
||||||
edus = body.edus.len(),
|
edus = body.edus.len(),
|
||||||
elapsed = ?txn_start_time.elapsed(),
|
elapsed = ?txn_start_time.elapsed(),
|
||||||
id = %body.transaction_id,
|
"Finished processing transaction"
|
||||||
origin = %body.origin(),
|
|
||||||
"Finished txn",
|
|
||||||
);
|
);
|
||||||
for (id, result) in &results {
|
|
||||||
if let Err(e) = result {
|
|
||||||
if matches!(e, Error::BadRequest(ErrorKind::NotFound, _)) {
|
|
||||||
warn!("Incoming PDU failed {id}: {e:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(send_transaction_message::v1::Response {
|
let response = send_transaction_message::v1::Response {
|
||||||
pdus: results
|
pdus: results
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(e, r)| (e, r.map_err(error::sanitized_message)))
|
.map(|(e, r)| (e, r.map_err(error::sanitized_message)))
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
};
|
||||||
|
|
||||||
|
services
|
||||||
|
.transactions
|
||||||
|
.finish_federation_txn(txn_key, sender, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles a failed federation transaction by sending the error through
|
||||||
|
/// the channel and cleaning up the transaction state. This allows waiters to
|
||||||
|
/// receive an appropriate error response.
|
||||||
|
fn fail_federation_txn(
|
||||||
|
services: crate::State,
|
||||||
|
txn_key: &TxnKey,
|
||||||
|
sender: &Sender<WrappedTransactionResponse>,
|
||||||
|
err: TransactionError,
|
||||||
|
) {
|
||||||
|
debug!("Transaction failed: {err}");
|
||||||
|
|
||||||
|
// Remove from active state so the transaction can be retried
|
||||||
|
services.transactions.remove_federation_txn(txn_key);
|
||||||
|
|
||||||
|
// Send the error to any waiters
|
||||||
|
if let Err(e) = sender.send(Some(Err(err))) {
|
||||||
|
debug_warn!("Failed to send transaction error to receivers: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a TransactionError into an appropriate HTTP error response.
|
||||||
|
fn transaction_error_to_response(err: &TransactionError) -> Error {
|
||||||
|
match err {
|
||||||
|
| TransactionError::ShuttingDown => Error::Request(
|
||||||
|
ErrorKind::Unknown,
|
||||||
|
"Server is shutting down, please retry later".into(),
|
||||||
|
StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
async fn handle(
|
async fn handle(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
client: &IpAddr,
|
client: &IpAddr,
|
||||||
origin: &ServerName,
|
origin: &ServerName,
|
||||||
started: Instant,
|
|
||||||
pdus: impl Stream<Item = Pdu> + Send,
|
pdus: impl Stream<Item = Pdu> + Send,
|
||||||
edus: impl Stream<Item = Edu> + Send,
|
edus: impl Stream<Item = Edu> + Send,
|
||||||
) -> Result<ResolvedMap> {
|
) -> std::result::Result<ResolvedMap, TransactionError> {
|
||||||
// group pdus by room
|
// group pdus by room
|
||||||
let pdus = pdus
|
let pdus = pdus
|
||||||
.collect()
|
.collect()
|
||||||
@@ -152,7 +252,7 @@ async fn handle(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.try_stream()
|
.try_stream()
|
||||||
.broad_and_then(|(room_id, pdus): (_, Vec<_>)| {
|
.broad_and_then(|(room_id, pdus): (_, Vec<_>)| {
|
||||||
handle_room(services, client, origin, started, room_id, pdus.into_iter())
|
handle_room(services, client, origin, room_id, pdus.into_iter())
|
||||||
.map_ok(Vec::into_iter)
|
.map_ok(Vec::into_iter)
|
||||||
.map_ok(IterStream::try_stream)
|
.map_ok(IterStream::try_stream)
|
||||||
})
|
})
|
||||||
@@ -169,14 +269,51 @@ async fn handle(
|
|||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to build a localised directed acyclic graph out of the given PDUs,
|
||||||
|
/// returning them in a topologically sorted order.
|
||||||
|
///
|
||||||
|
/// This is used to attempt to process PDUs in an order that respects their
|
||||||
|
/// dependencies, however it is ultimately the sender's responsibility to send
|
||||||
|
/// them in a processable order, so this is just a best effort attempt. It does
|
||||||
|
/// not account for power levels or other tie breaks.
|
||||||
|
async fn build_local_dag(
|
||||||
|
pdu_map: &HashMap<OwnedEventId, CanonicalJsonObject>,
|
||||||
|
) -> Result<Vec<OwnedEventId>> {
|
||||||
|
debug_assert!(pdu_map.len() >= 2, "needless call to build_local_dag with less than 2 PDUs");
|
||||||
|
let mut dag: HashMap<OwnedEventId, HashSet<OwnedEventId>> = HashMap::new();
|
||||||
|
|
||||||
|
for (event_id, value) in pdu_map {
|
||||||
|
let prev_events = value
|
||||||
|
.get("prev_events")
|
||||||
|
.expect("pdu must have prev_events")
|
||||||
|
.as_array()
|
||||||
|
.expect("prev_events must be an array")
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
OwnedEventId::parse(v.as_str().expect("prev_events values must be strings"))
|
||||||
|
.expect("prev_events must be valid event IDs")
|
||||||
|
})
|
||||||
|
.collect::<HashSet<OwnedEventId>>();
|
||||||
|
|
||||||
|
dag.insert(event_id.clone(), prev_events);
|
||||||
|
}
|
||||||
|
lexicographical_topological_sort(&dag, &|_| async {
|
||||||
|
// Note: we don't bother fetching power levels because that would massively slow
|
||||||
|
// this function down. This is a best-effort attempt to order events correctly
|
||||||
|
// for processing, however ultimately that should be the sender's job.
|
||||||
|
Ok((int!(0), MilliSecondsSinceUnixEpoch(uint!(0))))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(|e| err!("failed to resolve local graph: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_room(
|
async fn handle_room(
|
||||||
services: &Services,
|
services: &Services,
|
||||||
_client: &IpAddr,
|
_client: &IpAddr,
|
||||||
origin: &ServerName,
|
origin: &ServerName,
|
||||||
txn_start_time: Instant,
|
|
||||||
room_id: OwnedRoomId,
|
room_id: OwnedRoomId,
|
||||||
pdus: impl Iterator<Item = Pdu> + Send,
|
pdus: impl Iterator<Item = Pdu> + Send,
|
||||||
) -> Result<Vec<(OwnedEventId, Result)>> {
|
) -> std::result::Result<Vec<(OwnedEventId, Result)>, TransactionError> {
|
||||||
let _room_lock = services
|
let _room_lock = services
|
||||||
.rooms
|
.rooms
|
||||||
.event_handler
|
.event_handler
|
||||||
@@ -185,27 +322,40 @@ async fn handle_room(
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
let room_id = &room_id;
|
let room_id = &room_id;
|
||||||
pdus.try_stream()
|
let pdu_map: HashMap<OwnedEventId, CanonicalJsonObject> = pdus
|
||||||
.and_then(|(_, event_id, value)| async move {
|
.into_iter()
|
||||||
services.server.check_running()?;
|
.map(|(_, event_id, value)| (event_id, value))
|
||||||
let pdu_start_time = Instant::now();
|
.collect();
|
||||||
let result = services
|
// Try to sort PDUs by their dependencies, but fall back to arbitrary order on
|
||||||
.rooms
|
// failure (e.g., cycles). This is best-effort; proper ordering is the sender's
|
||||||
.event_handler
|
// responsibility.
|
||||||
.handle_incoming_pdu(origin, room_id, &event_id, value, true)
|
let sorted_event_ids = if pdu_map.len() >= 2 {
|
||||||
.await
|
build_local_dag(&pdu_map).await.unwrap_or_else(|e| {
|
||||||
.map(|_| ());
|
debug_warn!("Failed to build local DAG for room {room_id}: {e}");
|
||||||
|
pdu_map.keys().cloned().collect()
|
||||||
debug!(
|
|
||||||
pdu_elapsed = ?pdu_start_time.elapsed(),
|
|
||||||
txn_elapsed = ?txn_start_time.elapsed(),
|
|
||||||
"Finished PDU {event_id}",
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok((event_id, result))
|
|
||||||
})
|
})
|
||||||
.try_collect()
|
} else {
|
||||||
.await
|
pdu_map.keys().cloned().collect()
|
||||||
|
};
|
||||||
|
let mut results = Vec::with_capacity(sorted_event_ids.len());
|
||||||
|
for event_id in sorted_event_ids {
|
||||||
|
let value = pdu_map
|
||||||
|
.get(&event_id)
|
||||||
|
.expect("sorted event IDs must be from the original map")
|
||||||
|
.clone();
|
||||||
|
services
|
||||||
|
.server
|
||||||
|
.check_running()
|
||||||
|
.map_err(|_| TransactionError::ShuttingDown)?;
|
||||||
|
let result = services
|
||||||
|
.rooms
|
||||||
|
.event_handler
|
||||||
|
.handle_incoming_pdu(origin, room_id, &event_id, value, true)
|
||||||
|
.await
|
||||||
|
.map(|_| ());
|
||||||
|
results.push((event_id, result));
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_edu(services: &Services, client: &IpAddr, origin: &ServerName, edu: Edu) {
|
async fn handle_edu(services: &Services, client: &IpAddr, origin: &ServerName, edu: Edu) {
|
||||||
@@ -478,8 +628,8 @@ async fn handle_edu_direct_to_device(
|
|||||||
|
|
||||||
// Check if this is a new transaction id
|
// Check if this is a new transaction id
|
||||||
if services
|
if services
|
||||||
.transaction_ids
|
.transactions
|
||||||
.existing_txnid(sender, None, message_id)
|
.get_client_txn(sender, None, message_id)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
@@ -498,8 +648,8 @@ async fn handle_edu_direct_to_device(
|
|||||||
|
|
||||||
// Save transaction id with empty data
|
// Save transaction id with empty data
|
||||||
services
|
services
|
||||||
.transaction_ids
|
.transactions
|
||||||
.add_txnid(sender, None, message_id, &[]);
|
.add_client_txnid(sender, None, message_id, &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_edu_direct_to_device_user<Event: Send + Sync>(
|
async fn handle_edu_direct_to_device_user<Event: Send + Sync>(
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ libloading.optional = true
|
|||||||
log.workspace = true
|
log.workspace = true
|
||||||
num-traits.workspace = true
|
num-traits.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
|
rand_core = { version = "0.6.4", features = ["getrandom"] }
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
ring.workspace = true
|
ring.workspace = true
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ pub fn check(config: &Config) -> Result {
|
|||||||
if config.allow_registration
|
if config.allow_registration
|
||||||
&& config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
&& config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
||||||
&& config.registration_token.is_none()
|
&& config.registration_token.is_none()
|
||||||
|
&& config.registration_token_file.is_none()
|
||||||
{
|
{
|
||||||
warn!(
|
warn!(
|
||||||
"Open registration is enabled via setting \
|
"Open registration is enabled via setting \
|
||||||
|
|||||||
+132
-42
@@ -368,6 +368,31 @@ pub struct Config {
|
|||||||
#[serde(default = "default_max_fetch_prev_events")]
|
#[serde(default = "default_max_fetch_prev_events")]
|
||||||
pub max_fetch_prev_events: u16,
|
pub max_fetch_prev_events: u16,
|
||||||
|
|
||||||
|
/// How many incoming federation transactions the server is willing to be
|
||||||
|
/// processing at any given time before it becomes overloaded and starts
|
||||||
|
/// rejecting further transactions until some slots become available.
|
||||||
|
///
|
||||||
|
/// Setting this value too low or too high may result in unstable
|
||||||
|
/// federation, and setting it too high may cause runaway resource usage.
|
||||||
|
///
|
||||||
|
/// default: 150
|
||||||
|
#[serde(default = "default_max_concurrent_inbound_transactions")]
|
||||||
|
pub max_concurrent_inbound_transactions: usize,
|
||||||
|
|
||||||
|
/// Maximum age (in seconds) for cached federation transaction responses.
|
||||||
|
/// Entries older than this will be removed during cleanup.
|
||||||
|
///
|
||||||
|
/// default: 7200 (2 hours)
|
||||||
|
#[serde(default = "default_transaction_id_cache_max_age_secs")]
|
||||||
|
pub transaction_id_cache_max_age_secs: u64,
|
||||||
|
|
||||||
|
/// Maximum number of cached federation transaction responses.
|
||||||
|
/// When the cache exceeds this limit, older entries will be removed.
|
||||||
|
///
|
||||||
|
/// default: 8192
|
||||||
|
#[serde(default = "default_transaction_id_cache_max_entries")]
|
||||||
|
pub transaction_id_cache_max_entries: usize,
|
||||||
|
|
||||||
/// Default/base connection timeout (seconds). This is used only by URL
|
/// Default/base connection timeout (seconds). This is used only by URL
|
||||||
/// previews and update/news endpoint checks.
|
/// previews and update/news endpoint checks.
|
||||||
///
|
///
|
||||||
@@ -584,19 +609,25 @@ pub struct Config {
|
|||||||
pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool,
|
pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool,
|
||||||
|
|
||||||
/// A static registration token that new users will have to provide when
|
/// A static registration token that new users will have to provide when
|
||||||
/// creating an account. If unset and `allow_registration` is true,
|
/// creating an account. This token does not supersede tokens from other
|
||||||
/// you must set
|
/// sources, such as the `!admin token` command or the
|
||||||
/// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
|
/// `registration_token_file` configuration option.
|
||||||
/// to true to allow open registration without any conditions.
|
|
||||||
///
|
|
||||||
/// If you do not want to set a static token, the `!admin token` commands
|
|
||||||
/// may also be used to manage registration tokens.
|
|
||||||
///
|
///
|
||||||
/// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
/// example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
||||||
///
|
///
|
||||||
/// display: sensitive
|
/// display: sensitive
|
||||||
pub registration_token: Option<String>,
|
pub registration_token: Option<String>,
|
||||||
|
|
||||||
|
/// A path to a file containing static registration tokens, one per line.
|
||||||
|
/// Tokens in this file do not supersede tokens from other sources, such as
|
||||||
|
/// the `!admin token` command or the `registration_token` configuration
|
||||||
|
/// option.
|
||||||
|
///
|
||||||
|
/// The file will be read once, when Continuwuity starts. It is not
|
||||||
|
/// currently reread when the server configuration is reloaded. If the file
|
||||||
|
/// cannot be read, Continuwuity will fail to start.
|
||||||
|
pub registration_token_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// The public site key for reCaptcha. If this is provided, reCaptcha
|
/// The public site key for reCaptcha. If this is provided, reCaptcha
|
||||||
/// becomes required during registration. If both captcha *and*
|
/// becomes required during registration. If both captcha *and*
|
||||||
/// registration token are enabled, both will be required during
|
/// registration token are enabled, both will be required during
|
||||||
@@ -653,12 +684,6 @@ pub struct Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub allow_public_room_directory_over_federation: bool,
|
pub allow_public_room_directory_over_federation: bool,
|
||||||
|
|
||||||
/// Set this to true to allow your server's public room directory to be
|
|
||||||
/// queried without client authentication (access token) through the Client
|
|
||||||
/// APIs. Set this to false to protect against /publicRooms spiders.
|
|
||||||
#[serde(default)]
|
|
||||||
pub allow_public_room_directory_without_auth: bool,
|
|
||||||
|
|
||||||
/// Allow guests/unauthenticated users to access TURN credentials.
|
/// Allow guests/unauthenticated users to access TURN credentials.
|
||||||
///
|
///
|
||||||
/// This is the equivalent of Synapse's `turn_allow_guests` config option.
|
/// This is the equivalent of Synapse's `turn_allow_guests` config option.
|
||||||
@@ -1244,12 +1269,6 @@ pub struct Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub rocksdb_repair: bool,
|
pub rocksdb_repair: bool,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub rocksdb_read_only: bool,
|
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub rocksdb_secondary: bool,
|
|
||||||
|
|
||||||
/// Enables idle CPU priority for compaction thread. This is not enabled by
|
/// Enables idle CPU priority for compaction thread. This is not enabled by
|
||||||
/// default to prevent compaction from falling too far behind on busy
|
/// default to prevent compaction from falling too far behind on busy
|
||||||
/// systems.
|
/// systems.
|
||||||
@@ -1309,26 +1328,33 @@ pub struct Config {
|
|||||||
|
|
||||||
/// Allow local (your server only) presence updates/requests.
|
/// Allow local (your server only) presence updates/requests.
|
||||||
///
|
///
|
||||||
/// Note that presence on continuwuity is very fast unlike Synapse's. If
|
/// Local presence must be enabled for outgoing presence to function.
|
||||||
/// using outgoing presence, this MUST be enabled.
|
///
|
||||||
|
/// Note that local presence is not as heavy on the CPU as federated
|
||||||
|
/// presence, but will still become more expensive the more local users you
|
||||||
|
/// have.
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub allow_local_presence: bool,
|
pub allow_local_presence: bool,
|
||||||
|
|
||||||
/// Allow incoming federated presence updates/requests.
|
/// Allow incoming federated presence updates.
|
||||||
///
|
///
|
||||||
/// This option receives presence updates from other servers, but does not
|
/// This option enables processing inbound presence updates from other
|
||||||
/// send any unless `allow_outgoing_presence` is true. Note that presence on
|
/// servers. Without it, remote users will appear as if they are always
|
||||||
/// continuwuity is very fast unlike Synapse's.
|
/// offline to your local users. This does not affect typing indicators or
|
||||||
|
/// read receipts.
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub allow_incoming_presence: bool,
|
pub allow_incoming_presence: bool,
|
||||||
|
|
||||||
/// Allow outgoing presence updates/requests.
|
/// Allow outgoing presence updates/requests.
|
||||||
///
|
///
|
||||||
/// This option sends presence updates to other servers, but does not
|
/// This option sends presence updates to other servers, and requires that
|
||||||
/// receive any unless `allow_incoming_presence` is true. Note that presence
|
/// `allow_local_presence` is also enabled.
|
||||||
/// on continuwuity is very fast unlike Synapse's. If using outgoing
|
///
|
||||||
/// presence, you MUST enable `allow_local_presence` as well.
|
/// Note that outgoing presence is very heavy on the CPU and network, and
|
||||||
#[serde(default = "true_fn")]
|
/// will typically cause extreme strain and slowdowns for no real benefit.
|
||||||
|
/// There are only a few clients that even implement presence, so you
|
||||||
|
/// probably don't want to enable this.
|
||||||
|
#[serde(default)]
|
||||||
pub allow_outgoing_presence: bool,
|
pub allow_outgoing_presence: bool,
|
||||||
|
|
||||||
/// How many seconds without presence updates before you become idle.
|
/// How many seconds without presence updates before you become idle.
|
||||||
@@ -1366,6 +1392,10 @@ pub struct Config {
|
|||||||
pub allow_incoming_read_receipts: bool,
|
pub allow_incoming_read_receipts: bool,
|
||||||
|
|
||||||
/// Allow sending read receipts to remote servers.
|
/// Allow sending read receipts to remote servers.
|
||||||
|
///
|
||||||
|
/// Note that sending read receipts to remote servers in large rooms with
|
||||||
|
/// lots of other homeservers may cause additional strain on the CPU and
|
||||||
|
/// network.
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub allow_outgoing_read_receipts: bool,
|
pub allow_outgoing_read_receipts: bool,
|
||||||
|
|
||||||
@@ -1377,6 +1407,10 @@ pub struct Config {
|
|||||||
pub allow_local_typing: bool,
|
pub allow_local_typing: bool,
|
||||||
|
|
||||||
/// Allow outgoing typing updates to federation.
|
/// Allow outgoing typing updates to federation.
|
||||||
|
///
|
||||||
|
/// Note that sending typing indicators to remote servers in large rooms
|
||||||
|
/// with lots of other homeservers may cause additional strain on the CPU
|
||||||
|
/// and network.
|
||||||
#[serde(default = "true_fn")]
|
#[serde(default = "true_fn")]
|
||||||
pub allow_outgoing_typing: bool,
|
pub allow_outgoing_typing: bool,
|
||||||
|
|
||||||
@@ -1516,7 +1550,7 @@ pub struct Config {
|
|||||||
/// sender user's server name, inbound federation X-Matrix origin, and
|
/// sender user's server name, inbound federation X-Matrix origin, and
|
||||||
/// outbound federation handler.
|
/// outbound federation handler.
|
||||||
///
|
///
|
||||||
/// You can set this to ["*"] to block all servers by default, and then
|
/// You can set this to [".*"] to block all servers by default, and then
|
||||||
/// use `allowed_remote_server_names` to allow only specific servers.
|
/// use `allowed_remote_server_names` to allow only specific servers.
|
||||||
///
|
///
|
||||||
/// example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
|
/// example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
|
||||||
@@ -2040,6 +2074,16 @@ pub struct Config {
|
|||||||
pub allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure:
|
pub allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure:
|
||||||
bool,
|
bool,
|
||||||
|
|
||||||
|
/// Forcibly disables first-run mode.
|
||||||
|
///
|
||||||
|
/// This is intended to be used for Complement testing to allow the test
|
||||||
|
/// suite to register users, because first-run mode interferes with open
|
||||||
|
/// registration.
|
||||||
|
///
|
||||||
|
/// display: hidden
|
||||||
|
#[serde(default)]
|
||||||
|
pub force_disable_first_run_mode: bool,
|
||||||
|
|
||||||
/// display: nested
|
/// display: nested
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub ldap: LdapConfig,
|
pub ldap: LdapConfig,
|
||||||
@@ -2052,6 +2096,12 @@ pub struct Config {
|
|||||||
/// display: nested
|
/// display: nested
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub blurhashing: BlurhashConfig,
|
pub blurhashing: BlurhashConfig,
|
||||||
|
|
||||||
|
/// Configuration for MatrixRTC (MSC4143) transport discovery.
|
||||||
|
/// display: nested
|
||||||
|
#[serde(default)]
|
||||||
|
pub matrix_rtc: MatrixRtcConfig,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
#[allow(clippy::zero_sized_map_values)]
|
#[allow(clippy::zero_sized_map_values)]
|
||||||
// this is a catchall, the map shouldn't be zero at runtime
|
// this is a catchall, the map shouldn't be zero at runtime
|
||||||
@@ -2117,17 +2167,16 @@ pub struct WellKnownConfig {
|
|||||||
/// listed.
|
/// listed.
|
||||||
pub support_mxid: Option<OwnedUserId>,
|
pub support_mxid: Option<OwnedUserId>,
|
||||||
|
|
||||||
/// A list of MatrixRTC foci URLs which will be served as part of the
|
/// **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
|
||||||
/// MSC4143 client endpoint at /.well-known/matrix/client. If you're
|
|
||||||
/// setting up livekit, you'd want something like:
|
|
||||||
/// rtc_focus_server_urls = [
|
|
||||||
/// { type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
|
||||||
/// ]
|
|
||||||
///
|
///
|
||||||
/// To disable, set this to be an empty vector (`[]`).
|
/// A list of MatrixRTC foci URLs which will be served as part of the
|
||||||
|
/// MSC4143 client endpoint at /.well-known/matrix/client.
|
||||||
|
///
|
||||||
|
/// This option is deprecated and will be removed in a future release.
|
||||||
|
/// Please migrate to the new `[global.matrix_rtc]` config section.
|
||||||
///
|
///
|
||||||
/// default: []
|
/// default: []
|
||||||
#[serde(default = "default_rtc_focus_urls")]
|
#[serde(default)]
|
||||||
pub rtc_focus_server_urls: Vec<RtcFocusInfo>,
|
pub rtc_focus_server_urls: Vec<RtcFocusInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2156,6 +2205,43 @@ pub struct BlurhashConfig {
|
|||||||
pub blurhash_max_raw_size: u64,
|
pub blurhash_max_raw_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.matrix_rtc")]
|
||||||
|
pub struct MatrixRtcConfig {
|
||||||
|
/// A list of MatrixRTC foci (transports) which will be served via the
|
||||||
|
/// MSC4143 RTC transports endpoint at
|
||||||
|
/// `/_matrix/client/v1/rtc/transports`. If you're setting up livekit,
|
||||||
|
/// you'd want something like:
|
||||||
|
/// ```toml
|
||||||
|
/// [global.matrix_rtc]
|
||||||
|
/// foci = [
|
||||||
|
/// { type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// To disable, set this to an empty list (`[]`).
|
||||||
|
///
|
||||||
|
/// default: []
|
||||||
|
#[serde(default)]
|
||||||
|
pub foci: Vec<RtcFocusInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MatrixRtcConfig {
|
||||||
|
/// Returns the effective foci, falling back to the deprecated
|
||||||
|
/// `rtc_focus_server_urls` if the new config is empty.
|
||||||
|
#[must_use]
|
||||||
|
pub fn effective_foci<'a>(
|
||||||
|
&'a self,
|
||||||
|
deprecated_foci: &'a [RtcFocusInfo],
|
||||||
|
) -> &'a [RtcFocusInfo] {
|
||||||
|
if !self.foci.is_empty() {
|
||||||
|
&self.foci
|
||||||
|
} else {
|
||||||
|
deprecated_foci
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
#[derive(Clone, Debug, Default, Deserialize)]
|
||||||
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.ldap")]
|
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.ldap")]
|
||||||
pub struct LdapConfig {
|
pub struct LdapConfig {
|
||||||
@@ -2349,6 +2435,7 @@ const DEPRECATED_KEYS: &[&str] = &[
|
|||||||
"well_known_support_email",
|
"well_known_support_email",
|
||||||
"well_known_support_mxid",
|
"well_known_support_mxid",
|
||||||
"registration_token_file",
|
"registration_token_file",
|
||||||
|
"well_known.rtc_focus_server_urls",
|
||||||
];
|
];
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -2531,6 +2618,12 @@ fn default_pusher_idle_timeout() -> u64 { 15 }
|
|||||||
|
|
||||||
fn default_max_fetch_prev_events() -> u16 { 192_u16 }
|
fn default_max_fetch_prev_events() -> u16 { 192_u16 }
|
||||||
|
|
||||||
|
fn default_max_concurrent_inbound_transactions() -> usize { 150 }
|
||||||
|
|
||||||
|
fn default_transaction_id_cache_max_age_secs() -> u64 { 60 * 60 * 2 }
|
||||||
|
|
||||||
|
fn default_transaction_id_cache_max_entries() -> usize { 8192 }
|
||||||
|
|
||||||
fn default_tracing_flame_filter() -> String {
|
fn default_tracing_flame_filter() -> String {
|
||||||
cfg!(debug_assertions)
|
cfg!(debug_assertions)
|
||||||
.then_some("trace,h2=off")
|
.then_some("trace,h2=off")
|
||||||
@@ -2626,9 +2719,6 @@ fn default_rocksdb_stats_level() -> u8 { 1 }
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn default_default_room_version() -> RoomVersionId { RoomVersionId::V11 }
|
pub fn default_default_room_version() -> RoomVersionId { RoomVersionId::V11 }
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn default_rtc_focus_urls() -> Vec<RtcFocusInfo> { vec![] }
|
|
||||||
|
|
||||||
fn default_ip_range_denylist() -> Vec<String> {
|
fn default_ip_range_denylist() -> Vec<String> {
|
||||||
vec![
|
vec![
|
||||||
"127.0.0.0/8".to_owned(),
|
"127.0.0.0/8".to_owned(),
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ impl Error {
|
|||||||
| Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
| Self::Reqwest(error) => error.status().unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
|
||||||
| Self::Conflict(_) => StatusCode::CONFLICT,
|
| Self::Conflict(_) => StatusCode::CONFLICT,
|
||||||
| Self::Io(error) => response::io_error_code(error.kind()),
|
| Self::Io(error) => response::io_error_code(error.kind()),
|
||||||
|
| Self::Uiaa(_) => StatusCode::UNAUTHORIZED,
|
||||||
| _ => StatusCode::INTERNAL_SERVER_ERROR,
|
| _ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ static SEMANTIC: &str = env!("CARGO_PKG_VERSION");
|
|||||||
static VERSION: OnceLock<String> = OnceLock::new();
|
static VERSION: OnceLock<String> = OnceLock::new();
|
||||||
static VERSION_UA: OnceLock<String> = OnceLock::new();
|
static VERSION_UA: OnceLock<String> = OnceLock::new();
|
||||||
static USER_AGENT: OnceLock<String> = OnceLock::new();
|
static USER_AGENT: OnceLock<String> = OnceLock::new();
|
||||||
|
static USER_AGENT_MEDIA: OnceLock<String> = OnceLock::new();
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -21,14 +22,22 @@ pub fn name() -> &'static str { BRANDING }
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version() -> &'static str { VERSION.get_or_init(init_version) }
|
pub fn version() -> &'static str { VERSION.get_or_init(init_version) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version_ua() -> &'static str { VERSION_UA.get_or_init(init_version_ua) }
|
pub fn version_ua() -> &'static str { VERSION_UA.get_or_init(init_version_ua) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) }
|
pub fn user_agent() -> &'static str { USER_AGENT.get_or_init(init_user_agent) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn user_agent_media() -> &'static str { USER_AGENT_MEDIA.get_or_init(init_user_agent_media) }
|
||||||
|
|
||||||
fn init_user_agent() -> String { format!("{}/{} (bot; +{WEBSITE})", name(), version_ua()) }
|
fn init_user_agent() -> String { format!("{}/{} (bot; +{WEBSITE})", name(), version_ua()) }
|
||||||
|
|
||||||
|
fn init_user_agent_media() -> String {
|
||||||
|
format!("{}/{} (embedbot; facebookexternalhit/1.1; +{WEBSITE})", name(), version_ua())
|
||||||
|
}
|
||||||
|
|
||||||
fn init_version_ua() -> String {
|
fn init_version_ua() -> String {
|
||||||
conduwuit_build_metadata::version_tag()
|
conduwuit_build_metadata::version_tag()
|
||||||
.map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC}+{extra}"))
|
.map_or_else(|| SEMANTIC.to_owned(), |extra| format!("{SEMANTIC}+{extra}"))
|
||||||
|
|||||||
@@ -1046,7 +1046,7 @@ mod tests {
|
|||||||
// don't remove any events so we know it sorts them all correctly
|
// don't remove any events so we know it sorts them all correctly
|
||||||
let mut events_to_sort = events.keys().cloned().collect::<Vec<_>>();
|
let mut events_to_sort = events.keys().cloned().collect::<Vec<_>>();
|
||||||
|
|
||||||
events_to_sort.shuffle(&mut rand::thread_rng());
|
events_to_sort.shuffle(&mut rand::rng());
|
||||||
|
|
||||||
let power_level = resolved_power
|
let power_level = resolved_power
|
||||||
.get(&(StateEventType::RoomPowerLevels, "".into()))
|
.get(&(StateEventType::RoomPowerLevels, "".into()))
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ fn init_argon() -> Argon2<'static> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn password(password: &str) -> Result<String> {
|
pub(super) fn password(password: &str) -> Result<String> {
|
||||||
let salt = SaltString::generate(rand::thread_rng());
|
let salt = SaltString::generate(rand_core::OsRng);
|
||||||
ARGON
|
ARGON
|
||||||
.get_or_init(init_argon)
|
.get_or_init(init_argon)
|
||||||
.hash_password(password.as_bytes(), &salt)
|
.hash_password(password.as_bytes(), &salt)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub mod json;
|
|||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod mutex_map;
|
pub mod mutex_map;
|
||||||
pub mod rand;
|
pub mod rand;
|
||||||
|
pub mod response;
|
||||||
pub mod result;
|
pub mod result;
|
||||||
pub mod set;
|
pub mod set;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
|
|||||||
+7
-10
@@ -4,16 +4,16 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use arrayvec::ArrayString;
|
use arrayvec::ArrayString;
|
||||||
use rand::{Rng, seq::SliceRandom, thread_rng};
|
use rand::{RngExt, seq::SliceRandom};
|
||||||
|
|
||||||
pub fn shuffle<T>(vec: &mut [T]) {
|
pub fn shuffle<T>(vec: &mut [T]) {
|
||||||
let mut rng = thread_rng();
|
let mut rng = rand::rng();
|
||||||
vec.shuffle(&mut rng);
|
vec.shuffle(&mut rng);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn string(length: usize) -> String {
|
pub fn string(length: usize) -> String {
|
||||||
thread_rng()
|
rand::rng()
|
||||||
.sample_iter(&rand::distributions::Alphanumeric)
|
.sample_iter(&rand::distr::Alphanumeric)
|
||||||
.take(length)
|
.take(length)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect()
|
.collect()
|
||||||
@@ -22,8 +22,8 @@ pub fn string(length: usize) -> String {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn string_array<const LENGTH: usize>() -> ArrayString<LENGTH> {
|
pub fn string_array<const LENGTH: usize>() -> ArrayString<LENGTH> {
|
||||||
let mut ret = ArrayString::<LENGTH>::new();
|
let mut ret = ArrayString::<LENGTH>::new();
|
||||||
thread_rng()
|
rand::rng()
|
||||||
.sample_iter(&rand::distributions::Alphanumeric)
|
.sample_iter(&rand::distr::Alphanumeric)
|
||||||
.take(LENGTH)
|
.take(LENGTH)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.for_each(|c| ret.push(c));
|
.for_each(|c| ret.push(c));
|
||||||
@@ -40,7 +40,4 @@ pub fn time_from_now_secs(range: Range<u64>) -> SystemTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn secs(range: Range<u64>) -> Duration {
|
pub fn secs(range: Range<u64>) -> Duration { Duration::from_secs(rand::random_range(range)) }
|
||||||
let mut rng = thread_rng();
|
|
||||||
Duration::from_secs(rng.gen_range(range))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use futures::StreamExt;
|
||||||
|
use num_traits::ToPrimitive;
|
||||||
|
|
||||||
|
use crate::Err;
|
||||||
|
|
||||||
|
/// Reads the response body while enforcing a maximum size limit to prevent
|
||||||
|
/// memory exhaustion.
|
||||||
|
pub async fn limit_read(response: reqwest::Response, max_size: u64) -> crate::Result<Vec<u8>> {
|
||||||
|
if response.content_length().is_some_and(|len| len > max_size) {
|
||||||
|
return Err!(BadServerResponse("Response too large"));
|
||||||
|
}
|
||||||
|
let mut data = Vec::new();
|
||||||
|
let mut reader = response.bytes_stream();
|
||||||
|
|
||||||
|
while let Some(chunk) = reader.next().await {
|
||||||
|
let chunk = chunk?;
|
||||||
|
data.extend_from_slice(&chunk);
|
||||||
|
|
||||||
|
if data.len() > max_size.to_usize().expect("max_size must fit in usize") {
|
||||||
|
return Err!(BadServerResponse("Response too large"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the response body as text while enforcing a maximum size limit to
|
||||||
|
/// prevent memory exhaustion.
|
||||||
|
pub async fn limit_read_text(
|
||||||
|
response: reqwest::Response,
|
||||||
|
max_size: u64,
|
||||||
|
) -> crate::Result<String> {
|
||||||
|
let text = String::from_utf8(limit_read(response, max_size).await?)?;
|
||||||
|
Ok(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(async_fn_in_trait)]
|
||||||
|
pub trait LimitReadExt {
|
||||||
|
async fn limit_read(self, max_size: u64) -> crate::Result<Vec<u8>>;
|
||||||
|
async fn limit_read_text(self, max_size: u64) -> crate::Result<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LimitReadExt for reqwest::Response {
|
||||||
|
async fn limit_read(self, max_size: u64) -> crate::Result<Vec<u8>> {
|
||||||
|
limit_read(self, max_size).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn limit_read_text(self, max_size: u64) -> crate::Result<String> {
|
||||||
|
limit_read_text(self, max_size).await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,19 +3,17 @@ use futures::{
|
|||||||
stream::{Stream, TryStream},
|
stream::{Stream, TryStream},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Error, Result};
|
|
||||||
|
|
||||||
pub trait IterStream<I: IntoIterator + Send> {
|
pub trait IterStream<I: IntoIterator + Send> {
|
||||||
/// Convert an Iterator into a Stream
|
/// Convert an Iterator into a Stream
|
||||||
fn stream(self) -> impl Stream<Item = <I as IntoIterator>::Item> + Send;
|
fn stream(self) -> impl Stream<Item = <I as IntoIterator>::Item> + Send;
|
||||||
|
|
||||||
/// Convert an Iterator into a TryStream
|
/// Convert an Iterator into a TryStream with a generic error type
|
||||||
fn try_stream(
|
fn try_stream<E>(
|
||||||
self,
|
self,
|
||||||
) -> impl TryStream<
|
) -> impl TryStream<
|
||||||
Ok = <I as IntoIterator>::Item,
|
Ok = <I as IntoIterator>::Item,
|
||||||
Error = Error,
|
Error = E,
|
||||||
Item = Result<<I as IntoIterator>::Item, Error>,
|
Item = Result<<I as IntoIterator>::Item, E>,
|
||||||
> + Send;
|
> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,12 +26,12 @@ where
|
|||||||
fn stream(self) -> impl Stream<Item = <I as IntoIterator>::Item> + Send { stream::iter(self) }
|
fn stream(self) -> impl Stream<Item = <I as IntoIterator>::Item> + Send { stream::iter(self) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn try_stream(
|
fn try_stream<E>(
|
||||||
self,
|
self,
|
||||||
) -> impl TryStream<
|
) -> impl TryStream<
|
||||||
Ok = <I as IntoIterator>::Item,
|
Ok = <I as IntoIterator>::Item,
|
||||||
Error = Error,
|
Error = E,
|
||||||
Item = Result<<I as IntoIterator>::Item, Error>,
|
Item = Result<<I as IntoIterator>::Item, E>,
|
||||||
> + Send {
|
> + Send {
|
||||||
self.stream().map(Ok)
|
self.stream().map(Ok)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
//! Synchronous combinator extensions to futures::TryStream
|
//! Synchronous combinator extensions to futures::TryStream
|
||||||
|
|
||||||
|
use std::result::Result;
|
||||||
|
|
||||||
use futures::{TryFuture, TryStream, TryStreamExt};
|
use futures::{TryFuture, TryStream, TryStreamExt};
|
||||||
|
|
||||||
use super::automatic_width;
|
use super::automatic_width;
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
/// Concurrency extensions to augment futures::TryStreamExt. broad_ combinators
|
/// Concurrency extensions to augment futures::TryStreamExt. broad_ combinators
|
||||||
/// produce out-of-order
|
/// produce out-of-order
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ pub struct Engine {
|
|||||||
pub(crate) db: Db,
|
pub(crate) db: Db,
|
||||||
pub(crate) pool: Arc<Pool>,
|
pub(crate) pool: Arc<Pool>,
|
||||||
pub(crate) ctx: Arc<Context>,
|
pub(crate) ctx: Arc<Context>,
|
||||||
pub(super) read_only: bool,
|
|
||||||
pub(super) secondary: bool,
|
|
||||||
pub(crate) checksums: bool,
|
pub(crate) checksums: bool,
|
||||||
corks: AtomicU32,
|
corks: AtomicU32,
|
||||||
}
|
}
|
||||||
@@ -129,14 +127,6 @@ impl Engine {
|
|||||||
|
|
||||||
sequence
|
sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn is_read_only(&self) -> bool { self.secondary || self.read_only }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn is_secondary(&self) -> bool { self.secondary }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Engine {
|
impl Drop for Engine {
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ pub fn backup(&self) -> Result {
|
|||||||
let mut engine = self.backup_engine()?;
|
let mut engine = self.backup_engine()?;
|
||||||
let config = &self.ctx.server.config;
|
let config = &self.ctx.server.config;
|
||||||
if config.database_backups_to_keep > 0 {
|
if config.database_backups_to_keep > 0 {
|
||||||
let flush = !self.is_read_only();
|
|
||||||
engine
|
engine
|
||||||
.create_new_backup_flush(&self.db, flush)
|
.create_new_backup_flush(&self.db, true)
|
||||||
.map_err(map_err)?;
|
.map_err(map_err)?;
|
||||||
|
|
||||||
let engine_info = engine.get_backup_info();
|
let engine_info = engine.get_backup_info();
|
||||||
|
|||||||
@@ -35,14 +35,7 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug!("Opening database...");
|
debug!("Opening database...");
|
||||||
let db = if config.rocksdb_read_only {
|
let db = Db::open_cf_descriptors(&db_opts, path, cfds).or_else(or_else)?;
|
||||||
Db::open_cf_descriptors_read_only(&db_opts, path, cfds, false)
|
|
||||||
} else if config.rocksdb_secondary {
|
|
||||||
Db::open_cf_descriptors_as_secondary(&db_opts, path, path, cfds)
|
|
||||||
} else {
|
|
||||||
Db::open_cf_descriptors(&db_opts, path, cfds)
|
|
||||||
}
|
|
||||||
.or_else(or_else)?;
|
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
columns = num_cfds,
|
columns = num_cfds,
|
||||||
@@ -55,8 +48,6 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
|
|||||||
db,
|
db,
|
||||||
pool: ctx.pool.clone(),
|
pool: ctx.pool.clone(),
|
||||||
ctx: ctx.clone(),
|
ctx: ctx.clone(),
|
||||||
read_only: config.rocksdb_read_only,
|
|
||||||
secondary: config.rocksdb_secondary,
|
|
||||||
checksums: config.rocksdb_checksums,
|
checksums: config.rocksdb_checksums,
|
||||||
corks: AtomicU32::new(0),
|
corks: AtomicU32::new(0),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -362,6 +362,10 @@ pub(super) static MAPS: &[Descriptor] = &[
|
|||||||
name: "userid_blurhash",
|
name: "userid_blurhash",
|
||||||
..descriptor::RANDOM_SMALL
|
..descriptor::RANDOM_SMALL
|
||||||
},
|
},
|
||||||
|
Descriptor {
|
||||||
|
name: "userid_dehydrateddevice",
|
||||||
|
..descriptor::RANDOM_SMALL
|
||||||
|
},
|
||||||
Descriptor {
|
Descriptor {
|
||||||
name: "userid_devicelistversion",
|
name: "userid_devicelistversion",
|
||||||
..descriptor::RANDOM_SMALL
|
..descriptor::RANDOM_SMALL
|
||||||
|
|||||||
@@ -74,14 +74,6 @@ impl Database {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn keys(&self) -> impl Iterator<Item = &MapsKey> + Send + '_ { self.maps.keys() }
|
pub fn keys(&self) -> impl Iterator<Item = &MapsKey> + Send + '_ { self.maps.keys() }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn is_read_only(&self) -> bool { self.db.is_read_only() }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[must_use]
|
|
||||||
pub fn is_secondary(&self) -> bool { self.db.is_secondary() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<&str> for Database {
|
impl Index<&str> for Database {
|
||||||
|
|||||||
+1
-9
@@ -27,10 +27,6 @@ pub struct Args {
|
|||||||
#[arg(long, short('O'))]
|
#[arg(long, short('O'))]
|
||||||
pub option: Vec<String>,
|
pub option: Vec<String>,
|
||||||
|
|
||||||
/// Run in a stricter read-only --maintenance mode.
|
|
||||||
#[arg(long)]
|
|
||||||
pub read_only: bool,
|
|
||||||
|
|
||||||
/// Run in maintenance mode while refusing connections.
|
/// Run in maintenance mode while refusing connections.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub maintenance: bool,
|
pub maintenance: bool,
|
||||||
@@ -143,11 +139,7 @@ pub(crate) fn parse() -> Args { Args::parse() }
|
|||||||
|
|
||||||
/// Synthesize any command line options with configuration file options.
|
/// Synthesize any command line options with configuration file options.
|
||||||
pub(crate) fn update(mut config: Figment, args: &Args) -> Result<Figment> {
|
pub(crate) fn update(mut config: Figment, args: &Args) -> Result<Figment> {
|
||||||
if args.read_only {
|
if args.maintenance {
|
||||||
config = config.join(("rocksdb_read_only", true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.maintenance || args.read_only {
|
|
||||||
config = config.join(("startup_netburst", false));
|
config = config.join(("startup_netburst", false));
|
||||||
config = config.join(("listening", false));
|
config = config.join(("listening", false));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -530,7 +530,12 @@ impl Service {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_admin_command<E>(&self, event: &E, body: &str) -> Option<InvocationSource>
|
pub async fn is_admin_command<E>(
|
||||||
|
&self,
|
||||||
|
event: &E,
|
||||||
|
body: &str,
|
||||||
|
sent_locally: bool,
|
||||||
|
) -> Option<InvocationSource>
|
||||||
where
|
where
|
||||||
E: Event + Send + Sync,
|
E: Event + Send + Sync,
|
||||||
{
|
{
|
||||||
@@ -580,6 +585,15 @@ impl Service {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Escaped commands must be sent locally (via client API), not via federation
|
||||||
|
if !sent_locally {
|
||||||
|
conduwuit::warn!(
|
||||||
|
"Ignoring escaped admin command from {} that arrived via federation",
|
||||||
|
event.sender()
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
// Looks good
|
// Looks good
|
||||||
Some(InvocationSource::EscapedCommand)
|
Some(InvocationSource::EscapedCommand)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,8 @@
|
|||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use conduwuit::{Result, Server, debug, error, warn};
|
use conduwuit::{Result, Server, debug, error, utils::response::LimitReadExt, warn};
|
||||||
use database::{Deserialized, Map};
|
use database::{Deserialized, Map};
|
||||||
use rand::Rng;
|
|
||||||
use ruma::events::{Mentions, room::message::RoomMessageEventContent};
|
use ruma::events::{Mentions, room::message::RoomMessageEventContent};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
@@ -100,8 +99,7 @@ impl crate::Service for Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let first_check_jitter = {
|
let first_check_jitter = {
|
||||||
let mut rng = rand::thread_rng();
|
let jitter_percent = rand::random_range(-50.0..=10.0);
|
||||||
let jitter_percent = rng.gen_range(-50.0..=10.0);
|
|
||||||
self.interval.mul_f64(1.0 + jitter_percent / 100.0)
|
self.interval.mul_f64(1.0 + jitter_percent / 100.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -139,7 +137,7 @@ impl Service {
|
|||||||
.get(CHECK_FOR_ANNOUNCEMENTS_URL)
|
.get(CHECK_FOR_ANNOUNCEMENTS_URL)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.text()
|
.limit_read_text(1024 * 1024)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let response = serde_json::from_str::<CheckForAnnouncementsResponse>(&response)?;
|
let response = serde_json::from_str::<CheckForAnnouncementsResponse>(&response)?;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ impl crate::Service for Service {
|
|||||||
let url_preview_user_agent = config
|
let url_preview_user_agent = config
|
||||||
.url_preview_user_agent
|
.url_preview_user_agent
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| conduwuit::version::user_agent().to_owned());
|
.unwrap_or_else(|| conduwuit::version::user_agent_media().to_owned());
|
||||||
|
|
||||||
Ok(Arc::new(Self {
|
Ok(Arc::new(Self {
|
||||||
default: base(config)?
|
default: base(config)?
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ impl Service {
|
|||||||
/// Get the registration token set in the config file, if it exists.
|
/// Get the registration token set in the config file, if it exists.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_config_file_token(&self) -> Option<ValidToken> {
|
pub fn get_config_file_token(&self) -> Option<ValidToken> {
|
||||||
self.registration_token.clone().map(|token| ValidToken {
|
self.registration_token
|
||||||
token,
|
.clone()
|
||||||
source: ValidTokenSource::ConfigFile,
|
.map(|token| ValidToken { token, source: ValidTokenSource::Config })
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,6 @@ impl crate::Service for Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn worker(self: Arc<Self>) -> Result {
|
async fn worker(self: Arc<Self>) -> Result {
|
||||||
if self.services.globals.is_read_only() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.services.config.ldap.enable {
|
if self.services.config.ldap.enable {
|
||||||
warn!("emergency password feature not available with LDAP enabled.");
|
warn!("emergency password feature not available with LDAP enabled.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::{fmt::Debug, mem};
|
|||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err,
|
Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err, implement,
|
||||||
error::inspect_debug_log, implement, trace,
|
trace, utils::response::LimitReadExt,
|
||||||
};
|
};
|
||||||
use http::{HeaderValue, header::AUTHORIZATION};
|
use http::{HeaderValue, header::AUTHORIZATION};
|
||||||
use ipaddress::IPAddress;
|
use ipaddress::IPAddress;
|
||||||
@@ -133,7 +133,22 @@ async fn handle_response<T>(
|
|||||||
where
|
where
|
||||||
T: OutgoingRequest + Send,
|
T: OutgoingRequest + Send,
|
||||||
{
|
{
|
||||||
let response = into_http_response(dest, actual, method, url, response).await?;
|
const HUGE_ENDPOINTS: [&str; 2] =
|
||||||
|
["/_matrix/federation/v2/send_join/", "/_matrix/federation/v2/state/"];
|
||||||
|
let size_limit: u64 = if HUGE_ENDPOINTS.iter().any(|e| url.path().starts_with(e)) {
|
||||||
|
// Some federation endpoints can return huge response bodies, so we'll bump the
|
||||||
|
// limit for those endpoints specifically.
|
||||||
|
self.services
|
||||||
|
.server
|
||||||
|
.config
|
||||||
|
.max_request_size
|
||||||
|
.saturating_mul(10)
|
||||||
|
} else {
|
||||||
|
self.services.server.config.max_request_size
|
||||||
|
}
|
||||||
|
.try_into()
|
||||||
|
.expect("size_limit (usize) should fit within a u64");
|
||||||
|
let response = into_http_response(dest, actual, method, url, response, size_limit).await?;
|
||||||
|
|
||||||
T::IncomingResponse::try_from_http_response(response)
|
T::IncomingResponse::try_from_http_response(response)
|
||||||
.map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
|
.map_err(|e| err!(BadServerResponse("Server returned bad 200 response: {e:?}")))
|
||||||
@@ -145,6 +160,7 @@ async fn into_http_response(
|
|||||||
method: &Method,
|
method: &Method,
|
||||||
url: &Url,
|
url: &Url,
|
||||||
mut response: Response,
|
mut response: Response,
|
||||||
|
max_size: u64,
|
||||||
) -> Result<http::Response<Bytes>> {
|
) -> Result<http::Response<Bytes>> {
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
trace!(
|
trace!(
|
||||||
@@ -167,14 +183,14 @@ async fn into_http_response(
|
|||||||
);
|
);
|
||||||
|
|
||||||
trace!("Waiting for response body...");
|
trace!("Waiting for response body...");
|
||||||
let body = response
|
|
||||||
.bytes()
|
|
||||||
.await
|
|
||||||
.inspect_err(inspect_debug_log)
|
|
||||||
.unwrap_or_else(|_| Vec::new().into());
|
|
||||||
|
|
||||||
let http_response = http_response_builder
|
let http_response = http_response_builder
|
||||||
.body(body)
|
.body(
|
||||||
|
response
|
||||||
|
.limit_read(max_size)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
.expect("reqwest body is valid http body");
|
.expect("reqwest body is valid http body");
|
||||||
|
|
||||||
debug!("Got {status:?} for {method} {url}");
|
debug!("Got {status:?} for {method} {url}");
|
||||||
|
|||||||
@@ -67,15 +67,17 @@ impl crate::Service for Service {
|
|||||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||||
|
|
||||||
async fn worker(self: Arc<Self>) -> Result {
|
async fn worker(self: Arc<Self>) -> Result {
|
||||||
// first run mode will be enabled if there are no local users
|
// first run mode will be enabled if there are no local users, provided it's not
|
||||||
let is_first_run = self
|
// forcibly disabled for Complement tests
|
||||||
.services
|
let is_first_run = !self.services.config.force_disable_first_run_mode
|
||||||
.users
|
&& self
|
||||||
.list_local_users()
|
.services
|
||||||
.ready_filter(|user| *user != self.services.globals.server_user)
|
.users
|
||||||
.next()
|
.list_local_users()
|
||||||
.await
|
.ready_filter(|user| *user != self.services.globals.server_user)
|
||||||
.is_none();
|
.next()
|
||||||
|
.await
|
||||||
|
.is_none();
|
||||||
|
|
||||||
self.first_run_marker
|
self.first_run_marker
|
||||||
.set(if is_first_run {
|
.set(if is_first_run {
|
||||||
|
|||||||
@@ -156,7 +156,4 @@ impl Service {
|
|||||||
pub fn server_is_ours(&self, server_name: &ServerName) -> bool {
|
pub fn server_is_ours(&self, server_name: &ServerName) -> bool {
|
||||||
server_name == self.server_name()
|
server_name == self.server_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_read_only(&self) -> bool { self.db.db.is_read_only() }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ impl Data {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn clear_url_previews(&self) { self.url_previews.clear().await; }
|
||||||
|
|
||||||
pub(super) fn set_url_preview(
|
pub(super) fn set_url_preview(
|
||||||
&self,
|
&self,
|
||||||
url: &str,
|
url: &str,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use conduwuit::{Err, Result, debug, err};
|
use conduwuit::{Err, Result, debug, err, utils::response::LimitReadExt};
|
||||||
use conduwuit_core::implement;
|
use conduwuit_core::implement;
|
||||||
use ipaddress::IPAddress;
|
use ipaddress::IPAddress;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -37,6 +37,9 @@ pub async fn remove_url_preview(&self, url: &str) -> Result<()> {
|
|||||||
self.db.remove_url_preview(url)
|
self.db.remove_url_preview(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[implement(Service)]
|
||||||
|
pub async fn clear_url_previews(&self) { self.db.clear_url_previews().await; }
|
||||||
|
|
||||||
#[implement(Service)]
|
#[implement(Service)]
|
||||||
pub async fn set_url_preview(&self, url: &str, data: &UrlPreviewData) -> Result<()> {
|
pub async fn set_url_preview(&self, url: &str, data: &UrlPreviewData) -> Result<()> {
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now()
|
||||||
@@ -109,8 +112,22 @@ pub async fn download_image(&self, url: &str) -> Result<UrlPreviewData> {
|
|||||||
use image::ImageReader;
|
use image::ImageReader;
|
||||||
use ruma::Mxc;
|
use ruma::Mxc;
|
||||||
|
|
||||||
let image = self.services.client.url_preview.get(url).send().await?;
|
let image = self
|
||||||
let image = image.bytes().await?;
|
.services
|
||||||
|
.client
|
||||||
|
.url_preview
|
||||||
|
.get(url)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.limit_read(
|
||||||
|
self.services
|
||||||
|
.server
|
||||||
|
.config
|
||||||
|
.max_request_size
|
||||||
|
.try_into()
|
||||||
|
.expect("u64 should fit in usize"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let mxc = Mxc {
|
let mxc = Mxc {
|
||||||
server_name: self.services.globals.server_name(),
|
server_name: self.services.globals.server_name(),
|
||||||
media_id: &random_string(super::MXC_LENGTH),
|
media_id: &random_string(super::MXC_LENGTH),
|
||||||
@@ -148,24 +165,20 @@ async fn download_html(&self, url: &str) -> Result<UrlPreviewData> {
|
|||||||
use webpage::HTML;
|
use webpage::HTML;
|
||||||
|
|
||||||
let client = &self.services.client.url_preview;
|
let client = &self.services.client.url_preview;
|
||||||
let mut response = client.get(url).send().await?;
|
let body = client
|
||||||
|
.get(url)
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
.send()
|
||||||
while let Some(chunk) = response.chunk().await? {
|
.await?
|
||||||
bytes.extend_from_slice(&chunk);
|
.limit_read_text(
|
||||||
if bytes.len() > self.services.globals.url_preview_max_spider_size() {
|
self.services
|
||||||
debug!(
|
.server
|
||||||
"Response body from URL {} exceeds url_preview_max_spider_size ({}), not \
|
.config
|
||||||
processing the rest of the response body and assuming our necessary data is in \
|
.max_request_size
|
||||||
this range.",
|
.try_into()
|
||||||
url,
|
.expect("u64 should fit in usize"),
|
||||||
self.services.globals.url_preview_max_spider_size()
|
)
|
||||||
);
|
.await?;
|
||||||
break;
|
let Ok(html) = HTML::from_string(body.clone(), Some(url.to_owned())) else {
|
||||||
}
|
|
||||||
}
|
|
||||||
let body = String::from_utf8_lossy(&bytes);
|
|
||||||
let Ok(html) = HTML::from_string(body.to_string(), Some(url.to_owned())) else {
|
|
||||||
return Err!(Request(Unknown("Failed to parse HTML")));
|
return Err!(Request(Unknown("Failed to parse HTML")));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{fmt::Debug, time::Duration};
|
|||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Error, Result, debug_warn, err, implement,
|
Err, Error, Result, debug_warn, err, implement,
|
||||||
utils::content_disposition::make_content_disposition,
|
utils::{content_disposition::make_content_disposition, response::LimitReadExt},
|
||||||
};
|
};
|
||||||
use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue};
|
use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
@@ -286,10 +286,15 @@ async fn location_request(&self, location: &str) -> Result<FileMeta> {
|
|||||||
.and_then(Result::ok);
|
.and_then(Result::ok);
|
||||||
|
|
||||||
response
|
response
|
||||||
.bytes()
|
.limit_read(
|
||||||
|
self.services
|
||||||
|
.server
|
||||||
|
.config
|
||||||
|
.max_request_size
|
||||||
|
.try_into()
|
||||||
|
.expect("u64 should fit in usize"),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(Vec::from)
|
|
||||||
.map_err(Into::into)
|
|
||||||
.map(|content| FileMeta {
|
.map(|content| FileMeta {
|
||||||
content: Some(content),
|
content: Some(content),
|
||||||
content_type: content_type.clone(),
|
content_type: content_type.clone(),
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ pub mod rooms;
|
|||||||
pub mod sending;
|
pub mod sending;
|
||||||
pub mod server_keys;
|
pub mod server_keys;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod transaction_ids;
|
pub mod transactions;
|
||||||
pub mod uiaa;
|
pub mod uiaa;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{fmt::Debug, mem, sync::Arc};
|
use std::{fmt::Debug, mem, sync::Arc};
|
||||||
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
use conduwuit::utils::response::LimitReadExt;
|
||||||
use conduwuit_core::{
|
use conduwuit_core::{
|
||||||
Err, Event, Result, debug_warn, err, trace,
|
Err, Event, Result, debug_warn, err, trace,
|
||||||
utils::{stream::TryIgnore, string_from_bytes},
|
utils::{stream::TryIgnore, string_from_bytes},
|
||||||
@@ -30,7 +31,7 @@ use ruma::{
|
|||||||
uint,
|
uint,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Dep, client, globals, rooms, sending, users};
|
use crate::{Dep, client, config, globals, rooms, sending, users};
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
db: Data,
|
db: Data,
|
||||||
@@ -39,6 +40,7 @@ pub struct Service {
|
|||||||
|
|
||||||
struct Services {
|
struct Services {
|
||||||
globals: Dep<globals::Service>,
|
globals: Dep<globals::Service>,
|
||||||
|
config: Dep<config::Service>,
|
||||||
client: Dep<client::Service>,
|
client: Dep<client::Service>,
|
||||||
state_accessor: Dep<rooms::state_accessor::Service>,
|
state_accessor: Dep<rooms::state_accessor::Service>,
|
||||||
state_cache: Dep<rooms::state_cache::Service>,
|
state_cache: Dep<rooms::state_cache::Service>,
|
||||||
@@ -61,6 +63,7 @@ impl crate::Service for Service {
|
|||||||
services: Services {
|
services: Services {
|
||||||
globals: args.depend::<globals::Service>("globals"),
|
globals: args.depend::<globals::Service>("globals"),
|
||||||
client: args.depend::<client::Service>("client"),
|
client: args.depend::<client::Service>("client"),
|
||||||
|
config: args.depend::<config::Service>("config"),
|
||||||
state_accessor: args
|
state_accessor: args
|
||||||
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
||||||
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
|
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
|
||||||
@@ -245,7 +248,15 @@ impl Service {
|
|||||||
.expect("http::response::Builder is usable"),
|
.expect("http::response::Builder is usable"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let body = response.bytes().await?;
|
let body = response
|
||||||
|
.limit_read(
|
||||||
|
self.services
|
||||||
|
.config
|
||||||
|
.max_request_size
|
||||||
|
.try_into()
|
||||||
|
.expect("usize fits into u64"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
debug_warn!("Push gateway response body: {:?}", string_from_bytes(&body));
|
debug_warn!("Push gateway response body: {:?}", string_from_bytes(&body));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ mod data;
|
|||||||
|
|
||||||
use std::{future::ready, pin::Pin, sync::Arc};
|
use std::{future::ready, pin::Pin, sync::Arc};
|
||||||
|
|
||||||
use conduwuit::{Err, Result, utils};
|
use conduwuit::{Err, Result, err, utils};
|
||||||
use data::Data;
|
use data::Data;
|
||||||
pub use data::{DatabaseTokenInfo, TokenExpires};
|
pub use data::{DatabaseTokenInfo, TokenExpires};
|
||||||
use futures::{
|
use futures::{
|
||||||
@@ -18,6 +18,9 @@ const RANDOM_TOKEN_LENGTH: usize = 16;
|
|||||||
pub struct Service {
|
pub struct Service {
|
||||||
db: Data,
|
db: Data,
|
||||||
services: Services,
|
services: Services,
|
||||||
|
/// The registration tokens which were read from the registration token
|
||||||
|
/// file, if one is configured.
|
||||||
|
registration_tokens_from_file: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Services {
|
struct Services {
|
||||||
@@ -45,34 +48,54 @@ impl PartialEq<str> for ValidToken {
|
|||||||
/// The source of a valid database token.
|
/// The source of a valid database token.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ValidTokenSource {
|
pub enum ValidTokenSource {
|
||||||
/// The static token set in the homeserver's config file, which is
|
/// The static token set in the homeserver's config file.
|
||||||
/// always valid.
|
Config,
|
||||||
ConfigFile,
|
|
||||||
/// A database token which has been checked to be valid.
|
/// A database token which has been checked to be valid.
|
||||||
Database(DatabaseTokenInfo),
|
Database(DatabaseTokenInfo),
|
||||||
/// The single-use token which may be used to create the homeserver's first
|
/// The single-use token which may be used to create the homeserver's first
|
||||||
/// account.
|
/// account.
|
||||||
FirstAccount,
|
FirstAccount,
|
||||||
|
/// A registration token read from the registration token file set in the
|
||||||
|
/// config.
|
||||||
|
TokenFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ValidTokenSource {
|
impl std::fmt::Display for ValidTokenSource {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
| Self::ConfigFile => write!(f, "Token defined in config."),
|
| Self::Config => write!(f, "Static token set in the server configuration."),
|
||||||
| Self::Database(info) => info.fmt(f),
|
| Self::Database(info) => info.fmt(f),
|
||||||
| Self::FirstAccount => write!(f, "Initial setup token."),
|
| Self::FirstAccount => write!(f, "Initial setup token."),
|
||||||
|
| Self::TokenFile => write!(f, "Static token set in the registration token file."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Service for Service {
|
impl crate::Service for Service {
|
||||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||||
|
let registration_tokens_from_file = args.server.config.registration_token_file
|
||||||
|
.clone()
|
||||||
|
// If the token file option was set, read the path it points to
|
||||||
|
.map(std::fs::read_to_string)
|
||||||
|
.transpose()
|
||||||
|
.map_err(|err| err!("Failed to read registration token file: {err}"))
|
||||||
|
.map(|tokens| {
|
||||||
|
if let Some(tokens) = tokens {
|
||||||
|
// If the token file option was set, return the file's lines as tokens
|
||||||
|
tokens.lines().map(ToOwned::to_owned).collect()
|
||||||
|
} else {
|
||||||
|
// Otherwise, if the option wasn't set, return no tokens
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(Arc::new(Self {
|
Ok(Arc::new(Self {
|
||||||
db: Data::new(args.db),
|
db: Data::new(args.db),
|
||||||
services: Services {
|
services: Services {
|
||||||
config: args.depend::<config::Service>("config"),
|
config: args.depend::<config::Service>("config"),
|
||||||
firstrun: args.depend::<firstrun::Service>("firstrun"),
|
firstrun: args.depend::<firstrun::Service>("firstrun"),
|
||||||
},
|
},
|
||||||
|
registration_tokens_from_file,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,12 +120,23 @@ impl Service {
|
|||||||
(token, info)
|
(token, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all the "special" registration tokens that aren't defined in the
|
/// Get all the static registration tokens that aren't defined in the
|
||||||
/// database.
|
/// database.
|
||||||
fn iterate_static_tokens(&self) -> impl Iterator<Item = ValidToken> {
|
fn iterate_static_tokens(&self) -> impl Iterator<Item = ValidToken> {
|
||||||
// This does not include the first-account token, because it's special:
|
// This does not include the first-account token, because it has special
|
||||||
// no other registration tokens are valid when it is set.
|
// behavior: no other registration tokens are valid when it is set.
|
||||||
self.services.config.get_config_file_token().into_iter()
|
self.services
|
||||||
|
.config
|
||||||
|
.get_config_file_token()
|
||||||
|
.into_iter()
|
||||||
|
.chain(
|
||||||
|
self.registration_tokens_from_file
|
||||||
|
.iter()
|
||||||
|
.map(|token_string| ValidToken {
|
||||||
|
token: token_string.clone(),
|
||||||
|
source: ValidTokenSource::TokenFile,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate a registration token.
|
/// Validate a registration token.
|
||||||
@@ -158,7 +192,7 @@ impl Service {
|
|||||||
/// revoked.
|
/// revoked.
|
||||||
pub fn revoke_token(&self, ValidToken { token, source }: ValidToken) -> Result {
|
pub fn revoke_token(&self, ValidToken { token, source }: ValidToken) -> Result {
|
||||||
match source {
|
match source {
|
||||||
| ValidTokenSource::ConfigFile => {
|
| ValidTokenSource::Config => {
|
||||||
Err!(
|
Err!(
|
||||||
"The token set in the config file cannot be revoked. Edit the config file \
|
"The token set in the config file cannot be revoked. Edit the config file \
|
||||||
to change it."
|
to change it."
|
||||||
@@ -171,6 +205,12 @@ impl Service {
|
|||||||
| ValidTokenSource::FirstAccount => {
|
| ValidTokenSource::FirstAccount => {
|
||||||
Err!("The initial setup token cannot be revoked.")
|
Err!("The initial setup token cannot be revoked.")
|
||||||
},
|
},
|
||||||
|
| ValidTokenSource::TokenFile => {
|
||||||
|
Err!(
|
||||||
|
"Tokens set in the registration token file cannot be revoked. Edit the \
|
||||||
|
registration token file and restart Continuwuity to change them."
|
||||||
|
)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use conduwuit::{Result, debug, debug_error, debug_info, debug_warn, implement, trace};
|
use conduwuit::{
|
||||||
|
Result, debug, debug_error, debug_info, implement, trace, utils::response::LimitReadExt,
|
||||||
|
};
|
||||||
|
|
||||||
#[implement(super::Service)]
|
#[implement(super::Service)]
|
||||||
#[tracing::instrument(name = "well-known", level = "debug", skip(self, dest))]
|
#[tracing::instrument(name = "well-known", level = "debug", skip(self, dest))]
|
||||||
@@ -24,12 +26,8 @@ pub(super) async fn request_well_known(&self, dest: &str) -> Result<Option<Strin
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = response.text().await?;
|
let text = response.limit_read_text(8192).await?;
|
||||||
trace!("response text: {text:?}");
|
trace!("response text: {text:?}");
|
||||||
if text.len() >= 12288 {
|
|
||||||
debug_warn!("response contains junk");
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let body: serde_json::Value = serde_json::from_str(&text).unwrap_or_default();
|
let body: serde_json::Value = serde_json::from_str(&text).unwrap_or_default();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ mod remote;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use conduwuit::{
|
use conduwuit::{
|
||||||
Err, Event, Result, Server, err,
|
Err, Event, Result, err,
|
||||||
utils::{ReadyExt, stream::TryIgnore},
|
utils::{ReadyExt, stream::TryIgnore},
|
||||||
};
|
};
|
||||||
use database::{Deserialized, Ignore, Interfix, Map};
|
use database::{Deserialized, Ignore, Interfix, Map};
|
||||||
@@ -30,12 +30,12 @@ struct Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Services {
|
struct Services {
|
||||||
server: Arc<Server>,
|
|
||||||
admin: Dep<admin::Service>,
|
admin: Dep<admin::Service>,
|
||||||
appservice: Dep<appservice::Service>,
|
appservice: Dep<appservice::Service>,
|
||||||
globals: Dep<globals::Service>,
|
globals: Dep<globals::Service>,
|
||||||
sending: Dep<sending::Service>,
|
sending: Dep<sending::Service>,
|
||||||
state_accessor: Dep<rooms::state_accessor::Service>,
|
state_accessor: Dep<rooms::state_accessor::Service>,
|
||||||
|
state_cache: Dep<rooms::state_cache::Service>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Service for Service {
|
impl crate::Service for Service {
|
||||||
@@ -47,13 +47,13 @@ impl crate::Service for Service {
|
|||||||
aliasid_alias: args.db["aliasid_alias"].clone(),
|
aliasid_alias: args.db["aliasid_alias"].clone(),
|
||||||
},
|
},
|
||||||
services: Services {
|
services: Services {
|
||||||
server: args.server.clone(),
|
|
||||||
admin: args.depend::<admin::Service>("admin"),
|
admin: args.depend::<admin::Service>("admin"),
|
||||||
appservice: args.depend::<appservice::Service>("appservice"),
|
appservice: args.depend::<appservice::Service>("appservice"),
|
||||||
globals: args.depend::<globals::Service>("globals"),
|
globals: args.depend::<globals::Service>("globals"),
|
||||||
sending: args.depend::<sending::Service>("sending"),
|
sending: args.depend::<sending::Service>("sending"),
|
||||||
state_accessor: args
|
state_accessor: args
|
||||||
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
||||||
|
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -94,6 +94,12 @@ impl Service {
|
|||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
#[tracing::instrument(skip(self))]
|
||||||
pub async fn remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<()> {
|
pub async fn remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<()> {
|
||||||
|
if alias == self.services.globals.admin_alias
|
||||||
|
&& user_id != self.services.globals.server_user
|
||||||
|
{
|
||||||
|
return Err!(Request(Forbidden("Only the server user can remove this alias")));
|
||||||
|
}
|
||||||
|
|
||||||
if !self.user_can_remove_alias(alias, user_id).await? {
|
if !self.user_can_remove_alias(alias, user_id).await? {
|
||||||
return Err!(Request(Forbidden("User is not permitted to remove this alias.")));
|
return Err!(Request(Forbidden("User is not permitted to remove this alias.")));
|
||||||
}
|
}
|
||||||
@@ -117,6 +123,9 @@ impl Service {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves the given room ID or alias, returning the resolved room ID.
|
||||||
|
/// Unlike resolve_with_servers (the underlying call), potential resident
|
||||||
|
/// servers are not returned
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn resolve(&self, room: &RoomOrAliasId) -> Result<OwnedRoomId> {
|
pub async fn resolve(&self, room: &RoomOrAliasId) -> Result<OwnedRoomId> {
|
||||||
self.resolve_with_servers(room, None)
|
self.resolve_with_servers(room, None)
|
||||||
@@ -124,6 +133,14 @@ impl Service {
|
|||||||
.map(|(room_id, _)| room_id)
|
.map(|(room_id, _)| room_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves the given room ID or alias, returning the resolved room ID, and
|
||||||
|
/// any servers that might be able to assist in fetching room data.
|
||||||
|
///
|
||||||
|
/// If the input is a room ID, this simply returns it and <servers>.
|
||||||
|
/// If the input is an alias, this attempts to resolve it locally, then via
|
||||||
|
/// appservices, and finally remotely if the alias is not local.
|
||||||
|
/// If the alias is successfully resolved, the room ID and an empty list of
|
||||||
|
/// servers is returned.
|
||||||
pub async fn resolve_with_servers(
|
pub async fn resolve_with_servers(
|
||||||
&self,
|
&self,
|
||||||
room: &RoomOrAliasId,
|
room: &RoomOrAliasId,
|
||||||
@@ -134,28 +151,26 @@ impl Service {
|
|||||||
Ok((room_id.to_owned(), servers.unwrap_or_default()))
|
Ok((room_id.to_owned(), servers.unwrap_or_default()))
|
||||||
} else {
|
} else {
|
||||||
let alias: &RoomAliasId = room.try_into().expect("valid RoomAliasId");
|
let alias: &RoomAliasId = room.try_into().expect("valid RoomAliasId");
|
||||||
self.resolve_alias(alias, servers).await
|
self.resolve_alias(alias).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves the given room alias, returning the resolved room ID and any
|
||||||
|
/// servers that might be in the room.
|
||||||
#[tracing::instrument(skip(self), name = "resolve")]
|
#[tracing::instrument(skip(self), name = "resolve")]
|
||||||
pub async fn resolve_alias(
|
pub async fn resolve_alias(
|
||||||
&self,
|
&self,
|
||||||
room_alias: &RoomAliasId,
|
room_alias: &RoomAliasId,
|
||||||
servers: Option<Vec<OwnedServerName>>,
|
|
||||||
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
|
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
|
||||||
let server_name = room_alias.server_name();
|
let server_is_ours = self
|
||||||
let server_is_ours = self.services.globals.server_is_ours(server_name);
|
.services
|
||||||
let servers_contains_ours = || {
|
.globals
|
||||||
servers
|
.server_is_ours(room_alias.server_name());
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|servers| servers.contains(&self.services.server.name))
|
|
||||||
};
|
|
||||||
|
|
||||||
if !server_is_ours && !servers_contains_ours() {
|
if !server_is_ours {
|
||||||
return self
|
// TODO: The spec advises servers may cache remote room aliases temporarily.
|
||||||
.remote_resolve(room_alias, servers.unwrap_or_default())
|
// We might want to look at doing that.
|
||||||
.await;
|
return self.remote_resolve(room_alias).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let room_id = match self.resolve_local_alias(room_alias).await {
|
let room_id = match self.resolve_local_alias(room_alias).await {
|
||||||
@@ -163,10 +178,18 @@ impl Service {
|
|||||||
| Err(_) => self.resolve_appservice_alias(room_alias).await?,
|
| Err(_) => self.resolve_appservice_alias(room_alias).await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
room_id.map_or_else(
|
if let Some(room_id) = room_id {
|
||||||
|| Err!(Request(NotFound("Room with alias not found."))),
|
let servers: Vec<OwnedServerName> = self
|
||||||
|room_id| Ok((room_id, Vec::new())),
|
.services
|
||||||
)
|
.state_cache
|
||||||
|
.room_servers(&room_id)
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
return Ok((room_id, servers));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err!(Request(NotFound("Alias does not exist.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self), level = "debug")]
|
#[tracing::instrument(skip(self), level = "debug")]
|
||||||
@@ -206,12 +229,12 @@ impl Service {
|
|||||||
|
|
||||||
// The creator of an alias can remove it
|
// The creator of an alias can remove it
|
||||||
if self
|
if self
|
||||||
.who_created_alias(alias).await
|
.who_created_alias(alias).await
|
||||||
.is_ok_and(|user| user == user_id)
|
.is_ok_and(|user| user == user_id)
|
||||||
// Server admins can remove any local alias
|
// Server admins can remove any local alias
|
||||||
|| self.services.admin.user_is_admin(user_id).await
|
|| self.services.admin.user_is_admin(user_id).await
|
||||||
// Always allow the server service account to remove the alias, since there may not be an admin room
|
// Always allow the server service account to remove the alias, since there may not be an admin room
|
||||||
|| server_user == user_id
|
|| server_user == user_id
|
||||||
{
|
{
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user