Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b3986b3350 | |||
| d5db11eb45 | |||
| ba80fbd2a4 | |||
| 3a1941a972 | |||
| e6ab3ac2ad | |||
| fd428e9512 | |||
| 32188ba1f9 | |||
| a51bc163f5 | |||
| f27d98cfb5 | |||
| d62c01b3c0 | |||
| debc8b6164 | |||
| fee442c5c5 | |||
| 924adfb4e0 | |||
| f25764a158 | |||
| 59cdec4932 | |||
| b036a4fa75 | |||
| 00baef9c00 | |||
| 0e95b2d3cb | |||
| ea3834b19b | |||
| b9d7185290 | |||
| e5f00926d7 | |||
| ede82e7b90 | |||
| 8d1b597b6b | |||
| f43b8d449b | |||
| 15c3e03908 | |||
| fdc6e05443 | |||
| 05002267a7 | |||
| 2aec32c007 | |||
| 148628dbc8 | |||
| 7809f0a6ae | |||
| 85cb559002 | |||
| 61f49ecf0e | |||
| 5e880ac44a | |||
| 7f7bd91e8a | |||
| 96e1938616 | |||
| 694b926366 | |||
| c8c3a9ed43 | |||
| 896c372cfe | |||
| 2be43d3712 | |||
| 0301c7c083 | |||
| a2a7b81076 | |||
| ea6425712a | |||
| 19f313b91d | |||
| e2827a1a79 | |||
| b1b88601ab | |||
| e9ce642795 | |||
| a7966b8f05 | |||
| b78d79a45a | |||
| 8c3f946e97 | |||
| abceae26de | |||
| 95ea665649 | |||
| 6fe0ea05b8 | |||
| ba03d55879 | |||
| 9b5c4697bf | |||
| 16572a868a | |||
| b079b94715 | |||
| 19135eaa58 | |||
| e7c6b8c91b | |||
| 1ecbf55d68 | |||
| 4c841cd909 | |||
| c48535ef32 | |||
| 5473631e1d | |||
| de8f773620 | |||
| b4fa306a20 |
@@ -6,16 +6,23 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# Required to make some things output color
|
||||
TERM: ansi
|
||||
# Publishing to my nix binary cache
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
# Just in case incremental is still being set to true, speeds up CI
|
||||
CARGO_INCREMENTAL: 0
|
||||
# Custom nix binary cache if fork is being used
|
||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
@@ -247,4 +254,4 @@ jobs:
|
||||
if [[ -n "$GITHUB_REF_NAME" ]]; then
|
||||
docker manifest create $IMAGE_NAME:latest --amend $IMAGE_NAME:$GITHUB_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$GITHUB_SHA-$IMAGE_SUFFIX_ARM64V8
|
||||
docker manifest push $IMAGE_NAME:latest
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
name: Documentation and GitHub Pages
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# Required to make some things output color
|
||||
TERM: ansi
|
||||
# Publishing to my nix binary cache
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
# Custom nix binary cache if fork is being used
|
||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Documentation and GitHub Pages
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup GitHub Pages
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Install Nix (with flakes and nix-command enabled)
|
||||
uses: cachix/install-nix-action@v26
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
# Add `nix-community`, Crane, upstream Conduit, and conduwuit binary caches
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
extra-substituters = https://nix-community.cachix.org
|
||||
extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=
|
||||
extra-substituters = https://crane.cachix.org
|
||||
extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=
|
||||
extra-substituters = https://nix.computer.surgery/conduit
|
||||
extra-trusted-public-keys = conduit:ZGAf6P6LhNvnoJJ3Me3PRg7tlLSrPxcQ2RiE5LIppjo=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit
|
||||
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit
|
||||
extra-trusted-public-keys = conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
|
||||
- name: Add alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
echo "extra-substituters = ${{ env.ATTIC_ENDPOINT }}" >> /etc/nix/nix.conf
|
||||
echo "extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}" >> /etc/nix/nix.conf
|
||||
|
||||
- name: Pop/push Magic Nix Cache
|
||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Configure `nix-direnv`
|
||||
run: |
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
|
||||
- name: Install `direnv` and `nix-direnv`
|
||||
run: nix-env -f "<nixpkgs>" -iA direnv -iA nix-direnv
|
||||
|
||||
# Do this to shorten the logs for the real CI step
|
||||
- name: Populate `/nix/store`
|
||||
run: nix develop --command true
|
||||
|
||||
- name: Allow direnv
|
||||
run: direnv allow
|
||||
|
||||
- name: Cache x86_64 inputs for devShell
|
||||
run: |
|
||||
./bin/nix-build-and-cache .#devShells.x86_64-linux.default.inputDerivation
|
||||
|
||||
- name: Build documentation (book)
|
||||
run: |
|
||||
./bin/nix-build-and-cache .#book
|
||||
cp -r --dereference result public
|
||||
- name: Upload generated documentation (book) as normal artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: public
|
||||
path: public
|
||||
if-no-files-found: error
|
||||
# don't compress again
|
||||
compression-level: 0
|
||||
|
||||
- name: Upload generated documentation (book) as GitHub Pages artifact
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: public
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
if: github.event_name != 'pull_request'
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -75,5 +75,8 @@ test-conduit.toml
|
||||
# Gitlab CI cache
|
||||
/.gitlab-ci.d
|
||||
|
||||
# mdbook output
|
||||
public/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
+19
-2
@@ -105,6 +105,10 @@ artifacts:
|
||||
|
||||
- ./bin/nix-build-and-cache .#oci-image-aarch64-unknown-linux-musl
|
||||
- cp result oci-image-arm64v8.tar.gz
|
||||
|
||||
- ./bin/nix-build-and-cache .#book
|
||||
# We can't just copy the symlink, we need to dereference it https://gitlab.com/gitlab-org/gitlab/-/issues/19746
|
||||
- cp -r --dereference result public
|
||||
artifacts:
|
||||
paths:
|
||||
- x86_64-unknown-linux-musl
|
||||
@@ -112,6 +116,7 @@ artifacts:
|
||||
- x86_64-unknown-linux-musl.deb
|
||||
- oci-image-amd64.tar.gz
|
||||
- oci-image-arm64v8.tar.gz
|
||||
- public
|
||||
rules:
|
||||
# CI required for all MRs
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
@@ -124,9 +129,9 @@ artifacts:
|
||||
|
||||
.push-oci-image:
|
||||
stage: publish
|
||||
image: docker:25.0.4
|
||||
image: docker:25.0.5
|
||||
services:
|
||||
- docker:25.0.4-dind
|
||||
- docker:25.0.5-dind
|
||||
variables:
|
||||
IMAGE_SUFFIX_AMD64: amd64
|
||||
IMAGE_SUFFIX_ARM64V8: arm64v8
|
||||
@@ -164,3 +169,15 @@ oci-image:push-gitlab:
|
||||
IMAGE_NAME: $CI_REGISTRY_IMAGE/conduwuit
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
|
||||
pages:
|
||||
stage: publish
|
||||
dependencies:
|
||||
- artifacts
|
||||
only:
|
||||
- next
|
||||
script:
|
||||
- "true"
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Docs: Map markdown to html files
|
||||
- source: /docs/(.+)\.md/
|
||||
public: '\1.html'
|
||||
Generated
+114
-103
@@ -96,13 +96,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.77"
|
||||
version = "0.1.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
||||
checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -245,7 +245,7 @@ version = "0.69.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
@@ -256,7 +256,7 @@ dependencies = [
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -267,9 +267,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.2"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
@@ -355,6 +355,15 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.7.0"
|
||||
@@ -395,7 +404,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -412,7 +421,7 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "conduit"
|
||||
version = "0.7.0-alpha+conduwuit-0.1.7"
|
||||
version = "0.7.0-alpha+conduwuit-0.1.8"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"async-trait",
|
||||
@@ -421,11 +430,13 @@ dependencies = [
|
||||
"axum-server-dual-protocol",
|
||||
"base64 0.22.0",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"clap",
|
||||
"cyborgtime",
|
||||
"either",
|
||||
"figment",
|
||||
"futures-util",
|
||||
"hickory-resolver",
|
||||
"hmac",
|
||||
"http",
|
||||
"hyper",
|
||||
@@ -467,7 +478,6 @@ dependencies = [
|
||||
"tracing-flame",
|
||||
"tracing-opentelemetry",
|
||||
"tracing-subscriber",
|
||||
"trust-dns-resolver",
|
||||
"webpage",
|
||||
]
|
||||
|
||||
@@ -567,7 +577,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -664,7 +674,7 @@ dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -789,7 +799,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -950,6 +960,51 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna 0.4.0",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-resolver"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"hickory-proto",
|
||||
"ipconfig",
|
||||
"lru-cache",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
@@ -1112,9 +1167,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba6107a25f04af48ceeb4093eebc9b405ee5a1813a0bab5ecf1805d3eabb3337"
|
||||
checksum = "7a84a25dcae3ac487bc24ef280f9e20c79c9b1a3e5e32cbed3041d1c514aa87c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"thiserror",
|
||||
@@ -1301,7 +1356,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.28.0"
|
||||
source = "git+https://github.com/rusqlite/rusqlite?branch=master#983f08b8d753deb60649661c8a1c3bc3bd4f358a"
|
||||
source = "git+https://github.com/rusqlite/rusqlite?branch=master#20487e8c27c483de1a83c33176e47dd998e2a7d1"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
@@ -1470,7 +1525,7 @@ version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -1755,7 +1810,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1829,7 +1884,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1924,7 +1979,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
"version_check",
|
||||
"yansi",
|
||||
]
|
||||
@@ -2029,9 +2084,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.26"
|
||||
version = "0.11.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2"
|
||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
@@ -2039,6 +2094,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"hickory-resolver",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
@@ -2097,7 +2153,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.9.4"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"js_int",
|
||||
@@ -2116,7 +2172,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -2128,7 +2184,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.17.4"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
@@ -2147,7 +2203,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.12.1"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.21.7",
|
||||
@@ -2166,6 +2222,7 @@ dependencies = [
|
||||
"serde_html_form",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
@@ -2175,7 +2232,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.27.11"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap",
|
||||
@@ -2197,7 +2254,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -2209,7 +2266,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.3"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror",
|
||||
@@ -2218,7 +2275,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-identity-service-api"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -2228,7 +2285,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro-crate",
|
||||
@@ -2236,14 +2293,14 @@ dependencies = [
|
||||
"quote",
|
||||
"ruma-identifiers-validation",
|
||||
"serde",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
@@ -2255,7 +2312,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.14.0"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"ed25519-dalek",
|
||||
@@ -2271,7 +2328,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ruma-state-res"
|
||||
version = "0.10.0"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#b36cacb4d1e7e8ef0a9afe4b82de4cc5fd9e2b6c"
|
||||
source = "git+https://github.com/girlbossceo/ruma?branch=conduwuit-changes#5ab81eef0aff02f75570abc149f147af12417ca4"
|
||||
dependencies = [
|
||||
"itertools 0.11.0",
|
||||
"js_int",
|
||||
@@ -2286,9 +2343,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.31.0"
|
||||
source = "git+https://github.com/rusqlite/rusqlite?branch=master#983f08b8d753deb60649661c8a1c3bc3bd4f358a"
|
||||
source = "git+https://github.com/rusqlite/rusqlite?branch=master#20487e8c27c483de1a83c33176e47dd998e2a7d1"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
@@ -2298,8 +2355,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-librocksdb-sys"
|
||||
version = "0.18.1+8.11.3"
|
||||
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=3e4a0f632a8c0c2839c7d183725c53895110d907#3e4a0f632a8c0c2839c7d183725c53895110d907"
|
||||
version = "0.19.0+9.0.0"
|
||||
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=4b8491b9db45066115435881184902bbb23d3811#4b8491b9db45066115435881184902bbb23d3811"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"bzip2-sys",
|
||||
@@ -2314,8 +2371,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-rocksdb"
|
||||
version = "0.22.7"
|
||||
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=3e4a0f632a8c0c2839c7d183725c53895110d907#3e4a0f632a8c0c2839c7d183725c53895110d907"
|
||||
version = "0.22.8"
|
||||
source = "git+https://github.com/zaidoon1/rust-rocksdb?rev=4b8491b9db45066115435881184902bbb23d3811#4b8491b9db45066115435881184902bbb23d3811"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rust-librocksdb-sys",
|
||||
@@ -2474,7 +2531,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2544,9 +2601,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.32"
|
||||
version = "0.9.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
|
||||
checksum = "a0623d197252096520c6f2a5e1171ee436e5af99a5d7caa2891e55e61950e6d9"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
@@ -2740,9 +2797,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.52"
|
||||
version = "2.0.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
||||
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2804,7 +2861,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2942,7 +2999,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3048,7 +3105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"bitflags 2.4.2",
|
||||
"bitflags 2.5.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -3096,7 +3153,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3167,52 +3224,6 @@ dependencies = [
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-proto"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna 0.4.0",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-resolver"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"ipconfig",
|
||||
"lru-cache",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
@@ -3272,9 +3283,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
@@ -3307,9 +3318,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
@@ -3368,7 +3379,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3402,7 +3413,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3670,7 +3681,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
"syn 2.0.53",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+13
-6
@@ -6,7 +6,7 @@ authors = ["strawberry <strawberry@puppygock.gay>", "timokoesters <timo@koesters
|
||||
homepage = "https://puppygock.gay/conduwuit"
|
||||
repository = "https://gitlab.com/girlbossceo/conduwuit"
|
||||
readme = "README.md"
|
||||
version = "0.7.0-alpha+conduwuit-0.1.7"
|
||||
version = "0.7.0-alpha+conduwuit-0.1.8"
|
||||
edition = "2021"
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
@@ -27,7 +27,7 @@ base64 = "0.22.0"
|
||||
ring = "0.17.8"
|
||||
|
||||
# Used when querying the SRV record of other servers
|
||||
trust-dns-resolver = "0.23.2"
|
||||
hickory-resolver = "0.24.0"
|
||||
|
||||
# Used to find matching events for appservices
|
||||
regex = "1.10.3"
|
||||
@@ -48,7 +48,7 @@ serde_html_form = "0.2.5"
|
||||
hmac = "0.12.1"
|
||||
sha-1 = "0.10.1"
|
||||
|
||||
async-trait = "0.1.77"
|
||||
async-trait = "0.1.78"
|
||||
|
||||
# used for checking if an IP is in specific subnets / CIDR ranges easier
|
||||
ipaddress = "0.1.3"
|
||||
@@ -67,6 +67,12 @@ cyborgtime = "2.1.1"
|
||||
bytes = "1.5.0"
|
||||
http = "0.2.12"
|
||||
|
||||
# standard date and time tools
|
||||
[dependencies.chrono]
|
||||
version = "0.4.35"
|
||||
features = ["alloc"]
|
||||
default-features = false
|
||||
|
||||
# Web framework
|
||||
[dependencies.axum]
|
||||
version = "0.6.20"
|
||||
@@ -101,11 +107,12 @@ features = [
|
||||
]
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11.26"
|
||||
version = "0.11.27"
|
||||
default-features = false
|
||||
features = [
|
||||
"rustls-tls-native-roots",
|
||||
"socks",
|
||||
"trust-dns",
|
||||
]
|
||||
|
||||
# all the serde stuff
|
||||
@@ -115,7 +122,7 @@ version = "1.0.197"
|
||||
features = ["rc"]
|
||||
# Used for appservice registration files
|
||||
[dependencies.serde_yaml]
|
||||
version = "0.9.32"
|
||||
version = "0.9.33"
|
||||
# Used for ruma wrapper
|
||||
[dependencies.serde_json]
|
||||
version = "1.0.114"
|
||||
@@ -254,7 +261,7 @@ features = [
|
||||
[dependencies.rust-rocksdb]
|
||||
git = "https://github.com/zaidoon1/rust-rocksdb"
|
||||
#branch = "master"
|
||||
rev = "3e4a0f632a8c0c2839c7d183725c53895110d907"
|
||||
rev = "4b8491b9db45066115435881184902bbb23d3811"
|
||||
optional = true
|
||||
default-features = true
|
||||
features = [
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
# conduwuit
|
||||
### a well maintained fork of [Conduit](https://conduit.rs/)
|
||||
|
||||
[](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
|
||||
|
||||
<!-- ANCHOR: catchphrase -->
|
||||
### a well maintained fork of [Conduit](https://conduit.rs/)
|
||||
<!-- ANCHOR_END: catchphrase -->
|
||||
|
||||
Visit the [Conduwuit documentation](https://conduwuit.puppyirl.gay/) for more information.
|
||||
Alternatively you can open [docs/introduction.md](docs/introduction.md) in this repository.
|
||||
|
||||
<!-- ANCHOR: body -->
|
||||
#### What is Matrix?
|
||||
|
||||
[Matrix](https://matrix.org) is an open network for secure and decentralized
|
||||
communication. Users from every Matrix homeserver can chat with users from all
|
||||
other Matrix servers. You can even use bridges (also called Matrix Appservices)
|
||||
@@ -27,11 +35,7 @@ from time to time.
|
||||
|
||||
There are still a few nice to have features missing that some users may notice:
|
||||
|
||||
- Outgoing read receipts and typing indicators (receiving works)
|
||||
|
||||
#### What's different about your fork than upstream Conduit?
|
||||
|
||||
See [DIFFERENCES.md](DIFFERENCES.md)
|
||||
- Outgoing typing indicators (receiving works)
|
||||
|
||||
#### Why does this fork exist? Why don't you contribute back upstream?
|
||||
|
||||
@@ -45,16 +49,9 @@ I now intend on contributing back as time and mental energy sees fit, but my for
|
||||
- Have a **stable** testing ground for some MRs or new features and bug fixes
|
||||
|
||||
And various other reasons that may not be listed here.
|
||||
<!-- ANCHOR_END: body -->
|
||||
|
||||
#### How can I deploy my own?
|
||||
|
||||
conduwuit officially supports Linux, macOS, BSD, and Windows.
|
||||
|
||||
- Simple install (this was tested the most): [DEPLOY.md](DEPLOY.md)
|
||||
- Nix/NixOS (and binary cache): [nix/README.md](nix/README.md)
|
||||
|
||||
If you want to connect an Appservice to Conduit, take a look at [APPSERVICES.md](APPSERVICES.md).
|
||||
|
||||
<!-- ANCHOR: footer -->
|
||||
#### How can I contribute?
|
||||
|
||||
1. Look for an issue you would like to work on and make sure it's not assigned
|
||||
@@ -72,9 +69,9 @@ If you run into any question, feel free to
|
||||
|
||||
#### Donate
|
||||
|
||||
Liberapay: <https://liberapay.com/girlbossceo>\
|
||||
Ko-fi: <https://ko-fi.com/puppygock>\
|
||||
GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
|
||||
- Liberapay: <https://liberapay.com/girlbossceo>
|
||||
- Ko-fi: <https://ko-fi.com/puppygock>
|
||||
- GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
|
||||
|
||||
#### Logo
|
||||
|
||||
@@ -86,8 +83,9 @@ Both.
|
||||
|
||||
#### Mirrors of conduwuit
|
||||
|
||||
GitHub: <https://github.com/girlbossceo/conduwuit>\
|
||||
GitLab: <https://gitlab.com/girlbossceo/conduwuit>\
|
||||
git.gay: <https://git.gay/june/conduwuit>\
|
||||
Codeberg: <https://codeberg.org/girlbossceo/conduwuit>\
|
||||
sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
|
||||
- GitHub: <https://github.com/girlbossceo/conduwuit>
|
||||
- GitLab: <https://gitlab.com/girlbossceo/conduwuit>
|
||||
- git.gay: <https://git.gay/june/conduwuit>
|
||||
- Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
|
||||
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
|
||||
<!-- ANCHOR_END: footer -->
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[book]
|
||||
title = "conduwuit"
|
||||
description = "conduwuit, which is a fork of Conduit, is a simple, fast and reliable chat server for the Matrix protocol"
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "docs"
|
||||
|
||||
[build]
|
||||
build-dir = "public"
|
||||
create-missing = true
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/girlbossceo/conduwuit"
|
||||
edit-url-template = "https://github.com/girlbossceo/conduwuit/edit/main/{path}"
|
||||
git-repository-icon = "fa-github-square"
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 15
|
||||
+27
-6
@@ -161,7 +161,19 @@ registration_token = "change this token for something specific to your server"
|
||||
# controls whether non-admin local users are forbidden from sending room invites (local and remote),
|
||||
# and if non-admin users can receive remote room invites. admins are always allowed to send and receive all room invites.
|
||||
# defaults to false
|
||||
# block_non_admin_invites = falsse
|
||||
# block_non_admin_invites = false
|
||||
|
||||
# List of forbidden username patterns/strings. Values in this list are matched as *contains*.
|
||||
# This is checked upon username availability check, registration, and startup as warnings if any local users in your database
|
||||
# have a forbidden username.
|
||||
# No default.
|
||||
# forbidden_usernames = []
|
||||
|
||||
# List of forbidden room aliases and room IDs as patterns/strings. Values in this list are matched as *contains*.
|
||||
# This is checked upon room alias creation, custom room ID creation if used, and startup as warnings if any room aliases
|
||||
# in your database have a forbidden room alias/ID.
|
||||
# No default.
|
||||
# forbidden_alias_names = []
|
||||
|
||||
# Set this to true to allow your server's public room directory to be federated.
|
||||
# Set this to false to protect against /publicRooms spiders, but will forbid external users
|
||||
@@ -251,6 +263,12 @@ url_preview_check_root_domain = false
|
||||
# Defaults to true as this is the fastest option for federation.
|
||||
#query_trusted_key_servers_first = true
|
||||
|
||||
# List/vector of room **IDs** that conduwuit will make newly registered users join.
|
||||
# The room IDs specified must be rooms that you have joined at least once on the server, and must be public.
|
||||
#
|
||||
# No default.
|
||||
#auto_join_rooms = []
|
||||
|
||||
|
||||
|
||||
### Generic database options
|
||||
@@ -262,8 +280,8 @@ url_preview_check_root_domain = false
|
||||
|
||||
# Set this to any float value in megabytes for conduwuit to tell the database engine that this much memory is available for database-related caches.
|
||||
# May be useful if you have significant memory to spare to increase performance.
|
||||
# Defaults to 300.0
|
||||
#db_cache_capacity_mb = 300.0
|
||||
# Defaults to 256.0
|
||||
#db_cache_capacity_mb = 256.0
|
||||
|
||||
# Interval in seconds when conduwuit will run database cleanup operations.
|
||||
#
|
||||
@@ -302,7 +320,7 @@ url_preview_check_root_domain = false
|
||||
# Amount of threads that RocksDB will use for parallelism. Set to 0 to use all your physical cores.
|
||||
# Conduit eagerly spawns threads mainly for federation, so it may not be desirable to use all your cores / logical threads.
|
||||
#
|
||||
# Defaults to your CPU physical core count (not logical threads) count divided by 2 (half)
|
||||
# Defaults to your CPU physical core count (not logical threads).
|
||||
#rocksdb_parallelism_threads = 0
|
||||
|
||||
# Maximum number of LOG files RocksDB will keep. This must *not* be set to 0. It must be at least 1.
|
||||
@@ -345,7 +363,7 @@ url_preview_check_root_domain = false
|
||||
|
||||
|
||||
|
||||
### Presence
|
||||
### Presence / Typing Indicators / Read Receipts
|
||||
|
||||
# Config option to control local (your server only) presence updates/requests. Defaults to false.
|
||||
# Note that presence on conduwuit is very fast unlike Synapse's.
|
||||
@@ -373,6 +391,9 @@ url_preview_check_root_domain = false
|
||||
# Config option to control how many seconds before presence updates that you are offline. Defaults to 30 minutes.
|
||||
#presence_offline_timeout_s = 1800
|
||||
|
||||
# Config option to control whether we should receive remote incoming read receipts.
|
||||
# Defaults to true.
|
||||
#allow_incoming_read_receipts = true
|
||||
|
||||
|
||||
# Other options not in [global]:
|
||||
@@ -387,4 +408,4 @@ url_preview_check_root_domain = false
|
||||
# Whether to listen and allow for HTTP and HTTPS connections (insecure!)
|
||||
# This config option is only available if conduwuit was built with `axum_dual_protocol` feature (not default feature)
|
||||
# Defaults to false
|
||||
#dual_protocol = false
|
||||
#dual_protocol = false
|
||||
|
||||
Vendored
+1
-1
@@ -5,7 +5,7 @@ Installation
|
||||
------------
|
||||
|
||||
Information about downloading, building and deploying the Debian package, see
|
||||
the "Installing Conduit" section in [DEPLOY.md](../DEPLOY.md).
|
||||
the "Installing Conduit" section in the Deploying docs.
|
||||
All following sections until "Setting up the Reverse Proxy" be ignored because
|
||||
this is handled automatically by the packaging.
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Summary
|
||||
|
||||
- [Introduction](introduction.md)
|
||||
- [Differences from upstream Conduit](differences.md)
|
||||
|
||||
- [Example configuration](configuration.md)
|
||||
- [Deployment options](deploying.md)
|
||||
- [Simple (Recommended)](deploying/simple.md)
|
||||
- [Debian](deploying/debian.md)
|
||||
- [Docker](deploying/docker.md)
|
||||
- [Nix](deploying/nix.md)
|
||||
- [TURN](turn.md)
|
||||
- [Appservices](appservices.md)
|
||||
@@ -0,0 +1,5 @@
|
||||
# Example configuration
|
||||
|
||||
``` toml
|
||||
{{#include ../conduwuit-example.toml}}
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
# Deployment options
|
||||
|
||||
There are several ways to deploy a Conduit server.
|
||||
|
||||
- [Simple (Recommended)](deploying/simple.md) - This is the recommended way to set up Conduit.
|
||||
- [Debian](deploying/debian.md) - If you're using a debian-based system, you may find the `deb` package useful.
|
||||
- [Docker](deploying/docker.md) - We provide multi-arch OCI images for Conduit.
|
||||
- [Nix](deploying/nix.md) - Nix flake based setup.
|
||||
@@ -0,0 +1 @@
|
||||
{{#include ../../debian/README.md}}
|
||||
@@ -70,7 +70,7 @@ docker run -d -p 8448:6167 \
|
||||
|
||||
or you can use [docker-compose](#docker-compose).
|
||||
|
||||
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [here](../conduwuit-example.toml).
|
||||
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [here](../configuration.md).
|
||||
You can pass in different env vars to change config values on the fly. You can even configure Conduit completely by using env vars, but for that you need
|
||||
to pass `-e CONDUIT_CONFIG=""` into your container. For an overview of possible values, please take a look at the `docker-compose.yml` file.
|
||||
|
||||
@@ -89,7 +89,7 @@ When picking the traefik-related compose file, rename it so it matches `docker-c
|
||||
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
|
||||
for your server.
|
||||
|
||||
Additional info about deploying Conduit can be found [here](../DEPLOY.md).
|
||||
Additional info about deploying Conduit can be found [here](simple.md).
|
||||
|
||||
### Build
|
||||
|
||||
@@ -131,7 +131,7 @@ So...step by step:
|
||||
1. Copy [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
||||
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and [`docker-compose.override.yml`](docker-compose.override.yml) from the repository and remove `.for-traefik` (or `.with-traefik`) from the filename.
|
||||
2. Open both files and modify/adjust them to your needs. Meaning, change the `CONDUIT_SERVER_NAME` and the volume host mappings according to your needs.
|
||||
3. Create the `conduit.toml` config file, an example can be found [here](../conduwuit-example.toml), or set `CONDUIT_CONFIG=""` and configure Conduit per env vars.
|
||||
3. Create the `conduit.toml` config file, an example can be found [here](../configuration.md), or set `CONDUIT_CONFIG=""` and configure Conduit per env vars.
|
||||
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
|
||||
5. Create the files needed by the `well-known` service.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# Deploying Conduit
|
||||
# Simple setup
|
||||
|
||||
This is the recommended way to set up Conduit. It is the easiest way to get started and is suitable for most use cases.
|
||||
|
||||
### Please note that this documentation is not fully representative of conduwuit at the moment. Assume majority of it is outdated.
|
||||
|
||||
@@ -118,7 +120,7 @@ Now we need to create the Conduit's config file in `/etc/conduwuit/conduwuit.tom
|
||||
to read it. You need to change at least the server name.**
|
||||
You can also choose to use a different database backend, but right now only `rocksdb` and `sqlite` are recommended.
|
||||
|
||||
See the following example config at [conduwuit-example.toml](conduwuit-example.toml)
|
||||
See the following example config at [conduwuit-example.toml](../configuration.md)
|
||||
|
||||
## Setting the correct file permissions
|
||||
|
||||
@@ -282,7 +284,7 @@ $ sudo systemctl enable conduit
|
||||
|
||||
## How do I know it works?
|
||||
|
||||
You can open <https://app.element.io>, enter your homeserver and try to register.
|
||||
You can open [a Matrix client](https://matrix.org/ecosystem/clients), enter your homeserver and try to register.
|
||||
|
||||
You can also use these commands as a quick health check.
|
||||
|
||||
@@ -300,8 +302,8 @@ $ curl https://your.server.name:8448/_matrix/client/versions
|
||||
|
||||
## Audio/Video calls
|
||||
|
||||
For Audio/Video call functionality see the [TURN Guide](TURN.md).
|
||||
For Audio/Video call functionality see the [TURN Guide](../turn.md).
|
||||
|
||||
## Appservices
|
||||
|
||||
If you want to set up an appservice, take a look at the [Appservice Guide](APPSERVICES.md).
|
||||
If you want to set up an appservice, take a look at the [Appservice Guide](../appservices.md).
|
||||
@@ -82,3 +82,9 @@
|
||||
- Admin debug command to fetch a PDU from a remote server and inserts it into our database/timeline
|
||||
- Update rusqlite/sqlite (not that you should be using it)
|
||||
- Disable update check by default as it's not useful for conduwuit
|
||||
- Declare various missing server capabilities at `/_matrix/client/v3/capabilities` which also fixes FluffyChat password resets, and other clients that expose basic features based on this endpoint's response
|
||||
- Config option to disable incoming remote read receipts if desired
|
||||
- Extend clear cache admin command to support clearing DNS and TLS name override caches
|
||||
- Responsive outgoing read receipt EDU support
|
||||
- Federation destination DNS cache support to call *less* of the thread-blocking `getaddrinfo(3)` significantly less, improving federation ping/latency
|
||||
- Store the sender user with the MXC URL for all media uploads (`/upload`) (not for thumbnails or media requests which are unauthenticated)
|
||||
@@ -0,0 +1,17 @@
|
||||
# Conduit
|
||||
|
||||
{{#include ../README.md:catchphrase}}
|
||||
|
||||
{{#include ../README.md:body}}
|
||||
|
||||
#### What's different about your fork than upstream Conduit?
|
||||
|
||||
See [differences.md](differences.md)
|
||||
|
||||
#### How can I deploy my own?
|
||||
|
||||
- [Deployment options](deploying.md)
|
||||
|
||||
If you want to connect an Appservice to Conduit, take a look at the [appservices documentation](appservices.md).
|
||||
|
||||
{{#include ../README.md:footer}}
|
||||
+10
@@ -50,6 +50,11 @@ name = "cargo-deb"
|
||||
group = "versions"
|
||||
script = "cargo deb --version"
|
||||
|
||||
[[task]]
|
||||
name = "lychee"
|
||||
group = "versions"
|
||||
script = "lychee --version"
|
||||
|
||||
[[task]]
|
||||
name = "cargo-audit"
|
||||
group = "security"
|
||||
@@ -77,6 +82,11 @@ name = "cargo-clippy"
|
||||
group = "lints"
|
||||
script = "cargo clippy --workspace --all-targets --all-features --color=always -- -D warnings"
|
||||
|
||||
[[task]]
|
||||
name = "lychee"
|
||||
group = "lints"
|
||||
script = "lychee --offline docs"
|
||||
|
||||
[[task]]
|
||||
name = "cargo"
|
||||
group = "tests"
|
||||
|
||||
@@ -208,6 +208,35 @@
|
||||
packages = {
|
||||
default = package pkgsHost;
|
||||
oci-image = mkOciImage pkgsHost self.packages.${system}.default;
|
||||
|
||||
book =
|
||||
let
|
||||
package = self.packages.${system}.default;
|
||||
in
|
||||
pkgsHost.stdenv.mkDerivation {
|
||||
pname = "${package.pname}-book";
|
||||
version = package.version;
|
||||
|
||||
src = nix-filter {
|
||||
root = ./.;
|
||||
include = [
|
||||
"book.toml"
|
||||
"conduwuit-example.toml"
|
||||
"README.md"
|
||||
"debian/README.md"
|
||||
"docs"
|
||||
];
|
||||
};
|
||||
|
||||
nativeBuildInputs = (with pkgsHost; [
|
||||
mdbook
|
||||
]);
|
||||
|
||||
buildPhase = ''
|
||||
mdbook build
|
||||
mv public $out
|
||||
'';
|
||||
};
|
||||
}
|
||||
//
|
||||
builtins.listToAttrs
|
||||
@@ -276,6 +305,9 @@
|
||||
|
||||
# Needed for our script for Complement
|
||||
jq
|
||||
|
||||
# Needed for finding broken markdown links
|
||||
lychee
|
||||
]);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -12,10 +12,13 @@ use ruma::{
|
||||
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
|
||||
push, UserId,
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::{api::client_server, services, utils, Error, Result, Ruma};
|
||||
use crate::{
|
||||
api::client_server::{self, join_room_by_id_helper},
|
||||
services, utils, Error, Result, Ruma,
|
||||
};
|
||||
|
||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
|
||||
@@ -275,10 +278,43 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
|
||||
|
||||
// If this is the first real user, grant them admin privileges except for guest
|
||||
// users Note: the server user, @conduit:servername, is generated first
|
||||
if services().users.count()? == 2 && !is_guest {
|
||||
services().admin.make_user_admin(&user_id, displayname).await?;
|
||||
if !is_guest {
|
||||
if let Some(admin_room) = services().admin.get_admin_room()? {
|
||||
if services().rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
|
||||
services().admin.make_user_admin(&user_id, displayname).await?;
|
||||
|
||||
warn!("Granting {} admin privileges as the first user", user_id);
|
||||
warn!("Granting {} admin privileges as the first user", user_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !services().globals.config.auto_join_rooms.is_empty() {
|
||||
for room in &services().globals.config.auto_join_rooms {
|
||||
if !services().rooms.state_cache.server_in_room(services().globals.server_name(), room)? {
|
||||
warn!("Skipping room {room} to automatically join as we have never joined before.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(room_id_server_name) = room.server_name() {
|
||||
match join_room_by_id_helper(
|
||||
Some(&user_id),
|
||||
room,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
error!("Failed to automatically join room {room} for user {user_id}: {e}");
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(register::v3::Response {
|
||||
|
||||
@@ -21,7 +21,7 @@ pub async fn create_alias_route(body: Ruma<create_alias::v3::Request>) -> Result
|
||||
return Err(Error::BadRequest(ErrorKind::InvalidParam, "Alias is from another server."));
|
||||
}
|
||||
|
||||
if services().globals.forbidden_room_names().is_match(body.room_alias.alias()) {
|
||||
if services().globals.forbidden_alias_names().is_match(body.room_alias.alias()) {
|
||||
return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias is forbidden."));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use ruma::api::client::discovery::get_capabilities::{
|
||||
self, Capabilities, RoomVersionStability, RoomVersionsCapability,
|
||||
self, Capabilities, ChangePasswordCapability, RoomVersionStability, RoomVersionsCapability, SetAvatarUrlCapability,
|
||||
SetDisplayNameCapability, ThirdPartyIdChangesCapability,
|
||||
};
|
||||
|
||||
use crate::{services, Result, Ruma};
|
||||
|
||||
/// # `GET /_matrix/client/r0/capabilities`
|
||||
/// # `GET /_matrix/client/v3/capabilities`
|
||||
///
|
||||
/// Get information on the supported feature set and other relevent capabilities
|
||||
/// of this server.
|
||||
@@ -27,6 +28,23 @@ pub async fn get_capabilities_route(
|
||||
available,
|
||||
};
|
||||
|
||||
capabilities.change_password = ChangePasswordCapability {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
capabilities.set_avatar_url = SetAvatarUrlCapability {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
capabilities.set_displayname = SetDisplayNameCapability {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
// conduit does not implement 3PID stuff
|
||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
Ok(get_capabilities::v3::Response {
|
||||
capabilities,
|
||||
})
|
||||
|
||||
@@ -313,7 +313,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
||||
(
|
||||
server,
|
||||
tokio::time::timeout(
|
||||
Duration::from_secs(50),
|
||||
Duration::from_secs(90),
|
||||
services().sending.send_federation_request(
|
||||
server,
|
||||
federation::keys::get_keys::v1::Request {
|
||||
@@ -323,7 +323,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("get_keys_helper query took too long: {}", e);
|
||||
error!("get_keys_helper query took too long: {e}");
|
||||
Error::BadServerResponse("get_keys_helper query took too long")
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -138,6 +138,8 @@ pub async fn get_media_preview_v1_route(
|
||||
/// - Some metadata will be saved in the database
|
||||
/// - Media will be saved in the media/ directory
|
||||
pub async fn create_content_route(body: Ruma<create_content::v3::Request>) -> Result<create_content::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let mxc = format!(
|
||||
"mxc://{}/{}",
|
||||
services().globals.server_name(),
|
||||
@@ -147,6 +149,7 @@ pub async fn create_content_route(body: Ruma<create_content::v3::Request>) -> Re
|
||||
services()
|
||||
.media
|
||||
.create(
|
||||
Some(sender_user.clone()),
|
||||
mxc.clone(),
|
||||
body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(),
|
||||
body.content_type.as_deref(),
|
||||
@@ -175,6 +178,8 @@ pub async fn create_content_route(body: Ruma<create_content::v3::Request>) -> Re
|
||||
pub async fn create_content_v1_route(
|
||||
body: Ruma<create_content::v3::Request>,
|
||||
) -> Result<RumaResponse<create_content::v3::Response>> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let mxc = format!(
|
||||
"mxc://{}/{}",
|
||||
services().globals.server_name(),
|
||||
@@ -184,6 +189,7 @@ pub async fn create_content_v1_route(
|
||||
services()
|
||||
.media
|
||||
.create(
|
||||
Some(sender_user.clone()),
|
||||
mxc.clone(),
|
||||
body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(),
|
||||
body.content_type.as_deref(),
|
||||
@@ -231,6 +237,7 @@ pub async fn get_remote_content(
|
||||
services()
|
||||
.media
|
||||
.create(
|
||||
None,
|
||||
mxc.to_owned(),
|
||||
content_response.content_disposition.as_deref(),
|
||||
content_response.content_type.as_deref(),
|
||||
@@ -484,6 +491,7 @@ pub async fn get_content_thumbnail_route(
|
||||
services()
|
||||
.media
|
||||
.upload_thumbnail(
|
||||
None,
|
||||
mxc,
|
||||
None,
|
||||
get_thumbnail_response.content_type.as_deref(),
|
||||
@@ -566,6 +574,7 @@ pub async fn get_content_thumbnail_v1_route(
|
||||
services()
|
||||
.media
|
||||
.upload_thumbnail(
|
||||
None,
|
||||
mxc,
|
||||
None,
|
||||
get_thumbnail_response.content_type.as_deref(),
|
||||
@@ -589,7 +598,7 @@ async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPrevie
|
||||
utils::random_string(MXC_LENGTH)
|
||||
);
|
||||
|
||||
services().media.create(mxc.clone(), None, None, &image).await?;
|
||||
services().media.create(None, mxc.clone(), None, None, &image).await?;
|
||||
|
||||
let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
|
||||
Err(_) => (None, None),
|
||||
|
||||
@@ -474,7 +474,7 @@ pub async fn joined_members_route(body: Ruma<joined_members::v3::Request>) -> Re
|
||||
})
|
||||
}
|
||||
|
||||
async fn join_room_by_id_helper(
|
||||
pub(crate) async fn join_room_by_id_helper(
|
||||
sender_user: Option<&UserId>, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
|
||||
_third_party_signed: Option<&ThirdPartySigned>,
|
||||
) -> Result<join_room_by_id::v3::Response> {
|
||||
|
||||
@@ -81,6 +81,8 @@ pub async fn set_read_marker_route(body: Ruma<set_read_marker::v3::Request>) ->
|
||||
room_id: body.room_id.clone(),
|
||||
},
|
||||
)?;
|
||||
|
||||
services().sending.flush_room(&body.room_id)?;
|
||||
}
|
||||
|
||||
Ok(set_read_marker::v3::Response {})
|
||||
@@ -136,6 +138,8 @@ pub async fn create_receipt_route(body: Ruma<create_receipt::v3::Request>) -> Re
|
||||
room_id: body.room_id.clone(),
|
||||
},
|
||||
)?;
|
||||
|
||||
services().sending.flush_room(&body.room_id)?;
|
||||
},
|
||||
create_receipt::v3::ReceiptType::ReadPrivate => {
|
||||
let count = services()
|
||||
|
||||
@@ -80,7 +80,7 @@ pub async fn create_room_route(body: Ruma<create_room::v3::Request>) -> Result<c
|
||||
}
|
||||
|
||||
// apply forbidden room alias checks to custom room IDs too
|
||||
if services().globals.forbidden_room_names().is_match(&custom_room_id_s) {
|
||||
if services().globals.forbidden_alias_names().is_match(&custom_room_id_s) {
|
||||
return Err(Error::BadRequest(ErrorKind::Unknown, "Custom room ID is forbidden."));
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ pub async fn create_room_route(body: Ruma<create_room::v3::Request>) -> Result<c
|
||||
}
|
||||
|
||||
// check if room alias is forbidden
|
||||
if services().globals.forbidden_room_names().is_match(localpart) {
|
||||
if services().globals.forbidden_alias_names().is_match(localpart) {
|
||||
return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias name is forbidden."));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
use ruma::api::client::space::get_hierarchy;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{services, Result, Ruma};
|
||||
use ruma::{
|
||||
api::client::{error::ErrorKind, space::get_hierarchy},
|
||||
UInt,
|
||||
};
|
||||
|
||||
use crate::{service::rooms::spaces::PagnationToken, services, Error, Result, Ruma};
|
||||
|
||||
/// # `GET /_matrix/client/v1/rooms/{room_id}/hierarchy``
|
||||
///
|
||||
@@ -9,11 +14,32 @@ use crate::{services, Result, Ruma};
|
||||
pub async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>) -> Result<get_hierarchy::v1::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let skip = body.from.as_ref().and_then(|s| s.parse::<usize>().ok()).unwrap_or(0);
|
||||
let limit = body.limit.unwrap_or_else(|| UInt::from(10_u32)).min(UInt::from(100_u32));
|
||||
|
||||
let limit = body.limit.map_or(10, u64::from).min(100) as usize;
|
||||
let max_depth = body.max_depth.unwrap_or_else(|| UInt::from(3_u32)).min(UInt::from(10_u32));
|
||||
|
||||
let max_depth = body.max_depth.map_or(3, u64::from).min(10) as usize + 1; // +1 to skip the space room itself
|
||||
let key = body.from.as_ref().and_then(|s| PagnationToken::from_str(s).ok());
|
||||
|
||||
services().rooms.spaces.get_hierarchy(sender_user, &body.room_id, limit, skip, max_depth, body.suggested_only).await
|
||||
// Should prevent unexpeded behaviour in (bad) clients
|
||||
if let Some(ref token) = key {
|
||||
if token.suggested_only != body.suggested_only || token.max_depth != max_depth {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"suggested_only and max_depth cannot change on paginated requests",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.spaces
|
||||
.get_client_hierarchy(
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
u64::from(limit) as usize,
|
||||
key.map_or(0, |token| u64::from(token.skip) as usize),
|
||||
u64::from(max_depth) as usize,
|
||||
body.suggested_only,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ pub async fn send_state_event_for_empty_key_route(
|
||||
.into())
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/rooms/{roomid}/state`
|
||||
/// # `GET /_matrix/client/v3/rooms/{roomid}/state`
|
||||
///
|
||||
/// Get all state events for a room.
|
||||
///
|
||||
|
||||
@@ -1333,9 +1333,7 @@ pub async fn sync_events_v4_route(
|
||||
ruma::JsOption::Some(heroes_avatar)
|
||||
} else {
|
||||
match services().rooms.state_accessor.get_avatar(room_id)? {
|
||||
ruma::JsOption::Some(avatar) => {
|
||||
avatar.url.map_or(ruma::JsOption::Undefined, ruma::JsOption::Some)
|
||||
},
|
||||
ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url),
|
||||
ruma::JsOption::Null => ruma::JsOption::Null,
|
||||
ruma::JsOption::Undefined => ruma::JsOption::Undefined,
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ pub async fn get_supported_versions_route(
|
||||
],
|
||||
unstable_features: BTreeMap::from_iter([
|
||||
("org.matrix.e2e_cross_signing".to_owned(), true),
|
||||
//("org.matrix.msc2285.stable".to_owned(), true),
|
||||
("org.matrix.msc2836".to_owned(), true),
|
||||
("org.matrix.msc3827".to_owned(), true),
|
||||
("org.matrix.msc2946".to_owned(), true),
|
||||
|
||||
@@ -84,8 +84,7 @@ where
|
||||
appservice_registration
|
||||
{
|
||||
match metadata.authentication {
|
||||
// TODO: verify if just or'ing `AuthScheme::AppserviceToken` is correct here
|
||||
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken => {
|
||||
AuthScheme::AccessToken => {
|
||||
let user_id = query_params.user_id.map_or_else(
|
||||
|| {
|
||||
UserId::parse_with_server_name(
|
||||
@@ -97,6 +96,8 @@ where
|
||||
|s| UserId::parse(s).unwrap(),
|
||||
);
|
||||
|
||||
debug!("User ID: {:?}", user_id);
|
||||
|
||||
if !services().users.exists(&user_id)? {
|
||||
return Err(Error::BadRequest(ErrorKind::Forbidden, "User does not exist."));
|
||||
}
|
||||
@@ -104,6 +105,27 @@ where
|
||||
// TODO: Check if appservice is allowed to be that user
|
||||
(Some(user_id), None, None, true)
|
||||
},
|
||||
AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken => {
|
||||
let user_id = query_params.user_id.map_or_else(
|
||||
|| {
|
||||
UserId::parse_with_server_name(
|
||||
registration.sender_localpart.as_str(),
|
||||
services().globals.server_name(),
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
|s| UserId::parse(s).unwrap(),
|
||||
);
|
||||
|
||||
debug!("User ID: {:?}", user_id);
|
||||
|
||||
if !services().users.exists(&user_id)? {
|
||||
(None, None, None, true)
|
||||
} else {
|
||||
// TODO: Check if appservice is allowed to be that user
|
||||
(Some(user_id), None, None, true)
|
||||
}
|
||||
},
|
||||
AuthScheme::ServerSignatures => (None, None, None, true),
|
||||
AuthScheme::None => (None, None, None, true),
|
||||
}
|
||||
@@ -153,6 +175,10 @@ where
|
||||
// treat non-appservice registrations as None authentication
|
||||
AuthScheme::AppserviceToken => (None, None, None, false),
|
||||
AuthScheme::ServerSignatures => {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let TypedHeader(Authorization(x_matrix)) =
|
||||
parts.extract::<TypedHeader<Authorization<XMatrix>>>().await.map_err(|e| {
|
||||
warn!("Missing or invalid Authorization header: {}", e);
|
||||
|
||||
+71
-102
@@ -13,6 +13,7 @@ use std::{
|
||||
use axum::{response::IntoResponse, Json};
|
||||
use futures_util::future::TryFutureExt;
|
||||
use get_profile_information::v1::ProfileField;
|
||||
use hickory_resolver::{error::ResolveError, lookup::SrvLookup};
|
||||
use http::header::{HeaderValue, AUTHORIZATION};
|
||||
use ipaddress::IPAddress;
|
||||
use ruma::{
|
||||
@@ -28,6 +29,7 @@ use ruma::{
|
||||
keys::{claim_keys, get_keys},
|
||||
membership::{create_invite, create_join_event, prepare_join_event},
|
||||
query::{get_profile_information, get_room_information},
|
||||
space::get_hierarchy,
|
||||
transactions::{
|
||||
edu::{DeviceListUpdateContent, DirectDeviceContent, Edu, SigningKeyUpdateContent},
|
||||
send_transaction_message,
|
||||
@@ -52,7 +54,6 @@ use ruma::{
|
||||
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use trust_dns_resolver::{error::ResolveError, lookup::SrvLookup};
|
||||
|
||||
use crate::{
|
||||
api::client_server::{self, claim_keys_helper, get_keys_helper},
|
||||
@@ -364,7 +365,10 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
||||
None => {
|
||||
if let Some(pos) = destination_str.find(':') {
|
||||
debug!("2: Hostname with included port");
|
||||
|
||||
let (host, port) = destination_str.split_at(pos);
|
||||
query_and_cache_override(host, host, port.parse::<u16>().unwrap_or(8448)).await;
|
||||
|
||||
FedDest::Named(host.to_owned(), port.to_owned())
|
||||
} else {
|
||||
debug!("Requesting well known for {destination}");
|
||||
@@ -377,30 +381,23 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
||||
None => {
|
||||
if let Some(pos) = delegated_hostname.find(':') {
|
||||
debug!("3.2: Hostname with port in .well-known file");
|
||||
|
||||
let (host, port) = delegated_hostname.split_at(pos);
|
||||
query_and_cache_override(host, host, port.parse::<u16>().unwrap_or(8448)).await;
|
||||
|
||||
FedDest::Named(host.to_owned(), port.to_owned())
|
||||
} else {
|
||||
debug!("Delegated hostname has no port in this branch");
|
||||
if let Some(hostname_override) = query_srv_record(&delegated_hostname).await {
|
||||
debug!("3.3: SRV lookup successful");
|
||||
let force_port = hostname_override.port();
|
||||
|
||||
if let Ok(override_ip) = services()
|
||||
.globals
|
||||
.dns_resolver()
|
||||
.lookup_ip(hostname_override.hostname())
|
||||
.await
|
||||
{
|
||||
services().globals.tls_name_override.write().unwrap().insert(
|
||||
delegated_hostname.clone(),
|
||||
(override_ip.iter().collect(), force_port.unwrap_or(8448)),
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Using SRV record {}, but could not resolve to IP",
|
||||
hostname_override.hostname()
|
||||
);
|
||||
}
|
||||
let force_port = hostname_override.port();
|
||||
query_and_cache_override(
|
||||
&delegated_hostname,
|
||||
&hostname_override.hostname(),
|
||||
force_port.unwrap_or(8448),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(port) = force_port {
|
||||
FedDest::Named(delegated_hostname, format!(":{port}"))
|
||||
@@ -409,6 +406,7 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
||||
}
|
||||
} else {
|
||||
debug!("3.4: No SRV records, just use the hostname from .well-known");
|
||||
query_and_cache_override(&delegated_hostname, &delegated_hostname, 8448).await;
|
||||
add_port_to_hostname(&delegated_hostname)
|
||||
}
|
||||
}
|
||||
@@ -420,21 +418,14 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
||||
match query_srv_record(&destination_str).await {
|
||||
Some(hostname_override) => {
|
||||
debug!("4: SRV record found");
|
||||
let force_port = hostname_override.port();
|
||||
|
||||
if let Ok(override_ip) =
|
||||
services().globals.dns_resolver().lookup_ip(hostname_override.hostname()).await
|
||||
{
|
||||
services().globals.tls_name_override.write().unwrap().insert(
|
||||
hostname.clone(),
|
||||
(override_ip.iter().collect(), force_port.unwrap_or(8448)),
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"Using SRV record {}, but could not resolve to IP",
|
||||
hostname_override.hostname()
|
||||
);
|
||||
}
|
||||
let force_port = hostname_override.port();
|
||||
query_and_cache_override(
|
||||
&hostname,
|
||||
&hostname_override.hostname(),
|
||||
force_port.unwrap_or(8448),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(port) = force_port {
|
||||
FedDest::Named(hostname.clone(), format!(":{port}"))
|
||||
@@ -444,6 +435,7 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
||||
},
|
||||
None => {
|
||||
debug!("5: No SRV record found");
|
||||
query_and_cache_override(&destination_str, &destination_str, 8448).await;
|
||||
add_port_to_hostname(&destination_str)
|
||||
},
|
||||
}
|
||||
@@ -452,7 +444,6 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
||||
}
|
||||
},
|
||||
};
|
||||
debug!("Actual destination: {actual_destination:?}");
|
||||
|
||||
// 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
|
||||
@@ -466,9 +457,29 @@ async fn find_actual_destination(destination: &'_ ServerName) -> (FedDest, FedDe
|
||||
} else {
|
||||
FedDest::Named(hostname, ":8448".to_owned())
|
||||
};
|
||||
|
||||
debug!("Actual destination: {actual_destination:?} hostname: {hostname:?}");
|
||||
(actual_destination, hostname)
|
||||
}
|
||||
|
||||
async fn query_and_cache_override(overname: &'_ str, hostname: &'_ str, port: u16) {
|
||||
match services().globals.dns_resolver().lookup_ip(hostname.to_owned()).await {
|
||||
Ok(override_ip) => {
|
||||
debug!("Caching result of {:?} overriding {:?}", hostname, overname);
|
||||
|
||||
services()
|
||||
.globals
|
||||
.tls_name_override
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(overname.to_owned(), (override_ip.iter().collect(), port));
|
||||
},
|
||||
Err(e) => {
|
||||
debug!("Got {:?} for {:?} to override {:?}", e.kind(), hostname, overname);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
|
||||
fn handle_successful_srv(srv: &SrvLookup) -> Option<FedDest> {
|
||||
srv.iter().next().map(|result| {
|
||||
@@ -500,6 +511,10 @@ async fn query_srv_record(hostname: &'_ str) -> Option<FedDest> {
|
||||
}
|
||||
|
||||
async fn request_well_known(destination: &str) -> Option<String> {
|
||||
if !services().globals.tls_name_override.read().unwrap().contains_key(destination) {
|
||||
query_and_cache_override(destination, destination, 8448).await;
|
||||
}
|
||||
|
||||
let response = services()
|
||||
.globals
|
||||
.default_client()
|
||||
@@ -619,10 +634,6 @@ pub async fn get_server_keys_deprecated_route() -> impl IntoResponse { get_serve
|
||||
pub async fn get_public_rooms_filtered_route(
|
||||
body: Ruma<get_public_rooms_filtered::v1::Request>,
|
||||
) -> Result<get_public_rooms_filtered::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if !services().globals.allow_public_room_directory_over_federation() {
|
||||
return Err(Error::bad_config("Room directory is not public."));
|
||||
}
|
||||
@@ -650,10 +661,6 @@ pub async fn get_public_rooms_filtered_route(
|
||||
pub async fn get_public_rooms_route(
|
||||
body: Ruma<get_public_rooms::v1::Request>,
|
||||
) -> Result<get_public_rooms::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if !services().globals.allow_public_room_directory_over_federation() {
|
||||
return Err(Error::bad_config("Room directory is not public."));
|
||||
}
|
||||
@@ -707,10 +714,6 @@ pub fn parse_incoming_pdu(pdu: &RawJsonValue) -> Result<(OwnedEventId, Canonical
|
||||
pub async fn send_transaction_message_route(
|
||||
body: Ruma<send_transaction_message::v1::Request>,
|
||||
) -> Result<send_transaction_message::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
let mut resolved_map = BTreeMap::new();
|
||||
@@ -820,6 +823,10 @@ pub async fn send_transaction_message_route(
|
||||
}
|
||||
},
|
||||
Edu::Receipt(receipt) => {
|
||||
if !services().globals.allow_incoming_read_receipts() {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (room_id, room_updates) in receipt.receipts {
|
||||
for (user_id, user_updates) in room_updates.read {
|
||||
if let Some((event_id, _)) = user_updates
|
||||
@@ -946,10 +953,6 @@ pub async fn send_transaction_message_route(
|
||||
/// - Only works if a user of this server is currently invited or joined the
|
||||
/// room
|
||||
pub async fn get_event_route(body: Ruma<get_event::v1::Request>) -> Result<get_event::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
let event = services().rooms.timeline.get_pdu_json(&body.event_id)?.ok_or_else(|| {
|
||||
@@ -985,10 +988,6 @@ pub async fn get_event_route(body: Ruma<get_event::v1::Request>) -> Result<get_e
|
||||
/// Retrieves events from before the sender joined the room, if the room's
|
||||
/// history visibility allows.
|
||||
pub async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) -> Result<get_backfill::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
debug!("Got backfill request from: {}", sender_servername);
|
||||
@@ -1041,10 +1040,6 @@ pub async fn get_backfill_route(body: Ruma<get_backfill::v1::Request>) -> Result
|
||||
pub async fn get_missing_events_route(
|
||||
body: Ruma<get_missing_events::v1::Request>,
|
||||
) -> Result<get_missing_events::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
if !services().rooms.state_cache.server_in_room(sender_servername, &body.room_id)? {
|
||||
@@ -1118,10 +1113,6 @@ pub async fn get_missing_events_route(
|
||||
pub async fn get_event_authorization_route(
|
||||
body: Ruma<get_event_authorization::v1::Request>,
|
||||
) -> Result<get_event_authorization::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
if !services().rooms.state_cache.server_in_room(sender_servername, &body.room_id)? {
|
||||
@@ -1157,10 +1148,6 @@ pub async fn get_event_authorization_route(
|
||||
///
|
||||
/// Retrieves the current state of the room.
|
||||
pub async fn get_room_state_route(body: Ruma<get_room_state::v1::Request>) -> Result<get_room_state::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
if !services().rooms.state_cache.server_in_room(sender_servername, &body.room_id)? {
|
||||
@@ -1211,10 +1198,6 @@ pub async fn get_room_state_route(body: Ruma<get_room_state::v1::Request>) -> Re
|
||||
pub async fn get_room_state_ids_route(
|
||||
body: Ruma<get_room_state_ids::v1::Request>,
|
||||
) -> Result<get_room_state_ids::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
if !services().rooms.state_cache.server_in_room(sender_servername, &body.room_id)? {
|
||||
@@ -1253,10 +1236,6 @@ pub async fn get_room_state_ids_route(
|
||||
pub async fn create_join_event_template_route(
|
||||
body: Ruma<prepare_join_event::v1::Request>,
|
||||
) -> Result<prepare_join_event::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if !services().rooms.metadata.exists(&body.room_id)? {
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server."));
|
||||
}
|
||||
@@ -1343,10 +1322,6 @@ pub async fn create_join_event_template_route(
|
||||
async fn create_join_event(
|
||||
sender_servername: &ServerName, room_id: &RoomId, pdu: &RawJsonValue,
|
||||
) -> Result<create_join_event::v1::RoomState> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if !services().rooms.metadata.exists(room_id)? {
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server."));
|
||||
}
|
||||
@@ -1500,10 +1475,6 @@ pub async fn create_join_event_v2_route(
|
||||
///
|
||||
/// Invites a remote user to a room.
|
||||
pub async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Result<create_invite::v2::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
services().rooms.event_handler.acl_check(sender_servername, &body.room_id)?;
|
||||
@@ -1587,7 +1558,7 @@ pub async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Resu
|
||||
let mut event: JsonObject = serde_json::from_str(body.event.get())
|
||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid invite event bytes."))?;
|
||||
|
||||
event.insert("event_id".to_owned(), "$dummy".into());
|
||||
event.insert("event_id".to_owned(), "$placeholder".into());
|
||||
|
||||
let pdu: PduEvent = serde_json::from_value(event.into()).map_err(|e| {
|
||||
warn!("Invalid invite event: {}", e);
|
||||
@@ -1622,10 +1593,6 @@ pub async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Resu
|
||||
///
|
||||
/// Gets information on all devices of the user.
|
||||
pub async fn get_devices_route(body: Ruma<get_devices::v1::Request>) -> Result<get_devices::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if body.user_id.server_name() != services().globals.server_name() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
@@ -1673,10 +1640,6 @@ pub async fn get_devices_route(body: Ruma<get_devices::v1::Request>) -> Result<g
|
||||
pub async fn get_room_information_route(
|
||||
body: Ruma<get_room_information::v1::Request>,
|
||||
) -> Result<get_room_information::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let room_id = services()
|
||||
.rooms
|
||||
.alias
|
||||
@@ -1695,10 +1658,6 @@ pub async fn get_room_information_route(
|
||||
pub async fn get_profile_information_route(
|
||||
body: Ruma<get_profile_information::v1::Request>,
|
||||
) -> Result<get_profile_information::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if body.user_id.server_name() != services().globals.server_name() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
@@ -1738,10 +1697,6 @@ pub async fn get_profile_information_route(
|
||||
///
|
||||
/// Gets devices and identity keys for the given users.
|
||||
pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_keys::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if body.device_keys.iter().any(|(u, _)| u.server_name() != services().globals.server_name()) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
@@ -1768,10 +1723,6 @@ pub async fn get_keys_route(body: Ruma<get_keys::v1::Request>) -> Result<get_key
|
||||
///
|
||||
/// Claims one-time keys.
|
||||
pub async fn claim_keys_route(body: Ruma<claim_keys::v1::Request>) -> Result<claim_keys::v1::Response> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if body.one_time_keys.iter().any(|(u, _)| u.server_name() != services().globals.server_name()) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
@@ -1788,6 +1739,10 @@ pub async fn claim_keys_route(body: Ruma<claim_keys::v1::Request>) -> Result<cla
|
||||
|
||||
/// # `GET /.well-known/matrix/server`
|
||||
pub async fn well_known_server_route() -> Result<impl IntoResponse> {
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let server_url = match services().globals.well_known_server() {
|
||||
Some(url) => url.clone(),
|
||||
None => return Err(Error::BadRequest(ErrorKind::NotFound, "Not found.")),
|
||||
@@ -1798,6 +1753,20 @@ pub async fn well_known_server_route() -> Result<impl IntoResponse> {
|
||||
})))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/federation/v1/hierarchy/{roomId}`
|
||||
///
|
||||
/// Gets the space tree in a depth-first manner to locate child rooms of a given
|
||||
/// space.
|
||||
pub async fn get_hierarchy_route(body: Ruma<get_hierarchy::v1::Request>) -> Result<get_hierarchy::v1::Response> {
|
||||
let sender_servername = body.sender_servername.as_ref().expect("server is authenticated");
|
||||
|
||||
if services().rooms.metadata.exists(&body.room_id)? {
|
||||
services().rooms.spaces.get_federation_hierarchy(&body.room_id, sender_servername, body.suggested_only).await
|
||||
} else {
|
||||
Err(Error::BadRequest(ErrorKind::NotFound, "Room does not exist."))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{add_port_to_hostname, get_ip_with_port, FedDest};
|
||||
|
||||
+38
-8
@@ -10,7 +10,7 @@ use either::Either;
|
||||
use figment::Figment;
|
||||
use itertools::Itertools;
|
||||
use regex::RegexSet;
|
||||
use ruma::{OwnedServerName, RoomVersionId};
|
||||
use ruma::{OwnedRoomId, OwnedServerName, RoomVersionId};
|
||||
use serde::{de::IgnoredAny, Deserialize};
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
@@ -41,7 +41,10 @@ pub struct Config {
|
||||
pub server_name: OwnedServerName,
|
||||
#[serde(default = "default_database_backend")]
|
||||
pub database_backend: String,
|
||||
pub database_path: String,
|
||||
pub database_path: PathBuf,
|
||||
pub database_backup_path: Option<PathBuf>,
|
||||
#[serde(default = "default_database_backups_to_keep")]
|
||||
pub database_backups_to_keep: i16,
|
||||
#[serde(default = "default_db_cache_capacity_mb")]
|
||||
pub db_cache_capacity_mb: f64,
|
||||
#[serde(default = "default_new_user_displayname_suffix")]
|
||||
@@ -107,6 +110,9 @@ pub struct Config {
|
||||
#[serde(default = "default_turn_ttl")]
|
||||
pub turn_ttl: u64,
|
||||
|
||||
#[serde(default = "Vec::new")]
|
||||
pub auto_join_rooms: Vec<OwnedRoomId>,
|
||||
|
||||
#[serde(default = "default_rocksdb_log_level")]
|
||||
pub rocksdb_log_level: String,
|
||||
#[serde(default = "default_rocksdb_max_log_file_size")]
|
||||
@@ -144,6 +150,9 @@ pub struct Config {
|
||||
#[serde(default = "default_presence_offline_timeout_s")]
|
||||
pub presence_offline_timeout_s: u64,
|
||||
|
||||
#[serde(default = "true_fn")]
|
||||
pub allow_incoming_read_receipts: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub zstd_compression: bool,
|
||||
|
||||
@@ -169,7 +178,7 @@ pub struct Config {
|
||||
|
||||
#[serde(default = "RegexSet::empty")]
|
||||
#[serde(with = "serde_regex")]
|
||||
pub forbidden_room_names: RegexSet,
|
||||
pub forbidden_alias_names: RegexSet,
|
||||
|
||||
#[serde(default = "RegexSet::empty")]
|
||||
#[serde(with = "serde_regex")]
|
||||
@@ -248,7 +257,15 @@ impl fmt::Display for Config {
|
||||
let lines = [
|
||||
("Server name", self.server_name.host()),
|
||||
("Database backend", &self.database_backend),
|
||||
("Database path", &self.database_path),
|
||||
("Database path", &self.database_path.to_string_lossy()),
|
||||
(
|
||||
"Database backup path",
|
||||
match &self.database_backup_path {
|
||||
Some(path) => path.to_str().unwrap(),
|
||||
None => "",
|
||||
},
|
||||
),
|
||||
("Database backups to keep", &self.database_backups_to_keep.to_string()),
|
||||
("Database cache capacity (MB)", &self.db_cache_capacity_mb.to_string()),
|
||||
("Cache capacity modifier", &self.conduit_cache_capacity_modifier.to_string()),
|
||||
("PDU cache capacity", &self.pdu_cache_capacity.to_string()),
|
||||
@@ -282,6 +299,10 @@ impl fmt::Display for Config {
|
||||
"Allow local presence requests (updates)",
|
||||
&self.allow_local_presence.to_string(),
|
||||
),
|
||||
(
|
||||
"Allow incoming remote read receipts",
|
||||
&self.allow_incoming_read_receipts.to_string(),
|
||||
),
|
||||
(
|
||||
"Block non-admin room invites (local and remote, admins can still send and receive invites)",
|
||||
&self.block_non_admin_invites.to_string(),
|
||||
@@ -346,6 +367,13 @@ impl fmt::Display for Config {
|
||||
}
|
||||
&lst.join(", ")
|
||||
}),
|
||||
("Auto Join Rooms", {
|
||||
let mut lst = vec![];
|
||||
for room in &self.auto_join_rooms {
|
||||
lst.push(room);
|
||||
}
|
||||
&lst.into_iter().join(", ")
|
||||
}),
|
||||
#[cfg(feature = "compression-zstd")]
|
||||
("zstd Response Body Compression", &self.zstd_compression.to_string()),
|
||||
#[cfg(feature = "rocksdb")]
|
||||
@@ -398,8 +426,8 @@ impl fmt::Display for Config {
|
||||
("Forbidden usernames", {
|
||||
&self.forbidden_usernames.patterns().iter().join(", ")
|
||||
}),
|
||||
("Forbidden room names", {
|
||||
&self.forbidden_room_names.patterns().iter().join(", ")
|
||||
("Forbidden room aliases", {
|
||||
&self.forbidden_alias_names.patterns().iter().join(", ")
|
||||
}),
|
||||
(
|
||||
"URL preview domain contains allowlist",
|
||||
@@ -439,9 +467,11 @@ fn default_port() -> ListeningPort {
|
||||
|
||||
fn default_unix_socket_perms() -> u32 { 660 }
|
||||
|
||||
fn default_database_backups_to_keep() -> i16 { 1 }
|
||||
|
||||
fn default_database_backend() -> String { "rocksdb".to_owned() }
|
||||
|
||||
fn default_db_cache_capacity_mb() -> f64 { 300.0 }
|
||||
fn default_db_cache_capacity_mb() -> f64 { 256.0 }
|
||||
|
||||
fn default_conduit_cache_capacity_modifier() -> f64 { 1.0 }
|
||||
|
||||
@@ -482,7 +512,7 @@ fn default_rocksdb_max_log_file_size() -> usize {
|
||||
4 * 1024 * 1024
|
||||
}
|
||||
|
||||
fn default_rocksdb_parallelism_threads() -> usize { num_cpus::get_physical() / 2 }
|
||||
fn default_rocksdb_parallelism_threads() -> usize { 0 }
|
||||
|
||||
fn default_rocksdb_compression_algo() -> String { "zstd".to_owned() }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{future::Future, pin::Pin, sync::Arc};
|
||||
use std::{error::Error, future::Future, pin::Pin, sync::Arc};
|
||||
|
||||
use super::Config;
|
||||
use crate::Result;
|
||||
@@ -18,6 +18,8 @@ pub(crate) trait KeyValueDatabaseEngine: Send + Sync {
|
||||
Self: Sized;
|
||||
fn open_tree(&self, name: &'static str) -> Result<Arc<dyn KvTree>>;
|
||||
fn flush(&self) -> Result<()>;
|
||||
#[allow(dead_code)]
|
||||
fn sync(&self) -> Result<()> { Ok(()) }
|
||||
fn cleanup(&self) -> Result<()> { Ok(()) }
|
||||
fn memory_usage(&self) -> Result<String> {
|
||||
Ok("Current database engine does not support memory usage reporting.".to_owned())
|
||||
@@ -25,6 +27,10 @@ pub(crate) trait KeyValueDatabaseEngine: Send + Sync {
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn clear_caches(&self) {}
|
||||
|
||||
fn backup(&self) -> Result<(), Box<dyn Error>> { unimplemented!() }
|
||||
|
||||
fn backup_list(&self) -> Result<String> { Ok(String::new()) }
|
||||
}
|
||||
|
||||
pub(crate) trait KvTree: Send + Sync {
|
||||
@@ -39,16 +45,37 @@ pub(crate) trait KvTree: Send + Sync {
|
||||
}
|
||||
|
||||
fn insert(&self, key: &[u8], value: &[u8]) -> Result<()>;
|
||||
fn insert_batch(&self, iter: &mut dyn Iterator<Item = (Vec<u8>, Vec<u8>)>) -> Result<()>;
|
||||
fn insert_batch(&self, iter: &mut dyn Iterator<Item = (Vec<u8>, Vec<u8>)>) -> Result<()> {
|
||||
for (key, value) in iter {
|
||||
self.insert(&key, &value)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&self, key: &[u8]) -> Result<()>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn remove_batch(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()> {
|
||||
for key in iter {
|
||||
self.remove(&key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a>;
|
||||
|
||||
fn iter_from<'a>(&'a self, from: &[u8], backwards: bool) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a>;
|
||||
|
||||
fn increment(&self, key: &[u8]) -> Result<Vec<u8>>;
|
||||
fn increment_batch(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()>;
|
||||
fn increment_batch(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()> {
|
||||
for key in iter {
|
||||
self.increment(&key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scan_prefix<'a>(&'a self, prefix: Vec<u8>) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a>;
|
||||
|
||||
|
||||
@@ -4,19 +4,24 @@ use std::{
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_rocksdb::{
|
||||
backup::{BackupEngine, BackupEngineOptions},
|
||||
LogLevel::{Debug, Error, Fatal, Info, Warn},
|
||||
WriteBatchWithTransaction,
|
||||
};
|
||||
use tracing::{debug, info};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
use super::{super::Config, watchers::Watchers, KeyValueDatabaseEngine, KvTree};
|
||||
use crate::{utils, Result};
|
||||
|
||||
pub(crate) struct Engine {
|
||||
rocks: rust_rocksdb::DBWithThreadMode<rust_rocksdb::MultiThreaded>,
|
||||
cache: rust_rocksdb::Cache,
|
||||
row_cache: rust_rocksdb::Cache,
|
||||
col_cache: rust_rocksdb::Cache,
|
||||
old_cfs: Vec<String>,
|
||||
opts: rust_rocksdb::Options,
|
||||
env: rust_rocksdb::Env,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
@@ -27,24 +32,13 @@ struct RocksDbEngineTree<'a> {
|
||||
write_lock: RwLock<()>,
|
||||
}
|
||||
|
||||
fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rocksdb::Options {
|
||||
// block-based options: https://docs.rs/rocksdb/latest/rocksdb/struct.BlockBasedOptions.html#
|
||||
let mut block_based_options = rust_rocksdb::BlockBasedOptions::default();
|
||||
|
||||
block_based_options.set_block_cache(rocksdb_cache);
|
||||
|
||||
// "Difference of spinning disk"
|
||||
// https://zhangyuchi.gitbooks.io/rocksdbbook/content/RocksDB-Tuning-Guide.html
|
||||
block_based_options.set_block_size(64 * 1024);
|
||||
block_based_options.set_cache_index_and_filter_blocks(true);
|
||||
|
||||
block_based_options.set_bloom_filter(10.0, false);
|
||||
block_based_options.set_pin_l0_filter_and_index_blocks_in_cache(true);
|
||||
block_based_options.set_optimize_filters_for_memory(true);
|
||||
|
||||
fn db_options(
|
||||
config: &Config, env: &rust_rocksdb::Env, row_cache: &rust_rocksdb::Cache, col_cache: &rust_rocksdb::Cache,
|
||||
) -> rust_rocksdb::Options {
|
||||
// database options: https://docs.rs/rocksdb/latest/rocksdb/struct.Options.html#
|
||||
let mut db_opts = rust_rocksdb::Options::default();
|
||||
|
||||
// Logging
|
||||
let rocksdb_log_level = match config.rocksdb_log_level.as_ref() {
|
||||
"debug" => Debug,
|
||||
"info" => Info,
|
||||
@@ -52,7 +46,52 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
|
||||
"fatal" => Fatal,
|
||||
_ => Error,
|
||||
};
|
||||
db_opts.set_log_level(rocksdb_log_level);
|
||||
db_opts.set_max_log_file_size(config.rocksdb_max_log_file_size);
|
||||
db_opts.set_log_file_time_to_roll(config.rocksdb_log_time_to_roll);
|
||||
db_opts.set_keep_log_file_num(config.rocksdb_max_log_files);
|
||||
|
||||
// Processing
|
||||
let threads = if config.rocksdb_parallelism_threads == 0 {
|
||||
num_cpus::get_physical() // max cores if user specified 0
|
||||
} else {
|
||||
config.rocksdb_parallelism_threads
|
||||
};
|
||||
|
||||
db_opts.set_max_background_jobs(threads.try_into().unwrap());
|
||||
db_opts.set_max_subcompactions(threads.try_into().unwrap());
|
||||
|
||||
// IO
|
||||
db_opts.set_use_direct_reads(true);
|
||||
db_opts.set_use_direct_io_for_flush_and_compaction(true);
|
||||
if config.rocksdb_optimize_for_spinning_disks {
|
||||
db_opts.set_skip_stats_update_on_db_open(true); // speeds up opening DB on hard
|
||||
// drives
|
||||
}
|
||||
|
||||
// Blocks
|
||||
let mut block_based_options = rust_rocksdb::BlockBasedOptions::default();
|
||||
block_based_options.set_block_size(4 * 1024);
|
||||
block_based_options.set_metadata_block_size(4 * 1024);
|
||||
block_based_options.set_bloom_filter(9.6, true);
|
||||
block_based_options.set_optimize_filters_for_memory(true);
|
||||
block_based_options.set_cache_index_and_filter_blocks(true);
|
||||
block_based_options.set_pin_top_level_index_and_filter(true);
|
||||
block_based_options.set_block_cache(col_cache);
|
||||
db_opts.set_row_cache(row_cache);
|
||||
|
||||
// Buffers
|
||||
db_opts.set_write_buffer_size(2 * 1024 * 1024);
|
||||
db_opts.set_max_write_buffer_number(2);
|
||||
db_opts.set_min_write_buffer_number(1);
|
||||
|
||||
// Files
|
||||
db_opts.set_level_zero_file_num_compaction_trigger(1);
|
||||
db_opts.set_target_file_size_base(64 * 1024 * 1024);
|
||||
db_opts.set_max_bytes_for_level_base(128 * 1024 * 1024);
|
||||
db_opts.set_ttl(14 * 24 * 60 * 60);
|
||||
|
||||
// Compression
|
||||
let rocksdb_compression_algo = match config.rocksdb_compression_algo.as_ref() {
|
||||
"zstd" => rust_rocksdb::DBCompressionType::Zstd,
|
||||
"zlib" => rust_rocksdb::DBCompressionType::Zlib,
|
||||
@@ -61,29 +100,6 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
|
||||
_ => rust_rocksdb::DBCompressionType::Zstd,
|
||||
};
|
||||
|
||||
let threads = if config.rocksdb_parallelism_threads == 0 {
|
||||
num_cpus::get_physical() // max cores if user specified 0
|
||||
} else {
|
||||
config.rocksdb_parallelism_threads
|
||||
};
|
||||
|
||||
db_opts.set_log_level(rocksdb_log_level);
|
||||
db_opts.set_max_log_file_size(config.rocksdb_max_log_file_size);
|
||||
db_opts.set_log_file_time_to_roll(config.rocksdb_log_time_to_roll);
|
||||
db_opts.set_keep_log_file_num(config.rocksdb_max_log_files);
|
||||
|
||||
if config.rocksdb_optimize_for_spinning_disks {
|
||||
db_opts.set_skip_stats_update_on_db_open(true); // speeds up opening DB on hard drives
|
||||
db_opts.set_compaction_readahead_size(4 * 1024 * 1024); // "If you’re running RocksDB on spinning disks, you should set this to at least
|
||||
// 2MB. That way RocksDB’s compaction is doing sequential instead of random
|
||||
// reads."
|
||||
db_opts.set_target_file_size_base(256 * 1024 * 1024);
|
||||
} else {
|
||||
db_opts.set_max_bytes_for_level_base(512 * 1024 * 1024);
|
||||
db_opts.set_use_direct_reads(true);
|
||||
db_opts.set_use_direct_io_for_flush_and_compaction(true);
|
||||
}
|
||||
|
||||
if config.rocksdb_bottommost_compression {
|
||||
db_opts.set_bottommost_compression_type(rocksdb_compression_algo);
|
||||
db_opts.set_bottommost_zstd_max_train_bytes(0, true);
|
||||
@@ -94,19 +110,10 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
|
||||
|
||||
// -14 w_bits is only read by zlib.
|
||||
db_opts.set_compression_options(-14, config.rocksdb_compression_level, 0, 0);
|
||||
|
||||
db_opts.set_block_based_table_factory(&block_based_options);
|
||||
db_opts.create_if_missing(true);
|
||||
db_opts.increase_parallelism(
|
||||
threads.try_into().expect("Failed to convert \"rocksdb_parallelism_threads\" usize into i32"),
|
||||
);
|
||||
db_opts.set_compression_type(rocksdb_compression_algo);
|
||||
db_opts.optimize_level_style_compaction(10 * 1024 * 1024);
|
||||
|
||||
// https://github.com/facebook/rocksdb/wiki/Setup-Options-and-Basic-Tuning
|
||||
db_opts.set_level_compaction_dynamic_level_bytes(true);
|
||||
db_opts.set_max_background_jobs(6);
|
||||
db_opts.set_bytes_per_sync(1_048_576);
|
||||
// Misc
|
||||
db_opts.create_if_missing(true);
|
||||
|
||||
// https://github.com/facebook/rocksdb/wiki/WAL-Recovery-Modes#ktoleratecorruptedtailrecords
|
||||
//
|
||||
@@ -119,15 +126,21 @@ fn db_options(rocksdb_cache: &rust_rocksdb::Cache, config: &Config) -> rust_rock
|
||||
let prefix_extractor = rust_rocksdb::SliceTransform::create_fixed_prefix(1);
|
||||
db_opts.set_prefix_extractor(prefix_extractor);
|
||||
|
||||
db_opts.set_block_based_table_factory(&block_based_options);
|
||||
db_opts.set_env(env);
|
||||
db_opts
|
||||
}
|
||||
|
||||
impl KeyValueDatabaseEngine for Arc<Engine> {
|
||||
fn open(config: &Config) -> Result<Self> {
|
||||
let cache_capacity_bytes = (config.db_cache_capacity_mb * 1024.0 * 1024.0) as usize;
|
||||
let rocksdb_cache = rust_rocksdb::Cache::new_lru_cache(cache_capacity_bytes);
|
||||
let cache_capacity_bytes = config.db_cache_capacity_mb * 1024.0 * 1024.0;
|
||||
let row_cache_capacity_bytes = (cache_capacity_bytes * 0.25) as usize;
|
||||
let col_cache_capacity_bytes = (cache_capacity_bytes * 0.75) as usize;
|
||||
|
||||
let db_opts = db_options(&rocksdb_cache, config);
|
||||
let db_env = rust_rocksdb::Env::new()?;
|
||||
let row_cache = rust_rocksdb::Cache::new_lru_cache(row_cache_capacity_bytes);
|
||||
let col_cache = rust_rocksdb::Cache::new_lru_cache(col_cache_capacity_bytes);
|
||||
let db_opts = db_options(config, &db_env, &row_cache, &col_cache);
|
||||
|
||||
debug!("Listing column families in database");
|
||||
let cfs =
|
||||
@@ -139,13 +152,16 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
|
||||
let db = rust_rocksdb::DBWithThreadMode::<rust_rocksdb::MultiThreaded>::open_cf_descriptors(
|
||||
&db_opts,
|
||||
&config.database_path,
|
||||
cfs.iter().map(|name| rust_rocksdb::ColumnFamilyDescriptor::new(name, db_options(&rocksdb_cache, config))),
|
||||
cfs.iter().map(|name| rust_rocksdb::ColumnFamilyDescriptor::new(name, db_opts.clone())),
|
||||
)?;
|
||||
|
||||
Ok(Arc::new(Engine {
|
||||
rocks: db,
|
||||
cache: rocksdb_cache,
|
||||
row_cache,
|
||||
col_cache,
|
||||
old_cfs: cfs,
|
||||
opts: db_opts,
|
||||
env: db_env,
|
||||
config: config.clone(),
|
||||
}))
|
||||
}
|
||||
@@ -154,7 +170,7 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
|
||||
if !self.old_cfs.contains(&name.to_owned()) {
|
||||
// Create if it didn't exist
|
||||
debug!("Creating new column family in database: {}", name);
|
||||
let _ = self.rocks.create_cf(name, &db_options(&self.cache, &self.config));
|
||||
let _ = self.rocks.create_cf(name, &self.opts);
|
||||
}
|
||||
|
||||
Ok(Arc::new(RocksDbEngineTree {
|
||||
@@ -166,23 +182,35 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
|
||||
}
|
||||
|
||||
fn flush(&self) -> Result<()> {
|
||||
debug!("Running flush_wal (no sync)");
|
||||
rust_rocksdb::DBCommon::flush_wal(&self.rocks, false)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync(&self) -> Result<()> {
|
||||
rust_rocksdb::DBCommon::flush_wal(&self.rocks, true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn memory_usage(&self) -> Result<String> {
|
||||
let stats = rust_rocksdb::perf::get_memory_usage_stats(Some(&[&self.rocks]), Some(&[&self.cache]))?;
|
||||
let stats = rust_rocksdb::perf::get_memory_usage_stats(
|
||||
Some(&[&self.rocks]),
|
||||
Some(&[&self.row_cache, &self.col_cache]),
|
||||
)?;
|
||||
Ok(format!(
|
||||
"Approximate memory usage of all the mem-tables: {:.3} MB\nApproximate memory usage of un-flushed \
|
||||
mem-tables: {:.3} MB\nApproximate memory usage of all the table readers: {:.3} MB\nApproximate memory \
|
||||
usage by cache: {:.3} MB\nApproximate memory usage by cache pinned: {:.3} MB\n",
|
||||
usage by cache: {:.3} MB\nApproximate memory usage by row cache: {:.3} MB pinned: {:.3} MB\nApproximate \
|
||||
memory usage by column cache: {:.3} MB pinned: {:.3} MB\n",
|
||||
stats.mem_table_total as f64 / 1024.0 / 1024.0,
|
||||
stats.mem_table_unflushed as f64 / 1024.0 / 1024.0,
|
||||
stats.mem_table_readers_total as f64 / 1024.0 / 1024.0,
|
||||
stats.cache_total as f64 / 1024.0 / 1024.0,
|
||||
self.cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
|
||||
self.row_cache.get_usage() as f64 / 1024.0 / 1024.0,
|
||||
self.row_cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
|
||||
self.col_cache.get_usage() as f64 / 1024.0 / 1024.0,
|
||||
self.col_cache.get_pinned_usage() as f64 / 1024.0 / 1024.0,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -195,6 +223,69 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn backup(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let path = self.config.database_backup_path.as_ref();
|
||||
if path.is_none() || path.is_some_and(|path| path.as_os_str().is_empty()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let options = BackupEngineOptions::new(path.unwrap())?;
|
||||
let mut engine = BackupEngine::open(&options, &self.env)?;
|
||||
let ret = if self.config.database_backups_to_keep > 0 {
|
||||
match engine.create_new_backup_flush(&self.rocks, true) {
|
||||
Err(e) => return Err(Box::new(e)),
|
||||
Ok(_) => {
|
||||
let _info = engine.get_backup_info();
|
||||
let info = &_info.last().unwrap();
|
||||
info!(
|
||||
"Created database backup #{} using {} bytes in {} files",
|
||||
info.backup_id, info.size, info.num_files,
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
if self.config.database_backups_to_keep >= 0 {
|
||||
let keep = u32::try_from(self.config.database_backups_to_keep)?;
|
||||
if let Err(e) = engine.purge_old_backups(keep.try_into()?) {
|
||||
error!("Failed to purge old backup: {:?}", e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
fn backup_list(&self) -> Result<String> {
|
||||
let path = self.config.database_backup_path.as_ref();
|
||||
if path.is_none() || path.is_some_and(|path| path.as_os_str().is_empty()) {
|
||||
return Ok(
|
||||
"Configure database_backup_path to enable backups, or the path specified is not valid".to_owned(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut res = String::new();
|
||||
let options = BackupEngineOptions::new(path.unwrap())?;
|
||||
let engine = BackupEngine::open(&options, &self.env)?;
|
||||
for info in engine.get_backup_info() {
|
||||
std::fmt::write(
|
||||
&mut res,
|
||||
format_args!(
|
||||
"#{} {}: {} bytes, {} files\n",
|
||||
info.backup_id,
|
||||
DateTime::<Utc>::from_timestamp(info.timestamp, 0).unwrap_or_default().to_rfc2822(),
|
||||
info.size,
|
||||
info.num_files,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// TODO: figure out if this is needed for rocksdb
|
||||
#[allow(dead_code)]
|
||||
fn clear_caches(&self) {}
|
||||
@@ -252,6 +343,18 @@ impl KvTree for RocksDbEngineTree<'_> {
|
||||
Ok(self.db.rocks.delete_cf_opt(&self.cf(), key, &writeoptions)?)
|
||||
}
|
||||
|
||||
fn remove_batch(&self, iter: &mut dyn Iterator<Item = Vec<u8>>) -> Result<()> {
|
||||
let writeoptions = rust_rocksdb::WriteOptions::default();
|
||||
|
||||
let mut batch = WriteBatchWithTransaction::<false>::default();
|
||||
|
||||
for key in iter {
|
||||
batch.delete_cf(&self.cf(), key);
|
||||
}
|
||||
|
||||
Ok(self.db.rocks.write_opt(batch, &writeoptions)?)
|
||||
}
|
||||
|
||||
fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (Vec<u8>, Vec<u8>)> + 'a> {
|
||||
let mut readoptions = rust_rocksdb::ReadOptions::default();
|
||||
readoptions.set_total_order_seek(true);
|
||||
|
||||
@@ -52,7 +52,7 @@ impl service::globals::Data for KeyValueDatabase {
|
||||
|
||||
let mut futures = FuturesUnordered::new();
|
||||
|
||||
// Return when *any* user changed his key
|
||||
// Return when *any* user changed their key
|
||||
// TODO: only send for user they share a room with
|
||||
futures.push(self.todeviceid_events.watch_prefix(&userdeviceid_prefix));
|
||||
|
||||
@@ -272,4 +272,8 @@ lasttimelinecount_cache: {lasttimelinecount_cache}\n"
|
||||
self.global.insert(b"version", &new_version.to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn backup(&self) -> Result<(), Box<dyn std::error::Error>> { self.db.backup() }
|
||||
|
||||
fn backup_list(&self) -> Result<String> { self.db.backup_list() }
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use ruma::api::client::error::ErrorKind;
|
||||
use ruma::{api::client::error::ErrorKind, OwnedUserId};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
database::KeyValueDatabase,
|
||||
service::{self, media::UrlPreviewData},
|
||||
utils, Error, Result,
|
||||
utils::string_from_bytes,
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
impl service::media::Data for KeyValueDatabase {
|
||||
fn create_file_metadata(
|
||||
&self, mxc: String, width: u32, height: u32, content_disposition: Option<&str>, content_type: Option<&str>,
|
||||
&self, sender_user: Option<&str>, mxc: String, width: u32, height: u32, content_disposition: Option<&str>,
|
||||
content_type: Option<&str>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut key = mxc.as_bytes().to_vec();
|
||||
key.push(0xFF);
|
||||
@@ -22,6 +24,16 @@ impl service::media::Data for KeyValueDatabase {
|
||||
|
||||
self.mediaid_file.insert(&key, &[])?;
|
||||
|
||||
if let Some(user) = sender_user {
|
||||
let mut key = mxc.as_bytes().to_vec();
|
||||
key.push(0xFF);
|
||||
|
||||
let mut user = user.as_bytes().to_vec();
|
||||
user.push(0xFF);
|
||||
|
||||
self.mediaid_user.insert(&key, &user)?;
|
||||
}
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
@@ -31,13 +43,22 @@ impl service::media::Data for KeyValueDatabase {
|
||||
let mut prefix = mxc.as_bytes().to_vec();
|
||||
prefix.push(0xFF);
|
||||
|
||||
debug!("MXC db prefix: {:?}", prefix);
|
||||
debug!("MXC db prefix: {prefix:?}");
|
||||
|
||||
for (key, _) in self.mediaid_file.scan_prefix(prefix) {
|
||||
debug!("Deleting key: {:?}", key);
|
||||
self.mediaid_file.remove(&key)?;
|
||||
}
|
||||
|
||||
for (key, value) in self.mediaid_user.scan_prefix(mxc.as_bytes().to_vec()) {
|
||||
if key == mxc.as_bytes().to_vec() {
|
||||
let user = string_from_bytes(&value).unwrap_or_default();
|
||||
|
||||
debug!("Deleting key \"{key:?}\" which was uploaded by user {user}");
|
||||
self.mediaid_user.remove(&key)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -48,6 +69,8 @@ impl service::media::Data for KeyValueDatabase {
|
||||
let mut prefix = mxc.as_bytes().to_vec();
|
||||
prefix.push(0xFF);
|
||||
|
||||
debug!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA prefix: {:?}", prefix);
|
||||
|
||||
let mut keys: Vec<Vec<u8>> = vec![];
|
||||
|
||||
for (key, _) in self.mediaid_file.scan_prefix(prefix) {
|
||||
@@ -65,6 +88,23 @@ impl service::media::Data for KeyValueDatabase {
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
fn get_all_media_keys_by_user(&self, user_id: OwnedUserId) -> Result<Vec<Vec<u8>>> {
|
||||
debug!("User ID: {user_id:?}");
|
||||
|
||||
let mut user = user_id.as_bytes().to_vec();
|
||||
user.push(0xFF);
|
||||
|
||||
let mut keys: Vec<Vec<u8>> = vec![];
|
||||
|
||||
for (key, value) in self.mediaid_user.iter() {
|
||||
if value == user {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
fn search_file_metadata(
|
||||
&self, mxc: String, width: u32, height: u32,
|
||||
) -> Result<(Option<String>, Option<String>, Vec<u8>)> {
|
||||
@@ -85,7 +125,7 @@ impl service::media::Data for KeyValueDatabase {
|
||||
let content_type = parts
|
||||
.next()
|
||||
.map(|bytes| {
|
||||
utils::string_from_bytes(bytes)
|
||||
string_from_bytes(bytes)
|
||||
.map_err(|_| Error::bad_database("Content type in mediaid_file is invalid unicode."))
|
||||
})
|
||||
.transpose()?;
|
||||
@@ -97,7 +137,7 @@ impl service::media::Data for KeyValueDatabase {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
utils::string_from_bytes(content_disposition_bytes)
|
||||
string_from_bytes(content_disposition_bytes)
|
||||
.map_err(|_| Error::bad_database("Content Disposition in mediaid_file is invalid unicode."))?,
|
||||
)
|
||||
};
|
||||
|
||||
@@ -268,6 +268,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns the number of users which are currently in a room
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
||||
self.roomid_joinedcount
|
||||
@@ -276,6 +277,7 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Returns the number of users which are currently invited to a room
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> {
|
||||
self.roomid_invitedcount
|
||||
|
||||
@@ -90,6 +90,10 @@ impl service::sending::Data for KeyValueDatabase {
|
||||
|
||||
fn mark_as_active(&self, events: &[(SendingEventType, Vec<u8>)]) -> Result<()> {
|
||||
for (e, key) in events {
|
||||
if key.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = if let SendingEventType::Edu(value) = &e {
|
||||
&**value
|
||||
} else {
|
||||
|
||||
+10
-3
@@ -157,6 +157,7 @@ pub struct KeyValueDatabase {
|
||||
//pub media: media::Media,
|
||||
pub(super) mediaid_file: Arc<dyn KvTree>, // MediaId = MXC + WidthHeight + ContentDisposition + ContentType
|
||||
pub(super) url_previews: Arc<dyn KvTree>,
|
||||
pub(super) mediaid_user: Arc<dyn KvTree>,
|
||||
//pub key_backups: key_backups::KeyBackups,
|
||||
pub(super) backupid_algorithm: Arc<dyn KvTree>, // BackupId = UserId + Version(Count)
|
||||
pub(super) backupid_etag: Arc<dyn KvTree>, // BackupId = UserId + Version(Count)
|
||||
@@ -365,6 +366,7 @@ impl KeyValueDatabase {
|
||||
roomusertype_roomuserdataid: builder.open_tree("roomusertype_roomuserdataid")?,
|
||||
mediaid_file: builder.open_tree("mediaid_file")?,
|
||||
url_previews: builder.open_tree("url_previews")?,
|
||||
mediaid_user: builder.open_tree("mediaid_user")?,
|
||||
backupid_algorithm: builder.open_tree("backupid_algorithm")?,
|
||||
backupid_etag: builder.open_tree("backupid_etag")?,
|
||||
backupkeyid_backup: builder.open_tree("backupkeyid_backup")?,
|
||||
@@ -931,8 +933,13 @@ impl KeyValueDatabase {
|
||||
{
|
||||
let patterns = &services().globals.config.forbidden_usernames;
|
||||
if !patterns.is_empty() {
|
||||
for user in services().users.iter() {
|
||||
let user_id = user?;
|
||||
for user_id in services()
|
||||
.users
|
||||
.iter()
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|user| !services().users.is_deactivated(user).unwrap_or(true))
|
||||
.filter(|user| user.server_name() == services().globals.server_name())
|
||||
{
|
||||
let matches = patterns.matches(user_id.localpart());
|
||||
if matches.matched_any() {
|
||||
warn!(
|
||||
@@ -946,7 +953,7 @@ impl KeyValueDatabase {
|
||||
}
|
||||
|
||||
{
|
||||
let patterns = &services().globals.config.forbidden_room_names;
|
||||
let patterns = &services().globals.config.forbidden_alias_names;
|
||||
if !patterns.is_empty() {
|
||||
for address in services().rooms.metadata.iter_ids() {
|
||||
let room_id = address?;
|
||||
|
||||
+14
-4
@@ -280,7 +280,7 @@ async fn main() {
|
||||
}
|
||||
|
||||
if config.allow_outgoing_presence && !config.allow_local_presence {
|
||||
error!("Outgoing presence requires allowing local presence. Please enable \"allow_outgoing_presence\".");
|
||||
error!("Outgoing presence requires allowing local presence. Please enable \"allow_local_presence\".");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -535,11 +535,15 @@ async fn unrecognized_method<B: Send + 'static>(
|
||||
let uri = req.uri().clone();
|
||||
let inner = next.run(req).await;
|
||||
if inner.status() == StatusCode::METHOD_NOT_ALLOWED {
|
||||
warn!("Method not allowed: {method} {uri}");
|
||||
if uri.path().contains("_matrix/") {
|
||||
warn!("Method not allowed: {method} {uri}");
|
||||
} else {
|
||||
info!("Method not allowed: {method} {uri}");
|
||||
}
|
||||
return Ok(RumaResponse(UiaaResponse::MatrixError(RumaError {
|
||||
body: ErrorBody::Standard {
|
||||
kind: ErrorKind::Unrecognized,
|
||||
message: "M_UNRECOGNIZED: Unrecognized request".to_owned(),
|
||||
message: "M_UNRECOGNIZED: Method not allowed for endpoint".to_owned(),
|
||||
},
|
||||
status_code: StatusCode::METHOD_NOT_ALLOWED,
|
||||
}))
|
||||
@@ -739,6 +743,7 @@ fn routes() -> Router {
|
||||
.ruma_route(server_server::get_profile_information_route)
|
||||
.ruma_route(server_server::get_keys_route)
|
||||
.ruma_route(server_server::claim_keys_route)
|
||||
.ruma_route(server_server::get_hierarchy_route)
|
||||
.route("/_matrix/client/r0/rooms/:room_id/initialSync", get(initial_sync))
|
||||
.route("/_matrix/client/v3/rooms/:room_id/initialSync", get(initial_sync))
|
||||
.route("/client/server.json", get(client_server::syncv3_client_server_json))
|
||||
@@ -805,7 +810,12 @@ async fn shutdown_signal(handle: ServerHandle, tx: Sender<()>) -> Result<()> {
|
||||
}
|
||||
|
||||
async fn not_found(uri: Uri) -> impl IntoResponse {
|
||||
warn!("Not found: {uri}");
|
||||
if uri.path().contains("_matrix/") {
|
||||
warn!("Not found: {uri}");
|
||||
} else {
|
||||
info!("Not found: {uri}");
|
||||
}
|
||||
|
||||
Error::BadRequest(ErrorKind::Unrecognized, "Unrecognized request")
|
||||
}
|
||||
|
||||
|
||||
+312
-160
@@ -30,7 +30,9 @@ use tracing::{debug, error, info, warn};
|
||||
use super::pdu::PduBuilder;
|
||||
use crate::{
|
||||
api::{
|
||||
client_server::{get_alias_helper, leave_all_rooms, leave_room, AUTO_GEN_PASSWORD_LENGTH},
|
||||
client_server::{
|
||||
get_alias_helper, join_room_by_id_helper, leave_all_rooms, leave_room, AUTO_GEN_PASSWORD_LENGTH,
|
||||
},
|
||||
server_server::parse_incoming_pdu,
|
||||
},
|
||||
services,
|
||||
@@ -92,6 +94,12 @@ enum MediaCommand {
|
||||
event_id: Option<Box<EventId>>,
|
||||
},
|
||||
|
||||
/// Deletes all **uploaded** local media from the specified user.
|
||||
DeleteFromUser {
|
||||
/// User ID to delete all media from
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
/// - Deletes a codeblock list of MXC URLs from our database and on the
|
||||
/// filesystem
|
||||
DeleteList,
|
||||
@@ -103,6 +111,13 @@ enum MediaCommand {
|
||||
/// past 5 minutes
|
||||
duration: String,
|
||||
},
|
||||
|
||||
/// - Lists all **uploaded** local media from the specified user with their
|
||||
/// MXC URLs.
|
||||
ListFromUser {
|
||||
/// User ID to list all media from
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
@@ -396,6 +411,21 @@ enum DebugCommand {
|
||||
server: Box<ServerName>,
|
||||
},
|
||||
|
||||
/// - Gets all the room state events for the specified room.
|
||||
///
|
||||
/// This is functionally equivalent to `GET
|
||||
/// /_matrix/client/v3/rooms/{roomid}/state`, except the admin command does
|
||||
/// *not* check if the sender user is allowed to see state events. This is
|
||||
/// done because it's implied that server admins here have database access
|
||||
/// and can see/get room info themselves anyways if they were malicious
|
||||
/// admins.
|
||||
///
|
||||
/// Of course the check is still done on the actual client API.
|
||||
GetRoomState {
|
||||
/// Room ID
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Forces device lists for all local and remote users to be updated (as
|
||||
/// having new keys available)
|
||||
ForceDeviceListUpdates,
|
||||
@@ -421,6 +451,13 @@ enum ServerCommand {
|
||||
ClearServiceCaches {
|
||||
amount: u32,
|
||||
},
|
||||
|
||||
/// - Performs an online backup of the database (only available for RocksDB
|
||||
/// at the moment)
|
||||
BackupDatabase,
|
||||
|
||||
/// - List database backups
|
||||
ListBackups,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -458,60 +495,50 @@ impl Service {
|
||||
let conduit_user = UserId::parse(format!("@conduit:{}", services().globals.server_name()))
|
||||
.expect("@conduit:server_name is valid");
|
||||
|
||||
let conduit_room = services()
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(
|
||||
format!("#admins:{}", services().globals.server_name())
|
||||
.as_str()
|
||||
.try_into()
|
||||
.expect("#admins:server_name is a valid room alias"),
|
||||
)
|
||||
.expect("Database data for admin room alias must be valid")
|
||||
.expect("Admin room must exist");
|
||||
if let Ok(Some(conduit_room)) = services().admin.get_admin_room() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(event) = receiver.recv() => {
|
||||
let (mut message_content, reply) = match event {
|
||||
AdminRoomEvent::SendMessage(content) => (content, None),
|
||||
AdminRoomEvent::ProcessMessage(room_message, reply_id) => {
|
||||
(self.process_admin_message(room_message).await, Some(reply_id))
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(event) = receiver.recv() => {
|
||||
let (mut message_content, reply) = match event {
|
||||
AdminRoomEvent::SendMessage(content) => (content, None),
|
||||
AdminRoomEvent::ProcessMessage(room_message, reply_id) => {
|
||||
(self.process_admin_message(room_message).await, Some(reply_id))
|
||||
let mutex_state = Arc::clone(
|
||||
services().globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.await
|
||||
.entry(conduit_room.clone())
|
||||
.or_default(),
|
||||
);
|
||||
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
if let Some(reply) = reply {
|
||||
message_content.relates_to = Some(Reply { in_reply_to: InReplyTo { event_id: reply.into() } });
|
||||
}
|
||||
};
|
||||
|
||||
let mutex_state = Arc::clone(
|
||||
services().globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.await
|
||||
.entry(conduit_room.clone())
|
||||
.or_default(),
|
||||
);
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&message_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
},
|
||||
&conduit_user,
|
||||
&conduit_room,
|
||||
&state_lock)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
if let Some(reply) = reply {
|
||||
message_content.relates_to = Some(Reply { in_reply_to: InReplyTo { event_id: reply.into() } });
|
||||
drop(state_lock);
|
||||
}
|
||||
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&message_content)
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
},
|
||||
&conduit_user,
|
||||
&conduit_room,
|
||||
&state_lock)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
drop(state_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -792,6 +819,17 @@ impl Service {
|
||||
));
|
||||
}
|
||||
},
|
||||
MediaCommand::DeleteFromUser {
|
||||
user_id,
|
||||
} => {
|
||||
let deleted_count = services().media.delete_from_user(user_id.into()).await?;
|
||||
|
||||
debug!("Deleted {deleted_count} total media files.");
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deleted {deleted_count} total media files.",
|
||||
)));
|
||||
},
|
||||
MediaCommand::DeleteList => {
|
||||
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
|
||||
let mxc_list = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
||||
@@ -825,6 +863,25 @@ impl Service {
|
||||
deleted_count
|
||||
)));
|
||||
},
|
||||
MediaCommand::ListFromUser {
|
||||
user_id,
|
||||
} => {
|
||||
let mxc_list = services().media.list_all_media_by_user(user_id.clone().into()).await?;
|
||||
|
||||
let output_plain = format!(
|
||||
"All MXC URLs {user_id} Uploaded:\n{}",
|
||||
mxc_list.iter().map(ToOwned::to_owned).collect::<Vec<_>>().join("\n")
|
||||
);
|
||||
let output_html = format!(
|
||||
"<table><caption>All MXC URLs {user_id} \
|
||||
Uploaded</caption>\n<tr><th>URL</th></tr>\n{}</table>",
|
||||
mxc_list.iter().fold(String::new(), |mut output, mxc| {
|
||||
writeln!(output, "<tr><td>{}</td></tr>", mxc).unwrap();
|
||||
output
|
||||
})
|
||||
);
|
||||
RoomMessageEventContent::text_html(output_plain, output_html)
|
||||
},
|
||||
}
|
||||
},
|
||||
AdminCommand::Users(command) => match command {
|
||||
@@ -888,6 +945,35 @@ impl Service {
|
||||
.expect("to json value always works"),
|
||||
)?;
|
||||
|
||||
if !services().globals.config.auto_join_rooms.is_empty() {
|
||||
for room in &services().globals.config.auto_join_rooms {
|
||||
if !services().rooms.state_cache.server_in_room(services().globals.server_name(), room)? {
|
||||
warn!("Skipping room {room} to automatically join as we have never joined before.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(room_id_server_name) = room.server_name() {
|
||||
match join_room_by_id_helper(
|
||||
Some(&user_id),
|
||||
room,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
error!("Failed to automatically join room {room} for user {user_id}: {e}");
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we dont add a device since we're not the user, just the creator
|
||||
|
||||
// Inhibit login does not work for guests
|
||||
@@ -1096,14 +1182,13 @@ impl Service {
|
||||
format!("#admins:{}", services().globals.server_name())
|
||||
.try_into()
|
||||
.expect("#admins:server_name is a valid alias name");
|
||||
let admin_room_id = services()
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&admin_room_alias)?
|
||||
.expect("Admin room must exist");
|
||||
|
||||
if room.to_string().eq(&admin_room_id) || room.to_string().eq(&admin_room_alias) {
|
||||
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
|
||||
if let Some(admin_room_id) = services().admin.get_admin_room()? {
|
||||
if room.to_string().eq(&admin_room_id) || room.to_string().eq(&admin_room_alias) {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to ban the admin room.",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let room_id = if room.is_room_id() {
|
||||
@@ -1267,23 +1352,15 @@ impl Service {
|
||||
let mut room_ban_count = 0;
|
||||
let mut room_ids: Vec<&RoomId> = Vec::new();
|
||||
|
||||
let admin_room_alias: Box<RoomAliasId> =
|
||||
format!("#admins:{}", services().globals.server_name())
|
||||
.try_into()
|
||||
.expect("#admins:server_name is a valid alias name");
|
||||
let admin_room_id = services()
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&admin_room_alias)?
|
||||
.expect("Admin room must exist");
|
||||
|
||||
for &room_id in &rooms_s {
|
||||
match <&RoomId>::try_from(room_id) {
|
||||
Ok(owned_room_id) => {
|
||||
// silently ignore deleting admin room
|
||||
if owned_room_id.eq(&admin_room_id) {
|
||||
info!("User specified admin room in bulk ban list, ignoring");
|
||||
continue;
|
||||
if let Some(admin_room_id) = services().admin.get_admin_room()? {
|
||||
if owned_room_id.eq(&admin_room_id) {
|
||||
info!("User specified admin room in bulk ban list, ignoring");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
room_ids.push(owned_room_id);
|
||||
@@ -1870,6 +1947,35 @@ impl Service {
|
||||
|
||||
RoomMessageEventContent::text_plain("Done.")
|
||||
},
|
||||
ServerCommand::ListBackups => {
|
||||
let result = services().globals.db.backup_list()?;
|
||||
|
||||
if result.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain("No backups found."));
|
||||
}
|
||||
|
||||
RoomMessageEventContent::text_plain(result)
|
||||
},
|
||||
ServerCommand::BackupDatabase => {
|
||||
if !cfg!(feature = "rocksdb") {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Only RocksDB supports online backups in conduwuit.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut result = tokio::task::spawn_blocking(move || match services().globals.db.backup() {
|
||||
Ok(_) => String::new(),
|
||||
Err(e) => (*e).to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if result.is_empty() {
|
||||
result = services().globals.db.backup_list()?;
|
||||
}
|
||||
|
||||
RoomMessageEventContent::text_plain(&result)
|
||||
},
|
||||
},
|
||||
AdminCommand::Debug(command) => match command {
|
||||
DebugCommand::GetAuthChain {
|
||||
@@ -2061,6 +2167,41 @@ impl Service {
|
||||
},
|
||||
}
|
||||
},
|
||||
DebugCommand::GetRoomState {
|
||||
room_id,
|
||||
} => {
|
||||
let room_state = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full(&room_id)
|
||||
.await?
|
||||
.values()
|
||||
.map(|pdu| pdu.to_state_event())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if room_state.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Unable to find room state in our database (vector is empty)",
|
||||
));
|
||||
}
|
||||
|
||||
let json_text = serde_json::to_string_pretty(&room_state).map_err(|e| {
|
||||
error!("Failed converting room state vector in our database to pretty JSON: {e}");
|
||||
Error::bad_database(
|
||||
"Failed to convert room state events to pretty JSON, possible invalid room state events \
|
||||
in our database",
|
||||
)
|
||||
})?;
|
||||
|
||||
return Ok(RoomMessageEventContent::text_html(
|
||||
format!("{}\n```json\n{}\n```", "Found full room state", json_text),
|
||||
format!(
|
||||
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
|
||||
"Found full room state",
|
||||
HtmlEscape(&json_text)
|
||||
),
|
||||
));
|
||||
},
|
||||
DebugCommand::ForceDeviceListUpdates => {
|
||||
// Force E2EE device list updates for all users
|
||||
for user_id in services().users.iter().filter_map(std::result::Result::ok) {
|
||||
@@ -2393,105 +2534,113 @@ impl Service {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the room ID of the admin room
|
||||
///
|
||||
/// Errors are propagated from the database, and will have None if there is
|
||||
/// no admin room
|
||||
pub(crate) fn get_admin_room(&self) -> Result<Option<OwnedRoomId>> {
|
||||
let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", services().globals.server_name())
|
||||
.try_into()
|
||||
.expect("#admins:server_name is a valid alias name");
|
||||
|
||||
services().rooms.alias.resolve_local_alias(&admin_room_alias)
|
||||
}
|
||||
|
||||
/// Invite the user to the conduit admin room.
|
||||
///
|
||||
/// In conduit, this is equivalent to granting admin privileges.
|
||||
pub(crate) async fn make_user_admin(&self, user_id: &UserId, displayname: String) -> Result<()> {
|
||||
let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", services().globals.server_name())
|
||||
.try_into()
|
||||
.expect("#admins:server_name is a valid alias name");
|
||||
let room_id = services().rooms.alias.resolve_local_alias(&admin_room_alias)?.expect("Admin room must exist");
|
||||
if let Some(room_id) = services().admin.get_admin_room()? {
|
||||
let mutex_state =
|
||||
Arc::clone(services().globals.roomid_mutex_state.write().await.entry(room_id.clone()).or_default());
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
let mutex_state =
|
||||
Arc::clone(services().globals.roomid_mutex_state.write().await.entry(room_id.clone()).or_default());
|
||||
let state_lock = mutex_state.lock().await;
|
||||
// Use the server user to grant the new admin's power level
|
||||
let conduit_user = UserId::parse_with_server_name("conduit", services().globals.server_name())
|
||||
.expect("@conduit:server_name is valid");
|
||||
|
||||
// Use the server user to grant the new admin's power level
|
||||
let conduit_user = UserId::parse_with_server_name("conduit", services().globals.server_name())
|
||||
.expect("@conduit:server_name is valid");
|
||||
// Invite and join the real user
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Invite,
|
||||
displayname: None,
|
||||
avatar_url: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: None,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: Some(displayname),
|
||||
avatar_url: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: None,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
user_id,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Invite and join the real user
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Invite,
|
||||
displayname: None,
|
||||
avatar_url: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: None,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: Some(displayname),
|
||||
avatar_url: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: None,
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(user_id.to_string()),
|
||||
redacts: None,
|
||||
},
|
||||
user_id,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
// Set power level
|
||||
let mut users = BTreeMap::new();
|
||||
users.insert(conduit_user.clone(), 100.into());
|
||||
users.insert(user_id.to_owned(), 100.into());
|
||||
|
||||
// Set power level
|
||||
let mut users = BTreeMap::new();
|
||||
users.insert(conduit_user.clone(), 100.into());
|
||||
users.insert(user_id.to_owned(), 100.into());
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||
users,
|
||||
..Default::default()
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&RoomPowerLevelsEventContent {
|
||||
users,
|
||||
..Default::default()
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some("".to_owned()),
|
||||
redacts: None,
|
||||
},
|
||||
&conduit_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Send welcome message
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
// Send welcome message
|
||||
services().rooms.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&RoomMessageEventContent::text_html(
|
||||
@@ -2508,7 +2657,10 @@ impl Service {
|
||||
&state_lock,
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::{collections::BTreeMap, error::Error};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use ruma::{
|
||||
@@ -32,4 +32,6 @@ pub trait Data: Send + Sync {
|
||||
fn signing_keys_for(&self, origin: &ServerName) -> Result<BTreeMap<OwnedServerSigningKeyId, VerifyKey>>;
|
||||
fn database_version(&self) -> Result<u64>;
|
||||
fn bump_database_version(&self, new_version: u64) -> Result<()>;
|
||||
fn backup(&self) -> Result<(), Box<dyn Error>> { unimplemented!() }
|
||||
fn backup_list(&self) -> Result<String> { Ok(String::new()) }
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use argon2::Argon2;
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
pub use data::Data;
|
||||
use futures_util::FutureExt;
|
||||
use hickory_resolver::TokioAsyncResolver;
|
||||
use hyper::{
|
||||
client::connect::dns::{GaiResolver, Name},
|
||||
service::Service as HyperService,
|
||||
@@ -34,7 +35,6 @@ use ruma::{
|
||||
};
|
||||
use tokio::sync::{broadcast, watch::Receiver, Mutex, RwLock, Semaphore};
|
||||
use tracing::{error, info};
|
||||
use trust_dns_resolver::TokioAsyncResolver;
|
||||
|
||||
use crate::{api::server_server::FedDest, services, Config, Error, Result};
|
||||
|
||||
@@ -327,6 +327,8 @@ impl Service<'_> {
|
||||
|
||||
pub fn turn_secret(&self) -> &String { &self.config.turn_secret }
|
||||
|
||||
pub fn auto_join_rooms(&self) -> &[OwnedRoomId] { &self.config.auto_join_rooms }
|
||||
|
||||
pub fn notification_push_path(&self) -> &String { &self.config.notification_push_path }
|
||||
|
||||
pub fn emergency_password(&self) -> &Option<String> { &self.config.emergency_password }
|
||||
@@ -345,7 +347,7 @@ impl Service<'_> {
|
||||
|
||||
pub fn url_preview_check_root_domain(&self) -> bool { self.config.url_preview_check_root_domain }
|
||||
|
||||
pub fn forbidden_room_names(&self) -> &RegexSet { &self.config.forbidden_room_names }
|
||||
pub fn forbidden_alias_names(&self) -> &RegexSet { &self.config.forbidden_alias_names }
|
||||
|
||||
pub fn forbidden_usernames(&self) -> &RegexSet { &self.config.forbidden_usernames }
|
||||
|
||||
@@ -359,6 +361,8 @@ impl Service<'_> {
|
||||
|
||||
pub fn presence_offline_timeout_s(&self) -> u64 { self.config.presence_offline_timeout_s }
|
||||
|
||||
pub fn allow_incoming_read_receipts(&self) -> bool { self.config.allow_incoming_read_receipts }
|
||||
|
||||
pub fn rocksdb_log_level(&self) -> &String { &self.config.rocksdb_log_level }
|
||||
|
||||
pub fn rocksdb_max_log_file_size(&self) -> usize { self.config.rocksdb_max_log_file_size }
|
||||
@@ -495,7 +499,9 @@ fn reqwest_client_builder(config: &Config) -> Result<reqwest::ClientBuilder> {
|
||||
});
|
||||
|
||||
let mut reqwest_client_builder = reqwest::Client::builder()
|
||||
.pool_max_idle_per_host(0)
|
||||
.hickory_dns(true)
|
||||
.pool_max_idle_per_host(1)
|
||||
.pool_idle_timeout(Duration::from_secs(50))
|
||||
.connect_timeout(Duration::from_secs(60))
|
||||
.timeout(Duration::from_secs(60 * 5))
|
||||
.redirect(redirect_policy)
|
||||
@@ -522,9 +528,10 @@ fn url_preview_reqwest_client_builder(config: &Config) -> Result<reqwest::Client
|
||||
});
|
||||
|
||||
let mut reqwest_client_builder = reqwest::Client::builder()
|
||||
.hickory_dns(true)
|
||||
.pool_max_idle_per_host(0)
|
||||
.connect_timeout(Duration::from_secs(60))
|
||||
.timeout(Duration::from_secs(60 * 5))
|
||||
.connect_timeout(Duration::from_secs(20))
|
||||
.timeout(Duration::from_secs(30))
|
||||
.redirect(redirect_policy)
|
||||
.user_agent("Conduwuit".to_owned() + "/" + env!("CARGO_PKG_VERSION"));
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use ruma::OwnedUserId;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
pub trait Data: Send + Sync {
|
||||
fn create_file_metadata(
|
||||
&self, mxc: String, width: u32, height: u32, content_disposition: Option<&str>, content_type: Option<&str>,
|
||||
&self, sender_user: Option<&str>, mxc: String, width: u32, height: u32, content_disposition: Option<&str>,
|
||||
content_type: Option<&str>,
|
||||
) -> Result<Vec<u8>>;
|
||||
|
||||
fn delete_file_mxc(&self, mxc: String) -> Result<()>;
|
||||
@@ -14,6 +17,8 @@ pub trait Data: Send + Sync {
|
||||
|
||||
fn search_mxc_metadata_prefix(&self, mxc: String) -> Result<Vec<Vec<u8>>>;
|
||||
|
||||
fn get_all_media_keys_by_user(&self, user_id: OwnedUserId) -> Result<Vec<Vec<u8>>>;
|
||||
|
||||
fn get_all_media_keys(&self) -> Result<Vec<Vec<u8>>>;
|
||||
|
||||
fn remove_url_preview(&self, url: &str) -> Result<()>;
|
||||
|
||||
+95
-13
@@ -3,7 +3,7 @@ use std::{collections::HashMap, io::Cursor, sync::Arc, time::SystemTime};
|
||||
|
||||
pub(crate) use data::Data;
|
||||
use image::imageops::FilterType;
|
||||
use ruma::OwnedMxcUri;
|
||||
use ruma::{OwnedMxcUri, OwnedUserId};
|
||||
use serde::Serialize;
|
||||
use tokio::{
|
||||
fs::{self, File},
|
||||
@@ -12,7 +12,7 @@ use tokio::{
|
||||
};
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::{services, utils, Error, Result};
|
||||
use crate::{services, utils::string_from_bytes, Error, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileMeta {
|
||||
@@ -45,10 +45,15 @@ pub struct Service {
|
||||
impl Service {
|
||||
/// Uploads a file.
|
||||
pub async fn create(
|
||||
&self, mxc: String, content_disposition: Option<&str>, content_type: Option<&str>, file: &[u8],
|
||||
&self, sender_user: Option<OwnedUserId>, mxc: String, content_disposition: Option<&str>,
|
||||
content_type: Option<&str>, file: &[u8],
|
||||
) -> Result<()> {
|
||||
// Width, Height = 0 if it's not a thumbnail
|
||||
let key = self.db.create_file_metadata(mxc, 0, 0, content_disposition, content_type)?;
|
||||
let key = if let Some(user) = sender_user {
|
||||
self.db.create_file_metadata(Some(user.as_str()), mxc, 0, 0, content_disposition, content_type)?
|
||||
} else {
|
||||
self.db.create_file_metadata(None, mxc, 0, 0, content_disposition, content_type)?
|
||||
};
|
||||
|
||||
let path;
|
||||
|
||||
@@ -105,12 +110,83 @@ impl Service {
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes all media in the database and from the media directory by a
|
||||
/// specific user
|
||||
pub async fn delete_from_user(&self, user_id: OwnedUserId) -> Result<u32> {
|
||||
if let Ok(user_keys) = self.db.get_all_media_keys_by_user(user_id.clone()) {
|
||||
if user_keys.is_empty() {
|
||||
error!("User \"{user_id}\" has not uploaded any media.");
|
||||
return Err(Error::bad_database("User has not uploaded any media."));
|
||||
}
|
||||
|
||||
let mut mxc_deletion_count = 0;
|
||||
|
||||
for key in user_keys {
|
||||
let mxc = String::from_utf8_lossy(&key);
|
||||
|
||||
let keys = self.db.search_mxc_metadata_prefix(mxc.to_string())?; // the MXC alone does not determine the file path, it is only the prefix
|
||||
|
||||
for key in keys {
|
||||
let mxc = String::from_utf8_lossy(&key);
|
||||
|
||||
debug!("Deleting MXC {mxc} from database");
|
||||
|
||||
self.delete(mxc.to_string()).await?;
|
||||
|
||||
mxc_deletion_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mxc_deletion_count)
|
||||
} else {
|
||||
error!("Failed to find any media keys for user \"{user_id}\" in our database");
|
||||
Err(Error::bad_database(
|
||||
"Failed to find any media keys for the provided user in our database",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_all_media_by_user(&self, user_id: OwnedUserId) -> Result<Vec<String>> {
|
||||
if let Ok(keys) = self.db.get_all_media_keys_by_user(user_id.clone()) {
|
||||
if keys.is_empty() {
|
||||
error!("User \"{user_id}\" has not uploaded any media.");
|
||||
return Err(Error::bad_database("User has not uploaded any media."));
|
||||
}
|
||||
|
||||
let mut mxc_list: Vec<String> = vec![];
|
||||
|
||||
for key in keys {
|
||||
let mxc_string = string_from_bytes(&key).map_err(|e| {
|
||||
error!("Failed to convert MXC key to string in database for user \"{user_id}\": {e}");
|
||||
Error::bad_database("Failed to convert MXC key to string in database")
|
||||
})?;
|
||||
|
||||
// TODO: add the file name
|
||||
|
||||
mxc_list.push(mxc_string);
|
||||
}
|
||||
|
||||
Ok(mxc_list)
|
||||
} else {
|
||||
error!("Failed to find any media keys for user \"{user_id}\" in our database");
|
||||
Err(Error::bad_database(
|
||||
"Failed to find any media keys for the provided user in our database",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Uploads or replaces a file thumbnail.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn upload_thumbnail(
|
||||
&self, mxc: String, content_disposition: Option<&str>, content_type: Option<&str>, width: u32, height: u32,
|
||||
file: &[u8],
|
||||
&self, sender_user: Option<OwnedUserId>, mxc: String, content_disposition: Option<&str>,
|
||||
content_type: Option<&str>, width: u32, height: u32, file: &[u8],
|
||||
) -> Result<()> {
|
||||
let key = self.db.create_file_metadata(mxc, width, height, content_disposition, content_type)?;
|
||||
let key = if let Some(user) = sender_user {
|
||||
self.db.create_file_metadata(Some(user.as_str()), mxc, width, height, content_disposition, content_type)?
|
||||
} else {
|
||||
self.db.create_file_metadata(None, mxc, width, height, content_disposition, content_type)?
|
||||
};
|
||||
|
||||
let path;
|
||||
|
||||
#[allow(clippy::unnecessary_operation)] // error[E0658]: attributes on expressions are experimental
|
||||
@@ -183,13 +259,13 @@ impl Service {
|
||||
debug!("Full MXC key from database: {:?}", key);
|
||||
|
||||
// we need to get the MXC URL from the first part of the key (the first 0xff /
|
||||
// 255 push) this code does look kinda crazy but blame conduit for using magic
|
||||
// keys
|
||||
// 255 push). this is all necessary because of conduit using magic keys for
|
||||
// media
|
||||
let mut parts = key.split(|&b| b == 0xFF);
|
||||
let mxc = parts
|
||||
.next()
|
||||
.map(|bytes| {
|
||||
utils::string_from_bytes(bytes).map_err(|e| {
|
||||
string_from_bytes(bytes).map_err(|e| {
|
||||
error!("Failed to parse MXC unicode bytes from our database: {}", e);
|
||||
Error::bad_database("Failed to parse MXC unicode bytes from our database")
|
||||
})
|
||||
@@ -403,6 +479,7 @@ impl Service {
|
||||
|
||||
// Save thumbnail in database so we don't have to generate it again next time
|
||||
let thumbnail_key = self.db.create_file_metadata(
|
||||
None,
|
||||
mxc,
|
||||
width,
|
||||
height,
|
||||
@@ -470,7 +547,8 @@ mod tests {
|
||||
|
||||
impl Data for MockedKVDatabase {
|
||||
fn create_file_metadata(
|
||||
&self, mxc: String, width: u32, height: u32, content_disposition: Option<&str>, content_type: Option<&str>,
|
||||
&self, _sender_user: Option<&str>, mxc: String, width: u32, height: u32, content_disposition: Option<&str>,
|
||||
content_type: Option<&str>,
|
||||
) -> Result<Vec<u8>> {
|
||||
// copied from src/database/key_value/media.rs
|
||||
let mut key = mxc.as_bytes().to_vec();
|
||||
@@ -489,6 +567,8 @@ mod tests {
|
||||
|
||||
fn search_mxc_metadata_prefix(&self, _mxc: String) -> Result<Vec<Vec<u8>>> { todo!() }
|
||||
|
||||
fn get_all_media_keys_by_user(&self, _user_id: OwnedUserId) -> Result<Vec<Vec<u8>>> { todo!() }
|
||||
|
||||
fn get_all_media_keys(&self) -> Result<Vec<Vec<u8>>> { todo!() }
|
||||
|
||||
fn search_file_metadata(
|
||||
@@ -520,8 +600,10 @@ mod tests {
|
||||
let content_disposition = "attachment; filename=\"this is a very long file name with spaces and special \
|
||||
characters like äöüß and even emoji like 🦀.png\"";
|
||||
let content_type = "image/png";
|
||||
let key =
|
||||
media.db.create_file_metadata(mxc, width, height, Some(content_disposition), Some(content_type)).unwrap();
|
||||
let key = media
|
||||
.db
|
||||
.create_file_metadata(None, mxc, width, height, Some(content_disposition), Some(content_type))
|
||||
.unwrap();
|
||||
let mut r = PathBuf::new();
|
||||
r.push("/tmp");
|
||||
r.push("media");
|
||||
|
||||
+10
-4
@@ -134,7 +134,7 @@ impl Services<'_> {
|
||||
db,
|
||||
},
|
||||
spaces: rooms::spaces::Service {
|
||||
roomid_spacechunk_cache: Mutex::new(LruCache::new(
|
||||
roomid_spacehierarchy_cache: Mutex::new(LruCache::new(
|
||||
(100.0 * config.conduit_cache_capacity_modifier) as usize,
|
||||
)),
|
||||
},
|
||||
@@ -175,7 +175,7 @@ impl Services<'_> {
|
||||
let user_visibility_cache = self.rooms.state_accessor.user_visibility_cache.lock().unwrap().len();
|
||||
let stateinfo_cache = self.rooms.state_compressor.stateinfo_cache.lock().unwrap().len();
|
||||
let lasttimelinecount_cache = self.rooms.timeline.lasttimelinecount_cache.lock().await.len();
|
||||
let roomid_spacechunk_cache = self.rooms.spaces.roomid_spacechunk_cache.lock().await.len();
|
||||
let roomid_spacehierarchy_cache = self.rooms.spaces.roomid_spacehierarchy_cache.lock().await.len();
|
||||
|
||||
format!(
|
||||
"\
|
||||
@@ -184,7 +184,7 @@ server_visibility_cache: {server_visibility_cache}
|
||||
user_visibility_cache: {user_visibility_cache}
|
||||
stateinfo_cache: {stateinfo_cache}
|
||||
lasttimelinecount_cache: {lasttimelinecount_cache}
|
||||
roomid_spacechunk_cache: {roomid_spacechunk_cache}"
|
||||
roomid_spacehierarchy_cache: {roomid_spacehierarchy_cache}"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -205,7 +205,13 @@ roomid_spacechunk_cache: {roomid_spacechunk_cache}"
|
||||
self.rooms.timeline.lasttimelinecount_cache.lock().await.clear();
|
||||
}
|
||||
if amount > 5 {
|
||||
self.rooms.spaces.roomid_spacechunk_cache.lock().await.clear();
|
||||
self.rooms.spaces.roomid_spacehierarchy_cache.lock().await.clear();
|
||||
}
|
||||
if amount > 6 {
|
||||
self.globals.tls_name_override.write().unwrap().clear();
|
||||
}
|
||||
if amount > 7 {
|
||||
self.globals.dns_resolver().clear_cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1082
-303
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,7 @@ impl Service {
|
||||
.await?;
|
||||
},
|
||||
TimelineEventType::SpaceChild => {
|
||||
services().rooms.spaces.roomid_spacechunk_cache.lock().await.remove(&pdu.room_id);
|
||||
services().rooms.spaces.roomid_spacehierarchy_cache.lock().await.remove(&pdu.room_id);
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
@@ -230,10 +230,9 @@ impl Service {
|
||||
|
||||
pub fn get_name(&self, room_id: &RoomId) -> Result<Option<String>> {
|
||||
services().rooms.state_accessor.room_state_get(room_id, &StateEventType::RoomName, "")?.map_or(Ok(None), |s| {
|
||||
serde_json::from_str(s.content.get()).map(|c: RoomNameEventContent| Some(c.name)).map_err(|e| {
|
||||
error!("Invalid room name event in database for room {}. {}", room_id, e);
|
||||
Error::bad_database("Invalid room name event in database.")
|
||||
})
|
||||
Ok(serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomNameEventContent| Some(c.name))
|
||||
.unwrap_or_else(|_| None))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ use ruma::{
|
||||
state_res,
|
||||
state_res::{Event, RoomVersion},
|
||||
uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName,
|
||||
RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
|
||||
RoomId, RoomVersionId, ServerName, UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
||||
@@ -418,7 +418,7 @@ impl Service {
|
||||
},
|
||||
TimelineEventType::SpaceChild => {
|
||||
if let Some(_state_key) = &pdu.state_key {
|
||||
services().rooms.spaces.roomid_spacechunk_cache.lock().await.remove(&pdu.room_id);
|
||||
services().rooms.spaces.roomid_spacehierarchy_cache.lock().await.remove(&pdu.room_id);
|
||||
}
|
||||
},
|
||||
TimelineEventType::RoomMember => {
|
||||
@@ -461,10 +461,6 @@ impl Service {
|
||||
if let Some(body) = content.body {
|
||||
services().rooms.search.index_pdu(shortroomid, &pdu_id, &body)?;
|
||||
|
||||
let admin_room = services().rooms.alias.resolve_local_alias(
|
||||
<&RoomAliasId>::try_from(format!("#admins:{}", services().globals.server_name()).as_str())
|
||||
.expect("#admins:server_name is a valid room alias"),
|
||||
)?;
|
||||
let server_user = format!("@conduit:{}", services().globals.server_name());
|
||||
|
||||
let to_conduit = body.starts_with(&format!("{server_user}: "))
|
||||
@@ -477,8 +473,10 @@ impl Service {
|
||||
// the administrator can execute commands as conduit
|
||||
let from_conduit = pdu.sender == server_user && services().globals.emergency_password().is_none();
|
||||
|
||||
if to_conduit && !from_conduit && admin_room.as_ref() == Some(&pdu.room_id) {
|
||||
services().admin.process_message(body, pdu.event_id.clone());
|
||||
if let Some(admin_room) = services().admin.get_admin_room()? {
|
||||
if to_conduit && !from_conduit && admin_room == pdu.room_id {
|
||||
services().admin.process_message(body, pdu.event_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -720,84 +718,82 @@ impl Service {
|
||||
) -> Result<Arc<EventId>> {
|
||||
let (pdu, pdu_json) = self.create_hash_and_sign_event(pdu_builder, sender, room_id, state_lock)?;
|
||||
|
||||
let admin_room = services().rooms.alias.resolve_local_alias(
|
||||
<&RoomAliasId>::try_from(format!("#admins:{}", services().globals.server_name()).as_str())
|
||||
.expect("#admins:server_name is a valid room alias"),
|
||||
)?;
|
||||
if admin_room.filter(|v| v == room_id).is_some() {
|
||||
match pdu.event_type() {
|
||||
TimelineEventType::RoomEncryption => {
|
||||
warn!("Encryption is not allowed in the admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Encryption is not allowed in the admins room.",
|
||||
));
|
||||
},
|
||||
TimelineEventType::RoomMember => {
|
||||
#[derive(Deserialize)]
|
||||
struct ExtractMembership {
|
||||
membership: MembershipState,
|
||||
}
|
||||
|
||||
let target = pdu.state_key().filter(|v| v.starts_with('@')).unwrap_or(sender.as_str());
|
||||
let server_name = services().globals.server_name();
|
||||
let server_user = format!("@conduit:{server_name}");
|
||||
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
|
||||
|
||||
if content.membership == MembershipState::Leave {
|
||||
if target == server_user {
|
||||
warn!("Conduit user cannot leave from admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Conduit user cannot leave from admins room.",
|
||||
));
|
||||
if let Some(admin_room) = services().admin.get_admin_room()? {
|
||||
if admin_room == room_id {
|
||||
match pdu.event_type() {
|
||||
TimelineEventType::RoomEncryption => {
|
||||
warn!("Encryption is not allowed in the admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Encryption is not allowed in the admins room.",
|
||||
));
|
||||
},
|
||||
TimelineEventType::RoomMember => {
|
||||
#[derive(Deserialize)]
|
||||
struct ExtractMembership {
|
||||
membership: MembershipState,
|
||||
}
|
||||
|
||||
let count = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|m| m.server_name() == server_name)
|
||||
.filter(|m| m != target)
|
||||
.count();
|
||||
if count < 2 {
|
||||
warn!("Last admin cannot leave from admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Last admin cannot leave from admins room.",
|
||||
));
|
||||
}
|
||||
}
|
||||
let target = pdu.state_key().filter(|v| v.starts_with('@')).unwrap_or(sender.as_str());
|
||||
let server_name = services().globals.server_name();
|
||||
let server_user = format!("@conduit:{server_name}");
|
||||
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid content in pdu."))?;
|
||||
|
||||
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
|
||||
if target == server_user {
|
||||
warn!("Conduit user cannot be banned in admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Conduit user cannot be banned in admins room.",
|
||||
));
|
||||
if content.membership == MembershipState::Leave {
|
||||
if target == server_user {
|
||||
warn!("Conduit user cannot leave from admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Conduit user cannot leave from admins room.",
|
||||
));
|
||||
}
|
||||
|
||||
let count = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|m| m.server_name() == server_name)
|
||||
.filter(|m| m != target)
|
||||
.count();
|
||||
if count < 2 {
|
||||
warn!("Last admin cannot leave from admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Last admin cannot leave from admins room.",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let count = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|m| m.server_name() == server_name)
|
||||
.filter(|m| m != target)
|
||||
.count();
|
||||
if count < 2 {
|
||||
warn!("Last admin cannot be banned in admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Last admin cannot be banned in admins room.",
|
||||
));
|
||||
if content.membership == MembershipState::Ban && pdu.state_key().is_some() {
|
||||
if target == server_user {
|
||||
warn!("Conduit user cannot be banned in admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Conduit user cannot be banned in admins room.",
|
||||
));
|
||||
}
|
||||
|
||||
let count = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.filter_map(std::result::Result::ok)
|
||||
.filter(|m| m.server_name() == server_name)
|
||||
.filter(|m| m != target)
|
||||
.count();
|
||||
if count < 2 {
|
||||
warn!("Last admin cannot be banned in admins room");
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::Forbidden,
|
||||
"Last admin cannot be banned in admins room.",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1007,12 +1003,12 @@ impl Service {
|
||||
return Ok(());
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("{backfill_server} could not provide backfill: {e}");
|
||||
warn!("{backfill_server} failed to provide backfill: {e}");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
info!("No servers could backfill");
|
||||
info!("No servers could backfill, but backfill was needed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use ruma::{
|
||||
},
|
||||
device_id,
|
||||
events::{push_rules::PushRulesEvent, receipt::ReceiptType, AnySyncEphemeralRoomEvent, GlobalAccountDataEventType},
|
||||
push, uint, MilliSecondsSinceUnixEpoch, OwnedServerName, OwnedUserId, ServerName, UInt, UserId,
|
||||
push, uint, MilliSecondsSinceUnixEpoch, OwnedServerName, OwnedUserId, RoomId, ServerName, UInt, UserId,
|
||||
};
|
||||
use tokio::{
|
||||
select,
|
||||
@@ -80,6 +80,7 @@ impl OutgoingKind {
|
||||
pub enum SendingEventType {
|
||||
Pdu(Vec<u8>), // pduid
|
||||
Edu(Vec<u8>), // pdu json
|
||||
Flush, // none
|
||||
}
|
||||
|
||||
pub struct Service {
|
||||
@@ -237,9 +238,11 @@ impl Service {
|
||||
events.push(e);
|
||||
}
|
||||
} else {
|
||||
self.db.mark_as_active(&new_events)?;
|
||||
for (e, _) in new_events {
|
||||
events.push(e);
|
||||
if !new_events.is_empty() {
|
||||
self.db.mark_as_active(&new_events)?;
|
||||
for (e, _) in new_events {
|
||||
events.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
if let OutgoingKind::Normal(server_name) = outgoing_kind {
|
||||
@@ -360,11 +363,11 @@ impl Service {
|
||||
|
||||
for user_id in device_list_changes {
|
||||
// Empty prev id forces synapse to resync: https://github.com/matrix-org/synapse/blob/98aec1cc9da2bd6b8e34ffb282c85abf9b8b42ca/synapse/handlers/device.py#L767
|
||||
// Because synapse resyncs, we can just insert dummy data
|
||||
// Because synapse resyncs, we can just insert placeholder data
|
||||
let edu = Edu::DeviceListUpdate(DeviceListUpdateContent {
|
||||
user_id,
|
||||
device_id: device_id!("dummy").to_owned(),
|
||||
device_display_name: Some("Dummy".to_owned()),
|
||||
device_id: device_id!("placeholder").to_owned(),
|
||||
device_display_name: Some("Placeholder".to_owned()),
|
||||
stream_id: uint!(1),
|
||||
prev_id: Vec::new(),
|
||||
deleted: None,
|
||||
@@ -421,6 +424,29 @@ impl Service {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, room_id))]
|
||||
pub fn flush_room(&self, room_id: &RoomId) -> Result<()> {
|
||||
let servers: HashSet<OwnedServerName> =
|
||||
services().rooms.state_cache.room_servers(room_id).filter_map(std::result::Result::ok).collect();
|
||||
|
||||
self.flush_servers(servers.into_iter())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, servers))]
|
||||
pub fn flush_servers<I: Iterator<Item = OwnedServerName>>(&self, servers: I) -> Result<()> {
|
||||
let requests = servers
|
||||
.into_iter()
|
||||
.filter(|server| server != services().globals.server_name())
|
||||
.map(OutgoingKind::Normal)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for outgoing_kind in requests.into_iter() {
|
||||
self.sender.send((outgoing_kind, SendingEventType::Flush, Vec::<u8>::new())).unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleanup event data
|
||||
/// Used for instance after we remove an appservice registration
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -461,6 +487,9 @@ impl Service {
|
||||
SendingEventType::Edu(_) => {
|
||||
// Appservices don't need EDUs (?)
|
||||
},
|
||||
SendingEventType::Flush => {
|
||||
// flush only; no new content
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,6 +509,7 @@ impl Service {
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
|
||||
SendingEventType::Flush => &[],
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)))
|
||||
@@ -521,6 +551,9 @@ impl Service {
|
||||
SendingEventType::Edu(_) => {
|
||||
// Push gateways don't need EDUs (?)
|
||||
},
|
||||
SendingEventType::Flush => {
|
||||
// flush only; no new content
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,6 +634,9 @@ impl Service {
|
||||
edu_jsons.push(raw);
|
||||
}
|
||||
},
|
||||
SendingEventType::Flush => {
|
||||
// flush only; no new content
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,6 +654,7 @@ impl Service {
|
||||
.iter()
|
||||
.map(|e| match e {
|
||||
SendingEventType::Edu(b) | SendingEventType::Pdu(b) => &**b,
|
||||
SendingEventType::Flush => &[],
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)))
|
||||
@@ -647,6 +684,10 @@ impl Service {
|
||||
where
|
||||
T: OutgoingRequest + Debug,
|
||||
{
|
||||
if !services().globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
if destination.is_ip_literal() || IPAddress::is_valid(destination.host()) {
|
||||
info!(
|
||||
"Destination {} is an IP literal, checking against IP range denylist.",
|
||||
|
||||
Reference in New Issue
Block a user