Compare commits

...

68 Commits

Author SHA1 Message Date
Jacob Taylor
164b59a30c feat: Merge s1lv3r/rewrite-resolver 2026-04-09 19:33:43 -07:00
Jacob Taylor
96fba8fe12 feat: Merge nex/v12-publishing 2026-04-09 18:19:13 -07:00
Jacob Taylor
565d29448b fix: Clear All Clippy Lints 2026-04-09 17:57:25 -07:00
Jacob Taylor
36988b6d73 fix: Pre-Commit Lint Compliance Maneuver 2026-04-09 17:57:25 -07:00
timedout
e7b1de330b fix: Write-lock individual rooms when building sync for them 2026-04-09 17:57:25 -07:00
Jacob Taylor
240964ecfd fix warn 2026-04-09 17:57:25 -07:00
Joe Citrine
67e67d63e3 fix(resolver): port parser does not handle leading ':'
conditionally trim the leading colon
2026-04-09 17:57:25 -07:00
Jacob Taylor
4740e98909 upgrade some logs to info 2026-04-09 17:57:25 -07:00
Jade Ellis
5c13f77899 fix: Use to_str methods on room IDs 2026-04-09 17:57:25 -07:00
Jade Ellis
e39a95cbea chore: cleanup 2026-04-09 17:57:25 -07:00
Jade Ellis
307b4c6342 chore: Fix more complicated clippy warnings 2026-04-09 17:57:25 -07:00
Jade Ellis
deafc4bcf0 feat: Add command to purge sync tokens for empty rooms 2026-04-09 17:57:25 -07:00
Jade Ellis
ecfb105b44 feat: Add admin command to delete sync tokens from a room 2026-04-09 17:57:25 -07:00
Jacob Taylor
cb1a24317e exponential backoff is now just bees. did you want bees? no? well you have them now. congrats 2026-04-09 17:57:25 -07:00
Jacob Taylor
0de239a382 sender_workers scaling. this time, with feeling! 2026-04-09 17:57:25 -07:00
Jacob Taylor
f49be74edc log when there are zero extremities 2026-04-09 17:57:25 -07:00
Jacob Taylor
482afdbd4d enable converged 6g at the edge in continuwuity 2026-04-09 17:57:25 -07:00
Jacob Taylor
9ba500a141 bump the number of allowed immutable memtables by 1, to allow for greater flood protection
this should probably not be applied if you have rocksdb_atomic_flush = false (the default)
2026-04-09 17:57:25 -07:00
Jade Ellis
2325e8fa4c chore: Update generated docs 2026-04-09 17:24:45 +01:00
Jade Ellis
6906d63013 docs: Changelog 2026-04-09 17:24:44 +01:00
Jade Ellis
16de2a2cc0 feat: Add ability to inspect build information and features at runtime
Also re-adds ability to inspect used features
2026-04-09 17:24:44 +01:00
Jade Ellis
108a4fe336 ci: Remove caching of /target directory
This directory seemed to grow exponentially, with incremental
compilation reaching 11GB+ and dependencies not finishing
2026-04-09 17:17:03 +01:00
Renovate Bot
83396db5de chore(deps): update https://github.com/samueldr/lix-gha-installer-action digest to f5e9419 2026-04-09 05:02:05 +00:00
timedout
839138c02e chore: Add news frag 2026-04-08 20:49:59 +00:00
timedout
e03c90c2ac fix: Sign restricted joins when we're the authorising server 2026-04-08 20:49:59 +00:00
Henry-Hiles
379ef5014c fix: only run patchelf on linux 2026-04-08 20:14:36 +00:00
Henry-Hiles
2ab177f100 fix: fix continuwuity build on nix-darwin 2026-04-08 20:14:36 +00:00
Henry-Hiles
a818f51396 fix: devshell on darwin
Co-authored-by: thetayloredman <nutdriver716@gmail.com>
2026-04-08 20:14:36 +00:00
timedout
09bfe79a44 perf: Don't needlessly sign and re-hash events in send_join 2026-04-08 17:17:15 +00:00
timedout
d041adadc8 style: Fix large future clippy errors 2026-04-08 17:17:15 +00:00
timedout
189ed1c394 style: Fix large future clippy error 2026-04-08 17:17:15 +00:00
timedout
36c32938ae fix: Don't try to sign events that don't originate from us 2026-04-08 17:17:15 +00:00
Henry-Hiles
915643c965 feat: overridable rocksdb 2026-04-07 20:41:19 +00:00
Henry-Hiles
4063b2c7da fix: various issues with continuwuity build 2026-04-07 20:41:19 +00:00
Henry-Hiles
943bd81ce9 fix: fix typo in continuwuity build 2026-04-07 20:41:19 +00:00
Henry-Hiles
2942d9133e chore: remove old newline 2026-04-07 20:41:19 +00:00
Henry-Hiles
18a7a85fe4 chore: remove outdated comments 2026-04-07 20:41:19 +00:00
Henry-Hiles
0fdb1be938 feat: add customizable cargoExtraArgs 2026-04-07 20:41:19 +00:00
Henry-Hiles
867a3ac376 chore: Write news fragment 2026-04-07 20:41:19 +00:00
Henry-Hiles
7a6eff091a chore: Pin Lix installer to specific commit 2026-04-07 20:41:19 +00:00
Henry-Hiles
c278663f65 fix: devshell fixes
Co-authored-by: kraem <
me@kraem.xyz>
2026-04-07 20:41:19 +00:00
Henry-Hiles
c822c945e7 fix: make fmt run on correct toolchain 2026-04-07 20:41:19 +00:00
Henry-Hiles
6eb3dc1f9d fix: postPatch issue due to version override 2026-04-07 20:41:19 +00:00
Henry-Hiles
789ec71b75 fix: fix update flake hashes workflow 2026-04-07 20:41:19 +00:00
Henry-Hiles
1cfa3ff10b feat: add rocksdb updater nix app 2026-04-07 20:41:19 +00:00
Henry-Hiles
02cf6b5695 fix: use correct versioning for rocksdb 2026-04-07 20:41:19 +00:00
Henry-Hiles
4cc4893376 chore: remove now incorrect liburing comment in rocksdb nix build override 2026-04-07 20:41:19 +00:00
Henry-Hiles
7643b64f60 fix: patchelf binary to link to correct rocksdb 2026-04-07 20:41:19 +00:00
Henry-Hiles
3d9fd34012 feat: add meta to continuwuity build 2026-04-07 20:41:19 +00:00
Henry-Hiles
630963d6e1 fix: add bindgen hook to build 2026-04-07 20:41:19 +00:00
Henry-Hiles
36da6f5bf3 fix: recursively merge build configuration 2026-04-07 20:41:19 +00:00
Henry-Hiles
462ef63945 fix: bump rocksdb 2026-04-07 20:41:19 +00:00
Henry-Hiles
46bcfe5605 chore: rename toolchain packages 2026-04-07 20:41:19 +00:00
Henry-Hiles
16321cf467 fix: fix crane name in package build 2026-04-07 20:41:19 +00:00
Henry-Hiles
4d59e07006 chore: rewrite devshell, remove checks 2026-04-07 20:41:19 +00:00
Henry-Hiles
ec5f50c68e chore: rewrite continuwuity build 2026-04-07 20:41:19 +00:00
Henry-Hiles
db1b08532e chore: reorganize nix files 2026-04-07 20:41:19 +00:00
Henry-Hiles
d8f67e3b46 chore: simplify rocksdb build 2026-04-07 20:41:19 +00:00
ginger
2124fcf325 fix: Keep rustdoc from trying to run my TOML as a doctest 2026-04-07 18:40:43 +00:00
ezera
38b4065270 fix: use cfg to fix compiler warning for opts
Fixes #1621.
2026-04-07 12:58:23 +00:00
Ginger
2e62ca93a8 fix: Fix registration_terms default in example config 2026-04-07 12:55:56 +00:00
Ginger
b7a6c819b7 chore: News fragment 2026-04-07 12:55:56 +00:00
Ginger
eccc878ee9 feat: Add support for terms and conditions when registering 2026-04-07 12:55:56 +00:00
Tulir Asokan
8b762cf2e6 fix: Server name caching for SRV remotes 2026-04-06 19:57:05 +00:00
timedout
1ce9ae2cbf chore: Update example configuration file 2026-04-06 17:45:04 +00:00
thetayloredman
6a3370005e doc: remove reference to MSC unstable prefix 2026-04-06 17:45:04 +00:00
Logan Devine
675cfb964a feat: add support for MSC4439 PGP key URIs in wk-support
This commit introduces support for MSC4439, Encryption Key URIs
in `.well-known/matrix/support`. ([MSC](https://github.com/matrix-org/matrix-spec-proposals/pull/4439),
[Rendered](https://github.com/thetayloredman/matrix-spec-proposals/blob/msc4439/proposals/4439-support-contact-encryption.md))
via an additional config option.
2026-04-06 17:45:04 +00:00
Tulir Asokan
09312791a7 fix(ci): Add wget to fix llvm.sh in dockerfile
Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1616
Reviewed-by: nex <me@nexy7574.co.uk>
Co-authored-by: Tulir Asokan <tulir@maunium.net>
Co-committed-by: Tulir Asokan <tulir@maunium.net>
2026-04-06 15:44:18 +00:00
102 changed files with 1440 additions and 1576 deletions

2
.envrc
View File

@@ -2,7 +2,7 @@
dotenv_if_exists
if [ -f /etc/os-release ] && grep -q '^ID=nixos' /etc/os-release; then
if command -v nix >/dev/null 2>&1; then
use flake ".#${DIRENV_DEVSHELL:-default}"
fi

View File

@@ -149,37 +149,6 @@ runs:
- name: Setup sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Cache dependencies
id: deps-cache
uses: actions/cache@v4
with:
path: |
target/**/.fingerprint
target/**/deps
target/**/*.d
target/**/.cargo-lock
target/**/CACHEDIR.TAG
target/**/.rustc_info.json
/timelord/
# Dependencies cache - based on Cargo.lock, survives source code changes
key: >-
continuwuity-deps-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}
restore-keys: |
continuwuity-deps-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-
- name: Cache incremental compilation
id: incremental-cache
uses: actions/cache@v4
with:
path: |
target/**/incremental
# Incremental cache - based on source code changes
key: >-
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-${{ hashFiles('**/*.rs', '**/Cargo.toml') }}
restore-keys: |
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-${{ hashFiles('rust-toolchain.toml', '**/Cargo.lock') }}-
continuwuity-incremental-${{ steps.runner-os.outputs.slug }}-${{ steps.runner-os.outputs.arch }}-${{ steps.rust-setup.outputs.version }}${{ inputs.cache-key-suffix && format('-{0}', inputs.cache-key-suffix) || '' }}-
- name: End build cache restore group
shell: bash
run: echo "::endgroup::"

View File

@@ -16,48 +16,19 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: false
fetch-single-branch: true
submodules: false
persist-credentials: true
token: ${{ secrets.FORGEJO_TOKEN }}
- uses: https://github.com/cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0
- name: Install Lix
uses: https://github.com/samueldr/lix-gha-installer-action@f5e94192f565f53d84f41a056956dc0d3183b343
with:
nix_path: nixpkgs=channel:nixos-unstable
# We can skip getting a toolchain hash if this was ran as a dispatch with the intent
# to update just the rocksdb hash. If this was ran as a dispatch and the toolchain
# files are changed, we still update them, as well as the rocksdb import.
- name: Detect changed files
id: changes
run: |
git fetch origin ${{ github.base_ref }} --depth=1 || true
if [ -n "${{ github.event.pull_request.base.sha }}" ]; then
base=${{ github.event.pull_request.base.sha }}
else
base=$(git rev-parse HEAD~1)
fi
echo "Base: $base"
echo "HEAD: $(git rev-parse HEAD)"
git diff --name-only $base HEAD > changed_files.txt
echo "detected changes in $(cat changed_files.txt)"
# Join files with commas
files=$(paste -sd, changed_files.txt)
echo "files=$files" >> $FORGEJO_OUTPUT
- name: Debug output
run: |
echo "State of output"
echo "Changed files: ${{ steps.changes.outputs.files }}"
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
- name: Get new toolchain hash
if: contains(steps.changes.outputs.files, 'Cargo.toml') || contains(steps.changes.outputs.files, 'Cargo.lock') || contains(steps.changes.outputs.files, 'rust-toolchain.toml')
run: |
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
awk '/fromToolchainFile *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/packages/rust.nix > temp.nix
mv temp.nix nix/packages/rust.nix
awk '/fromToolchainFile *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/rust.nix > temp.nix
mv temp.nix nix/rust.nix
# Build continuwuity and filter for the new hash
# We do `|| true` because we want this to fail without stopping the workflow
@@ -65,36 +36,17 @@ jobs:
# Place the new hash in place of the empty hash
new_hash=$(cat new_toolchain_hash.txt)
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/packages/rust.nix
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/rust.nix
echo "New hash:"
awk -F'"' '/fromToolchainFile/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/packages/rust.nix
awk -F'"' '/fromToolchainFile/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/rust.nix
echo "Expected new hash:"
cat new_toolchain_hash.txt
rm new_toolchain_hash.txt
- name: Get new rocksdb hash
if: contains(steps.changes.outputs.files, '.nix') || contains(steps.changes.outputs.files, 'flake.lock')
run: |
# Set the current sha256 to an empty hash to make `nix build` calculate a new one
awk '/repo = "rocksdb";/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/packages/rocksdb/package.nix > temp.nix
mv temp.nix nix/packages/rocksdb/package.nix
# Build continuwuity and filter for the new hash
# We do `|| true` because we want this to fail without stopping the workflow
nix build .#default 2>&1 | tee >(grep 'got:' | awk '{print $2}' > new_rocksdb_hash.txt) || true
# Place the new hash in place of the empty hash
new_hash=$(cat new_rocksdb_hash.txt)
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/packages/rocksdb/package.nix
echo "New hash:"
awk -F'"' '/repo = "rocksdb";/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/packages/rocksdb/package.nix
echo "Expected new hash:"
cat new_rocksdb_hash.txt
rm new_rocksdb_hash.txt
- name: Update rocksdb
run: nix run .#update-rocksdb
- name: Show diff
run: git diff flake.nix nix

271
Cargo.lock generated
View File

@@ -785,6 +785,12 @@ dependencies = [
"shlex",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cexpr"
version = "0.6.0"
@@ -909,6 +915,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "combine"
version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
"bytes",
"memchr",
]
[[package]]
name = "compression-codecs"
version = "0.4.37"
@@ -948,10 +964,12 @@ dependencies = [
"conduwuit_build_metadata",
"conduwuit_core",
"conduwuit_database",
"conduwuit_macros",
"conduwuit_router",
"conduwuit_service",
"console-subscriber",
"const-str",
"ctor",
"hardened_malloc-rs",
"log",
"opentelemetry",
@@ -981,9 +999,11 @@ dependencies = [
"conduwuit_macros",
"conduwuit_service",
"const-str",
"ctor",
"futures",
"lettre",
"log",
"resolvematrix",
"ruma",
"serde-saphyr",
"serde_json",
@@ -1003,8 +1023,10 @@ dependencies = [
"base64 0.22.1",
"bytes",
"conduwuit_core",
"conduwuit_macros",
"conduwuit_service",
"const-str",
"ctor",
"futures",
"hmac",
"http",
@@ -1015,7 +1037,7 @@ dependencies = [
"lettre",
"log",
"rand 0.10.0",
"reqwest",
"reqwest 0.12.28",
"ruma",
"serde",
"serde_html_form",
@@ -1030,6 +1052,7 @@ name = "conduwuit_build_metadata"
version = "0.5.7-alpha.1"
dependencies = [
"built",
"cargo_metadata",
]
[[package]]
@@ -1073,7 +1096,8 @@ dependencies = [
"rand 0.10.0",
"rand_core 0.6.4",
"regex",
"reqwest",
"reqwest 0.12.28",
"resolvematrix",
"ring",
"ruma",
"sanitize-filename",
@@ -1102,7 +1126,9 @@ version = "0.5.7-alpha.1"
dependencies = [
"async-channel",
"conduwuit_core",
"conduwuit_macros",
"const-str",
"ctor",
"futures",
"log",
"minicbor",
@@ -1118,6 +1144,7 @@ dependencies = [
name = "conduwuit_macros"
version = "0.5.7-alpha.1"
dependencies = [
"cargo_toml",
"itertools 0.14.0",
"proc-macro2",
"quote",
@@ -1136,9 +1163,11 @@ dependencies = [
"conduwuit_admin",
"conduwuit_api",
"conduwuit_core",
"conduwuit_macros",
"conduwuit_service",
"conduwuit_web",
"const-str",
"ctor",
"futures",
"http",
"http-body-util",
@@ -1169,7 +1198,9 @@ dependencies = [
"bytes",
"conduwuit_core",
"conduwuit_database",
"conduwuit_macros",
"const-str",
"ctor",
"either",
"futures",
"governor",
@@ -1187,7 +1218,8 @@ dependencies = [
"rand 0.10.0",
"recaptcha-verify",
"regex",
"reqwest",
"reqwest 0.12.28",
"resolvematrix",
"ruma",
"rustyline-async",
"sd-notify",
@@ -1291,7 +1323,7 @@ dependencies = [
[[package]]
name = "continuwuity-admin-api"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"ruma-common",
"serde",
@@ -1726,7 +1758,7 @@ dependencies = [
[[package]]
name = "draupnir-antispam"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"ruma-common",
"serde",
@@ -2858,6 +2890,50 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "jni"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [
"cesu8",
"cfg-if",
"combine",
"jni-sys 0.3.1",
"log",
"thiserror 1.0.69",
"walkdir",
"windows-sys 0.45.0",
]
[[package]]
name = "jni-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
dependencies = [
"jni-sys 0.4.1",
]
[[package]]
name = "jni-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
dependencies = [
"jni-sys-macros",
]
[[package]]
name = "jni-sys-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "jobserver"
version = "0.1.34"
@@ -3241,7 +3317,7 @@ dependencies = [
[[package]]
name = "meowlnir-antispam"
version = "0.1.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"ruma-common",
"serde",
@@ -3743,7 +3819,7 @@ dependencies = [
"bytes",
"http",
"opentelemetry",
"reqwest",
"reqwest 0.12.28",
]
[[package]]
@@ -3758,7 +3834,7 @@ dependencies = [
"opentelemetry-proto",
"opentelemetry_sdk",
"prost",
"reqwest",
"reqwest 0.12.28",
"thiserror 2.0.18",
"tokio",
"tonic",
@@ -4229,6 +4305,7 @@ version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"aws-lc-rs",
"bytes",
"getrandom 0.3.4",
"lru-slab",
@@ -4425,7 +4502,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "409bf11a93fe93093f3c0254aab67576524f1e0524692615b5b63091dbc88a79"
dependencies = [
"reqwest",
"reqwest 0.12.28",
"serde",
"serde_json",
]
@@ -4516,12 +4593,62 @@ dependencies = [
"webpki-roots",
]
[[package]]
name = "reqwest"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-core",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "resolv-conf"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
[[package]]
name = "resolvematrix"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52dfcc6f56a81348db1fc6591356cfea9dc840408c75553b2fe225f86de43274"
dependencies = [
"hickory-resolver",
"reqwest 0.13.2",
"serde",
"thiserror 2.0.18",
"tracing",
]
[[package]]
name = "rgb"
version = "0.8.53"
@@ -4545,7 +4672,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"assign",
"continuwuity-admin-api",
@@ -4568,7 +4695,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"js_int",
"ruma-common",
@@ -4580,7 +4707,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"as_variant",
"assign",
@@ -4603,7 +4730,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"as_variant",
"base64 0.22.1",
@@ -4635,7 +4762,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"as_variant",
"indexmap",
@@ -4660,7 +4787,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"bytes",
"headers",
@@ -4682,7 +4809,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"js_int",
"thiserror 2.0.18",
@@ -4691,7 +4818,7 @@ dependencies = [
[[package]]
name = "ruma-identity-service-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"js_int",
"ruma-common",
@@ -4701,7 +4828,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"cfg-if",
"proc-macro-crate",
@@ -4716,7 +4843,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"js_int",
"ruma-common",
@@ -4728,7 +4855,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a97b91adcc012ef04991d823b8b5a79c6686ae48#a97b91adcc012ef04991d823b8b5a79c6686ae48"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@@ -4859,6 +4986,33 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.9"
@@ -4991,7 +5145,7 @@ checksum = "d92d893ba7469d361a6958522fa440e4e2bc8bf4c5803cd1bf40b9af63f8f9a8"
dependencies = [
"cfg_aliases",
"httpdate",
"reqwest",
"reqwest 0.12.28",
"rustls",
"sentry-backtrace",
"sentry-contexts",
@@ -6492,6 +6646,15 @@ dependencies = [
"url",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "1.0.6"
@@ -6556,6 +6719,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
@@ -6601,6 +6773,21 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
@@ -6649,6 +6836,12 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@@ -6667,6 +6860,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@@ -6685,6 +6884,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@@ -6715,6 +6920,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@@ -6733,6 +6944,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@@ -6751,6 +6968,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@@ -6769,6 +6992,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"

View File

@@ -344,7 +344,7 @@ version = "0.1.2"
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes"
rev = "a97b91adcc012ef04991d823b8b5a79c6686ae48"
rev = "1415caf8a32af4d943580c5ea4e12be1974593c2"
features = [
"compat",
"rand",
@@ -383,7 +383,8 @@ features = [
"unstable-pdu",
"unstable-msc4155",
"unstable-msc4143", # livekit well_known response
"unstable-msc4284"
"unstable-msc4284",
"unstable-msc4439", # pgp_key in .well_known/matrix/support
]
[workspace.dependencies.rust-rocksdb]
@@ -569,6 +570,9 @@ features = ["std"]
[workspace.dependencies.nonzero_ext]
version = "0.3.0"
[workspace.dependencies.resolvematrix]
version = "0.0.3"
#
# Patches
#

View File

@@ -0,0 +1 @@
Added support for requiring users to accept terms and conditions when registering.

View File

@@ -0,0 +1 @@
Rewrite the resolver service to use an external crate to make it easier to maintain. Rewrite by @s1lv3r, crate by @Jade

1
changelog.d/1596.bugfix Normal file
View File

@@ -0,0 +1 @@
Refactored nix package. Breaking, since `all-features` package no longer exists. Continuwuity is now built with jemalloc and liburing by default. Contributed by @Henry-Hiles (QuadRadical).

View File

@@ -0,0 +1,2 @@
Add new config option for [MSC4439](https://github.com/matrix-org/matrix-spec-proposals/pull/4439)
PGP key URIs. Contributed by LogN.

1
changelog.d/1615.bugfix Normal file
View File

@@ -0,0 +1 @@
Fixed resolving IP of servers that only use SRV delegation. Contributed by @tulir.

1
changelog.d/1620.misc Normal file
View File

@@ -0,0 +1 @@
Fixed compiler warning in cf_opts.rs when building in release. Contributed by @ezera.

1
changelog.d/1623.bugfix Normal file
View File

@@ -0,0 +1 @@
Fixed "Sender must be a local user" error for make_join, make_knock, and make_leave federation routes. Contributed by @nex.

View File

@@ -0,0 +1 @@
Added admin commands to get build information and features. Contributed by @Jade

1
changelog.d/1630.bugfix Normal file
View File

@@ -0,0 +1 @@
Fixed restricted joins not being signed when we are being used as an authorising server. Contributed by @nex, reported by [vel](matrix:u/vel:nhjkl.com?action=chat).

View File

@@ -523,6 +523,18 @@
#
#recaptcha_private_site_key =
# Policy documents, such as terms and conditions or a privacy policy,
# which users must agree to when registering an account.
#
# Example:
# ```ignore
# [global.registration_terms.privacy_policy]
# en = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
# es = { name = "Política de Privacidad", url = "https://homeserver.example/es/privacy_policy.html" }
# ```
#
#registration_terms = {}
# Controls whether encrypted rooms and events are allowed.
#
#allow_encryption = true
@@ -1787,11 +1799,9 @@
#stream_amplification = 1024
# Number of sender task workers; determines sender parallelism. Default is
# '0' which means the value is determined internally, likely matching the
# number of tokio worker-threads or number of cores, etc. Override by
# setting a non-zero value.
# core count. Override by setting a different value.
#
#sender_workers = 0
#sender_workers = core count
# Enables listener sockets; can be set to false to disable listening. This
# option is intended for developer/diagnostic purposes only.
@@ -1869,6 +1879,11 @@
#
#support_mxid =
# PGP key URI for server support contacts, to be served as part of the
# MSC1929 server support endpoint.
#
#support_pgp_key =
# **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
#
# A list of MatrixRTC foci URLs which will be served as part of the

View File

@@ -15,13 +15,13 @@ ARG LLVM_VERSION=21
# Install repo tools
# Line one: compiler tools
# Line two: curl, for downloading binaries
# Line two: curl, for downloading binaries and wget because llvm.sh is broken with curl
# Line three: for xx-verify
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y \
pkg-config make jq \
curl git software-properties-common \
wget curl git software-properties-common \
file
# LLVM packages

View File

@@ -133,6 +133,18 @@ ### `!admin query pusher get-pushers`
Returns all the pushers for the user
### `!admin query pusher delete-pusher`
Deletes a specific pusher by ID
### `!admin query pusher delete-all-user`
Deletes all pushers for a user
### `!admin query pusher delete-all-device`
Deletes all pushers associated with a device ID
## `!admin query short`
short service

View File

@@ -47,3 +47,11 @@ ## `!admin server restart`
## `!admin server shutdown`
Shutdown the server
## `!admin server list-features`
List features built into the server
## `!admin server build-info`
Build information

View File

@@ -157,3 +157,7 @@ ## `!admin users force-join-all-local-users`
At least 1 server admin must be in the room to reduce abuse.
Requires the `--yes-i-want-to-do-this` flag.
## `!admin users reset-push-rules`
Resets the push-rules (notification settings) of the target user to the server defaults

View File

@@ -29,7 +29,6 @@
url = "github:edolstra/flake-compat?ref=master";
flake = false;
};
};
outputs =
@@ -37,10 +36,10 @@
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ ./nix ];
systems = [
# good support
"x86_64-linux"
# support untested but theoretically there
"aarch64-linux"
# support untested but theoretically there
"aarch64-darwin"
];
};
}

View File

@@ -1,107 +0,0 @@
{ inputs, ... }:
{
perSystem =
{
self',
lib,
pkgs,
...
}:
let
uwulib = inputs.self.uwulib.init pkgs;
rocksdbAllFeatures = self'.packages.rocksdb.override {
enableJemalloc = true;
};
commonAttrs = (uwulib.build.commonAttrs { }) // {
buildInputs = [
pkgs.liburing
pkgs.rust-jemalloc-sys-unprefixed
rocksdbAllFeatures
];
nativeBuildInputs = [
pkgs.pkg-config
# bindgen needs the build platform's libclang. Apparently due to "splicing
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
# right thing here.
pkgs.rustPlatform.bindgenHook
];
env = {
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.llvmPackages.libclang.lib ];
LD_LIBRARY_PATH = lib.makeLibraryPath [
pkgs.liburing
pkgs.rust-jemalloc-sys-unprefixed
rocksdbAllFeatures
];
}
// uwulib.environment.buildPackageEnv
// {
ROCKSDB_INCLUDE_DIR = "${rocksdbAllFeatures}/include";
ROCKSDB_LIB_DIR = "${rocksdbAllFeatures}/lib";
};
};
cargoArtifacts = self'.packages.continuwuity-all-features-deps;
in
{
# taken from
#
# https://crane.dev/examples/quick-start.html
checks = {
continuwuity-all-features-build = self'.packages.continuwuity-all-features-bin;
continuwuity-all-features-clippy = uwulib.build.craneLibForChecks.cargoClippy (
commonAttrs
// {
inherit cargoArtifacts;
cargoClippyExtraArgs = "-- --deny warnings";
}
);
continuwuity-all-features-docs = uwulib.build.craneLibForChecks.cargoDoc (
commonAttrs
// {
inherit cargoArtifacts;
# This can be commented out or tweaked as necessary, e.g. set to
# `--deny rustdoc::broken-intra-doc-links` to only enforce that lint
env.RUSTDOCFLAGS = "--deny warnings";
}
);
# Check formatting
continuwuity-all-features-fmt = uwulib.build.craneLibForChecks.cargoFmt {
src = uwulib.build.src;
};
continuwuity-all-features-toml-fmt = uwulib.build.craneLibForChecks.taploFmt {
src = pkgs.lib.sources.sourceFilesBySuffices uwulib.build.src [ ".toml" ];
# taplo arguments can be further customized below as needed
taploExtraArgs = "--config ${inputs.self}/taplo.toml";
};
# Audit dependencies
continuwuity-all-features-audit = uwulib.build.craneLibForChecks.cargoAudit {
inherit (inputs) advisory-db;
src = uwulib.build.src;
};
# Audit licenses
continuwuity-all-features-deny = uwulib.build.craneLibForChecks.cargoDeny {
src = uwulib.build.src;
};
# Run tests with cargo-nextest
# Consider setting `doCheck = false` on `continuwuity-all-features` if you do not want
# the tests to run twice
continuwuity-all-features-nextest = uwulib.build.craneLibForChecks.cargoNextest (
commonAttrs
// {
inherit cargoArtifacts;
partitions = 1;
partitionType = "count";
cargoNextestPartitionsExtraArgs = "--no-tests=pass";
}
);
};
};
}

14
nix/crane.nix Normal file
View File

@@ -0,0 +1,14 @@
{ inputs, ... }:
{
perSystem =
{
pkgs,
self',
...
}:
{
_module.args.craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (
pkgs: self'.packages.stable-toolchain
);
};
}

View File

@@ -1,11 +1,10 @@
{
imports = [
./checks
./rust.nix
./crane.nix
./packages
./shells
./tests
./hydra.nix
./devshell.nix
./fmt.nix
./rocksdb-updater.nix
];
}

42
nix/devshell.nix Normal file
View File

@@ -0,0 +1,42 @@
{
perSystem =
{
craneLib,
self',
lib,
pkgs,
...
}:
{
# basic nix shell containing all things necessary to build continuwuity in all flavors manually (on x86_64-linux)
devShells.default = craneLib.devShell {
packages = [
self'.packages.rocksdb
pkgs.nodejs
pkgs.pkg-config
]
++ lib.optionals pkgs.stdenv.isLinux [
pkgs.liburing
pkgs.rust-jemalloc-sys-unprefixed
];
env = {
LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.llvmPackages.libclang.lib ];
LD_LIBRARY_PATH = lib.makeLibraryPath (
[
pkgs.stdenv.cc.cc.lib
]
++ lib.optionals pkgs.stdenv.isLinux [
pkgs.liburing
pkgs.jemalloc
]
);
}
// lib.optionalAttrs pkgs.stdenv.isLinux {
PKG_CONFIG_PATH = lib.makeSearchPath "lib/pkgconfig" [
pkgs.liburing.dev
];
};
};
};
}

View File

@@ -1,9 +0,0 @@
{ inputs, ... }:
let
lib = inputs.nixpkgs.lib;
in
{
flake.hydraJobs.packages = builtins.mapAttrs (
_name: lib.hydraJob
) inputs.self.packages.x86_64-linux;
}

View File

@@ -0,0 +1,65 @@
{
lib,
self,
stdenv,
liburing,
craneLib,
pkg-config,
callPackage,
rustPlatform,
cargoExtraArgs ? "",
rocksdb ? callPackage ./rocksdb.nix { },
}:
let
# see https://crane.dev/API.html#cranelibfiltercargosources
# we need to keep the `web` directory which would be filtered out by the regular source filtering function
# https://crane.dev/API.html#cranelibcleancargosource
isWebTemplate = path: _type: builtins.match ".*(src/(web|service)|docs).*" path != null;
isRust = craneLib.filterCargoSources;
isNix = path: _type: builtins.match ".+/nix.*" path != null;
webOrRustNotNix = p: t: !(isNix p t) && (isWebTemplate p t || isRust p t);
src = lib.cleanSourceWith {
src = self;
filter = webOrRustNotNix;
name = "source";
};
attrs = {
inherit src;
nativeBuildInputs = [
pkg-config
rustPlatform.bindgenHook
];
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ liburing ];
env = {
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
};
};
in
craneLib.buildPackage (
lib.recursiveUpdate attrs {
inherit cargoExtraArgs;
cargoArtifacts = craneLib.buildDepsOnly attrs;
# Needed to make continuwuity link to rocksdb
postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
old_rpath="$(patchelf --print-rpath $out/bin/conduwuit)"
extra_rpath="${
lib.makeLibraryPath [
rocksdb
]
}"
patchelf --set-rpath "$old_rpath:$extra_rpath" $out/bin/conduwuit
'';
meta = {
description = "A community-driven Matrix homeserver in Rust";
mainProgram = "conduwuit";
platforms = lib.platforms.all;
maintainers = with lib.maintainers; [ quadradical ];
};
}
)

View File

@@ -1,59 +0,0 @@
{ inputs, ... }:
{
perSystem =
{
self',
lib,
pkgs,
...
}:
let
uwulib = inputs.self.uwulib.init pkgs;
in
{
packages =
lib.pipe
[
# this is the default variant
{
variantName = "default";
commonAttrsArgs.profile = "release";
rocksdb = self'.packages.rocksdb;
features = { };
}
# this is the variant with all features enabled (liburing + jemalloc)
{
variantName = "all-features";
commonAttrsArgs.profile = "release";
rocksdb = self'.packages.rocksdb.override {
enableJemalloc = true;
};
features = {
enabledFeatures = "all";
disabledFeatures = uwulib.features.defaultDisabledFeatures ++ [ "bindgen-static" ];
};
}
]
[
(builtins.map (cfg: rec {
deps = {
name = "continuwuity-${cfg.variantName}-deps";
value = uwulib.build.buildDeps {
features = uwulib.features.calcFeatures cfg.features;
inherit (cfg) commonAttrsArgs rocksdb;
};
};
bin = {
name = "continuwuity-${cfg.variantName}-bin";
value = uwulib.build.buildPackage {
deps = self'.packages.${deps.name};
features = uwulib.features.calcFeatures cfg.features;
inherit (cfg) commonAttrsArgs rocksdb;
};
};
}))
(builtins.concatMap builtins.attrValues)
builtins.listToAttrs
];
};
}

View File

@@ -1,14 +1,18 @@
{
imports = [
./continuwuity
./rocksdb
./rust.nix
./uwulib
];
self,
...
}:
{
perSystem =
{ self', ... }:
{
packages.default = self'.packages.continuwuity-default-bin;
pkgs,
craneLib,
...
}:
{
packages = {
rocksdb = pkgs.callPackage ./rocksdb.nix { };
default = pkgs.callPackage ./continuwuity.nix { inherit self craneLib; };
};
};
}

34
nix/packages/rocksdb.nix Normal file
View File

@@ -0,0 +1,34 @@
{
stdenv,
rocksdb,
fetchFromGitea,
rust-jemalloc-sys-unprefixed,
...
}:
(rocksdb.override {
# rocksdb fails to build with prefixed jemalloc, which is required on
# darwin due to [1]. In this case, fall back to building rocksdb with
# libc malloc. This should not cause conflicts, because all of the
# jemalloc symbols are prefixed.
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
jemalloc = rust-jemalloc-sys-unprefixed;
enableJemalloc = stdenv.hostPlatform.isLinux;
}).overrideAttrs
({
version = "continuwuity-v0.5.0-unstable-2026-03-27";
src = fetchFromGitea {
domain = "forgejo.ellis.link";
owner = "continuwuation";
repo = "rocksdb";
rev = "463f47afceebfe088f6922420265546bd237f249";
hash = "sha256-1ef75IDMs5Hba4VWEyXPJb02JyShy5k4gJfzGDhopRk=";
};
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
# Unsetting `patches` so we don't have to revert it and make this nix exclusive
patches = [ ];
# Unset postPatch, as our version override breaks version-specific sed calls in the original package
postPatch = "";
})

View File

@@ -1,12 +0,0 @@
{
perSystem =
{
pkgs,
...
}:
{
packages = {
rocksdb = pkgs.callPackage ./package.nix { };
};
};
}

View File

@@ -1,87 +0,0 @@
{
lib,
stdenv,
rocksdb,
liburing,
rust-jemalloc-sys-unprefixed,
enableJemalloc ? false,
fetchFromGitea,
...
}:
let
notDarwin = !stdenv.hostPlatform.isDarwin;
in
(rocksdb.override {
# Override the liburing input for the build with our own so
# we have it built with the library flag
inherit liburing;
jemalloc = rust-jemalloc-sys-unprefixed;
# rocksdb fails to build with prefixed jemalloc, which is required on
# darwin due to [1]. In this case, fall back to building rocksdb with
# libc malloc. This should not cause conflicts, because all of the
# jemalloc symbols are prefixed.
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
enableJemalloc = enableJemalloc && notDarwin;
# for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely
enableLiburing = notDarwin;
}).overrideAttrs
(old: {
src = fetchFromGitea {
domain = "forgejo.ellis.link";
owner = "continuwuation";
repo = "rocksdb";
rev = "10.5.fb";
sha256 = "sha256-X4ApGLkHF9ceBtBg77dimEpu720I79ffLoyPa8JMHaU=";
};
version = "10.5.fb";
cmakeFlags =
lib.subtractLists (builtins.map (flag: lib.cmakeBool flag true) [
# No real reason to have snappy or zlib, no one uses this
"WITH_SNAPPY"
"ZLIB"
"WITH_ZLIB"
# We don't need to use ldb or sst_dump (core_tools)
"WITH_CORE_TOOLS"
# We don't need to build rocksdb tests
"WITH_TESTS"
# We use rust-rocksdb via C interface and don't need C++ RTTI
"USE_RTTI"
# This doesn't exist in RocksDB, and USE_SSE is deprecated for
# PORTABLE=$(march)
"FORCE_SSE42"
]) old.cmakeFlags
++ (builtins.map (flag: lib.cmakeBool flag false) [
# No real reason to have snappy, no one uses this
"WITH_SNAPPY"
"ZLIB"
"WITH_ZLIB"
# We don't need to use ldb or sst_dump (core_tools)
"WITH_CORE_TOOLS"
# We don't need trace tools
"WITH_TRACE_TOOLS"
# We don't need to build rocksdb tests
"WITH_TESTS"
# We use rust-rocksdb via C interface and don't need C++ RTTI
"USE_RTTI"
]);
enableLiburing = notDarwin;
# outputs has "tools" which we don't need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we don't need or use
preInstall = "";
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
# Unsetting `patches` so we don't have to revert it and make this nix exclusive
patches = [ ];
})

View File

@@ -1,122 +0,0 @@
args@{ pkgs, inputs, ... }:
let
inherit (pkgs) lib;
uwuenv = import ./environment.nix args;
selfpkgs = inputs.self.packages.${pkgs.stdenv.system};
in
rec {
# basic, very minimal instance of the crane library with a minimal rust toolchain
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (_: selfpkgs.build-toolchain);
# the checks require more rust toolchain components, hence we have this separate instance of the crane library
craneLibForChecks = (inputs.crane.mkLib pkgs).overrideToolchain (_: selfpkgs.dev-toolchain);
# meta information (name, version, etc) of the rust crate based on the Cargo.toml
crateInfo = craneLib.crateNameFromCargoToml { cargoToml = "${inputs.self}/Cargo.toml"; };
src =
let
# see https://crane.dev/API.html#cranelibfiltercargosources
#
# we need to keep the `web` directory which would be filtered out by the regular source filtering function
#
# https://crane.dev/API.html#cranelibcleancargosource
isWebTemplate = path: _type: builtins.match ".*(src/(web|service)|docs).*" path != null;
isRust = craneLib.filterCargoSources;
isNix = path: _type: builtins.match ".+/nix.*" path != null;
webOrRustNotNix = p: t: !(isNix p t) && (isWebTemplate p t || isRust p t);
in
lib.cleanSourceWith {
src = inputs.self;
filter = webOrRustNotNix;
name = "source";
};
# common attrs that are shared between building continuwuity's deps and the package itself
commonAttrs =
{
profile ? "dev",
...
}:
{
inherit (crateInfo)
pname
version
;
inherit src;
# this prevents unnecessary rebuilds
strictDeps = true;
dontStrip = profile == "dev" || profile == "test";
dontPatchELF = profile == "dev" || profile == "test";
doCheck = true;
nativeBuildInputs = [
# bindgen needs the build platform's libclang. Apparently due to "splicing
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
# right thing here.
pkgs.rustPlatform.bindgenHook
];
};
makeRocksDBEnv =
{ rocksdb }:
{
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
};
# function that builds the continuwuity dependencies derivation
buildDeps =
{
rocksdb,
features,
commonAttrsArgs,
}:
craneLib.buildDepsOnly (
(commonAttrs commonAttrsArgs)
// {
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;
}
);
# function that builds the continuwuity package
buildPackage =
{
deps,
rocksdb,
features,
commonAttrsArgs,
}:
let
rocksdbEnv = makeRocksDBEnv { inherit rocksdb; };
in
craneLib.buildPackage (
(commonAttrs commonAttrsArgs)
// {
postFixup = ''
patchelf --set-rpath "$(${pkgs.patchelf}/bin/patchelf --print-rpath $out/bin/${crateInfo.pname}):${rocksdb}/lib" $out/bin/${crateInfo.pname}
'';
cargoArtifacts = deps;
doCheck = true;
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;
meta.mainProgram = crateInfo.pname;
inherit (features) cargoExtraArgs;
}
);
}

View File

@@ -1,10 +0,0 @@
{ inputs, ... }:
{
flake.uwulib = {
init = pkgs: {
features = import ./features.nix { inherit pkgs inputs; };
environment = import ./environment.nix { inherit pkgs inputs; };
build = import ./build.nix { inherit pkgs inputs; };
};
};
}

View File

@@ -1,18 +0,0 @@
args@{ pkgs, inputs, ... }:
let
uwubuild = import ./build.nix args;
in
rec {
buildDepsOnlyEnv = {
# https://crane.dev/faq/rebuilds-bindgen.html
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
CARGO_PROFILE = "release";
}
// uwubuild.craneLib.mkCrossToolchainEnv (p: pkgs.clangStdenv);
buildPackageEnv = {
GIT_COMMIT_HASH = inputs.self.rev or inputs.self.dirtyRev or "";
GIT_COMMIT_HASH_SHORT = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
}
// buildDepsOnlyEnv;
}

View File

@@ -1,77 +0,0 @@
{ pkgs, inputs, ... }:
let
inherit (pkgs) lib;
in
rec {
defaultDisabledFeatures = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
# we don't want to enable this feature set by default but be more specific about it
"full"
];
# We perform default-feature unification in nix, because some of the dependencies
# on the nix side depend on feature values.
calcFeatures =
{
tomlPath ? "${inputs.self}/src/main",
# either a list of feature names or a string "all" which enables all non-default features
enabledFeatures ? [ ],
disabledFeatures ? defaultDisabledFeatures,
default_features ? true,
disable_release_max_log_level ? false,
}:
let
# simple helper to get the contents of a Cargo.toml file in a nix format
getToml = path: lib.importTOML "${path}/Cargo.toml";
# get all the features except for the default features
allFeatures = lib.pipe tomlPath [
getToml
(manifest: manifest.features)
lib.attrNames
(lib.remove "default")
];
# get just the default enabled features
allDefaultFeatures = lib.pipe tomlPath [
getToml
(manifest: manifest.features.default)
];
# depending on the value of enabledFeatures choose just a set or all non-default features
#
# - [ list of features ] -> choose exactly the features listed
# - "all" -> choose all non-default features
additionalFeatures = if enabledFeatures == "all" then allFeatures else enabledFeatures;
# unification with default features (if enabled)
features = lib.unique (additionalFeatures ++ lib.optionals default_features allDefaultFeatures);
# prepare the features that are subtracted from the set
disabledFeatures' =
disabledFeatures ++ lib.optionals disable_release_max_log_level [ "release_max_log_level" ];
# construct the final feature set
finalFeatures = lib.subtractLists disabledFeatures' features;
in
{
# final feature set, useful for querying it
features = finalFeatures;
# crane flag with the relevant features
cargoExtraArgs = builtins.concatStringsSep " " [
"--no-default-features"
"--locked"
(lib.optionalString (finalFeatures != [ ]) "--features")
(builtins.concatStringsSep "," finalFeatures)
];
};
}

14
nix/rocksdb-updater.nix Normal file
View File

@@ -0,0 +1,14 @@
{
perSystem =
{ pkgs, ... }:
{
apps.update-rocksdb = {
type = "app";
program = pkgs.writeShellApplication {
name = "update-rocksdb";
runtimeInputs = [ pkgs.nix-update ];
text = "nix-update rocksdb -F --version branch";
};
};
};
}

View File

@@ -4,6 +4,7 @@
{
system,
lib,
pkgs,
...
}:
{
@@ -11,7 +12,7 @@
let
fnx = inputs.fenix.packages.${system};
stable = fnx.fromToolchainFile {
stable-toolchain = fnx.fromToolchainFile {
file = inputs.self + "/rust-toolchain.toml";
# See also `rust-toolchain.toml`
@@ -19,11 +20,10 @@
};
in
{
# used for building nix stuff (doesn't include rustfmt overhead)
build-toolchain = stable;
# used for dev shells
inherit stable-toolchain;
dev-toolchain = fnx.combine [
stable
stable-toolchain
# use the nightly rustfmt because we use nightly features
fnx.complete.rustfmt
];

View File

@@ -1,29 +0,0 @@
{ inputs, ... }:
{
perSystem =
{
self',
lib,
pkgs,
...
}:
let
uwulib = inputs.self.uwulib.init pkgs;
rocksdbAllFeatures = self'.packages.rocksdb.override {
enableJemalloc = true;
};
in
{
# basic nix shell containing all things necessary to build continuwuity in all flavors manually (on x86_64-linux)
devShells.default = uwulib.build.craneLib.devShell {
packages = [
pkgs.nodejs
pkgs.pkg-config
pkgs.liburing
pkgs.rust-jemalloc-sys-unprefixed
rocksdbAllFeatures
];
env.LIBCLANG_PATH = lib.makeLibraryPath [ pkgs.llvmPackages.libclang.lib ];
};
};
}

View File

@@ -1,150 +0,0 @@
{
perSystem =
{
self',
lib,
pkgs,
...
}:
let
baseTestScript =
pkgs.writers.writePython3Bin "do_test" { libraries = [ pkgs.python3Packages.matrix-nio ]; }
''
import asyncio
import nio
async def main() -> None:
# Connect to continuwuity
client = nio.AsyncClient("http://continuwuity:6167", "alice")
# Register as user alice
response = await client.register("alice", "my-secret-password")
# Log in as user alice
response = await client.login("my-secret-password")
# Create a new room
response = await client.room_create(federate=False)
print("Matrix room create response:", response)
assert isinstance(response, nio.RoomCreateResponse)
room_id = response.room_id
# Join the room
response = await client.join(room_id)
print("Matrix join response:", response)
assert isinstance(response, nio.JoinResponse)
# Send a message to the room
response = await client.room_send(
room_id=room_id,
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": "Hello continuwuity!"
}
)
print("Matrix room send response:", response)
assert isinstance(response, nio.RoomSendResponse)
# Sync responses
response = await client.sync(timeout=30000)
print("Matrix sync response:", response)
assert isinstance(response, nio.SyncResponse)
# Check the message was received by continuwuity
last_message = response.rooms.join[room_id].timeline.events[-1].body
assert last_message == "Hello continuwuity!"
# Leave the room
response = await client.room_leave(room_id)
print("Matrix room leave response:", response)
assert isinstance(response, nio.RoomLeaveResponse)
# Close the client
await client.close()
if __name__ == "__main__":
asyncio.run(main())
'';
in
{
# run some nixos tests as checks
checks = lib.pipe self'.packages [
# we take all packages (names)
builtins.attrNames
# we filter out all packages that end with `-bin` (which we are interested in for testing)
(builtins.filter (lib.hasSuffix "-bin"))
# for each of these binaries we built the basic nixos test
#
# this test was initially yoinked from
#
# https://github.com/NixOS/nixpkgs/blob/960ce26339661b1b69c6f12b9063ca51b688615f/nixos/tests/matrix/continuwuity.nix
(builtins.concatMap (
name:
builtins.map
(
{ config, suffix }:
{
name = "test-${name}-${suffix}";
value = pkgs.testers.runNixOSTest {
inherit name;
nodes = {
continuwuity = {
services.matrix-continuwuity = {
enable = true;
package = self'.packages.${name};
settings = config;
extraEnvironment.RUST_BACKTRACE = "yes";
};
networking.firewall.allowedTCPPorts = [ 6167 ];
};
client.environment.systemPackages = [ baseTestScript ];
};
testScript = ''
start_all()
with subtest("start continuwuity"):
continuwuity.wait_for_unit("continuwuity.service")
continuwuity.wait_for_open_port(6167)
with subtest("ensure messages can be exchanged"):
client.succeed("${lib.getExe baseTestScript} >&2")
'';
};
}
)
[
{
suffix = "base";
config = {
global = {
server_name = name;
address = [ "0.0.0.0" ];
allow_registration = true;
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true;
};
};
}
{
suffix = "with-room-version";
config = {
global = {
server_name = name;
address = [ "0.0.0.0" ];
allow_registration = true;
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true;
default_room_version = "12";
};
};
}
]
))
builtins.listToAttrs
];
};
}

View File

@@ -2,6 +2,7 @@
name = "conduwuit_admin"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
@@ -79,6 +80,7 @@ conduwuit-database.workspace = true
conduwuit-macros.workspace = true
conduwuit-service.workspace = true
const-str.workspace = true
ctor.workspace = true
futures.workspace = true
lettre.workspace = true
log.workspace = true
@@ -88,6 +90,7 @@ serde-saphyr.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
resolvematrix.workspace = true
[lints]
workspace = true

View File

@@ -20,6 +20,7 @@
};
use futures::{FutureExt, StreamExt, TryStreamExt};
use lettre::message::Mailbox;
use resolvematrix::server::{MatrixResolver, ResolvedDestination};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
@@ -736,13 +737,20 @@ pub(super) async fn resolve_true_destination(
);
}
let actual = self
.services
.resolver
.resolve_actual_dest(&server_name, !no_cache)
.await?;
let resolver: &MatrixResolver = if no_cache {
&MatrixResolver::new()?
} else {
&self.services.resolver.resolver
};
let msg = format!("Destination: {}\nHostname URI: {}", actual.dest, actual.host);
let actual = resolver.resolve_server(server_name.as_str()).await?;
let destination = match actual.destination {
| ResolvedDestination::Literal(addr) => addr.to_string(),
| ResolvedDestination::Named(host, port) => format!("{host}:{port}"),
};
let msg = format!("Destination: {}\nHostname URI (SNI): {}", destination, actual.host);
self.write_str(&msg).await
}

View File

@@ -3,6 +3,8 @@
#![allow(clippy::enum_glob_use)]
#![allow(clippy::too_many_arguments)]
conduwuit_macros::introspect_crate! {}
pub(crate) mod admin;
pub(crate) mod context;
pub(crate) mod processor;

View File

@@ -46,7 +46,7 @@ async fn destinations_cache(&self, server_name: Option<OwnedServerName>) -> Resu
writeln!(self, "| Server Name | Destination | Hostname | Expires |").await?;
writeln!(self, "| ----------- | ----------- | -------- | ------- |").await?;
let mut destinations = self.services.resolver.cache.destinations().boxed();
let mut destinations = self.services.resolver.dns.cache.destinations().boxed();
while let Some((name, CachedDest { dest, host, expire })) = destinations.next().await {
if let Some(server_name) = server_name.as_ref() {
@@ -70,7 +70,7 @@ async fn overrides_cache(&self, server_name: Option<String>) -> Result {
writeln!(self, "| Server Name | IP | Port | Expires | Overriding |").await?;
writeln!(self, "| ----------- | --- | ----:| ------- | ---------- |").await?;
let mut overrides = self.services.resolver.cache.overrides().boxed();
let mut overrides = self.services.resolver.dns.cache.overrides().boxed();
while let Some((name, CachedOverride { ips, port, expire, overriding })) =
overrides.next().await
@@ -92,11 +92,11 @@ async fn overrides_cache(&self, server_name: Option<String>) -> Result {
#[admin_command]
async fn flush_cache(&self, name: Option<OwnedServerName>, all: bool) -> Result {
if all {
self.services.resolver.cache.clear().await;
self.services.resolver.dns.cache.clear().await;
writeln!(self, "Resolver caches cleared!").await
} else if let Some(name) = name {
self.services.resolver.cache.del_destination(&name);
self.services.resolver.cache.del_override(&name);
self.services.resolver.dns.cache.del_destination(&name);
self.services.resolver.dns.cache.del_override(&name);
self.write_str(&format!("Cleared {name} from resolver caches!"))
.await
} else {

View File

@@ -1,6 +1,6 @@
use conduwuit::{Err, Result};
use futures::StreamExt;
use ruma::OwnedRoomId;
use ruma::{OwnedRoomId, OwnedRoomOrAliasId};
use crate::{PAGE_SIZE, admin_command, get_room_info};
@@ -82,3 +82,185 @@ pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result {
self.write_str(&format!("{result}")).await
}
#[admin_command]
pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result {
// Resolve the room ID from the room or alias ID
let room_id = self.services.rooms.alias.resolve(&room).await?;
// Delete all tokens for this room using the service method
let Ok(deleted_count) = self.services.rooms.user.delete_room_tokens(&room_id).await else {
return Err!("Failed to delete sync tokens for room {}", room_id.as_str());
};
self.write_str(&format!(
"Successfully deleted {deleted_count} sync tokens for room {}",
room_id.as_str()
))
.await
}
/// Target options for room purging
#[derive(Default, Debug, clap::ValueEnum, Clone)]
pub enum RoomTargetOption {
#[default]
/// Target all rooms
All,
/// Target only disabled rooms
DisabledOnly,
/// Target only banned rooms
BannedOnly,
}
#[admin_command]
pub(super) async fn purge_all_sync_tokens(
&self,
target_option: Option<RoomTargetOption>,
execute: bool,
) -> Result {
use conduwuit::{debug, info};
let mode = if !execute { "Simulating" } else { "Starting" };
// strictly, we should check if these reach the max value after the loop and
// warn the user that the count is too large
let mut total_rooms_checked: usize = 0;
let mut total_tokens_deleted: usize = 0;
let mut error_count: u32 = 0;
let mut skipped_rooms: usize = 0;
info!("{} purge of sync tokens", mode);
// Get all rooms in the server
let all_rooms = self
.services
.rooms
.metadata
.iter_ids()
.collect::<Vec<_>>()
.await;
info!("Found {} rooms total on the server", all_rooms.len());
// Filter rooms based on options
let mut rooms = Vec::new();
for room_id in all_rooms {
if let Some(target) = &target_option {
match target {
| RoomTargetOption::DisabledOnly => {
if !self.services.rooms.metadata.is_disabled(room_id).await {
debug!("Skipping room {} as it's not disabled", room_id.as_str());
skipped_rooms = skipped_rooms.saturating_add(1);
continue;
}
},
| RoomTargetOption::BannedOnly => {
if !self.services.rooms.metadata.is_banned(room_id).await {
debug!("Skipping room {} as it's not banned", room_id.as_str());
skipped_rooms = skipped_rooms.saturating_add(1);
continue;
}
},
| RoomTargetOption::All => {},
}
}
rooms.push(room_id);
}
// Total number of rooms we'll be checking
let total_rooms = rooms.len();
info!(
"Processing {} rooms after filtering (skipped {} rooms)",
total_rooms, skipped_rooms
);
// Process each room
for room_id in rooms {
total_rooms_checked = total_rooms_checked.saturating_add(1);
// Log progress periodically
if total_rooms_checked.is_multiple_of(100) || total_rooms_checked == total_rooms {
info!(
"Progress: {}/{} rooms checked, {} tokens {}",
total_rooms_checked,
total_rooms,
total_tokens_deleted,
if !execute { "would be deleted" } else { "deleted" }
);
}
// In dry run mode, just count what would be deleted, don't actually delete
debug!(
"Room {}: {}",
room_id.as_str(),
if !execute {
"would purge sync tokens"
} else {
"purging sync tokens"
}
);
if !execute {
// For dry run mode, count tokens without deleting
match self.services.rooms.user.count_room_tokens(room_id).await {
| Ok(count) =>
if count > 0 {
debug!(
"Would delete {} sync tokens for room {}",
count,
room_id.as_str()
);
total_tokens_deleted = total_tokens_deleted.saturating_add(count);
} else {
debug!("No sync tokens found for room {}", room_id.as_str());
},
| Err(e) => {
debug!("Error counting sync tokens for room {}: {:?}", room_id.as_str(), e);
error_count = error_count.saturating_add(1);
},
}
} else {
// Real deletion mode
match self.services.rooms.user.delete_room_tokens(room_id).await {
| Ok(count) =>
if count > 0 {
debug!("Deleted {} sync tokens for room {}", count, room_id.as_str());
total_tokens_deleted = total_tokens_deleted.saturating_add(count);
} else {
debug!("No sync tokens found for room {}", room_id.as_str());
},
| Err(e) => {
debug!("Error purging sync tokens for room {}: {:?}", room_id.as_str(), e);
error_count = error_count.saturating_add(1);
},
}
}
}
let action = if !execute { "would be deleted" } else { "deleted" };
info!(
"Finished {}: checked {} rooms out of {} total, {} tokens {}, errors: {}",
if !execute {
"purge simulation"
} else {
"purging sync tokens"
},
total_rooms_checked,
total_rooms,
total_tokens_deleted,
action,
error_count
);
self.write_str(&format!(
"Finished {}: checked {} rooms out of {} total, {} tokens {}, errors: {}",
if !execute { "simulation" } else { "purging sync tokens" },
total_rooms_checked,
total_rooms,
total_tokens_deleted,
action,
error_count
))
.await
}

View File

@@ -5,8 +5,9 @@
mod moderation;
use clap::Subcommand;
use commands::RoomTargetOption;
use conduwuit::Result;
use ruma::OwnedRoomId;
use ruma::{OwnedRoomId, OwnedRoomOrAliasId};
use self::{
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand,
@@ -60,4 +61,25 @@ pub enum RoomCommand {
Exists {
room_id: OwnedRoomId,
},
/// - Delete all sync tokens for a room
PurgeSyncTokens {
/// Room ID or alias to purge sync tokens for
#[arg(value_parser)]
room: OwnedRoomOrAliasId,
},
/// - Delete sync tokens for all rooms that have no local users
///
/// By default, processes all empty rooms.
PurgeAllSyncTokens {
/// Target specific room types
#[arg(long, value_enum)]
target_option: Option<RoomTargetOption>,
/// Execute token deletions. Otherwise,
/// Performs a dry run without actually deleting any tokens
#[arg(long)]
execute: bool,
},
}

View File

@@ -1,4 +1,4 @@
use std::{path::PathBuf, sync::Arc};
use std::{fmt::Write, path::PathBuf, sync::Arc};
use conduwuit::{
Err, Result,
@@ -153,3 +153,97 @@ pub(super) async fn shutdown(&self) -> Result {
self.write_str("Shutting down server...").await
}
#[admin_command]
pub(super) async fn list_features(&self) -> Result {
let mut enabled_features = conduwuit::info::introspection::ENABLED_FEATURES
.lock()
.expect("locked")
.iter()
.flat_map(|(_, f)| f.iter())
.collect::<Vec<_>>();
enabled_features.sort_unstable();
enabled_features.dedup();
let mut available_features = conduwuit::build_metadata::WORKSPACE_FEATURES
.iter()
.flat_map(|(_, f)| f.iter())
.collect::<Vec<_>>();
available_features.sort_unstable();
available_features.dedup();
let mut features = String::new();
for feature in available_features {
let active = enabled_features.contains(&feature);
let emoji = if active { "" } else { "" };
let remark = if active { "[enabled]" } else { "" };
writeln!(features, "{emoji} {feature} {remark}")?;
}
self.write_str(&features).await
}
#[admin_command]
pub(super) async fn build_info(&self) -> Result {
use conduwuit::build_metadata::built;
let mut info = String::new();
// Version information
writeln!(info, "# Build Information\n")?;
writeln!(info, "**Version:** {}", built::PKG_VERSION)?;
writeln!(info, "**Package:** {}", built::PKG_NAME)?;
writeln!(info, "**Description:** {}", built::PKG_DESCRIPTION)?;
// Git information
writeln!(info, "\n## Git Information\n")?;
if let Some(hash) = conduwuit::build_metadata::GIT_COMMIT_HASH {
writeln!(info, "**Commit Hash:** {hash}")?;
}
if let Some(hash) = conduwuit::build_metadata::GIT_COMMIT_HASH_SHORT {
writeln!(info, "**Commit Hash (short):** {hash}")?;
}
if let Some(url) = conduwuit::build_metadata::GIT_REMOTE_WEB_URL {
writeln!(info, "**Repository:** {url}")?;
}
if let Some(url) = conduwuit::build_metadata::GIT_REMOTE_COMMIT_URL {
writeln!(info, "**Commit URL:** {url}")?;
}
// Build environment
writeln!(info, "\n## Build Environment\n")?;
writeln!(info, "**Profile:** {}", built::PROFILE)?;
writeln!(info, "**Optimization Level:** {}", built::OPT_LEVEL)?;
writeln!(info, "**Debug:** {}", built::DEBUG)?;
writeln!(info, "**Target:** {}", built::TARGET)?;
writeln!(info, "**Host:** {}", built::HOST)?;
// Rust compiler information
writeln!(info, "\n## Compiler Information\n")?;
writeln!(info, "**Rustc Version:** {}", built::RUSTC_VERSION)?;
if !built::RUSTDOC_VERSION.is_empty() {
writeln!(info, "**Rustdoc Version:** {}", built::RUSTDOC_VERSION)?;
}
// Target configuration
writeln!(info, "\n## Target Configuration\n")?;
writeln!(info, "**Architecture:** {}", built::CFG_TARGET_ARCH)?;
writeln!(info, "**OS:** {}", built::CFG_OS)?;
writeln!(info, "**Family:** {}", built::CFG_FAMILY)?;
writeln!(info, "**Endianness:** {}", built::CFG_ENDIAN)?;
writeln!(info, "**Pointer Width:** {} bits", built::CFG_POINTER_WIDTH)?;
if !built::CFG_ENV.is_empty() {
writeln!(info, "**Environment:** {}", built::CFG_ENV)?;
}
// CI information
if let Some(ci) = built::CI_PLATFORM {
writeln!(info, "\n## CI Platform\n")?;
writeln!(info, "**Platform:** {ci}")?;
}
self.write_str(&info).await
}

View File

@@ -52,4 +52,10 @@ pub enum ServerCommand {
/// Shutdown the server
Shutdown,
/// List features built into the server
ListFeatures,
/// Build information
BuildInfo,
}

View File

@@ -2,6 +2,7 @@
name = "conduwuit_api"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
@@ -76,8 +77,10 @@ axum.workspace = true
base64.workspace = true
bytes.workspace = true
conduwuit-core.workspace = true
conduwuit-macros.workspace = true
conduwuit-service.workspace = true
const-str.workspace = true
ctor.workspace = true
futures.workspace = true
hmac.workspace = true
http.workspace = true

View File

@@ -425,7 +425,9 @@ pub async fn full_user_deactivate(
// TODO: Redact all messages sent by the user in the room
}
super::update_all_rooms(services, pdu_queue, user_id).await;
super::update_all_rooms(services, pdu_queue, user_id)
.boxed()
.await;
for room_id in all_joined_rooms {
services.rooms.state_cache.forget(room_id, user_id);
}

View File

@@ -462,6 +462,31 @@ async fn create_registration_uiaa_session(
flows.push(untrusted_flow);
}
// Require all users to agree to the terms and conditions, if configured
let terms = &services.config.registration_terms;
if !terms.is_empty() {
let mut terms =
serde_json::to_value(terms.clone()).expect("failed to serialize terms");
// Insert a dummy `version` field
for (_, documents) in terms.as_object_mut().unwrap() {
let documents = documents.as_object_mut().unwrap();
documents.insert("version".to_owned(), "latest".into());
}
params.insert(
AuthType::Terms.as_str().to_owned(),
serde_json::json!({
"policies": terms,
}),
);
for flow in &mut flows {
flow.stages.insert(0, AuthType::Terms);
}
}
if flows.is_empty() {
// No flows are configured. Bail out by default
// unless open registration was explicitly enabled.

View File

@@ -1,7 +1,9 @@
use std::iter::once;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Event, Result, err, info,
Err, Event, Result, RoomVersion, err, info,
utils::{
TryFutureExtExt,
math::Expected,
@@ -30,12 +32,14 @@
events::{
StateEventType,
room::{
create::RoomCreateEventContent,
join_rules::{JoinRule, RoomJoinRulesEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
},
uint,
};
use tokio::join;
use crate::Ruma;
@@ -339,36 +343,63 @@ pub(crate) async fn get_public_rooms_filtered_helper(
})
}
/// Check whether the user can publish to the room directory via power levels of
/// room history visibility event or room creator
/// Checks whether the given user ID is allowed to publish the target room to
/// the server's public room directory. Users are allowed to publish rooms if
/// they are server admins, room creators (in v12), or have the power level to
/// send `m.room.canonical_alias`.
async fn user_can_publish_room(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
) -> Result<bool> {
match services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")
.await
if services.users.is_admin(user_id).await {
// Server admins can always publish to their own room directory.
return Ok(true);
}
let (create_event, room_version, power_levels_content) = join!(
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, ""),
services.rooms.state.get_room_version(room_id),
services
.rooms
.state_accessor
.room_state_get_content::<RoomPowerLevelsEventContent>(
room_id,
&StateEventType::RoomPowerLevels,
""
)
);
let room_version = room_version
.as_ref()
.map_err(|_| err!(Request(NotFound("Unknown room"))))?;
let create_event = create_event.map_err(|_| err!(Request(NotFound("Unknown room"))))?;
if RoomVersion::new(room_version)
.expect("room version must be supported")
.explicitly_privilege_room_creators
{
| Ok(event) => serde_json::from_str(event.content().get())
.map_err(|_| err!(Database("Invalid event content for m.room.power_levels")))
.map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content)
.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
}),
| _ => {
match services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
{
| Ok(event) => Ok(event.sender() == user_id),
| _ => Err!(Request(Forbidden("User is not allowed to publish this room"))),
}
},
let create_content: RoomCreateEventContent =
serde_json::from_str(create_event.content().get())
.map_err(|_| err!(Database("Invalid event content for m.room.create")))?;
let is_creator = create_content
.additional_creators
.unwrap_or_default()
.into_iter()
.chain(once(create_event.sender().to_owned()))
.any(|sender| sender == user_id);
if is_creator {
return Ok(true);
}
}
match power_levels_content.map(RoomPowerLevels::from) {
| Ok(pl) => Ok(pl.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)),
| Err(e) =>
if e.is_not_found() {
Ok(create_event.sender() == user_id)
} else {
Err!(Database("Invalid event content for m.room.power_levels: {e}"))
},
}
}

View File

@@ -7,7 +7,7 @@
};
use conduwuit_service::Services;
use futures::{
StreamExt, TryStreamExt,
FutureExt, StreamExt, TryStreamExt,
future::{join, join3, join4},
};
use ruma::{
@@ -51,6 +51,7 @@ pub(crate) async fn set_displayname_route(
.await;
update_displayname(&services, &body.user_id, body.displayname.clone(), &all_joined_rooms)
.boxed()
.await;
if services.config.allow_local_presence {
@@ -149,6 +150,7 @@ pub(crate) async fn set_avatar_url_route(
body.blurhash.clone(),
&all_joined_rooms,
)
.boxed()
.await;
if services.config.allow_local_presence {
@@ -344,7 +346,9 @@ pub async fn update_displayname(
.collect()
.await;
update_all_rooms(services, all_joined_rooms, user_id).await;
update_all_rooms(services, all_joined_rooms, user_id)
.boxed()
.await;
}
pub async fn update_avatar_url(
@@ -394,7 +398,9 @@ pub async fn update_avatar_url(
.collect()
.await;
update_all_rooms(services, all_joined_rooms, user_id).await;
update_all_rooms(services, all_joined_rooms, user_id)
.boxed()
.await;
}
pub async fn update_all_rooms(

View File

@@ -137,6 +137,7 @@ pub(crate) async fn upgrade_room_route(
Some(&body.room_id),
&state_lock,
)
.boxed()
.await?;
// Change lock to replacement room
drop(state_lock);

View File

@@ -60,6 +60,7 @@ pub(crate) async fn send_state_event_for_key_route(
None
},
)
.boxed()
.await?,
})
}

View File

@@ -65,6 +65,7 @@ pub(super) async fn load_joined_room(
and `join*` functions are used to perform steps in parallel which do not depend on each other.
*/
let insert_lock = services.rooms.timeline.mutex_insert.lock(room_id).await;
let (
account_data,
ephemeral,
@@ -82,6 +83,7 @@ pub(super) async fn load_joined_room(
)
.boxed()
.await?;
drop(insert_lock);
if !timeline.is_empty() || !state_events.is_empty() {
trace!(

View File

@@ -3,7 +3,7 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{Err, Result};
use futures::StreamExt;
use futures::{FutureExt, StreamExt};
use ruma::{
OwnedRoomId,
api::{
@@ -112,6 +112,7 @@ pub(crate) async fn set_profile_key_route(
Some(display_name.to_owned()),
&all_joined_rooms,
)
.boxed()
.await;
} else if body.key_name == "avatar_url" {
let Some(avatar_url) = profile_key_value.as_str() else {
@@ -127,7 +128,9 @@ pub(crate) async fn set_profile_key_route(
.collect()
.await;
update_avatar_url(&services, &body.user_id, Some(mxc), None, &all_joined_rooms).await;
update_avatar_url(&services, &body.user_id, Some(mxc), None, &all_joined_rooms)
.boxed()
.await;
} else {
services.users.set_profile_key(
&body.user_id,
@@ -178,7 +181,9 @@ pub(crate) async fn delete_profile_key_route(
.collect()
.await;
update_displayname(&services, &body.user_id, None, &all_joined_rooms).await;
update_displayname(&services, &body.user_id, None, &all_joined_rooms)
.boxed()
.await;
} else if body.key_name == "avatar_url" {
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
@@ -188,7 +193,9 @@ pub(crate) async fn delete_profile_key_route(
.collect()
.await;
update_avatar_url(&services, &body.user_id, None, None, &all_joined_rooms).await;
update_avatar_url(&services, &body.user_id, None, None, &all_joined_rooms)
.boxed()
.await;
} else {
services
.users

View File

@@ -71,6 +71,7 @@ pub(crate) async fn well_known_support(
let email_address = services.config.well_known.support_email.clone();
let matrix_id = services.config.well_known.support_mxid.clone();
let pgp_key = services.config.well_known.support_pgp_key.clone();
// TODO: support defining multiple contacts in the config
let mut contacts: Vec<Contact> = vec![];
@@ -88,6 +89,7 @@ pub(crate) async fn well_known_support(
role: role_value.clone(),
email_address: email_address.clone(),
matrix_id: matrix_id.clone(),
pgp_key: pgp_key.clone(),
});
}
@@ -104,6 +106,7 @@ pub(crate) async fn well_known_support(
role: role_value.clone(),
email_address: None,
matrix_id: Some(user_id.to_owned()),
pgp_key: None,
});
}
}

View File

@@ -3,6 +3,9 @@
extern crate conduwuit_core as conduwuit;
extern crate conduwuit_service as service;
conduwuit_macros::introspect_crate! {}
pub mod client;
pub mod router;
pub mod server;

View File

@@ -1,11 +1,13 @@
use std::borrow::ToOwned;
use axum::extract::State;
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
use conduwuit::{
Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, utils, warn,
};
use conduwuit_service::Services;
use futures::StreamExt;
use ruma::{
CanonicalJsonObject, OwnedUserId, RoomId, RoomVersionId, UserId,
OwnedUserId, RoomId, RoomVersionId, UserId,
api::{client::error::ErrorKind, federation::membership::prepare_join_event},
events::{
StateEventType,
@@ -40,6 +42,7 @@ pub(crate) async fn create_join_event_template_route(
{
info!(
origin = body.origin().as_str(),
room_id = %body.room_id,
"Refusing to serve make_join for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
@@ -133,10 +136,10 @@ pub(crate) async fn create_join_event_template_route(
}
}
let (_pdu, mut pdu_json) = services
let (pdu, _) = services
.rooms
.timeline
.create_hash_and_sign_event(
.create_event(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
@@ -147,6 +150,8 @@ pub(crate) async fn create_join_event_template_route(
)
.await?;
drop(state_lock);
let mut pdu_json = utils::to_canonical_object(&pdu)
.expect("Barebones PDU should be convertible to canonical JSON");
pdu_json.remove("event_id");
Ok(prepare_join_event::v1::Response {
@@ -297,18 +302,3 @@ pub(crate) async fn user_can_perform_restricted_join(
)))
}
}
pub(crate) fn maybe_strip_event_id(
pdu_json: &mut CanonicalJsonObject,
room_version_id: &RoomVersionId,
) -> Result {
use RoomVersionId::*;
match room_version_id {
| V1 | V2 => Ok(()),
| _ => {
pdu_json.remove("event_id");
Ok(())
},
}
}

View File

@@ -1,6 +1,6 @@
use RoomVersionId::*;
use axum::extract::State;
use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PduBuilder, warn};
use conduwuit::{Err, Error, Result, debug_warn, info, matrix::pdu::PduBuilder, utils, warn};
use ruma::{
RoomVersionId,
api::{client::error::ErrorKind, federation::knock::create_knock_event_template},
@@ -28,6 +28,7 @@ pub(crate) async fn create_knock_event_template_route(
{
info!(
origin = body.origin().as_str(),
room_id = %body.room_id,
"Refusing to serve make_knock for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
@@ -98,10 +99,10 @@ pub(crate) async fn create_knock_event_template_route(
}
}
let (_pdu, mut pdu_json) = services
let (pdu, _) = services
.rooms
.timeline
.create_hash_and_sign_event(
.create_event(
PduBuilder::state(
body.user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Knock),
@@ -113,9 +114,9 @@ pub(crate) async fn create_knock_event_template_route(
.await?;
drop(state_lock);
// room v3 and above removed the "event_id" field from remote PDU format
super::maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
let mut pdu_json = utils::to_canonical_object(&pdu)
.expect("Barebones PDU should be convertible to canonical JSON");
pdu_json.remove("event_id");
Ok(create_knock_event_template::v1::Response {
room_version: room_version_id,

View File

@@ -1,12 +1,11 @@
use axum::extract::State;
use conduwuit::{Err, Result, info, matrix::pdu::PduBuilder};
use conduwuit::{Err, Result, info, matrix::pdu::PduBuilder, utils};
use ruma::{
api::federation::membership::prepare_leave_event,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use serde_json::value::to_raw_value;
use super::make_join::maybe_strip_event_id;
use crate::Ruma;
/// # `GET /_matrix/federation/v1/make_leave/{roomId}/{eventId}`
@@ -49,10 +48,10 @@ pub(crate) async fn create_leave_event_template_route(
let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?;
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let (_pdu, mut pdu_json) = services
let (pdu, _) = services
.rooms
.timeline
.create_hash_and_sign_event(
.create_event(
PduBuilder::state(
body.user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Leave),
@@ -64,9 +63,9 @@ pub(crate) async fn create_leave_event_template_route(
.await?;
drop(state_lock);
// room v3 and above removed the "event_id" field from remote PDU format
maybe_strip_event_id(&mut pdu_json, &room_version_id)?;
let mut pdu_json = utils::to_canonical_object(&pdu)
.expect("Barebones PDU should be convertible to canonical JSON");
pdu_json.remove("event_id");
Ok(prepare_leave_event::v1::Response {
room_version: Some(room_version_id),

View File

@@ -187,13 +187,14 @@ async fn create_join_event(
"Joining user did not pass restricted room's rules."
)));
}
}
trace!("Signing send_join event");
services
.server_keys
.hash_and_sign_event(&mut value, &room_version_id)
.map_err(|e| err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}")))))?;
services
.server_keys
.hash_and_sign_event(&mut value, &room_version_id)
.map_err(|e| {
err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}"))))
})?;
}
let mutex_lock = services
.rooms

View File

@@ -2,6 +2,7 @@
name = "conduwuit_build_metadata"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
@@ -27,6 +28,6 @@ crate-type = [
[build-dependencies]
built = { version = "0.8", features = [] }
cargo_metadata = { version = "0.23.1" }
[lints]
workspace = true

View File

@@ -1,5 +1,9 @@
use std::process::Command;
use std::{
collections::BTreeMap, env, fmt::Write as FmtWrite, fs, io::Write, path::Path,
process::Command,
};
use cargo_metadata::MetadataCommand;
fn run_git_command(args: &[&str]) -> Option<String> {
Command::new("git")
.args(args)
@@ -11,12 +15,60 @@ fn run_git_command(args: &[&str]) -> Option<String> {
.filter(|s| !s.is_empty())
}
fn get_env(env_var: &str) -> Option<String> {
match std::env::var(env_var) {
match env::var(env_var) {
| Ok(val) if !val.is_empty() => Some(val),
| _ => None,
}
}
fn main() {
println!("cargo:rerun-if-changed=Cargo.toml");
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); // Cargo.toml path
let manifest_path = Path::new(&manifest_dir).join("Cargo.toml");
let metadata = MetadataCommand::new()
.manifest_path(&manifest_path)
.no_deps()
.exec()
.expect("failed to parse `cargo metadata`");
let workspace_packages = metadata
.workspace_members
.iter()
.map(|package| {
let package = metadata.packages.iter().find(|p| p.id == *package).unwrap();
println!("cargo:rerun-if-changed={}", package.manifest_path.as_str());
package
})
.collect::<Vec<_>>();
// Extract available features from workspace packages
let mut available_features: BTreeMap<String, Vec<String>> = BTreeMap::new();
for package in &workspace_packages {
let crate_name = package
.name
.trim_start_matches("conduwuit-")
.replace('-', "_");
let features: Vec<String> = package.features.keys().cloned().collect();
if !features.is_empty() {
available_features.insert(crate_name, features);
}
}
// Generate Rust code for available features
let features_code = generate_features_code(&available_features);
let features_dst =
Path::new(&env::var("OUT_DIR").expect("OUT_DIR not set")).join("available_features.rs");
let mut features_file = fs::File::create(features_dst).unwrap();
features_file.write_all(features_code.as_bytes()).unwrap();
let dst = Path::new(&env::var("OUT_DIR").expect("OUT_DIR not set")).join("pkg.json");
let mut out_file = fs::File::create(dst).unwrap();
out_file
.write_all(format!("{workspace_packages:?}").as_bytes())
.unwrap();
// built gets the default crate from the workspace. Not sure if this is intended
// behavior, but it's what we want.
built::write_built_file().expect("Failed to acquire build-time information");
@@ -91,3 +143,30 @@ fn main() {
println!("cargo:rerun-if-env-changed=GIT_REMOTE_URL");
println!("cargo:rerun-if-env-changed=GIT_REMOTE_COMMIT_URL");
}
fn generate_features_code(features: &BTreeMap<String, Vec<String>>) -> String {
let mut code = String::from(
r#"
/// All available features for workspace crates
pub const WORKSPACE_FEATURES: &[(&str, &[&str])] = &[
"#,
);
for (crate_name, feature_list) in features {
write!(code, " (\"{crate_name}\", &[").unwrap();
for (i, feature) in feature_list.iter().enumerate() {
if i > 0 {
code.push_str(", ");
}
write!(code, "\"{feature}\"").unwrap();
}
code.push_str("]),\n");
}
code.push_str(
r#"];
"#,
);
code
}

View File

@@ -2,6 +2,10 @@ pub mod built {
include!(concat!(env!("OUT_DIR"), "/built.rs"));
}
// Include generated available features
// This provides: pub const WORKSPACE_FEATURES: &[(&str, &[&str])]
include!(concat!(env!("OUT_DIR"), "/available_features.rs"));
pub static GIT_COMMIT_HASH: Option<&str> = option_env!("GIT_COMMIT_HASH");
pub static GIT_COMMIT_HASH_SHORT: Option<&str> = option_env!("GIT_COMMIT_HASH_SHORT");

View File

@@ -2,6 +2,7 @@
name = "conduwuit_core"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
@@ -116,6 +117,7 @@ url.workspace = true
parking_lot.workspace = true
lock_api.workspace = true
hyper-util.workspace = true
resolvematrix.workspace = true
[target.'cfg(unix)'.dependencies]
nix.workspace = true

View File

@@ -4,7 +4,7 @@
pub mod proxy;
use std::{
collections::{BTreeMap, BTreeSet},
collections::{BTreeMap, BTreeSet, HashMap},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
};
@@ -22,7 +22,7 @@
OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, RoomVersionId,
api::client::discovery::{discover_homeserver::RtcFocusInfo, discover_support::ContactRole},
};
use serde::{Deserialize, de::IgnoredAny};
use serde::{Deserialize, Serialize, de::IgnoredAny};
use url::Url;
use self::proxy::ProxyConfig;
@@ -655,6 +655,20 @@ pub struct Config {
/// even if `recaptcha_site_key` is set.
pub recaptcha_private_site_key: Option<String>,
/// Policy documents, such as terms and conditions or a privacy policy,
/// which users must agree to when registering an account.
///
/// Example:
/// ```ignore
/// [global.registration_terms.privacy_policy]
/// en = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
/// es = { name = "Política de Privacidad", url = "https://homeserver.example/es/privacy_policy.html" }
/// ```
///
/// default: {}
#[serde(default)]
pub registration_terms: HashMap<String, HashMap<String, TermsDocument>>,
/// Controls whether encrypted rooms and events are allowed.
#[serde(default = "true_fn")]
pub allow_encryption: bool,
@@ -2059,12 +2073,10 @@ pub struct Config {
pub stream_amplification: usize,
/// Number of sender task workers; determines sender parallelism. Default is
/// '0' which means the value is determined internally, likely matching the
/// number of tokio worker-threads or number of cores, etc. Override by
/// setting a non-zero value.
/// core count. Override by setting a different value.
///
/// default: 0
#[serde(default)]
/// default: core count
#[serde(default = "default_sender_workers")]
pub sender_workers: usize,
/// Enables listener sockets; can be set to false to disable listening. This
@@ -2191,6 +2203,10 @@ pub struct WellKnownConfig {
/// listed.
pub support_mxid: Option<OwnedUserId>,
/// PGP key URI for server support contacts, to be served as part of the
/// MSC1929 server support endpoint.
pub support_pgp_key: Option<String>,
/// **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
///
/// A list of MatrixRTC foci URLs which will be served as part of the
@@ -2494,6 +2510,13 @@ pub struct SmtpConfig {
pub require_email_for_token_registration: bool,
}
/// A policy document for use with a m.login.terms stage.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TermsDocument {
pub name: String,
pub url: String,
}
const DEPRECATED_KEYS: &[&str] = &[
"cache_capacity",
"conduit_cache_capacity_modifier",
@@ -2592,45 +2615,47 @@ fn default_database_backups_to_keep() -> i16 { 1 }
fn default_db_write_buffer_capacity_mb() -> f64 { 48.0 + parallelism_scaled_f64(4.0) }
fn default_db_cache_capacity_mb() -> f64 { 128.0 + parallelism_scaled_f64(64.0) }
fn default_db_cache_capacity_mb() -> f64 { 512.0 + parallelism_scaled_f64(512.0) }
fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(50_000).saturating_add(100_000) }
fn default_cache_capacity_modifier() -> f64 { 1.0 }
fn default_auth_chain_cache_capacity() -> u32 {
parallelism_scaled_u32(10_000).saturating_add(100_000)
}
fn default_shorteventid_cache_capacity() -> u32 {
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_shorteventid_cache_capacity() -> u32 {
parallelism_scaled_u32(100_000).saturating_add(100_000)
}
fn default_eventidshort_cache_capacity() -> u32 {
parallelism_scaled_u32(25_000).saturating_add(100_000)
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_eventid_pdu_cache_capacity() -> u32 {
parallelism_scaled_u32(25_000).saturating_add(100_000)
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_shortstatekey_cache_capacity() -> u32 {
parallelism_scaled_u32(10_000).saturating_add(100_000)
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_statekeyshort_cache_capacity() -> u32 {
parallelism_scaled_u32(10_000).saturating_add(100_000)
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_servernameevent_data_cache_capacity() -> u32 {
parallelism_scaled_u32(100_000).saturating_add(500_000)
parallelism_scaled_u32(100_000).saturating_add(100_000)
}
fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(100) }
fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(500).clamp(100, 12000) }
fn default_roomid_spacehierarchy_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
fn default_roomid_spacehierarchy_cache_capacity() -> u32 {
parallelism_scaled_u32(500).clamp(100, 12000)
}
fn default_dns_cache_entries() -> u32 { 32768 }
fn default_dns_cache_entries() -> u32 { 327_680 }
fn default_dns_min_ttl() -> u64 { 60 * 180 }
@@ -2838,15 +2863,26 @@ fn default_admin_log_capture() -> String {
fn default_admin_room_tag() -> String { "m.server_notice".to_owned() }
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
pub fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
fn parallelism_scaled_u32(val: u32) -> u32 {
let val = val.try_into().expect("failed to cast u32 to usize");
parallelism_scaled(val).try_into().unwrap_or(u32::MAX)
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
pub fn parallelism_scaled_u32(val: u32) -> u32 {
val.saturating_mul(sys::available_parallelism() as u32)
}
fn parallelism_scaled(val: usize) -> usize { val.saturating_mul(sys::available_parallelism()) }
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn parallelism_scaled_i32(val: i32) -> i32 {
val.saturating_mul(sys::available_parallelism() as i32)
}
#[must_use]
pub fn parallelism_scaled(val: usize) -> usize {
val.saturating_mul(sys::available_parallelism())
}
fn default_trusted_server_batch_size() -> usize { 256 }
@@ -2866,6 +2902,8 @@ fn default_stream_width_scale() -> f32 { 1.0 }
fn default_stream_amplification() -> usize { 1024 }
fn default_sender_workers() -> usize { parallelism_scaled(1) }
fn default_client_receive_timeout() -> u64 { 75 }
fn default_client_request_timeout() -> u64 { 180 }

View File

@@ -86,6 +86,8 @@ pub enum Error {
YamlDe(#[from] serde_saphyr::Error),
#[error(transparent)]
YamlSer(#[from] serde_saphyr::ser_error::Error),
#[error(transparent)]
ResolveServer(#[from] resolvematrix::server::ResolveServerError),
// ruma/conduwuit
#[error("Arithmetic operation failed: {0}")]

View File

@@ -0,0 +1,7 @@
//! Information about features the crates were compiled with.
//! Only available for crates that have called the `introspect_crate` macro
use std::collections::BTreeMap;
pub static ENABLED_FEATURES: std::sync::Mutex<BTreeMap<&str, &[&str]>> =
std::sync::Mutex::new(BTreeMap::new());

View File

@@ -1,3 +1,4 @@
pub mod introspection;
pub mod room_version;
pub mod version;

View File

@@ -19,6 +19,7 @@
pub use ::smallvec;
pub use ::toml;
pub use ::tracing;
pub use conduwuit_build_metadata as build_metadata;
pub use config::Config;
pub use error::Error;
pub use info::{
@@ -34,6 +35,8 @@
pub use crate as conduwuit_core;
conduwuit_macros::introspect_crate! {}
#[cfg(any(not(conduwuit_mods), not(feature = "conduwuit_mods")))]
pub mod mods {
#[macro_export]

View File

@@ -2,6 +2,7 @@
name = "conduwuit_database"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
@@ -54,7 +55,9 @@ bindgen-runtime = [
[dependencies]
async-channel.workspace = true
conduwuit-core.workspace = true
conduwuit-macros.workspace = true
const-str.workspace = true
ctor.workspace = true
futures.workspace = true
log.workspace = true
minicbor.workspace = true

View File

@@ -29,7 +29,7 @@ fn descriptor_cf_options(
set_table_options(&mut opts, &desc, cache)?;
opts.set_min_write_buffer_number(1);
opts.set_max_write_buffer_number(2);
opts.set_max_write_buffer_number(3);
opts.set_write_buffer_size(desc.write_size);
opts.set_target_file_size_base(desc.file_size);
@@ -70,17 +70,19 @@ fn descriptor_cf_options(
);
}
let mut opts = opts
let opts = opts
.get_options_from_string("{{arena_block_size=2097152;}}")
.map_err(map_err)?;
#[cfg(debug_assertions)]
let opts = opts
.get_options_from_string(
let opts = {
let mut opts = opts;
opts.get_options_from_string(
"{{paranoid_checks=true;paranoid_file_checks=true;force_consistency_checks=true;\
verify_sst_unique_id_in_manifest=true;}}",
)
.map_err(map_err)?;
.map_err(map_err)?
};
Ok(opts)
}

View File

@@ -3,6 +3,8 @@
extern crate conduwuit_core as conduwuit;
extern crate rust_rocksdb as rocksdb;
conduwuit_macros::introspect_crate! {}
conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {}

View File

@@ -2,6 +2,7 @@
name = "conduwuit_macros"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
@@ -17,6 +18,7 @@ syn.workspace = true
quote.workspace = true
proc-macro2.workspace = true
itertools.workspace = true
cargo_toml.workspace = true
[lints]
workspace = true

63
src/macros/build_info.rs Normal file
View File

@@ -0,0 +1,63 @@
use proc_macro2::TokenStream;
use quote::quote;
use crate::Result;
pub(super) fn introspect(_args: TokenStream) -> Result<TokenStream> {
let cargo_crate_name = std::env::var("CARGO_CRATE_NAME").unwrap();
let crate_name = cargo_crate_name.trim_start_matches("conduwuit_");
let is_core = cargo_crate_name == "conduwuit_core";
let flags = std::env::args().collect::<Vec<_>>();
let mut enabled_features = Vec::new();
append_features(&mut enabled_features, flags);
let enabled_count = enabled_features.len();
let import_path = if is_core {
quote! { use crate::conduwuit_core; }
} else {
quote! { use ::conduwuit_core; }
};
let ret = quote! {
#[doc(hidden)]
mod __compile_introspection {
#import_path
/// Features that were enabled when this crate was compiled
const ENABLED: [&str; #enabled_count] = [#( #enabled_features ),*];
const CRATE_NAME: &str = #crate_name;
/// Register this crate's features with the global registry during static initialization
#[::ctor::ctor]
fn register() {
conduwuit_core::info::introspection::ENABLED_FEATURES.lock().unwrap().insert(#crate_name, &ENABLED);
}
#[::ctor::dtor]
fn unregister() {
conduwuit_core::info::introspection::ENABLED_FEATURES.lock().unwrap().remove(#crate_name);
}
}
};
Ok(ret)
}
fn append_features(features: &mut Vec<String>, flags: Vec<String>) {
let mut next_is_cfg = false;
for flag in flags {
let is_cfg = flag == "--cfg";
let is_feature = flag.starts_with("feature=");
if std::mem::replace(&mut next_is_cfg, is_cfg) && is_feature {
if let Some(feature) = flag
.split_once('=')
.map(|(_, feature)| feature.trim_matches('"'))
{
features.push(feature.to_owned());
}
}
}
}

View File

@@ -1,4 +1,5 @@
mod admin;
mod build_info;
mod config;
mod debug;
mod implement;
@@ -44,6 +45,13 @@ pub fn config_example_generator(args: TokenStream, input: TokenStream) -> TokenS
attribute_macro::<ItemStruct, _>(args, input, config::example_generator)
}
#[proc_macro]
pub fn introspect_crate(input: TokenStream) -> TokenStream {
build_info::introspect(input.into())
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
fn attribute_macro<I, F>(args: TokenStream, input: TokenStream, func: F) -> TokenStream
where
F: Fn(I, &[Meta]) -> Result<TokenStream>,

View File

@@ -207,8 +207,10 @@ conduwuit-database.workspace = true
conduwuit-router.workspace = true
conduwuit-service.workspace = true
conduwuit-build-metadata.workspace = true
conduwuit-macros.workspace = true
clap.workspace = true
ctor.workspace = true
console-subscriber.optional = true
console-subscriber.workspace = true
const-str.workspace = true

View File

@@ -4,6 +4,8 @@
use conduwuit_core::{debug_info, error};
conduwuit_macros::introspect_crate! {}
mod clap;
mod deadlock;
mod logging;

View File

@@ -2,6 +2,7 @@
name = "conduwuit_router"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
@@ -99,9 +100,11 @@ bytes.workspace = true
conduwuit-admin.workspace = true
conduwuit-api.workspace = true
conduwuit-core.workspace = true
conduwuit-macros.workspace = true
conduwuit-service.workspace = true
conduwuit-web.workspace = true
const-str.workspace = true
ctor.workspace = true
futures.workspace = true
http.workspace = true
http-body-util.workspace = true

View File

@@ -8,6 +8,8 @@
extern crate conduwuit_core as conduwuit;
conduwuit_macros::introspect_crate! {}
use std::{panic::AssertUnwindSafe, pin::Pin, sync::Arc};
use conduwuit::{Error, Result, Server};

View File

@@ -8,7 +8,7 @@
extract::State,
response::{IntoResponse, Response},
};
use conduwuit::{Result, debug, debug_error, debug_warn, err, error, trace};
use conduwuit::{Result, debug_warn, err, error, info, trace};
use conduwuit_service::Services;
use futures::FutureExt;
use http::{Method, StatusCode, Uri};
@@ -102,11 +102,11 @@ fn handle_result(method: &Method, uri: &Uri, result: Response) -> Result<Respons
let reason = status.canonical_reason().unwrap_or("Unknown Reason");
if status.is_server_error() {
error!(%method, %uri, "{code} {reason}");
info!(%method, %uri, "{code} {reason}");
} else if status.is_client_error() {
debug_error!(%method, %uri, "{code} {reason}");
info!(%method, %uri, "{code} {reason}");
} else if status.is_redirection() {
debug!(%method, %uri, "{code} {reason}");
trace!(%method, %uri, "{code} {reason}");
} else {
trace!(%method, %uri, "{code} {reason}");
}

View File

@@ -2,6 +2,7 @@
name = "conduwuit_service"
description.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
@@ -88,7 +89,9 @@ base64.workspace = true
bytes.workspace = true
conduwuit-core.workspace = true
conduwuit-database.workspace = true
conduwuit-macros.workspace = true
const-str.workspace = true
ctor.workspace = true
either.workspace = true
futures.workspace = true
governor.workspace = true
@@ -126,6 +129,7 @@ blurhash.optional = true
recaptcha-verify = { version = "0.2.0", default-features = false }
yansi.workspace = true
lettre.workspace = true
resolvematrix.workspace = true
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
sd-notify.workspace = true

View File

@@ -43,7 +43,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
default: base(config)?
.dns_resolver(resolver.resolver.clone())
.dns_resolver(resolver.dns.resolver.clone())
.build()?,
url_preview: base(config)
@@ -51,19 +51,19 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
builder_interface(builder, url_preview_bind_iface.as_deref())
})?
.local_address(url_preview_bind_addr)
.dns_resolver(resolver.resolver.clone())
.dns_resolver(resolver.dns.resolver.clone())
.timeout(Duration::from_secs(config.url_preview_timeout))
.redirect(redirect::Policy::limited(3))
.user_agent(url_preview_user_agent)
.build()?,
extern_media: base(config)?
.dns_resolver(resolver.resolver.clone())
.dns_resolver(resolver.dns.resolver.clone())
.redirect(redirect::Policy::limited(3))
.build()?,
well_known: base(config)?
.dns_resolver(resolver.resolver.clone())
.dns_resolver(resolver.dns.resolver.clone())
.connect_timeout(Duration::from_secs(config.well_known_conn_timeout))
.read_timeout(Duration::from_secs(config.well_known_timeout))
.timeout(Duration::from_secs(config.well_known_timeout))
@@ -72,7 +72,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
.build()?,
federation: base(config)?
.dns_resolver(resolver.resolver.hooked.clone())
.dns_resolver(resolver.dns.resolver.hooked.clone())
.connect_timeout(Duration::from_secs(config.federation_conn_timeout))
.read_timeout(Duration::from_secs(config.federation_timeout))
.timeout(Duration::from_secs(
@@ -86,7 +86,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
.build()?,
synapse: base(config)?
.dns_resolver(resolver.resolver.hooked.clone())
.dns_resolver(resolver.dns.resolver.hooked.clone())
.connect_timeout(Duration::from_secs(config.federation_conn_timeout))
.read_timeout(Duration::from_secs(config.federation_timeout.saturating_mul(6)))
.timeout(Duration::from_secs(
@@ -100,7 +100,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
.build()?,
sender: base(config)?
.dns_resolver(resolver.resolver.hooked.clone())
.dns_resolver(resolver.dns.resolver.hooked.clone())
.connect_timeout(Duration::from_secs(config.federation_conn_timeout))
.read_timeout(Duration::from_secs(config.sender_timeout))
.timeout(Duration::from_secs(config.sender_timeout))
@@ -110,7 +110,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
.build()?,
appservice: base(config)?
.dns_resolver(resolver.resolver.clone())
.dns_resolver(resolver.dns.resolver.clone())
.connect_timeout(Duration::from_secs(5))
.read_timeout(Duration::from_secs(config.appservice_timeout))
.timeout(Duration::from_secs(config.appservice_timeout))
@@ -120,7 +120,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
.build()?,
pusher: base(config)?
.dns_resolver(resolver.resolver.clone())
.dns_resolver(resolver.dns.resolver.clone())
.connect_timeout(Duration::from_secs(config.pusher_conn_timeout))
.timeout(Duration::from_secs(config.pusher_timeout))
.pool_max_idle_per_host(1)

View File

@@ -8,6 +8,7 @@
use http::{HeaderValue, header::AUTHORIZATION};
use ipaddress::IPAddress;
use reqwest::{Client, Method, Request, Response, Url};
use resolvematrix::server::Resolution;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId,
api::{
@@ -17,8 +18,6 @@
serde::Base64,
};
use crate::resolver::actual::ActualDest;
/// Sends a request to a federation server
#[implement(super::Service)]
#[tracing::instrument(skip_all, name = "request", level = "debug")]
@@ -68,7 +67,12 @@ pub async fn execute_on<T>(
return Err!(Request(Forbidden(debug_warn!("Federation with {dest} is not allowed."))));
}
let actual = self.services.resolver.get_actual_dest(dest).await?;
let actual = self
.services
.resolver
.resolver
.resolve_server(dest.as_str())
.await?;
let request = into_http_request::<T>(&actual, request)?;
let request = self.prepare(dest, request)?;
self.perform::<T>(dest, &actual, request, client).await
@@ -78,7 +82,7 @@ pub async fn execute_on<T>(
async fn perform<T>(
&self,
dest: &ServerName,
actual: &ActualDest,
actual: &Resolution,
request: Request,
client: &Client,
) -> Result<T::IncomingResponse>
@@ -125,7 +129,7 @@ fn validate_url(&self, url: &Url) -> Result<()> {
async fn handle_response<T>(
&self,
dest: &ServerName,
actual: &ActualDest,
actual: &Resolution,
method: &Method,
url: &Url,
response: Response,
@@ -156,7 +160,7 @@ async fn handle_response<T>(
async fn into_http_response(
dest: &ServerName,
actual: &ActualDest,
actual: &Resolution,
method: &Method,
url: &Url,
mut response: Response,
@@ -168,7 +172,7 @@ async fn into_http_response(
request_url = %url,
response_url = %response.url(),
"Received response from {}",
actual.string(),
actual.base_url(),
);
let mut http_response_builder = http::Response::builder()
@@ -205,7 +209,7 @@ async fn into_http_response(
}
fn handle_error(
actual: &ActualDest,
actual: &Resolution,
method: &Method,
url: &Url,
mut e: reqwest::Error,
@@ -300,7 +304,7 @@ fn sign_request(&self, http_request: &mut http::Request<Vec<u8>>, dest: &ServerN
debug_assert!(authorization.is_none(), "Authorization header already present");
}
fn into_http_request<T>(actual: &ActualDest, request: T) -> Result<http::Request<Vec<u8>>>
fn into_http_request<T>(actual: &Resolution, request: T) -> Result<http::Request<Vec<u8>>>
where
T: OutgoingRequest + Send,
{
@@ -308,7 +312,7 @@ fn into_http_request<T>(actual: &ActualDest, request: T) -> Result<http::Request
let http_request = request
.try_into_http_request::<Vec<u8>>(
actual.string().as_str(),
actual.base_url().as_str(),
SendAccessToken::None,
&VERSIONS,
)

View File

@@ -6,7 +6,7 @@
use askama::Template;
use async_trait::async_trait;
use conduwuit::{Result, info, utils::ReadyExt};
use futures::StreamExt;
use futures::{FutureExt, StreamExt};
use ruma::{UserId, events::room::message::RoomMessageEventContent};
use crate::{
@@ -133,7 +133,7 @@ struct WelcomeMessage<'a> {
return Ok(false);
}
self.services.admin.make_user_admin(user).await?;
self.services.admin.make_user_admin(user).boxed().await?;
// Send the welcome message
let welcome_message = WelcomeMessage {

View File

@@ -3,6 +3,9 @@
extern crate conduwuit_core as conduwuit;
extern crate conduwuit_database as database;
conduwuit_macros::introspect_crate! {}
mod manager;
mod migrations;
mod service;

View File

@@ -100,7 +100,7 @@ pub async fn get_presence(&self, user_id: &UserId) -> Result<PresenceEvent> {
/// Pings the presence of the given user in the given room, setting the
/// specified state.
pub async fn ping_presence(&self, user_id: &UserId, new_state: &PresenceState) -> Result<()> {
const REFRESH_TIMEOUT: u64 = 60 * 1000;
const REFRESH_TIMEOUT: u64 = 60 * 1000 * 4;
let last_presence = self.db.get_presence(user_id).await;
let state_changed = match last_presence {

View File

@@ -1,398 +0,0 @@
use std::{
fmt::Debug,
net::{IpAddr, SocketAddr},
};
use conduwuit::{Err, Result, debug, debug_info, err, error, trace};
use futures::{FutureExt, TryFutureExt};
use hickory_resolver::ResolveError;
use ipaddress::IPAddress;
use ruma::ServerName;
use super::{
cache::{CachedDest, CachedOverride, MAX_IPS},
fed::{FedDest, PortString, add_port_to_hostname, get_ip_with_port},
};
#[derive(Clone, Debug)]
pub(crate) struct ActualDest {
pub(crate) dest: FedDest,
pub(crate) host: String,
}
impl ActualDest {
#[inline]
pub(crate) fn string(&self) -> String { self.dest.https_string() }
}
impl super::Service {
#[tracing::instrument(skip_all, level = "debug", name = "resolve")]
pub(crate) async fn get_actual_dest(&self, server_name: &ServerName) -> Result<ActualDest> {
let (CachedDest { dest, host, .. }, _cached) =
self.lookup_actual_dest(server_name).await?;
Ok(ActualDest { dest, host })
}
pub(crate) async fn lookup_actual_dest(
&self,
server_name: &ServerName,
) -> Result<(CachedDest, bool)> {
if let Ok(result) = self.cache.get_destination(server_name).await {
return Ok((result, true));
}
let _dedup = self.resolving.lock(server_name.as_str());
if let Ok(result) = self.cache.get_destination(server_name).await {
return Ok((result, true));
}
self.resolve_actual_dest(server_name, true)
.inspect_ok(|result| self.cache.set_destination(server_name, result))
.map_ok(|result| (result, false))
.boxed()
.await
}
/// Returns: `actual_destination`, host header
/// Implemented according to the specification at <https://matrix.org/docs/spec/server_server/r0.1.4#resolving-server-names>
/// Numbers in comments below refer to bullet points in linked section of
/// specification
#[tracing::instrument(name = "actual", level = "debug", skip(self, cache))]
pub async fn resolve_actual_dest(
&self,
dest: &ServerName,
cache: bool,
) -> Result<CachedDest> {
self.validate_dest(dest)?;
let mut host = dest.as_str().to_owned();
let actual_dest = match get_ip_with_port(dest.as_str()) {
| Some(host_port) => Self::actual_dest_1(host_port)?,
| None =>
if let Some(pos) = dest.as_str().find(':') {
self.actual_dest_2(dest, cache, pos).await?
} else {
self.conditional_query_and_cache(dest.as_str(), 8448, true)
.await?;
self.services.server.check_running()?;
match self.request_well_known(dest.as_str()).await? {
| Some(delegated) =>
self.actual_dest_3(&mut host, cache, delegated).await?,
| _ => match self.query_srv_record(dest.as_str()).await? {
| Some(overrider) =>
self.actual_dest_4(&host, cache, overrider).await?,
| _ => self.actual_dest_5(dest, cache).await?,
},
}
},
};
// Can't use get_ip_with_port here because we don't want to add a port
// to an IP address if it wasn't specified
let host = if let Ok(addr) = host.parse::<SocketAddr>() {
FedDest::Literal(addr)
} else if let Ok(addr) = host.parse::<IpAddr>() {
FedDest::Named(addr.to_string(), FedDest::default_port())
} else if let Some(pos) = host.find(':') {
let (host, port) = host.split_at(pos);
FedDest::Named(
host.to_owned(),
port.try_into().unwrap_or_else(|_| FedDest::default_port()),
)
} else {
FedDest::Named(host, FedDest::default_port())
};
debug!("Actual destination: {actual_dest:?} hostname: {host:?}");
Ok(CachedDest {
dest: actual_dest,
host: host.uri_string(),
expire: CachedDest::default_expire(),
})
}
fn actual_dest_1(host_port: FedDest) -> Result<FedDest> {
debug!("1: IP literal with provided or default port");
Ok(host_port)
}
async fn actual_dest_2(&self, dest: &ServerName, cache: bool, pos: usize) -> Result<FedDest> {
debug!("2: Hostname with included port");
let (host, port) = dest.as_str().split_at(pos);
self.conditional_query_and_cache(host, port.parse::<u16>().unwrap_or(8448), cache)
.await?;
Ok(FedDest::Named(
host.to_owned(),
port.try_into().unwrap_or_else(|_| FedDest::default_port()),
))
}
async fn actual_dest_3(
&self,
host: &mut String,
cache: bool,
delegated: String,
) -> Result<FedDest> {
debug!("3: A .well-known file is available");
*host = add_port_to_hostname(&delegated).uri_string();
match get_ip_with_port(&delegated) {
| Some(host_and_port) => Self::actual_dest_3_1(host_and_port),
| None =>
if let Some(pos) = delegated.find(':') {
self.actual_dest_3_2(cache, delegated, pos).await
} else {
trace!("Delegated hostname has no port in this branch");
match self.query_srv_record(&delegated).await? {
| Some(overrider) =>
self.actual_dest_3_3(cache, delegated, overrider).await,
| _ => self.actual_dest_3_4(cache, delegated).await,
}
},
}
}
fn actual_dest_3_1(host_and_port: FedDest) -> Result<FedDest> {
debug!("3.1: IP literal in .well-known file");
Ok(host_and_port)
}
async fn actual_dest_3_2(
&self,
cache: bool,
delegated: String,
pos: usize,
) -> Result<FedDest> {
debug!("3.2: Hostname with port in .well-known file");
let (host, port) = delegated.split_at(pos);
self.conditional_query_and_cache(host, port.parse::<u16>().unwrap_or(8448), cache)
.await?;
Ok(FedDest::Named(
host.to_owned(),
port.try_into().unwrap_or_else(|_| FedDest::default_port()),
))
}
async fn actual_dest_3_3(
&self,
cache: bool,
delegated: String,
overrider: FedDest,
) -> Result<FedDest> {
debug!("3.3: SRV lookup successful");
let force_port = overrider.port();
self.conditional_query_and_cache_override(
&delegated,
&overrider.hostname(),
force_port.unwrap_or(8448),
cache,
)
.await?;
if let Some(port) = force_port {
return Ok(FedDest::Named(
delegated,
format!(":{port}")
.as_str()
.try_into()
.unwrap_or_else(|_| FedDest::default_port()),
));
}
Ok(add_port_to_hostname(&delegated))
}
async fn actual_dest_3_4(&self, cache: bool, delegated: String) -> Result<FedDest> {
debug!("3.4: No SRV records, just use the hostname from .well-known");
self.conditional_query_and_cache(&delegated, 8448, cache)
.await?;
Ok(add_port_to_hostname(&delegated))
}
async fn actual_dest_4(
&self,
host: &str,
cache: bool,
overrider: FedDest,
) -> Result<FedDest> {
debug!("4: No .well-known; SRV record found");
let force_port = overrider.port();
self.conditional_query_and_cache_override(
host,
&overrider.hostname(),
force_port.unwrap_or(8448),
cache,
)
.await?;
if let Some(port) = force_port {
let port = format!(":{port}");
return Ok(FedDest::Named(
host.to_owned(),
PortString::from(port.as_str()).unwrap_or_else(|_| FedDest::default_port()),
));
}
Ok(add_port_to_hostname(host))
}
async fn actual_dest_5(&self, dest: &ServerName, cache: bool) -> Result<FedDest> {
debug!("5: No SRV record found");
self.conditional_query_and_cache(dest.as_str(), 8448, cache)
.await?;
Ok(add_port_to_hostname(dest.as_str()))
}
#[inline]
async fn conditional_query_and_cache(
&self,
hostname: &str,
port: u16,
cache: bool,
) -> Result {
self.conditional_query_and_cache_override(hostname, hostname, port, cache)
.await
}
#[inline]
async fn conditional_query_and_cache_override(
&self,
untername: &str,
hostname: &str,
port: u16,
cache: bool,
) -> Result {
if !cache {
return Ok(());
}
if self.cache.has_override(untername).await {
return Ok(());
}
self.query_and_cache_override(untername, hostname, port)
.await
}
#[tracing::instrument(name = "ip", level = "debug", skip(self))]
async fn query_and_cache_override(
&self,
untername: &'_ str,
hostname: &'_ str,
port: u16,
) -> Result {
self.services.server.check_running()?;
debug!("querying IP for {untername:?} ({hostname:?}:{port})");
match self.resolver.resolver.lookup_ip(hostname.to_owned()).await {
| Err(e) => Self::handle_resolve_error(&e, hostname),
| Ok(override_ip) => {
self.cache.set_override(untername, &CachedOverride {
ips: override_ip.into_iter().take(MAX_IPS).collect(),
port,
expire: CachedOverride::default_expire(),
overriding: (hostname != untername)
.then_some(hostname.into())
.inspect(|_| debug_info!("{untername:?} overridden by {hostname:?}")),
});
Ok(())
},
}
}
#[tracing::instrument(name = "srv", level = "debug", skip(self))]
async fn query_srv_record(&self, hostname: &'_ str) -> Result<Option<FedDest>> {
let hostnames =
[format!("_matrix-fed._tcp.{hostname}."), format!("_matrix._tcp.{hostname}.")];
for hostname in hostnames {
self.services.server.check_running()?;
debug!("querying SRV for {hostname:?}");
let hostname = hostname.trim_end_matches('.');
match self.resolver.resolver.srv_lookup(hostname).await {
| Err(e) => Self::handle_resolve_error(&e, hostname)?,
| Ok(result) => {
return Ok(result.iter().next().map(|result| {
FedDest::Named(
result.target().to_string().trim_end_matches('.').to_owned(),
format!(":{}", result.port())
.as_str()
.try_into()
.unwrap_or_else(|_| FedDest::default_port()),
)
}));
},
}
}
Ok(None)
}
fn handle_resolve_error(e: &ResolveError, host: &'_ str) -> Result<()> {
use hickory_resolver::{ResolveErrorKind::Proto, proto::ProtoErrorKind};
match e.kind() {
| Proto(e) => match e.kind() {
| ProtoErrorKind::NoRecordsFound { .. } => {
// Raise to debug_warn if we can find out the result wasn't from cache
debug!(%host, "No DNS records found: {e}");
Ok(())
},
| ProtoErrorKind::Timeout => {
Err!(warn!(%host, "DNS {e}"))
},
| ProtoErrorKind::NoConnections => {
error!(
"Your DNS server is overloaded and has ran out of connections. It is \
strongly recommended you remediate this issue to ensure proper \
federation connectivity."
);
Err!(error!(%host, "DNS error: {e}"))
},
| _ => Err!(error!(%host, "DNS error: {e}")),
},
| _ => Err!(error!(%host, "DNS error: {e}")),
}
}
fn validate_dest(&self, dest: &ServerName) -> Result<()> {
if dest == self.services.server.name && !self.services.server.config.federation_loopback {
return Err!("Won't send federation request to ourselves");
}
if dest.is_ip_literal() || IPAddress::is_valid(dest.host()) {
self.validate_dest_ip_literal(dest)?;
}
Ok(())
}
fn validate_dest_ip_literal(&self, dest: &ServerName) -> Result<()> {
trace!("Destination is an IP literal, checking against IP range denylist.",);
debug_assert!(
dest.is_ip_literal() || !IPAddress::is_valid(dest.host()),
"Destination is not an IP literal."
);
let ip = IPAddress::parse(dest.host()).map_err(|e| {
err!(BadServerResponse(debug_error!("Failed to parse IP literal from string: {e}")))
})?;
self.validate_ip(&ip)?;
Ok(())
}
pub(crate) fn validate_ip(&self, ip: &IPAddress) -> Result<()> {
if !self.services.client.valid_cidr_range(ip) {
return Err!(BadServerResponse("Not allowed to send requests to this IP"));
}
Ok(())
}
}

View File

@@ -4,7 +4,7 @@
Result,
arrayvec::ArrayVec,
at, err, implement,
utils::{math::Expected, rand, stream::TryIgnore},
utils::{math::Expected, stream::TryIgnore},
};
use database::{Cbor, Deserialized, Map};
use futures::{Stream, StreamExt, future::join};
@@ -127,11 +127,6 @@ impl CachedDest {
#[must_use]
pub fn valid(&self) -> bool { self.expire > SystemTime::now() }
#[must_use]
pub(crate) fn default_expire() -> SystemTime {
rand::time_from_now_secs(60 * 60 * 18..60 * 60 * 36)
}
#[inline]
#[must_use]
pub fn size(&self) -> usize {
@@ -147,11 +142,6 @@ impl CachedOverride {
#[must_use]
pub fn valid(&self) -> bool { self.expire > SystemTime::now() }
#[must_use]
pub(crate) fn default_expire() -> SystemTime {
rand::time_from_now_secs(60 * 60 * 6..60 * 60 * 12)
}
#[inline]
#[must_use]
pub fn size(&self) -> usize { size_of_val(self) }

View File

@@ -53,9 +53,9 @@ pub(super) fn build(server: &Arc<Server>, cache: Arc<Cache>) -> Result<Arc<Self>
opts.cache_size = config.dns_cache_entries as usize;
opts.preserve_intermediates = true;
opts.negative_min_ttl = Some(Duration::from_secs(config.dns_min_ttl_nxdomain));
opts.negative_max_ttl = Some(Duration::from_secs(60 * 60 * 24 * 30));
opts.negative_max_ttl = Some(Duration::from_secs(60 * 60 * 24));
opts.positive_min_ttl = Some(Duration::from_secs(config.dns_min_ttl));
opts.positive_max_ttl = Some(Duration::from_secs(60 * 60 * 24 * 7));
opts.positive_max_ttl = Some(Duration::from_secs(60 * 60 * 24));
opts.timeout = Duration::from_secs(config.dns_timeout);
opts.attempts = config.dns_attempts as usize;
opts.try_tcp_on_error = config.dns_tcp_fallback;

View File

@@ -1,16 +1,12 @@
use std::{
borrow::Cow,
fmt,
net::{IpAddr, SocketAddr},
};
use std::{fmt, net::SocketAddr};
use conduwuit::{arrayvec::ArrayString, utils::math::Expected};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub enum FedDest {
Literal(SocketAddr),
Named(String, PortString),
Literal(SocketAddr), // "ip:port"
Named(String, PortString), // ("hostname", ":port")
}
/// numeric or service-name
@@ -18,36 +14,7 @@ pub enum FedDest {
const DEFAULT_PORT: &str = ":8448";
pub(crate) fn get_ip_with_port(dest_str: &str) -> Option<FedDest> {
if let Ok(dest) = dest_str.parse::<SocketAddr>() {
Some(FedDest::Literal(dest))
} else if let Ok(ip_addr) = dest_str.parse::<IpAddr>() {
Some(FedDest::Literal(SocketAddr::new(ip_addr, 8448)))
} else {
None
}
}
pub(crate) fn add_port_to_hostname(dest: &str) -> FedDest {
let (host, port) = match dest.find(':') {
| None => (dest, DEFAULT_PORT),
| Some(pos) => dest.split_at(pos),
};
FedDest::Named(
host.to_owned(),
PortString::from(port).unwrap_or_else(|_| FedDest::default_port()),
)
}
impl FedDest {
pub(crate) fn https_string(&self) -> String {
match self {
| Self::Literal(addr) => format!("https://{addr}"),
| Self::Named(host, port) => format!("https://{host}{port}"),
}
}
pub(crate) fn uri_string(&self) -> String {
match self {
| Self::Literal(addr) => addr.to_string(),
@@ -55,23 +22,6 @@ pub(crate) fn uri_string(&self) -> String {
}
}
#[inline]
pub(crate) fn hostname(&self) -> Cow<'_, str> {
match &self {
| Self::Literal(addr) => addr.ip().to_string().into(),
| Self::Named(host, _) => host.into(),
}
}
#[inline]
#[allow(clippy::string_slice)]
pub(crate) fn port(&self) -> Option<u16> {
match &self {
| Self::Literal(addr) => Some(addr.port()),
| Self::Named(_, port) => port[1..].parse().ok(),
}
}
#[inline]
#[must_use]
pub fn default_port() -> PortString {

View File

@@ -1,33 +1,31 @@
pub mod actual;
pub mod cache;
mod dns;
pub mod fed;
#[cfg(test)]
mod tests;
mod well_known;
use std::sync::Arc;
use async_trait::async_trait;
use conduwuit::{Result, Server, arrayvec::ArrayString, utils::MutexMap};
use conduwuit::{Err, Result, implement};
use ipaddress::IPAddress;
use resolvematrix::server::MatrixResolver;
use self::{cache::Cache, dns::Resolver};
use crate::{Dep, client};
pub struct Service {
pub cache: Arc<Cache>,
pub resolver: Arc<Resolver>,
resolving: Resolving,
pub resolver: MatrixResolver,
pub dns: Dns,
services: Services,
}
struct Services {
server: Arc<Server>,
client: Dep<client::Service>,
}
type Resolving = MutexMap<NameBuf, ()>;
type NameBuf = ArrayString<256>;
pub struct Dns {
pub cache: Arc<Cache>,
pub resolver: Arc<Resolver>,
}
#[async_trait]
impl crate::Service for Service {
@@ -35,20 +33,31 @@ impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
let cache = Cache::new(&args);
Ok(Arc::new(Self {
cache: cache.clone(),
resolver: Resolver::build(args.server, cache)?,
resolving: MutexMap::new(),
resolver: MatrixResolver::new()?,
dns: Dns {
cache: cache.clone(),
resolver: Resolver::build(args.server, cache)?,
},
services: Services {
server: args.server.clone(),
client: args.depend::<client::Service>("client"),
},
}))
}
async fn clear_cache(&self) {
self.resolver.clear_cache();
self.cache.clear().await;
// No ability to clean resolvematrix cache at the moment
self.dns.resolver.clear_cache();
self.dns.cache.clear().await;
}
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
fn name(&self) -> &str { crate::service::make_name(module_path!()) }
}
#[implement(Service)]
pub fn validate_ip(&self, ip: &IPAddress) -> Result<()> {
if !self.services.client.valid_cidr_range(ip) {
return Err!(BadServerResponse("Not allowed to send requests to this IP"));
}
Ok(())
}

View File

@@ -1,41 +0,0 @@
use super::fed::{FedDest, add_port_to_hostname, get_ip_with_port};
#[test]
fn ips_get_default_ports() {
assert_eq!(
get_ip_with_port("1.1.1.1"),
Some(FedDest::Literal("1.1.1.1:8448".parse().unwrap()))
);
assert_eq!(
get_ip_with_port("dead:beef::"),
Some(FedDest::Literal("[dead:beef::]:8448".parse().unwrap()))
);
}
#[test]
fn ips_keep_custom_ports() {
assert_eq!(
get_ip_with_port("1.1.1.1:1234"),
Some(FedDest::Literal("1.1.1.1:1234".parse().unwrap()))
);
assert_eq!(
get_ip_with_port("[dead::beef]:8933"),
Some(FedDest::Literal("[dead::beef]:8933".parse().unwrap()))
);
}
#[test]
fn hostnames_get_default_ports() {
assert_eq!(
add_port_to_hostname("example.com"),
FedDest::Named(String::from("example.com"), ":8448".try_into().unwrap())
);
}
#[test]
fn hostnames_keep_custom_ports() {
assert_eq!(
add_port_to_hostname("example.com:1337"),
FedDest::Named(String::from("example.com"), ":1337".try_into().unwrap())
);
}

View File

@@ -1,50 +0,0 @@
use conduwuit::{
Result, debug, debug_error, debug_info, implement, trace, utils::response::LimitReadExt,
};
#[implement(super::Service)]
#[tracing::instrument(name = "well-known", level = "debug", skip(self, dest))]
pub(super) async fn request_well_known(&self, dest: &str) -> Result<Option<String>> {
trace!("Requesting well known for {dest}");
let response = self
.services
.client
.well_known
.get(format!("https://{dest}/.well-known/matrix/server"))
.send()
.await;
trace!("response: {response:?}");
if let Err(e) = &response {
debug!("error: {e:?}");
return Ok(None);
}
let response = response?;
if !response.status().is_success() {
debug!("response not 2XX");
return Ok(None);
}
let Ok(text) = response.limit_read_text(8192).await else {
debug!("failed to read well-known response (too large or non-text content)");
return Ok(None);
};
trace!("response text: {text:?}");
let body: serde_json::Value = serde_json::from_str(&text).unwrap_or_default();
let m_server = body
.get("m.server")
.unwrap_or(&serde_json::Value::Null)
.as_str()
.unwrap_or_default();
if ruma::identifiers_validation::server_name::validate(m_server).is_err() {
debug_error!("response content missing or invalid");
return Ok(None);
}
debug_info!("{dest:?} found at {m_server:?}");
Ok(Some(m_server.to_owned()))
}

View File

@@ -80,7 +80,7 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>(
{
// Exponential backoff
const MIN_DURATION: u64 = 60 * 2;
const MAX_DURATION: u64 = 60 * 60 * 8;
const MAX_DURATION: u64 = 60 * 60;
if continue_exponential_backoff_secs(
MIN_DURATION,
MAX_DURATION,

View File

@@ -46,7 +46,7 @@ pub(super) async fn handle_prev_pdu<'a, Pdu>(
{
// Exponential backoff
const MIN_DURATION: u64 = 5 * 60;
const MAX_DURATION: u64 = 60 * 60 * 24;
const MAX_DURATION: u64 = 60 * 60;
if continue_exponential_backoff_secs(MIN_DURATION, MAX_DURATION, time.elapsed(), *tries) {
debug!(
?tries,

View File

@@ -197,6 +197,15 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu<Pdu>(
.await;
extremities.push(incoming_pdu.event_id().to_owned());
if extremities.is_empty() {
info!(
"Retained zero extremities when upgrading outlier PDU to timeline PDU with {} \
previous events, event id: {}",
incoming_pdu.prev_events.len(),
incoming_pdu.event_id
);
}
debug!(
"Retained {} extremities checked against {} prev_events",
extremities.len(),

View File

@@ -56,36 +56,32 @@ pub fn pdu_fits(owned_obj: &mut CanonicalJsonObject) -> bool {
}
}
/// Pulls the room version ID out of the given (create) event.
fn room_version_from_event(
room_id: OwnedRoomId,
event_type: &TimelineEventType,
content: &RawValue,
) -> Result<RoomVersionId> {
if event_type == &TimelineEventType::RoomCreate {
let content: RoomCreateEventContent = serde_json::from_str(content.get())?;
Ok(content.room_version)
} else {
Err(Error::InconsistentRoomState(
"non-create event for room of unknown version",
room_id,
))
}
}
// Creates an event, but does not hash or sign it.
#[implement(super::Service)]
pub async fn create_hash_and_sign_event(
pub async fn create_event(
&self,
pdu_builder: PduBuilder,
sender: &UserId,
room_id: Option<&RoomId>,
_mutex_lock: &RoomMutexGuard, /* Take mutex guard to make sure users get the room
* state mutex */
) -> Result<(PduEvent, CanonicalJsonObject)> {
#[allow(clippy::boxed_local)]
fn from_evt(
room_id: OwnedRoomId,
event_type: &TimelineEventType,
content: &RawValue,
) -> Result<RoomVersionId> {
if event_type == &TimelineEventType::RoomCreate {
let content: RoomCreateEventContent = serde_json::from_str(content.get())?;
Ok(content.room_version)
} else {
Err(Error::InconsistentRoomState(
"non-create event for room of unknown version",
room_id,
))
}
}
if !self.services.globals.user_is_local(sender) {
return Err!(Request(Forbidden("Sender must be a local user")));
}
_mutex_lock: &RoomMutexGuard,
) -> Result<(PduEvent, RoomVersionId)> {
let PduBuilder {
event_type,
content,
@@ -108,12 +104,16 @@ fn from_evt(
.get_room_version(room_id)
.await
.or_else(|_| {
from_evt(room_id.to_owned(), &event_type.clone(), &content.clone())
room_version_from_event(
room_id.to_owned(),
&event_type.clone(),
&content.clone(),
)
})?
},
| None => {
trace!("No room ID, assuming room creation");
from_evt(
room_version_from_event(
RoomId::new(self.services.globals.server_name()),
&event_type.clone(),
&content.clone(),
@@ -186,7 +186,7 @@ fn from_evt(
}
}
let mut pdu = PduEvent {
let pdu = PduEvent {
event_id: ruma::event_id!("$thiswillbefilledinlater").into(),
room_id: room_id.map(ToOwned::to_owned),
sender: sender.to_owned(),
@@ -259,19 +259,29 @@ fn from_evt(
pdu.event_id,
pdu.room_id.as_ref().map_or("None", |id| id.as_str())
);
Ok((pdu, room_version_id))
}
#[implement(super::Service)]
pub async fn create_hash_and_sign_event(
&self,
pdu_builder: PduBuilder,
sender: &UserId,
room_id: Option<&RoomId>,
mutex_lock: &RoomMutexGuard, /* Take mutex guard to make sure users get the room
* state mutex */
) -> Result<(PduEvent, CanonicalJsonObject)> {
if !self.services.globals.user_is_local(sender) {
return Err!(Request(Forbidden("Sender must be a local user")));
}
let (mut pdu, room_version_id) = self
.create_event(pdu_builder, sender, room_id, mutex_lock)
.await?;
// Hash and sign
let mut pdu_json = utils::to_canonical_object(&pdu).map_err(|e| {
err!(Request(BadJson(warn!("Failed to convert PDU to canonical JSON: {e}"))))
})?;
// room v3 and above removed the "event_id" field from remote PDU format
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
pdu_json.remove("event_id");
},
}
pdu_json.remove("event_id");
trace!("hashing and signing event {}", pdu.event_id);
if let Err(e) = self

View File

@@ -127,3 +127,63 @@ pub async fn get_token_shortstatehash(
.await
.deserialized()
}
/// Count how many sync tokens exist for a room without deleting them
///
/// This is useful for dry runs to see how many tokens would be deleted
#[implement(Service)]
pub async fn count_room_tokens(&self, room_id: &RoomId) -> Result<usize> {
use futures::TryStreamExt;
let shortroomid = self.services.short.get_shortroomid(room_id).await?;
// Create a prefix to search by - all entries for this room will start with its
// short ID
let prefix = &[shortroomid];
// Collect all keys into a Vec and count them
let keys = self
.db
.roomsynctoken_shortstatehash
.keys_prefix_raw(prefix)
.map_ok(|_| ()) // We only need to count, not store the keys
.try_collect::<Vec<_>>()
.await?;
Ok(keys.len())
}
/// Delete all sync tokens associated with a room
///
/// This helps clean up the database as these tokens are never otherwise removed
#[implement(Service)]
pub async fn delete_room_tokens(&self, room_id: &RoomId) -> Result<usize> {
use futures::TryStreamExt;
let shortroomid = self.services.short.get_shortroomid(room_id).await?;
// Create a prefix to search by - all entries for this room will start with its
// short ID
let prefix = &[shortroomid];
// Collect all keys into a Vec first, then delete them
let keys = self
.db
.roomsynctoken_shortstatehash
.keys_prefix_raw(prefix)
.map_ok(|key| {
// Clone the key since we can't store references in the Vec
Vec::from(key)
})
.try_collect::<Vec<_>>()
.await?;
// Delete each key individually
for key in &keys {
self.db.roomsynctoken_shortstatehash.del(key);
}
let count = keys.len();
Ok(count)
}

Some files were not shown because too many files have changed in this diff Show More