Compare commits

...

70 Commits

Author SHA1 Message Date
Jade Ellis 00c7e220bb chore: Release 2025-07-25 14:10:06 +01:00
Jade Ellis 87be4d1a52 feat: Almost-functional musl builds on Alpine
Lots of fiddling, still can't get stuff to work

Next step is a debian builder copying the static libs from alpine
2025-07-24 23:22:27 +01:00
Jade Ellis 205506f206 chore: Update deps 2025-07-24 22:18:10 +01:00
Jade Ellis 66181c61af chore: Update rocksdb, feature flag changes
Most of the way to static musl builds, just zlib I think
2025-07-24 21:51:52 +01:00
Jade Ellis b7a0442298 feat: Musl images in docker
Not working at the moment, need to upgrade the rust-rocksdb and possibly
zstd to stop them force-enabling dynamic libclang
2025-07-24 19:00:41 +01:00
Jade Ellis 1bc663e1c8 docs: Fix spacing at the top 2025-07-24 13:37:52 +01:00
Jade Ellis 68b0140c42 docs: Add vias to matrix.to links 2025-07-24 13:31:58 +01:00
nexy7574 f32f60d056 fix(policy-server): Return the correct result when an event is marked as spam 2025-07-23 18:01:46 +01:00
nexy7574 fe06d78c8e fix(policy-server): Update ask_policy_server docstring 2025-07-23 17:58:33 +01:00
nexy7574 99ebe022ed fix(policy-server): Correctly default to 10 second timeout 2025-07-23 17:56:45 +01:00
nexy7574 f335f45017 feat(policy-server): Add configurable timeout 2025-07-23 17:49:08 +01:00
nexy7574 1726633c0f fix(policy-server): Fixup refactor 2025-07-23 17:49:08 +01:00
nexy7574 dfda27fadc feat(policy-server): Don't fail-closed & refactor references 2025-07-23 17:49:08 +01:00
Jade Ellis 9465c5df1f style: Improve logging and comments 2025-07-23 17:49:07 +01:00
nexy7574 2d475b1220 style(policy-server): Run clippy 2025-07-23 17:49:07 +01:00
nexy7574 d7fa624fd2 feat(policy-server): Optimise policy server lookups 2025-07-23 17:49:07 +01:00
nexy7574 cc9202b0c4 feat(policy-server): Limit policy server request timeout to 10 seconds 2025-07-23 17:49:07 +01:00
nexy7574 a3d62ed0d9 feat(policy-server): Prevent local events that fail the policy check 2025-07-23 17:49:07 +01:00
nexy7574 78b7175677 feat(policy-server): Soft-fail redactions for failed events 2025-07-23 17:49:07 +01:00
nexy7574 74d60f256b style(policy-server): Restructure logging 2025-07-23 17:49:07 +01:00
nexy7574 732c69f5ca fix(policy-server): Avoid unnecessary database lookup 2025-07-23 17:49:07 +01:00
nexy7574 8e7801f323 chore: Update ruwuma & fix lints 2025-07-23 17:49:06 +01:00
nexy7574 9017efe45b feat(policy-server): Policy server following 2025-07-23 17:49:06 +01:00
Jade Ellis 7e2f04a78a chore: Check all features in CI and docs 2025-07-20 21:25:27 +01:00
Jade Ellis d74514f305 ci: Fix inverted latest tag 2025-07-20 20:59:29 +01:00
Jade Ellis 95610499c7 chore: Disable direnv's nix flake interfering with cargo cache 2025-07-20 16:36:01 +01:00
Jade Ellis f593cac58a feat: Enable hardware-lock-elision and deadlock_detection 2025-07-20 16:35:59 +01:00
Jade Ellis 1c985c59f5 refactor: Allow with_lock to return data and take an async closure 2025-07-20 16:34:48 +01:00
Jade Ellis b635e825d2 refactor: Implement with_lock for lock_api 2025-07-20 16:34:36 +01:00
Jade Ellis 6d29098d1a refactor: Replace remaining std RwLocks 2025-07-20 16:33:36 +01:00
Jade Ellis 374fb2745c refactor: Replace remaining std Mutexes 2025-07-20 16:32:48 +01:00
Jade Ellis a1d616e3e3 refactor: Replace std RwLock with parking_lot 2025-07-20 16:31:55 +01:00
Jade Ellis 30a8c06fd9 refactor: Replace std Mutex with parking_lot 2025-07-20 16:31:02 +01:00
rooot 0631094350 docs(config): warn about federation key query timeout caveat
Signed-off-by: rooot <hey@rooot.gay>
2025-07-20 16:24:56 +01:00
rooot 9051ce63f7 feat(config): introduce federation connection timeout setting
fixes #906

Signed-off-by: rooot <hey@rooot.gay>
2025-07-20 16:24:26 +01:00
Jade Ellis f513cb7598 chore: Remove false positives in typo checks 2025-07-19 20:31:54 +01:00
nexy7574 c639228f4d style(space-upgrades): Remove unused import left over from 6691b7672b 2025-07-19 18:37:45 +01:00
nexy7574 331832616f feat(space-upgrades): MSC4168: Override space child vias 2025-07-19 18:37:45 +01:00
nexy7574 b2b18002ea fix(space-upgrades): Remove unused helper function 2025-07-19 18:37:45 +01:00
nexy7574 57868a008c feat(space-upgrades): Skip empty state events in room upgrade 2025-07-19 18:37:45 +01:00
nexy7574 f063814d94 fix(space-upgrades): Incorrectly updated parent children events 2025-07-19 18:37:38 +01:00
nexy7574 3b5335630d feat(space-upgrades): Transfer all state keys during upgrade
Before this change, only state events with an
empty state key would be cloned.
This allows m.space.child to be cloned appropriately.
2025-07-19 18:35:59 +01:00
nexy7574 b2883c3d6e feat(space-upgrades): Update parent spaces in upgrade
This relies on the room being upgraded referencing
the space itself, but there isn't an easy way to
do it otherwise.
2025-07-19 18:35:58 +01:00
nexy7574 62bdfe1ce8 feat(space-upgrades): Copy over space child & parent states 2025-07-19 18:35:56 +01:00
Jade Ellis 843e501902 docs: Add section for testing TURN servers 2025-07-16 23:47:41 +01:00
Jade Ellis 0a8c13ffd2 fix: Use boolean where expected in services
Fixes https://forgejo.ellis.link/continuwuation/continuwuity/issues/905
2025-07-16 23:27:14 +01:00
Jade Ellis a89ceb93d8 docs: Update Docker and generic instructions
Add instructions for proxying .well-known to Continuwuity in with
Traefik. Clarify and expand build instructions in generic deployment,
separating Rust toolchain and Nix approaches.
2025-07-16 23:27:14 +01:00
Jade Ellis 13de0ac822 docs: Update and improve NixOS documentation
Documentation now mentions the official package
2025-07-16 23:27:14 +01:00
Jade Ellis 4a5b122d77 docs: Improve grammar in Debian package 2025-07-16 23:27:14 +01:00
Jade Ellis 2655acf269 docs: Improve grammar in deployment documentation 2025-07-16 23:27:14 +01:00
Jade Ellis 3c320f6d6e docs: Fix code examples in style guide 2025-07-16 23:27:14 +01:00
Jade Ellis 946449d3e5 docs: Add link to UV docs 2025-07-16 23:27:13 +01:00
Jade Ellis b17f278803 docs: Add code style guide 2025-07-16 23:27:13 +01:00
Jade Ellis 6a4905271e refactor: Add with_lock traits 2025-07-16 23:27:13 +01:00
Jade Ellis cfc64ddb40 docs: Note python requirements 2025-07-16 23:27:13 +01:00
Jade Ellis 6aceac3833 docs: Note policy on large formatting diffs 2025-07-16 23:27:13 +01:00
Nyx 5bf20db8e7 Add /_continuwuity/ paths 2025-07-14 17:49:06 +00:00
nexy7574 1abe8f7835 fix: Creation bug 2025-07-12 22:37:49 +01:00
nexy7574 ce84c46459 style(902): Fix clippy complaining about cast 2025-07-09 15:28:31 +01:00
nexy7574 7b60f5368d feat(902): Upload files for admin commands that are too long 2025-07-09 15:11:09 +01:00
nexy7574 e61a593932 fix: Implement MSC4307 2025-07-08 22:40:44 +00:00
nexy7574 b71186d958 chore(recaptcha): Update example config file
Unsure how this managed to get past the `git commit -S -a`
but sure
2025-07-08 19:47:42 +01:00
nexy7574 c362499cef docs(recaptcha): Clarify registration when token & captcha are configured 2025-07-08 19:34:47 +01:00
nexy7574 14774fa153 feat(recaptcha): Don't allow pubkey without associated privkey 2025-07-08 19:32:23 +01:00
nexy7574 ff805d8ae1 feat(recaptcha): Fix linting issues after the linter fix lied to me 2025-07-08 19:27:51 +01:00
nexy7574 f0994355d4 feat(recaptcha): Fix linting issues 2025-07-08 19:15:55 +01:00
nexy7574 980774a275 feat(recaptcha): Update example config after previous changes 2025-07-08 19:08:25 +01:00
nexy7574 e4a6abe15e feat(recaptcha): Disable treating captcha-enabled servers as abuse-prone 2025-07-08 19:07:27 +01:00
nexy7574 df1cb10a8e feat(recaptcha): Add documentation for new fields 2025-07-08 19:03:35 +01:00
nexy7574 651d07a609 feat: Add ReCaptcha registration flow 2025-07-08 18:58:05 +01:00
88 changed files with 2180 additions and 783 deletions
+1 -1
View File
@@ -2,6 +2,6 @@
dotenv_if_exists
use flake ".#${DIRENV_DEVSHELL:-default}"
# use flake ".#${DIRENV_DEVSHELL:-default}"
PATH_add bin
+1 -1
View File
@@ -262,7 +262,7 @@ jobs:
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }}
type=ref,event=pr
type=sha,format=long
type=raw,value=latest,enable=${{ !startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
images: ${{needs.define-variables.outputs.images}}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env:
+2
View File
@@ -73,6 +73,7 @@ jobs:
run: |
cargo clippy \
--workspace \
--features full \
--locked \
--no-deps \
--profile test \
@@ -132,6 +133,7 @@ jobs:
run: |
cargo test \
--workspace \
--features full \
--locked \
--profile test \
--all-targets \
+15 -1
View File
@@ -1,5 +1,19 @@
[files]
extend-exclude = ["*.csr"]
extend-exclude = ["*.csr", "*.lock", "pnpm-lock.yaml"]
[default]
extend-ignore-re = [
"(?Rm)^.*(#|//|<!--)\\s*spellchecker:disable-line(\\s*-->)$", # Ignore a line by making it trail with a `spellchecker:disable-line` comment
"^[0-9a-f]{7,}$", # Commit hashes
# some heuristics for base64 strings
"[A-Za-z0-9+=]{72,}",
"([A-Za-z0-9+=]|\\\\\\s\\*){72,}",
"[0-9+][A-Za-z0-9+]{30,}[a-z0-9+]",
"\\$[A-Z0-9+][A-Za-z0-9+]{6,}[a-z0-9+]",
"\\b[a-z0-9+/=][A-Za-z0-9+/=]{7,}[a-z0-9+/=][A-Z]\\b",
]
[default.extend-words]
"allocatedp" = "allocatedp"
+1 -1
View File
@@ -59,7 +59,7 @@ representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement over Matrix at [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org) or email at <tom@tcpip.uk>, <jade@continuwuity.org> and <nex@continuwuity.org> respectively.
reported to the community leaders responsible for enforcement over Matrix at [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) or email at <tom@tcpip.uk>, <jade@continuwuity.org> and <nex@continuwuity.org> respectively.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
+19 -48
View File
@@ -1,25 +1,15 @@
# Contributing guide
This page is about contributing to Continuwuity. The
[development](./development.md) page may be of interest for you as well.
[development](./development.md) and [code style guide](./development/code_style.md) pages may be of interest for you as well.
If you would like to work on an [issue][issues] that is not assigned, preferably
ask in the Matrix room first at [#continuwuity:continuwuity.org][continuwuity-matrix],
and comment on it.
### Linting and Formatting
### Code Style
It is mandatory all your changes satisfy the lints (clippy, rustc, rustdoc, etc)
and your code is formatted via the **nightly** rustfmt (`cargo +nightly fmt`). A lot of the
`rustfmt.toml` features depend on nightly toolchain. It would be ideal if they
weren't nightly-exclusive features, but they currently still are. CI's rustfmt
uses nightly.
If you need to allow a lint, please make sure it's either obvious as to why
(e.g. clippy saying redundant clone but it's actually required) or it has a
comment saying why. Do not write inefficient code for the sake of satisfying
lints. If a lint is wrong and provides a more inefficient solution or
suggestion, allow the lint and mention that in a comment.
Please review and follow the [code style guide](./development/code_style.md) for formatting, linting, naming conventions, and other code standards.
### Pre-commit Checks
@@ -36,6 +26,10 @@ You can run these checks locally by installing [prefligit](https://github.com/j1
```bash
# Requires UV: https://docs.astral.sh/uv/getting-started/installation/
# Mac/linux: curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# Install prefligit using cargo-binstall
cargo binstall prefligit
@@ -48,6 +42,8 @@ prefligit --all-files
Alternatively, you can use [pre-commit](https://pre-commit.com/):
```bash
# Requires python
# Install pre-commit
pip install pre-commit
@@ -58,7 +54,7 @@ pre-commit install
pre-commit run --all-files
```
These same checks are run in CI via the prefligit-checks workflow to ensure consistency.
These same checks are run in CI via the prefligit-checks workflow to ensure consistency. These must pass before the PR is merged.
### Running tests locally
@@ -69,11 +65,11 @@ Tests, compilation, and linting can be run with standard Cargo commands:
cargo test
# Check compilation
cargo check --workspace
cargo check --workspace --features full
# Run lints
cargo clippy --workspace
# Auto-fix: cargo clippy --workspace --fix --allow-staged;
cargo clippy --workspace --features full
# Auto-fix: cargo clippy --workspace --features full --fix --allow-staged;
# Format code (must use nightly)
cargo +nightly fmt
@@ -107,37 +103,13 @@ To build the documentation locally:
The output of the mdbook generation is in `public/`. You can open the HTML files directly in your browser without needing a web server.
### Inclusivity and Diversity
All **MUST** code and write with inclusivity and diversity in mind. See the
[following page by Google on writing inclusive code and
documentation](https://developers.google.com/style/inclusive-documentation).
This **EXPLICITLY** forbids usage of terms like "blacklist"/"whitelist" and
"master"/"slave", [forbids gender-specific words and
phrases](https://developers.google.com/style/pronouns#gender-neutral-pronouns),
forbids ableist language like "sanity-check", "cripple", or "insane", and
forbids culture-specific language (e.g. US-only holidays or cultures).
No exceptions are allowed. Dependencies that may use these terms are allowed but
[do not replicate the name in your functions or
variables](https://developers.google.com/style/inclusive-documentation#write-around).
In addition to language, write and code with the user experience in mind. This
is software that intends to be used by everyone, so make it easy and comfortable
for everyone to use. 🏳️‍⚧️
### Variable, comment, function, etc standards
Rust's default style and standards with regards to [function names, variable
names, comments](https://rust-lang.github.io/api-guidelines/naming.html), etc
applies here.
### Commit Messages
Continuwuity follows the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. This provides a standardized format that makes the commit history more readable and enables automated tools to generate changelogs.
The basic structure is:
```
<type>[(optional scope)]: <description>
@@ -178,11 +150,10 @@ of it, especially when the CI completed successfully and everything so it
Before submitting a pull request, please ensure:
1. Your code passes all CI checks (formatting, linting, typo detection, etc.)
2. Your commit messages follow the conventional commits format
3. Tests are added for new functionality
4. Documentation is updated if needed
2. Your code follows the [code style guide](./development/code_style.md)
3. Your commit messages follow the conventional commits format
4. Tests are added for new functionality
5. Documentation is updated if needed
Direct all PRs/MRs to the `main` branch.
@@ -195,7 +166,7 @@ their contributions accepted. This includes users who have been banned from
continuwuity Matrix rooms for Code of Conduct violations.
[issues]: https://forgejo.ellis.link/continuwuation/continuwuity/issues
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
[complement]: https://github.com/matrix-org/complement/
[sytest]: https://github.com/matrix-org/sytest/
[mdbook]: https://rust-lang.github.io/mdBook/
Generated
+190 -135
View File
@@ -52,9 +52,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.18"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -73,27 +73,27 @@ checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.8"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
@@ -217,9 +217,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.25"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4"
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
dependencies = [
"brotli",
"flate2",
@@ -301,18 +301,18 @@ dependencies = [
[[package]]
name = "avif-serialize"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e"
checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42"
dependencies = [
"arrayvec",
]
[[package]]
name = "aws-lc-rs"
version = "1.13.1"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7"
checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba"
dependencies = [
"aws-lc-sys",
"zeroize",
@@ -320,9 +320,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.29.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079"
checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff"
dependencies = [
"bindgen 0.69.5",
"cc",
@@ -527,9 +527,9 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.71.1"
version = "0.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
dependencies = [
"bitflags 2.9.1",
"cexpr",
@@ -629,9 +629,9 @@ checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
[[package]]
name = "bumpalo"
version = "3.18.1"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
@@ -685,9 +685,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.27"
version = "1.2.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
dependencies = [
"jobserver",
"libc",
@@ -756,9 +756,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.40"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
dependencies = [
"clap_builder",
"clap_derive",
@@ -775,9 +775,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.40"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
dependencies = [
"anstream",
"anstyle",
@@ -787,9 +787,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.40"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [
"heck",
"proc-macro2",
@@ -805,9 +805,9 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "clap_mangen"
version = "0.2.26"
version = "0.2.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "724842fa9b144f9b89b3f3d371a89f3455eea660361d13a554f68f8ae5d6c13a"
checksum = "e2fb6d3f935bbb9819391528b0e7cf655e78a0bc7a7c3d227211a1d24fc11db1"
dependencies = [
"clap",
"roff",
@@ -830,9 +830,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "concurrent-queue"
@@ -845,7 +845,7 @@ dependencies = [
[[package]]
name = "conduwuit"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"clap",
"conduwuit_admin",
@@ -875,7 +875,7 @@ dependencies = [
[[package]]
name = "conduwuit_admin"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"clap",
"conduwuit_api",
@@ -896,7 +896,7 @@ dependencies = [
[[package]]
name = "conduwuit_api"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"async-trait",
"axum",
@@ -928,14 +928,14 @@ dependencies = [
[[package]]
name = "conduwuit_build_metadata"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"built 0.8.0",
]
[[package]]
name = "conduwuit_core"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"argon2",
"arrayvec",
@@ -963,10 +963,12 @@ dependencies = [
"itertools 0.14.0",
"libc",
"libloading",
"lock_api",
"log",
"maplit",
"nix",
"num-traits",
"parking_lot",
"rand 0.8.5",
"regex",
"reqwest",
@@ -994,7 +996,7 @@ dependencies = [
[[package]]
name = "conduwuit_database"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"async-channel",
"conduwuit_core",
@@ -1012,7 +1014,7 @@ dependencies = [
[[package]]
name = "conduwuit_macros"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"itertools 0.14.0",
"proc-macro2",
@@ -1022,7 +1024,7 @@ dependencies = [
[[package]]
name = "conduwuit_router"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"axum",
"axum-client-ip",
@@ -1056,7 +1058,7 @@ dependencies = [
[[package]]
name = "conduwuit_service"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"async-trait",
"base64 0.22.1",
@@ -1076,6 +1078,7 @@ dependencies = [
"loole",
"lru-cache",
"rand 0.8.5",
"recaptcha-verify",
"regex",
"reqwest",
"ruma",
@@ -1093,7 +1096,7 @@ dependencies = [
[[package]]
name = "conduwuit_web"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"askama",
"axum",
@@ -1152,15 +1155,15 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const-str"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e991226a70654b49d34de5ed064885f0bef0348a8e70018b8ff1ac80aa984a2"
checksum = "041fbfcf8e7054df725fb9985297e92422cdc80fcf313665f5ca3d761bb63f4c"
[[package]]
name = "const_panic"
version = "0.2.12"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2459fc9262a1aa204eb4b5764ad4f189caec88aea9634389c0a25f8be7f6265e"
checksum = "b98d1483e98c9d67f341ab4b3915cfdc54740bd6f5cccc9226ee0535d86aa8fb"
[[package]]
name = "convert_case"
@@ -1217,9 +1220,9 @@ dependencies = [
[[package]]
name = "crc32fast"
version = "1.4.2"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
@@ -1325,7 +1328,7 @@ dependencies = [
"futures-core",
"mio",
"parking_lot",
"rustix 1.0.7",
"rustix 1.0.8",
"signal-hook",
"signal-hook-mio",
"winapi",
@@ -1342,9 +1345,9 @@ dependencies = [
[[package]]
name = "crunchy"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "crypto-common"
@@ -1510,9 +1513,9 @@ dependencies = [
[[package]]
name = "ed25519-dalek"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
dependencies = [
"curve25519-dalek",
"ed25519",
@@ -1656,6 +1659,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.1.2"
@@ -1858,9 +1867,9 @@ dependencies = [
[[package]]
name = "gif"
version = "0.13.1"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
dependencies = [
"color_quant",
"weezl",
@@ -1880,9 +1889,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "h2"
version = "0.4.10"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
dependencies = [
"atomic-waker",
"bytes",
@@ -1890,7 +1899,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http",
"indexmap 2.9.0",
"indexmap 2.10.0",
"slab",
"tokio",
"tokio-util",
@@ -2020,7 +2029,7 @@ dependencies = [
"idna",
"ipnet",
"once_cell",
"rand 0.9.1",
"rand 0.9.2",
"ring",
"serde",
"thiserror 2.0.12",
@@ -2064,7 +2073,7 @@ dependencies = [
"moka",
"once_cell",
"parking_lot",
"rand 0.9.1",
"rand 0.9.2",
"resolv-conf",
"serde",
"smallvec",
@@ -2213,7 +2222,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots 1.0.1",
"webpki-roots 1.0.2",
]
[[package]]
@@ -2406,9 +2415,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.9.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown 0.15.4",
@@ -2438,6 +2447,17 @@ dependencies = [
"syn",
]
[[package]]
name = "io-uring"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
"libc",
]
[[package]]
name = "ipaddress"
version = "0.1.3"
@@ -2521,9 +2541,9 @@ dependencies = [
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
[[package]]
name = "js-sys"
@@ -2622,9 +2642,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "libfuzzer-sys"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
dependencies = [
"arbitrary",
"cc",
@@ -3104,7 +3124,7 @@ checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a"
dependencies = [
"futures-core",
"futures-sink",
"indexmap 2.9.0",
"indexmap 2.10.0",
"js-sys",
"once_cell",
"pin-project-lite",
@@ -3217,10 +3237,13 @@ version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"backtrace",
"cfg-if",
"libc",
"petgraph",
"redox_syscall",
"smallvec",
"thread-id",
"windows-targets 0.52.6",
]
@@ -3270,6 +3293,16 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "petgraph"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.10.0",
]
[[package]]
name = "phf"
version = "0.11.3"
@@ -3358,12 +3391,12 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plist"
version = "1.7.2"
version = "1.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed"
checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
dependencies = [
"base64 0.22.1",
"indexmap 2.9.0",
"indexmap 2.10.0",
"quick-xml",
"serde",
"time",
@@ -3420,9 +3453,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "prettyplease"
version = "0.2.35"
version = "0.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2"
dependencies = [
"proc-macro2",
"syn",
@@ -3461,18 +3494,18 @@ dependencies = [
[[package]]
name = "profiling"
version = "1.0.16"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
dependencies = [
"profiling-procmacros",
]
[[package]]
name = "profiling-procmacros"
version = "1.0.16"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
dependencies = [
"quote",
"syn",
@@ -3545,9 +3578,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.37.5"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
dependencies = [
"memchr",
]
@@ -3581,7 +3614,7 @@ dependencies = [
"bytes",
"getrandom 0.3.3",
"lru-slab",
"rand 0.9.1",
"rand 0.9.2",
"ring",
"rustc-hash 2.1.1",
"rustls",
@@ -3635,9 +3668,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.9.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
@@ -3718,9 +3751,9 @@ dependencies = [
[[package]]
name = "ravif"
version = "0.11.12"
version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6"
checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b"
dependencies = [
"avif-serialize",
"imgref",
@@ -3752,10 +3785,21 @@ dependencies = [
]
[[package]]
name = "redox_syscall"
version = "0.5.13"
name = "recaptcha-verify"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
checksum = "71e3be7b2e46e24637ac96b0c9f70070f188652018573f36f4e511dcad09738a"
dependencies = [
"reqwest",
"serde",
"serde_json",
]
[[package]]
name = "redox_syscall"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
dependencies = [
"bitflags 2.9.1",
]
@@ -3861,9 +3905,9 @@ source = "git+https://forgejo.ellis.link/continuwuation/resolv-conf?rev=56251316
[[package]]
name = "rgb"
version = "0.8.50"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
[[package]]
name = "ring"
@@ -3888,7 +3932,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]]
name = "ruma"
version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"assign",
"js_int",
@@ -3908,7 +3952,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"js_int",
"ruma-common",
@@ -3920,7 +3964,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"as_variant",
"assign",
@@ -3943,7 +3987,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"as_variant",
"base64 0.22.1",
@@ -3951,7 +3995,7 @@ dependencies = [
"form_urlencoded",
"getrandom 0.2.16",
"http",
"indexmap 2.9.0",
"indexmap 2.10.0",
"js_int",
"konst",
"percent-encoding",
@@ -3975,10 +4019,10 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"as_variant",
"indexmap 2.9.0",
"indexmap 2.10.0",
"js_int",
"js_option",
"percent-encoding",
@@ -4000,7 +4044,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"bytes",
"headers",
@@ -4022,7 +4066,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"js_int",
"thiserror 2.0.12",
@@ -4031,7 +4075,7 @@ dependencies = [
[[package]]
name = "ruma-identity-service-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"js_int",
"ruma-common",
@@ -4041,7 +4085,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"cfg-if",
"proc-macro-crate",
@@ -4056,7 +4100,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"js_int",
"ruma-common",
@@ -4068,7 +4112,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@@ -4083,10 +4127,10 @@ dependencies = [
[[package]]
name = "rust-librocksdb-sys"
version = "0.33.0+9.11.1"
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd#fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd"
version = "0.38.0+10.4.2"
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=99b0319416b64830dd6f8943e1f65e15aeef18bc#99b0319416b64830dd6f8943e1f65e15aeef18bc"
dependencies = [
"bindgen 0.71.1",
"bindgen 0.72.0",
"bzip2-sys",
"cc",
"glob",
@@ -4100,8 +4144,8 @@ dependencies = [
[[package]]
name = "rust-rocksdb"
version = "0.37.0"
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd#fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd"
version = "0.42.1"
source = "git+https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1?rev=99b0319416b64830dd6f8943e1f65e15aeef18bc#99b0319416b64830dd6f8943e1f65e15aeef18bc"
dependencies = [
"libc",
"rust-librocksdb-sys",
@@ -4149,22 +4193,22 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
name = "rustls"
version = "0.23.28"
version = "0.23.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
dependencies = [
"aws-lc-rs",
"log",
@@ -4209,9 +4253,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.103.3"
version = "0.103.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
dependencies = [
"aws-lc-rs",
"ring",
@@ -4475,7 +4519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4"
dependencies = [
"form_urlencoded",
"indexmap 2.9.0",
"indexmap 2.10.0",
"itoa",
"ryu",
"serde",
@@ -4483,9 +4527,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.140"
version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
dependencies = [
"itoa",
"memchr",
@@ -4540,7 +4584,7 @@ version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.9.0",
"indexmap 2.10.0",
"itoa",
"ryu",
"serde",
@@ -4880,6 +4924,16 @@ dependencies = [
"syn",
]
[[package]]
name = "thread-id"
version = "4.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "thread_local"
version = "1.1.9"
@@ -5008,16 +5062,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.45.1"
version = "1.46.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
dependencies = [
"backtrace",
"bytes",
"io-uring",
"libc",
"mio",
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"tokio-macros",
"tracing",
@@ -5037,9 +5093,9 @@ dependencies = [
[[package]]
name = "tokio-metrics"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7817b32d36c9b94744d7aa3f8fc13526aa0f5112009d7045f3c659413a6e44ac"
checksum = "23ff82f660c98e4ff60da5eb8fa864a4130f34b56d92d5cd23d6fdfcc14e95fa"
dependencies = [
"futures-util",
"pin-project-lite",
@@ -5120,7 +5176,7 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.9.0",
"indexmap 2.10.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -5344,9 +5400,9 @@ checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "typewit"
version = "1.11.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb77c29baba9e4d3a6182d51fa75e3215c7fd1dab8f4ea9d107c716878e55fc0"
checksum = "97e72ba082eeb9da9dc68ff5a2bf727ef6ce362556e8d29ec1aed3bd05e7d86a"
dependencies = [
"typewit_proc_macros",
]
@@ -5658,14 +5714,14 @@ version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.1",
"webpki-roots 1.0.2",
]
[[package]]
name = "webpki-roots"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502"
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
dependencies = [
"rustls-pki-types",
]
@@ -6076,9 +6132,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
dependencies = [
"memchr",
]
@@ -6121,7 +6177,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"clap",
"serde",
@@ -6130,7 +6186,7 @@ dependencies = [
[[package]]
name = "xtask-generate-commands"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
dependencies = [
"clap-markdown",
"clap_builder",
@@ -6273,7 +6329,6 @@ version = "2.0.15+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
dependencies = [
"bindgen 0.71.1",
"cc",
"pkg-config",
]
@@ -6295,9 +6350,9 @@ dependencies = [
[[package]]
name = "zune-jpeg"
version = "0.4.17"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f6fe2e33d02a98ee64423802e16df3de99c43e5cf5ff983767e1128b394c8ac"
checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a"
dependencies = [
"zune-core",
]
+11 -3
View File
@@ -21,7 +21,7 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
rust-version = "1.86.0"
version = "0.5.0-rc.6"
version = "0.5.0-rc.7"
[workspace.metadata.crane]
name = "conduwuit"
@@ -352,7 +352,7 @@ version = "0.1.2"
[workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes"
rev = "a4b948b40417a65ab0282ae47cc50035dd455e02"
rev = "b753738047d1f443aca870896ef27ecaacf027da"
features = [
"compat",
"rand",
@@ -391,7 +391,7 @@ features = [
[workspace.dependencies.rust-rocksdb]
git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
rev = "fc9a99ac54a54208f90fdcba33ae6ee8bc3531dd"
rev = "99b0319416b64830dd6f8943e1f65e15aeef18bc"
default-features = false
features = [
"multi-threaded-cf",
@@ -515,6 +515,14 @@ version = "1.0"
[workspace.dependencies.proc-macro2]
version = "1.0"
[workspace.dependencies.parking_lot]
version = "0.12.4"
features = ["hardware-lock-elision", "deadlock_detection"] # TODO: Check if deadlock_detection has a perf impact, if it does only enable with debug_assertions
# Use this when extending with_lock::WithLock to parking_lot
[workspace.dependencies.lock_api]
version = "0.4.13"
[workspace.dependencies.bytesize]
version = "2.0"
+1 -1
View File
@@ -115,7 +115,7 @@ When incorporating code from other forks:
#### Contact
Join our [Matrix room](https://matrix.to/#/#continuwuity:continuwuity.org) and [space](https://matrix.to/#/#space:continuwuity.org) to chat with us about the project!
Join our [Matrix room](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) and [space](https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) to chat with us about the project!
<!-- ANCHOR_END: footer -->
+1 -1
View File
@@ -18,7 +18,7 @@ StandardInput=tty-force
StandardOutput=tty
StandardError=journal+console
Environment="CONTINUWUITY_LOG_TO_JOURNALD=1"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
TTYReset=yes
+45
View File
@@ -325,12 +325,37 @@
#
#well_known_timeout = 10
# Federation client connection timeout (seconds). You should not set this
# to high values, as dead homeservers can significantly slow down
# federation, specifically key retrieval, which will take roughly the
# amount of time you configure here given that a homeserver doesn't
# respond. This will cause most clients to time out /keys/query, causing
# E2EE and device verification to fail.
#
#federation_conn_timeout = 10
# Federation client request timeout (seconds). You most definitely want
# this to be high to account for extremely large room joins, slow
# homeservers, your own resources etc.
#
#federation_timeout = 300
# MSC4284 Policy server request timeout (seconds). Generally policy
# servers should respond near instantly, however may slow down under
# load. If a policy server doesn't respond in a short amount of time, the
# room it is configured in may become unusable if this limit is set too
# high. 10 seconds is a good default, however dropping this to 3-5 seconds
# can be acceptable.
#
# Please be aware that policy requests are *NOT* currently re-tried, so if
# a spam check request fails, the event will be assumed to be not spam,
# which in some cases may result in spam being sent to or received from
# the room that would typically be prevented.
#
# About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
#
#policy_server_request_timeout = 10
# Federation client idle connection pool timeout (seconds).
#
#federation_idle_timeout = 25
@@ -441,6 +466,26 @@
#
#registration_token_file =
# The public site key for reCaptcha. If this is provided, reCaptcha
# becomes required during registration. If both captcha *and*
# registration token are enabled, both will be required during
# registration.
#
# IMPORTANT: "Verify the origin of reCAPTCHA solutions" **MUST** BE
# DISABLED IF YOU WANT THE CAPTCHA TO WORK IN 3RD PARTY CLIENTS, OR
# CLIENTS HOSTED ON DOMAINS OTHER THAN YOUR OWN!
#
# Registration must be enabled (`allow_registration` must be true) for
# this to have any effect.
#
#recaptcha_site_key =
# The private site key for reCaptcha.
# If this is omitted, captcha registration will not work,
# even if `recaptcha_site_key` is set.
#
#recaptcha_private_site_key =
# Controls whether encrypted rooms and events are allowed.
#
#allow_encryption = true
+8 -14
View File
@@ -1,29 +1,23 @@
# Continuwuity for Debian
Information about downloading and deploying the Debian package. This may also be
referenced for other `apt`-based distros such as Ubuntu.
This document provides information about downloading and deploying the Debian package. You can also use this guide for other `apt`-based distributions such as Ubuntu.
### Installation
It is recommended to see the [generic deployment guide](../deploying/generic.md)
for further information if needed as usage of the Debian package is generally
related.
See the [generic deployment guide](../deploying/generic.md) for additional information about using the Debian package.
No `apt` repository is currently offered yet, it is in the works/development.
No `apt` repository is currently available. This feature is in development.
### Configuration
When installed, the example config is placed at `/etc/conduwuit/conduwuit.toml`
as the default config. The config mentions things required to be changed before
starting.
After installation, Continuwuity places the example configuration at `/etc/conduwuit/conduwuit.toml` as the default configuration file. The configuration file indicates which settings you must change before starting the service.
You can tweak more detailed settings by uncommenting and setting the config
options in `/etc/conduwuit/conduwuit.toml`.
You can customize additional settings by uncommenting and modifying the configuration options in `/etc/conduwuit/conduwuit.toml`.
### Running
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop Continuwuity. The binary is installed at `/usr/sbin/conduwuit`.
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop Continuwuity. The binary installs at `/usr/sbin/conduwuit`.
This package assumes by default that conduwuit will be placed behind a reverse proxy. The default config options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS, so you will need to set up TLS certificates and renewal for it to work properly if you intend to federate.
By default, this package assumes that Continuwuity runs behind a reverse proxy. The default configuration options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS. To federate properly, you must set up TLS certificates and certificate renewal.
Consult various online documentation and guides on setting up a reverse proxy and TLS. Caddy is documented at the [generic deployment guide](../deploying/generic.md#setting-up-the-reverse-proxy) as it's the easiest and most user friendly.
For information about setting up a reverse proxy and TLS, consult online documentation and guides. The [generic deployment guide](../deploying/generic.md#setting-up-the-reverse-proxy) documents Caddy, which is the most user-friendly option for reverse proxy configuration.
+1 -1
View File
@@ -14,7 +14,7 @@ Type=notify
Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=1"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
ExecStart=/usr/sbin/conduwuit
+22 -19
View File
@@ -78,7 +78,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
WORKDIR /app
COPY ./rust-toolchain.toml .
RUN rustc --version \
&& rustup target add $(xx-cargo --print-target-triple)
&& xx-cargo --setup-target-triple
# Build binary
# We disable incremental compilation to save disk space, as it only produces a minimal speedup for this case.
@@ -87,8 +87,10 @@ RUN echo "CARGO_INCREMENTAL=0" >> /etc/environment
# Configure pkg-config
RUN <<EOF
set -o xtrace
echo "PKG_CONFIG_LIBDIR=/usr/lib/$(xx-info)/pkgconfig" >> /etc/environment
echo "PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /etc/environment
if command -v "$(xx-info)-pkg-config" >/dev/null 2>/dev/null; then
echo "PKG_CONFIG_LIBDIR=/usr/lib/$(xx-info)/pkgconfig" >> /etc/environment
echo "PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /etc/environment
fi
echo "PKG_CONFIG_ALLOW_CROSS=true" >> /etc/environment
EOF
@@ -109,16 +111,17 @@ RUN <<EOF
EOF
# Apply CPU-specific optimizations if TARGET_CPU is provided
ARG TARGET_CPU=
ARG TARGET_CPU
RUN <<EOF
set -o allexport
set -o xtrace
. /etc/environment
if [ -n "${TARGET_CPU}" ]; then
echo "CFLAGS='${CFLAGS} -march=${TARGET_CPU}'" >> /etc/environment
echo "CXXFLAGS='${CXXFLAGS} -march=${TARGET_CPU}'" >> /etc/environment
echo "RUSTFLAGS='${RUSTFLAGS} -C target-cpu=${TARGET_CPU}'" >> /etc/environment
fi
set -o allexport
set -o xtrace
. /etc/environment
if [ -n "${TARGET_CPU}" ]; then
echo "CFLAGS='${CFLAGS} -march=${TARGET_CPU}'" >> /etc/environment
echo "CXXFLAGS='${CXXFLAGS} -march=${TARGET_CPU}'" >> /etc/environment
echo "RUSTFLAGS='${RUSTFLAGS} -C target-cpu=${TARGET_CPU}'" >> /etc/environment
fi
EOF
# Prepare output directories
@@ -136,12 +139,12 @@ ARG TARGETPLATFORM
RUN xx-cargo --print-target-triple
# Conduwuit version info
ARG GIT_COMMIT_HASH=
ARG GIT_COMMIT_HASH_SHORT=
ARG GIT_REMOTE_URL=
ARG GIT_REMOTE_COMMIT_URL=
ARG CONDUWUIT_VERSION_EXTRA=
ARG CONTINUWUITY_VERSION_EXTRA=
ARG GIT_COMMIT_HASH
ARG GIT_COMMIT_HASH_SHORT
ARG GIT_REMOTE_URL
ARG GIT_REMOTE_COMMIT_URL
ARG CONDUWUIT_VERSION_EXTRA
ARG CONTINUWUITY_VERSION_EXTRA
ENV GIT_COMMIT_HASH=$GIT_COMMIT_HASH
ENV GIT_COMMIT_HASH_SHORT=$GIT_COMMIT_HASH_SHORT
ENV GIT_REMOTE_URL=$GIT_REMOTE_URL
@@ -169,7 +172,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
for BINARY in "${BINARIES[@]}"; do
echo $BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY /out/sbin/$BINARY
done
EOF
+200
View File
@@ -0,0 +1,200 @@
# Why does this exist?
# Debian doesn't provide prebuilt musl packages
# rocksdb requires a prebuilt liburing, and linking fails if a gnu one is provided
ARG RUST_VERSION=1
ARG ALPINE_VERSION=3.22
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-alpine${ALPINE_VERSION} AS base
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-alpine${ALPINE_VERSION} AS toolchain
# Install repo tools and dependencies
RUN --mount=type=cache,target=/etc/apk/cache apk add \
build-base pkgconfig make jq bash \
curl git file \
llvm-dev clang clang-static lld
# Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.13.0
# renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree
ENV LDDTREE_VERSION=0.3.7
# Install unpackaged tools
RUN <<EOF
set -o xtrace
curl --retry 5 -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm cargo-sbom --version $CARGO_SBOM_VERSION
cargo binstall --no-confirm lddtree --version $LDDTREE_VERSION
EOF
# Set up xx (cross-compilation scripts)
COPY --from=xx / /
ARG TARGETPLATFORM
# Install libraries linked by the binary
RUN --mount=type=cache,target=/etc/apk/cache xx-apk add musl-dev gcc g++ liburing-dev
# Set up Rust toolchain
WORKDIR /app
COPY ./rust-toolchain.toml .
RUN rustc --version \
&& xx-cargo --setup-target-triple
# Build binary
# We disable incremental compilation to save disk space, as it only produces a minimal speedup for this case.
RUN echo "CARGO_INCREMENTAL=0" >> /etc/environment
# Configure pkg-config
RUN <<EOF
set -o xtrace
if command -v "$(xx-info)-pkg-config" >/dev/null 2>/dev/null; then
echo "PKG_CONFIG_LIBDIR=/usr/lib/$(xx-info)/pkgconfig" >> /etc/environment
echo "PKG_CONFIG=/usr/bin/$(xx-info)-pkg-config" >> /etc/environment
fi
echo "PKG_CONFIG_ALLOW_CROSS=true" >> /etc/environment
EOF
# Configure cc to use clang version
RUN <<EOF
set -o xtrace
echo "CC=clang" >> /etc/environment
echo "CXX=clang++" >> /etc/environment
EOF
# Cross-language LTO
RUN <<EOF
set -o xtrace
echo "CFLAGS=-flto" >> /etc/environment
echo "CXXFLAGS=-flto" >> /etc/environment
# Linker is set to target-compatible clang by xx
echo "RUSTFLAGS='-Clinker-plugin-lto -Clink-arg=-fuse-ld=lld'" >> /etc/environment
EOF
# Apply CPU-specific optimizations if TARGET_CPU is provided
ARG TARGET_CPU
RUN <<EOF
set -o allexport
set -o xtrace
. /etc/environment
if [ -n "${TARGET_CPU}" ]; then
echo "CFLAGS='${CFLAGS} -march=${TARGET_CPU}'" >> /etc/environment
echo "CXXFLAGS='${CXXFLAGS} -march=${TARGET_CPU}'" >> /etc/environment
echo "RUSTFLAGS='${RUSTFLAGS} -C target-cpu=${TARGET_CPU}'" >> /etc/environment
fi
EOF
# Prepare output directories
RUN mkdir /out
FROM toolchain AS builder
# Get source
COPY . .
ARG TARGETPLATFORM
# Verify environment configuration
RUN xx-cargo --print-target-triple
# Conduwuit version info
ARG GIT_COMMIT_HASH
ARG GIT_COMMIT_HASH_SHORT
ARG GIT_REMOTE_URL
ARG GIT_REMOTE_COMMIT_URL
ARG CONDUWUIT_VERSION_EXTRA
ARG CONTINUWUITY_VERSION_EXTRA
ENV GIT_COMMIT_HASH=$GIT_COMMIT_HASH
ENV GIT_COMMIT_HASH_SHORT=$GIT_COMMIT_HASH_SHORT
ENV GIT_REMOTE_URL=$GIT_REMOTE_URL
ENV GIT_REMOTE_COMMIT_URL=$GIT_REMOTE_COMMIT_URL
ENV CONDUWUIT_VERSION_EXTRA=$CONDUWUIT_VERSION_EXTRA
ENV CONTINUWUITY_VERSION_EXTRA=$CONTINUWUITY_VERSION_EXTRA
ARG RUST_PROFILE=release
# Build the binary
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/app/target,id=cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-musl-${RUST_PROFILE} \
bash <<'EOF'
set -o allexport
set -o xtrace
. /etc/environment
TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \
jq -r ".target_directory"))
mkdir /out/sbin
PACKAGE=conduwuit
xx-cargo build --locked --profile ${RUST_PROFILE} \
-p $PACKAGE --no-default-features --features bindgen-static,release_max_log_level,standard;
BINARIES=($(cargo metadata --no-deps --format-version 1 | \
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
for BINARY in "${BINARIES[@]}"; do
echo $BINARY
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/release/$BINARY /out/sbin/$BINARY
done
EOF
# Generate Software Bill of Materials (SBOM)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git/db \
bash <<'EOF'
set -o xtrace
mkdir /out/sbom
typeset -A PACKAGES
for BINARY in /out/sbin/*; do
BINARY_BASE=$(basename ${BINARY})
package=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.targets[] | select( .kind | map(. == \"bin\") | any ) | .name == \"$BINARY_BASE\") | .name")
if [ -z "$package" ]; then
continue
fi
PACKAGES[$package]=1
done
for PACKAGE in $(echo ${!PACKAGES[@]}); do
echo $PACKAGE
cargo sbom --cargo-package $PACKAGE > /out/sbom/$PACKAGE.spdx.json
done
EOF
# Extract dynamically linked dependencies
RUN <<EOF
set -o xtrace
mkdir /out/libs
mkdir /out/libs-root
for BINARY in /out/sbin/*; do
lddtree "$BINARY" | awk '{print $(NF-0) " " $1}' | sort -u -k 1,1 | awk '{print "install", "-D", $1, (($2 ~ /^\//) ? "/out/libs-root" $2 : "/out/libs/" $2)}' | xargs -I {} sh -c {}
done
EOF
FROM scratch
WORKDIR /
# Copy root certs for tls into image
# You can also mount the certs from the host
# --volume /etc/ssl/certs:/etc/ssl/certs:ro
COPY --from=base /etc/ssl/certs /etc/ssl/certs
# Copy our build
COPY --from=builder /out/sbin/ /sbin/
# Copy SBOM
COPY --from=builder /out/sbom/ /sbom/
# Copy dynamic libraries to root
COPY --from=builder /out/libs-root/ /
COPY --from=builder /out/libs/ /usr/lib/
# Inform linker where to find libraries
ENV LD_LIBRARY_PATH=/usr/lib
# Continuwuity default port
EXPOSE 8008
CMD ["/sbin/conduwuit"]
+1
View File
@@ -18,6 +18,7 @@
- [Admin Command Reference](admin_reference.md)
- [Development](development.md)
- [Contributing](contributing.md)
- [Code Style Guide](development/code_style.md)
- [Testing](development/testing.md)
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
- [Community (and Guidelines)](community.md)
+1 -1
View File
@@ -3,7 +3,7 @@
## Getting help
If you run into any problems while setting up an Appservice: ask us in
[#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org) or
[#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) or
[open an issue on Forgejo](https://forgejo.ellis.link/continuwuation/continuwuity/issues/new).
## Set up the appservice - general instructions
+4 -4
View File
@@ -75,9 +75,9 @@ subject to enforcement action.
## Matrix Community
These Community Guidelines apply to the entire
[Continuwuity Matrix Space](https://matrix.to/#/#space:continuwuity.org) and its rooms, including:
[Continuwuity Matrix Space](https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) and its rooms, including:
### [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org)
### [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
This room is for support and discussions about Continuwuity. Ask questions, share insights, and help
each other out while adhering to these guidelines.
@@ -85,7 +85,7 @@ each other out while adhering to these guidelines.
We ask that this room remain focused on the Continuwuity software specifically: the team are
typically happy to engage in conversations about related subjects in the off-topic room.
### [#offtopic:continuwuity.org](https://matrix.to/#/#offtopic:continuwuity.org)
### [#offtopic:continuwuity.org](https://matrix.to/#/#offtopic:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
For off-topic community conversations about any subject. While this room allows for a wide range of
topics, the same guidelines apply. Please keep discussions respectful and inclusive, and avoid
@@ -95,7 +95,7 @@ care and respect for diverse viewpoints.
General topics, such as world events, are welcome as long as they follow the guidelines. If a member
of the team asks for the conversation to end, please respect their decision.
### [#dev:continuwuity.org](https://matrix.to/#/#dev:continuwuity.org)
### [#dev:continuwuity.org](https://matrix.to/#/#dev:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
This room is dedicated to discussing active development of Continuwuity, including ongoing issues or
code development. Collaboration here must follow these guidelines, and please consider raising
+2 -2
View File
@@ -1,5 +1,5 @@
# Continuwuity for Arch Linux
Continuwuity is available on the `archlinuxcn` repository and AUR, with the same package name `continuwuity`, which includes latest taggged version. The development version is available on AUR as `continuwuity-git`
Continuwuity is available in the `archlinuxcn` repository and AUR with the same package name `continuwuity`, which includes the latest tagged version. The development version is available on AUR as `continuwuity-git`.
Simply install the `continuwuity` package. Configure the service in `/etc/conduwuit/conduwuit.toml`, then enable/start the continuwuity.service.
Simply install the `continuwuity` package. Configure the service in `/etc/conduwuit/conduwuit.toml`, then enable and start the continuwuity.service.
+23 -21
View File
@@ -2,7 +2,7 @@
## Docker
To run Continuwuity with Docker you can either build the image yourself or pull it
To run Continuwuity with Docker, you can either build the image yourself or pull it
from a registry.
### Use a registry
@@ -26,7 +26,7 @@ to pull it to your machine.
### Run
When you have the image you can simply run it with
When you have the image, you can simply run it with
```bash
docker run -d -p 8448:6167 \
@@ -36,7 +36,7 @@ docker run -d -p 8448:6167 \
--name continuwuity $LINK
```
or you can use [docker compose](#docker-compose).
or you can use [Docker Compose](#docker-compose).
The `-d` flag lets the container run in detached mode. You may supply an
optional `continuwuity.toml` config file, the example config can be found
@@ -46,15 +46,15 @@ using env vars. For an overview of possible values, please take a look at the
[`docker-compose.yml`](docker-compose.yml) file.
If you just want to test Continuwuity for a short time, you can use the `--rm`
flag, which will clean up everything related to your container after you stop
flag, which cleans up everything related to your container after you stop
it.
### Docker-compose
If the `docker run` command is not for you or your setup, you can also use one
If the `docker run` command is not suitable for you or your setup, you can also use one
of the provided `docker-compose` files.
Depending on your proxy setup, you can use one of the following files;
Depending on your proxy setup, you can use one of the following files:
- If you already have a `traefik` instance set up, use
[`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml)
@@ -65,7 +65,7 @@ Depending on your proxy setup, you can use one of the following files;
`example.com` placeholders with your own domain
- For any other reverse proxy, use [`docker-compose.yml`](docker-compose.yml)
When picking the traefik-related compose file, rename it so it matches
When picking the Traefik-related compose file, rename it to
`docker-compose.yml`, and rename the override file to
`docker-compose.override.yml`. Edit the latter with the values you want for your
server.
@@ -77,18 +77,18 @@ create the `caddy` network before spinning up the containers:
docker network create caddy
```
After that, you can rename it so it matches `docker-compose.yml` and spin up the
After that, you can rename it to `docker-compose.yml` and spin up the
containers!
Additional info about deploying Continuwuity can be found [here](generic.md).
### Build
Official Continuwuity images are built using **Docker Buildx** and the Dockerfile found at [`docker/Dockerfile`][dockerfile-path]. This approach uses common Docker tooling and enables multi-platform builds efficiently.
Official Continuwuity images are built using **Docker Buildx** and the Dockerfile found at [`docker/Dockerfile`][dockerfile-path]. This approach uses common Docker tooling and enables efficient multi-platform builds.
The resulting images are broadly compatible with Docker and other container runtimes like Podman or containerd.
The resulting images are widely compatible with Docker and other container runtimes like Podman or containerd.
The images *do not contain a shell*. They contain only the Continuwuity binary, required libraries, TLS certificates and metadata. Please refer to the [`docker/Dockerfile`][dockerfile-path] for the specific details of the image composition.
The images *do not contain a shell*. They contain only the Continuwuity binary, required libraries, TLS certificates, and metadata. Please refer to the [`docker/Dockerfile`][dockerfile-path] for the specific details of the image composition.
To build an image locally using Docker Buildx, you can typically run a command like:
@@ -109,8 +109,8 @@ Refer to the Docker Buildx documentation for more advanced build options.
### Run
If you already have built the image or want to use one from the registries, you
can just start the container and everything else in the compose file in detached
If you have already built the image or want to use one from the registries, you
can start the container and everything else in the compose file in detached
mode with:
```bash
@@ -121,22 +121,24 @@ docker compose up -d
### Use Traefik as Proxy
As a container user, you probably know about Traefik. It is a easy to use
reverse proxy for making containerized app and services available through the
As a container user, you probably know about Traefik. It is an easy-to-use
reverse proxy for making containerized apps and services available through the
web. With the two provided files,
[`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), it is equally easy
to deploy and use Continuwuity, with a little caveat. If you already took a look at
the files, then you should have seen the `well-known` service, and that is the
little caveat. Traefik is simply a proxy and loadbalancer and is not able to
serve any kind of content, but for Continuwuity to federate, we need to either
expose ports `443` and `8448` or serve two endpoints `.well-known/matrix/client`
to deploy and use Continuwuity, with a small caveat. If you have already looked at
the files, you should have seen the `well-known` service, which is the
small caveat. Traefik is simply a proxy and load balancer and cannot
serve any kind of content. For Continuwuity to federate, we need to either
expose ports `443` and `8448` or serve two endpoints: `.well-known/matrix/client`
and `.well-known/matrix/server`.
With the service `well-known` we use a single `nginx` container that will serve
With the service `well-known`, we use a single `nginx` container that serves
those two files.
Alternatively, you can use Continuwuity's built-in delegation file capability. Set up the delegation files in the configuration file, and then proxy paths under `/.well-known/matrix` to continuwuity. For example, the label ``traefik.http.routers.continuwuity.rule=(Host(`matrix.ellis.link`) || (Host(`ellis.link`) && PathPrefix(`/.well-known/matrix`)))`` does this for the domain `ellis.link`.
## Voice communication
See the [TURN](../turn.md) page.
+2 -2
View File
@@ -1,5 +1,5 @@
# Continuwuity for FreeBSD
Continuwuity at the moment does not provide FreeBSD builds or have FreeBSD packaging, however Continuwuity does build and work on FreeBSD using the system-provided RocksDB.
Continuwuity currently does not provide FreeBSD builds or FreeBSD packaging. However, Continuwuity does build and work on FreeBSD using the system-provided RocksDB.
Contributions for getting Continuwuity packaged are welcome.
Contributions to get Continuwuity packaged for FreeBSD are welcome.
+59 -54
View File
@@ -13,31 +13,42 @@
You may simply download the binary that fits your machine architecture (x86_64
or aarch64). Run `uname -m` to see what you need.
Prebuilt fully static musl binaries can be downloaded from the latest tagged
You can download prebuilt fully static musl binaries from the latest tagged
release [here](https://forgejo.ellis.link/continuwuation/continuwuity/releases/latest) or
`main` CI branch workflow artifact output. These also include Debian/Ubuntu
from the `main` CI branch workflow artifact output. These also include Debian/Ubuntu
packages.
These can be curl'd directly from. `ci-bins` are CI workflow binaries by commit
You can download these directly using curl. The `ci-bins` are CI workflow binaries organized by commit
hash/revision, and `releases` are tagged releases. Sort by descending last
modified for the latest.
modified date to find the latest.
These binaries have jemalloc and io_uring statically linked and included with
them, so no additional dynamic dependencies need to be installed.
For the **best** performance; if using an `x86_64` CPU made in the last ~15 years,
we recommend using the `-haswell-` optimised binaries. This sets
`-march=haswell` which is the most compatible and highest performance with
optimised binaries. The database backend, RocksDB, most benefits from this as it
will then use hardware accelerated CRC32 hashing/checksumming which is critical
For the **best** performance: if you are using an `x86_64` CPU made in the last ~15 years,
we recommend using the `-haswell-` optimized binaries. These set
`-march=haswell`, which provides the most compatible and highest performance with
optimized binaries. The database backend, RocksDB, benefits most from this as it
uses hardware-accelerated CRC32 hashing/checksumming, which is critical
for performance.
### Compiling
Alternatively, you may compile the binary yourself. We recommend using
Nix (or [Lix](https://lix.systems)) to build Continuwuity as this has the most
guaranteed reproducibiltiy and easiest to get a build environment and output
going. This also allows easy cross-compilation.
Alternatively, you may compile the binary yourself.
### Building with the Rust toolchain
If wanting to build using standard Rust toolchains, make sure you install:
- (On linux) `liburing-dev` on the compiling machine, and `liburing` on the target host
- (On linux) `pkg-config` on the compiling machine to allow finding `liburing`
- A C++ compiler and (on linux) `libclang` for RocksDB
You can build Continuwuity using `cargo build --release`.
### Building with Nix
If you prefer, you can use Nix (or [Lix](https://lix.systems)) to build Continuwuity. This provides improved reproducibility and makes it easy to set up a build environment and generate output. This approach also allows for easy cross-compilation.
You can run the `nix build -L .#static-x86_64-linux-musl-all-features` or
`nix build -L .#static-aarch64-linux-musl-all-features` commands based
@@ -45,17 +56,11 @@ on architecture to cross-compile the necessary static binary located at
`result/bin/conduwuit`. This is reproducible with the static binaries produced
in our CI.
If wanting to build using standard Rust toolchains, make sure you install:
- `liburing-dev` on the compiling machine, and `liburing` on the target host
- LLVM and libclang for RocksDB
You can build Continuwuity using `cargo build --release --all-features`
## Adding a Continuwuity user
While Continuwuity can run as any user it is better to use dedicated users for
different services. This also allows you to make sure that the file permissions
are correctly set up.
While Continuwuity can run as any user, it is better to use dedicated users for
different services. This also ensures that the file permissions
are set up correctly.
In Debian, you can use this command to create a Continuwuity user:
@@ -71,18 +76,18 @@ sudo useradd -r --shell /usr/bin/nologin --no-create-home continuwuity
## Forwarding ports in the firewall or the router
Matrix's default federation port is port 8448, and clients must be using port 443.
If you would like to use only port 443, or a different port, you will need to setup
delegation. Continuwuity has config options for doing delegation, or you can configure
your reverse proxy to manually serve the necessary JSON files to do delegation
Matrix's default federation port is 8448, and clients must use port 443.
If you would like to use only port 443 or a different port, you will need to set up
delegation. Continuwuity has configuration options for delegation, or you can configure
your reverse proxy to manually serve the necessary JSON files for delegation
(see the `[global.well_known]` config section).
If Continuwuity runs behind a router or in a container and has a different public
IP address than the host system these public ports need to be forwarded directly
or indirectly to the port mentioned in the config.
IP address than the host system, you need to forward these public ports directly
or indirectly to the port mentioned in the configuration.
Note for NAT users; if you have trouble connecting to your server from the inside
of your network, you need to research your router and see if it supports "NAT
Note for NAT users: if you have trouble connecting to your server from inside
your network, check if your router supports "NAT
hairpinning" or "NAT loopback".
If your router does not support this feature, you need to research doing local
@@ -92,19 +97,19 @@ on the network level, consider something like NextDNS or Pi-Hole.
## Setting up a systemd service
Two example systemd units for Continuwuity can be found
You can find two example systemd units for Continuwuity
[on the configuration page](../configuration/examples.md#debian-systemd-unit-file).
You may need to change the `ExecStart=` path to where you placed the Continuwuity
binary if it is not `/usr/bin/conduwuit`.
You may need to change the `ExecStart=` path to match where you placed the Continuwuity
binary if it is not in `/usr/bin/conduwuit`.
On systems where rsyslog is used alongside journald (i.e. Red Hat-based distros
and OpenSUSE), put `$EscapeControlCharactersOnReceive off` inside
`/etc/rsyslog.conf` to allow color in logs.
If you are using a different `database_path` other than the systemd unit
If you are using a different `database_path` than the systemd unit's
configured default `/var/lib/conduwuit`, you need to add your path to the
systemd unit's `ReadWritePaths=`. This can be done by either directly editing
`conduwuit.service` and reloading systemd, or running `systemctl edit conduwuit.service`
systemd unit's `ReadWritePaths=`. You can do this by either directly editing
`conduwuit.service` and reloading systemd, or by running `systemctl edit conduwuit.service`
and entering the following:
```
@@ -114,8 +119,8 @@ ReadWritePaths=/path/to/custom/database/path
## Creating the Continuwuity configuration file
Now we need to create the Continuwuity's config file in
`/etc/continuwuity/continuwuity.toml`. The example config can be found at
Now you need to create the Continuwuity configuration file in
`/etc/continuwuity/continuwuity.toml`. You can find an example configuration at
[conduwuit-example.toml](../configuration/examples.md).
**Please take a moment to read the config. You need to change at least the
@@ -125,8 +130,8 @@ RocksDB is the only supported database backend.
## Setting the correct file permissions
If you are using a dedicated user for Continuwuity, you will need to allow it to
read the config. To do that you can run this:
If you are using a dedicated user for Continuwuity, you need to allow it to
read the configuration. To do this, run:
```bash
sudo chown -R root:root /etc/conduwuit
@@ -143,13 +148,13 @@ sudo chmod 700 /var/lib/conduwuit/
## Setting up the Reverse Proxy
We recommend Caddy as a reverse proxy, as it is trivial to use, handling TLS certificates, reverse proxy headers, etc transparently with proper defaults.
We recommend Caddy as a reverse proxy because it is trivial to use and handles TLS certificates, reverse proxy headers, etc. transparently with proper defaults.
For other software, please refer to their respective documentation or online guides.
### Caddy
After installing Caddy via your preferred method, create `/etc/caddy/conf.d/conduwuit_caddyfile`
and enter this (substitute for your server name).
and enter the following (substitute your actual server name):
```caddyfile
your.server.name, your.server.name:8448 {
@@ -168,11 +173,11 @@ sudo systemctl enable --now caddy
### Other Reverse Proxies
As we would prefer our users to use Caddy, we will not provide configuration files for other proxys.
As we prefer our users to use Caddy, we do not provide configuration files for other proxies.
You will need to reverse proxy everything under following routes:
You will need to reverse proxy everything under the following routes:
- `/_matrix/` - core Matrix C-S and S-S APIs
- `/_conduwuit/` - ad-hoc Continuwuity routes such as `/local_user_count` and
- `/_conduwuit/` and/or `/_continuwuity/` - ad-hoc Continuwuity routes such as `/local_user_count` and
`/server_version`
You can optionally reverse proxy the following individual routes:
@@ -193,16 +198,16 @@ Examples of delegation:
For Apache and Nginx there are many examples available online.
Lighttpd is not supported as it seems to mess with the `X-Matrix` Authorization
header, making federation non-functional. If a workaround is found, feel free to share to get it added to the documentation here.
Lighttpd is not supported as it appears to interfere with the `X-Matrix` Authorization
header, making federation non-functional. If you find a workaround, please share it so we can add it to this documentation.
If using Apache, you need to use `nocanon` in your `ProxyPass` directive to prevent httpd from messing with the `X-Matrix` header (note that Apache isn't very good as a general reverse proxy and we discourage the usage of it if you can).
If using Apache, you need to use `nocanon` in your `ProxyPass` directive to prevent httpd from interfering with the `X-Matrix` header (note that Apache is not ideal as a general reverse proxy, so we discourage using it if alternatives are available).
If using Nginx, you need to give Continuwuity the request URI using `$request_uri`, or like so:
If using Nginx, you need to pass the request URI to Continuwuity using `$request_uri`, like this:
- `proxy_pass http://127.0.0.1:6167$request_uri;`
- `proxy_pass http://127.0.0.1:6167;`
Nginx users need to increase `client_max_body_size` (default is 1M) to match
Nginx users need to increase the `client_max_body_size` setting (default is 1M) to match the
`max_request_size` defined in conduwuit.toml.
## You're done
@@ -222,7 +227,7 @@ sudo systemctl enable conduwuit
## How do I know it works?
You can open [a Matrix client](https://matrix.org/ecosystem/clients), enter your
homeserver and try to register.
homeserver address, and try to register.
You can also use these commands as a quick health check (replace
`your.server.name`).
@@ -237,10 +242,10 @@ curl https://your.server.name:8448/_conduwuit/server_version
curl https://your.server.name:8448/_matrix/federation/v1/version
```
- To check if your server can talk with other homeservers, you can use the
- To check if your server can communicate with other homeservers, use the
[Matrix Federation Tester](https://federationtester.matrix.org/). If you can
register but cannot join federated rooms check your config again and also check
if the port 8448 is open and forwarded correctly.
register but cannot join federated rooms, check your configuration and verify
that port 8448 is open and forwarded correctly.
# What's next?
+3 -3
View File
@@ -1,9 +1,9 @@
# Continuwuity for Kubernetes
Continuwuity doesn't support horizontal scalability or distributed loading
natively, however a community maintained Helm Chart is available here to run
natively. However, a community-maintained Helm Chart is available here to run
conduwuit on Kubernetes: <https://gitlab.cronce.io/charts/conduwuit>
This should be compatible with continuwuity, but you will need to change the image reference.
This should be compatible with Continuwuity, but you will need to change the image reference.
Should changes need to be made, please reach out to the maintainer as this is not maintained/controlled by the Continuwuity maintainers.
If changes need to be made, please reach out to the maintainer, as this is not maintained or controlled by the Continuwuity maintainers.
+103 -48
View File
@@ -1,75 +1,130 @@
# Continuwuity for NixOS
Continuwuity can be acquired by Nix (or [Lix][lix]) from various places:
NixOS packages Continuwuity as `matrix-continuwuity`. This package includes both the Continuwuity software and a dedicated NixOS module for configuration and deployment.
* The `flake.nix` at the root of the repo
* The `default.nix` at the root of the repo
* From Continuwuity's binary cache
## Installation methods
### NixOS module
You can acquire Continuwuity with Nix (or [Lix][lix]) from these sources:
The `flake.nix` and `default.nix` do not currently provide a NixOS module (contributions
welcome!), so [`services.matrix-conduit`][module] from Nixpkgs can be used to configure
Continuwuity.
* Directly from Nixpkgs using the official package (`pkgs.matrix-continuwuity`)
* The `flake.nix` at the root of the Continuwuity repo
* The `default.nix` at the root of the Continuwuity repo
### Conduit NixOS Config Module and SQLite
## NixOS module
Beware! The [`services.matrix-conduit`][module] module defaults to SQLite as a database backend.
Continuwuity dropped SQLite support in favor of exclusively supporting the much faster RocksDB.
Make sure that you are using the RocksDB backend before migrating!
Continuwuity now has an official NixOS module that simplifies configuration and deployment. The module is available in Nixpkgs as `services.matrix-continuwuity` from NixOS 25.05.
There is a [tool to migrate a Conduit SQLite database to
RocksDB](https://github.com/ShadowJonathan/conduit_toolbox/).
Here's a basic example of how to use the module:
If you want to run the latest code, you should get Continuwuity from the `flake.nix`
or `default.nix` and set [`services.matrix-conduit.package`][package]
appropriately to use Continuwuity instead of Conduit.
```nix
{ config, pkgs, ... }:
{
services.matrix-continuwuity = {
enable = true;
settings = {
global = {
server_name = "example.com";
# Listening on localhost by default
# address and port are handled automatically
allow_registration = false;
allow_encryption = true;
allow_federation = true;
trusted_servers = [ "matrix.org" ];
};
};
};
}
```
### Available options
The NixOS module provides these configuration options:
- `enable`: Enable the Continuwuity service
- `user`: The user to run Continuwuity as (defaults to "continuwuity")
- `group`: The group to run Continuwuity as (defaults to "continuwuity")
- `extraEnvironment`: Extra environment variables to pass to the Continuwuity server
- `package`: The Continuwuity package to use
- `settings`: The Continuwuity configuration (in TOML format)
Use the `settings` option to configure Continuwuity itself. See the [example configuration file](../configuration/examples.md#example-configuration) for all available options.
### UNIX sockets
Due to the lack of a Continuwuity NixOS module, when using the `services.matrix-conduit` module
a workaround like the one below is necessary to use UNIX sockets. This is because the UNIX
socket option does not exist in Conduit, and the module forcibly sets the `address` and
`port` config options.
The NixOS module natively supports UNIX sockets through the `global.unix_socket_path` option. When using UNIX sockets, set `global.address` to `null`:
```nix
options.services.matrix-conduit.settings = lib.mkOption {
apply = old: old // (
if (old.global ? "unix_socket_path")
then { global = builtins.removeAttrs old.global [ "address" "port" ]; }
else { }
);
services.matrix-continuwuity = {
enable = true;
settings = {
global = {
server_name = "example.com";
address = null; # Must be null when using unix_socket_path
unix_socket_path = "/run/continuwuity/continuwuity.sock";
unix_socket_perms = 660; # Default permissions for the socket
# ...
};
};
};
```
Additionally, the [`matrix-conduit` systemd unit][systemd-unit] in the module does not allow
the `AF_UNIX` socket address family in their systemd unit's `RestrictAddressFamilies=` which
disallows the namespace from accessing or creating UNIX sockets and has to be enabled like so:
The module automatically sets the correct `RestrictAddressFamilies` in the systemd service configuration to allow access to UNIX sockets.
```nix
systemd.services.conduit.serviceConfig.RestrictAddressFamilies = [ "AF_UNIX" ];
```
### RocksDB database
Even though those workarounds are feasible a Continuwuity NixOS configuration module, developed and
published by the community, would be appreciated.
Continuwuity exclusively uses RocksDB as its database backend. The system configures the database path automatically to `/var/lib/continuwuity/` and you cannot change it due to the service's reliance on systemd's StateDir.
If you're migrating from Conduit with SQLite, use this [tool to migrate a Conduit SQLite database to RocksDB](https://github.com/ShadowJonathan/conduit_toolbox/).
### jemalloc and hardened profile
Continuwuity uses jemalloc by default. This may interfere with the [`hardened.nix` profile][hardened.nix]
due to them using `scudo` by default. You must either disable/hide `scudo` from Continuwuity, or
disable jemalloc like so:
Continuwuity uses jemalloc by default. This may interfere with the [`hardened.nix` profile][hardened.nix] because it uses `scudo` by default. Either disable/hide `scudo` from Continuwuity or disable jemalloc like this:
```nix
let
conduwuit = pkgs.unstable.conduwuit.override {
enableJemalloc = false;
};
in
services.matrix-continuwuity = {
enable = true;
package = pkgs.matrix-continuwuity.override {
enableJemalloc = false;
};
# ...
};
```
## Upgrading from Conduit
If you previously used Conduit with the `services.matrix-conduit` module:
1. Ensure your Conduit uses the RocksDB backend, or migrate from SQLite using the [migration tool](https://github.com/ShadowJonathan/conduit_toolbox/)
2. Switch to the new module by changing `services.matrix-conduit` to `services.matrix-continuwuity` in your configuration
3. Update any custom configuration to match the new module's structure
## Reverse proxy configuration
You'll need to set up a reverse proxy (like nginx or caddy) to expose Continuwuity to the internet. Configure your reverse proxy to forward requests to `/_matrix` on port 443 and 8448 to your Continuwuity instance.
Here's an example nginx configuration:
```nginx
server {
listen 443 ssl;
listen [::]:443 ssl;
listen 8448 ssl;
listen [::]:8448 ssl;
server_name example.com;
# SSL configuration here...
location /_matrix/ {
proxy_pass http://127.0.0.1:6167$request_uri;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
[lix]: https://lix.systems/
[module]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit
[package]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit.package
[hardened.nix]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix#L22
[systemd-unit]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/matrix/conduit.nix#L132
[hardened.nix]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix
+1 -1
View File
@@ -2,7 +2,7 @@
Information about developing the project. If you are only interested in using
it, you can safely ignore this page. If you plan on contributing, see the
[contributor's guide](./contributing.md).
[contributor's guide](./contributing.md) and [code style guide](./development/code_style.md).
## Continuwuity project layout
+331
View File
@@ -0,0 +1,331 @@
# Code Style Guide
This guide outlines the coding standards and best practices for Continuwuity development. These guidelines help avoid bugs and maintain code consistency, readability, and quality across the project.
These guidelines apply to new code on a best-effort basis. When modifying existing code, follow existing patterns in the immediate area you're changing and then gradually improve code style when making substantial changes.
## General Principles
- **Clarity over cleverness**: Write code that is easy to understand and maintain
- **Consistency**: Pragmatically follow existing patterns in the codebase, rather than adding new dependencies.
- **Safety**: Prefer safe, explicit code over unsafe code with implicit requirements
- **Performance**: Consider performance implications, but not at the expense of correctness or maintainability
## Formatting and Linting
All code must satisfy lints (clippy, rustc, rustdoc, etc) and be formatted using **nightly** rustfmt (`cargo +nightly fmt`). Many of the `rustfmt.toml` features depend on the nightly toolchain.
If you need to allow a lint, ensure it's either obvious why (e.g. clippy saying redundant clone but it's actually required) or add a comment explaining the reason. Do not write inefficient code just to satisfy lints. If a lint is wrong and provides a less efficient solution, allow the lint and mention that in a comment.
If making large formatting changes across unrelated files, create a separate commit so it can be added to the `.git-blame-ignore-revs` file.
## Rust-Specific Guidelines
### Naming Conventions
Follow standard Rust naming conventions as outlined in the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/naming.html):
- Use `snake_case` for functions, variables, and modules
- Use `PascalCase` for types, traits, and enum variants
- Use `SCREAMING_SNAKE_CASE` for constants and statics
- Use descriptive names that clearly indicate purpose
```rs
// Good
fn process_user_request(user_id: &UserId) -> Result<Response, Error> { ... }
const MAX_RETRY_ATTEMPTS: usize = 3;
struct UserSession {
session_id: String,
created_at: SystemTime,
}
// Avoid
fn proc_reqw(id: &str) -> Result<Resp, Err> { ... }
```
### Error Handling
- Use `Result<T, E>` for operations that can fail
- Prefer specific error types over generic ones
- Use `?` operator for error propagation
- Provide meaningful error messages
- If needed, create or use an error enum.
```rs
// Good
fn parse_server_name(input: &str) -> Result<ServerName, InvalidServerNameError> {
ServerName::parse(input)
.map_err(|_| InvalidServerNameError::new(input))
}
// Avoid
fn parse_server_name(input: &str) -> Result<ServerName, Box<dyn Error>> {
Ok(ServerName::parse(input).unwrap())
}
```
### Option Handling
- Prefer explicit `Option` handling over unwrapping
- Use combinators like `map`, `and_then`, `unwrap_or_else` when appropriate
```rs
// Good
let display_name = user.display_name
.as_ref()
.map(|name| name.trim())
.filter(|name| !name.is_empty())
.unwrap_or(&user.localpart);
// Avoid
let display_name = if user.display_name.is_some() {
user.display_name.as_ref().unwrap()
} else {
&user.localpart
};
```
## Logging Guidelines
### Structured Logging
**Always use structured logging instead of string interpolation.** This improves log parsing, filtering, and observability.
```rs
// Good - structured parameters
debug!(
room_id = %room_id,
user_id = %user_id,
event_type = ?event.event_type(),
"Processing room event"
);
info!(
server_name = %server_name,
response_time_ms = response_time.as_millis(),
"Federation request completed successfully"
);
// Avoid - string interpolation
debug!("Processing room event for {room_id} from {user_id}");
info!("Federation request to {server_name} took {response_time:?}");
```
### Log Levels
Use appropriate log levels:
- `error!`: Unrecoverable errors that affect functionality
- `warn!`: Potentially problematic situations that don't stop execution
- `info!`: General information about application flow
- `debug!`: Detailed information for debugging
- `trace!`: Very detailed information, typically only useful during development
Keep in mind the frequency that the log will be reached, and the relevancy to a server operator.
```rs
// Good
error!(
error = %err,
room_id = %room_id,
"Failed to send event to room"
);
warn!(
server_name = %server_name,
attempt = retry_count,
"Federation request failed, retrying"
);
info!(
user_id = %user_id,
"User registered successfully"
);
debug!(
event_id = %event_id,
auth_events = ?auth_event_ids,
"Validating event authorization"
);
```
### Sensitive Information
Never log sensitive information such as:
- Access tokens
- Passwords
- Private keys
- Personal user data (unless specifically needed for debugging)
```rs
// Good
debug!(
user_id = %user_id,
session_id = %session_id,
"Processing authenticated request"
);
// Avoid
debug!(
user_id = %user_id,
access_token = %access_token,
"Processing authenticated request"
);
```
## Lock Management
### Explicit Lock Scopes
**Always use closure guards instead of implicitly dropped guards.** This makes lock scopes explicit and helps prevent deadlocks.
Use the `WithLock` trait from `core::utils::with_lock`:
```rs
use conduwuit::utils::with_lock::WithLock;
// Good - explicit closure guard
shared_data.with_lock(|data| {
data.counter += 1;
data.last_updated = SystemTime::now();
// Lock is explicitly released here
});
// Avoid - implicit guard
{
let mut data = shared_data.lock().unwrap();
data.counter += 1;
data.last_updated = SystemTime::now();
// Lock released when guard goes out of scope - less explicit
}
```
For async contexts, use the async variant:
```rs
use conduwuit::utils::with_lock::WithLockAsync;
// Good - async closure guard
async_shared_data.with_lock(|data| {
data.process_async_update();
}).await;
```
### Lock Ordering
When acquiring multiple locks, always acquire them in a consistent order to prevent deadlocks:
```rs
// Good - consistent ordering (e.g., by memory address or logical hierarchy)
let locks = [&lock_a, &lock_b, &lock_c];
locks.sort_by_key(|lock| lock as *const _ as usize);
for lock in locks {
lock.with_lock(|data| {
// Process data
});
}
// Avoid - inconsistent ordering that can cause deadlocks
lock_b.with_lock(|data_b| {
lock_a.with_lock(|data_a| {
// Deadlock risk if another thread acquires in A->B order
});
});
```
## Documentation
### Code Comments
- Reference related documentation or parts of the specification
- When a task has multiple ways of being acheved, explain your reasoning for your decision
- Update comments when code changes
```rs
/// Processes a federation request with automatic retries and backoff.
///
/// Implements exponential backoff to handle temporary
/// network issues and server overload gracefully.
pub async fn send_federation_request(
destination: &ServerName,
request: FederationRequest,
) -> Result<FederationResponse, FederationError> {
// Retry with exponential backoff because federation can be flaky
// due to network issues or temporary server overload
let mut retry_delay = Duration::from_millis(100);
for attempt in 1..=MAX_RETRIES {
match try_send_request(destination, &request).await {
Ok(response) => return Ok(response),
Err(err) if err.is_retriable() && attempt < MAX_RETRIES => {
warn!(
destination = %destination,
attempt = attempt,
error = %err,
retry_delay_ms = retry_delay.as_millis(),
"Federation request failed, retrying"
);
tokio::time::sleep(retry_delay).await;
retry_delay *= 2; // Exponential backoff
}
Err(err) => return Err(err),
}
}
unreachable!("Loop should have returned or failed by now")
}
```
### Async Patterns
- Use `async`/`await` appropriately
- Avoid blocking operations in async contexts
- Consider using `tokio::task::spawn_blocking` for CPU-intensive work
```rs
// Good - non-blocking async operation
pub async fn fetch_user_profile(
&self,
user_id: &UserId,
) -> Result<UserProfile, Error> {
let profile = self.db
.get_user_profile(user_id)
.await?;
Ok(profile)
}
// Good - CPU-intensive work moved to blocking thread
pub async fn generate_thumbnail(
&self,
image_data: Vec<u8>,
) -> Result<Vec<u8>, Error> {
tokio::task::spawn_blocking(move || {
image::generate_thumbnail(image_data)
})
.await
.map_err(|_| Error::TaskJoinError)?
}
```
## Inclusivity and Diversity Guidelines
All code and documentation must be written with inclusivity and diversity in mind. This ensures our software is welcoming and accessible to all users and contributors. Follow the [Google guide on writing inclusive code and documentation](https://developers.google.com/style/inclusive-documentation) for comprehensive guidance.
The following types of language are explicitly forbidden in all code, comments, documentation, and commit messages:
**Ableist language:** Avoid terms like "sanity check", "crazy", "insane", "cripple", or "blind to". Use alternatives like "validation", "unexpected", "disable", or "unaware of".
**Socially-charged technical terms:** Replace overly divisive terminology with neutral alternatives:
- "whitelist/blacklist" → "allowlist/denylist" or "permitted/blocked"
- "master/slave" → "primary/replica", "controller/worker", or "parent/child"
When working with external dependencies that use non-inclusive terminology, avoid propagating them in your own APIs and variable names.
Use diverse examples in documentation that avoid culturally-specific references, assumptions about user demographics, or unnecessarily gendered language. Design with accessibility and inclusivity in mind by providing clear error messages and considering diverse user needs.
This software is intended to be used by everyone regardless of background, identity, or ability. Write code and documentation that reflects this commitment to inclusivity.
+1 -1
View File
@@ -196,5 +196,5 @@ The initial implementation PR is available [here][1].
[4]: https://github.com/rust-lang/rust/issues/28794#issuecomment-368693049
[5]: https://github.com/rust-lang/cargo/issues/12746
[6]: https://crates.io/crates/hot-lib-reloader/
[7]: https://matrix.to/#/#continuwuity:continuwuity.org
[7]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
[8]: https://crates.io/crates/libloading
+24
View File
@@ -68,3 +68,27 @@ documentation](https://github.com/coturn/coturn/blob/master/docker/coturn/README
For security recommendations see Synapse's [Coturn
documentation](https://element-hq.github.io/synapse/latest/turn-howto.html).
### Testing
To make sure turn credentials are being correctly served to clients, you can manually make a HTTP request to the turnServer endpoint.
`curl "https://<matrix.example.com>/_matrix/client/r0/voip/turnServer" -H 'Authorization: Bearer <your_client_token>' | jq`
You should get a response like this:
```json
{
"username": "1752792167:@jade:example.com",
"password": "KjlDlawdPbU9mvP4bhdV/2c/h65=",
"uris": [
"turns:coturn.example.com?transport=udp",
"turns:coturn.example.com?transport=tcp",
"turn:coturn.example.com?transport=udp",
"turn:coturn.example.com?transport=tcp"
],
"ttl": 86400
}
```
You can test these credentials work using [Trickle ICE](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/)
+3 -2
View File
@@ -83,7 +83,7 @@ env DIRENV_DEVSHELL=all-features \
--workspace \
--locked \
--profile test \
--all-features \
--features full \
--no-deps \
--document-private-items \
--color always
@@ -96,6 +96,7 @@ script = """
direnv exec . \
cargo clippy \
--workspace \
--features full \
--locked \
--profile test \
--color=always \
@@ -113,7 +114,7 @@ env DIRENV_DEVSHELL=all-features \
--workspace \
--locked \
--profile test \
--all-features \
--features full \
--color=always \
-- \
-D warnings
+1 -2
View File
@@ -26,8 +26,7 @@ pub(super) async fn incoming_federation(&self) -> Result {
.rooms
.event_handler
.federation_handletime
.read()
.expect("locked");
.read();
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
for (r, (e, i)) in map.iter() {
+2 -10
View File
@@ -37,11 +37,7 @@ pub use crate::admin::AdminCommand;
/// Install the admin command processor
pub async fn init(admin_service: &service::admin::Service) {
_ = admin_service
.complete
.write()
.expect("locked for writing")
.insert(processor::complete);
_ = admin_service.complete.write().insert(processor::complete);
_ = admin_service
.handle
.write()
@@ -52,9 +48,5 @@ pub async fn init(admin_service: &service::admin::Service) {
/// Uninstall the admin command handler
pub async fn fini(admin_service: &service::admin::Service) {
_ = admin_service.handle.write().await.take();
_ = admin_service
.complete
.write()
.expect("locked for writing")
.take();
_ = admin_service.complete.write().take();
}
+5 -11
View File
@@ -1,14 +1,8 @@
use std::{
fmt::Write,
mem::take,
panic::AssertUnwindSafe,
sync::{Arc, Mutex},
time::SystemTime,
};
use std::{fmt::Write, mem::take, panic::AssertUnwindSafe, sync::Arc, time::SystemTime};
use clap::{CommandFactory, Parser};
use conduwuit::{
Error, Result, debug, error,
Error, Result, SyncMutex, debug, error,
log::{
capture,
capture::Capture,
@@ -123,7 +117,7 @@ async fn process(
let mut output = String::new();
// Prepend the logs only if any were captured
let logs = logs.lock().expect("locked");
let logs = logs.lock();
if logs.lines().count() > 2 {
writeln!(&mut output, "{logs}").expect("failed to format logs to command output");
}
@@ -132,7 +126,7 @@ async fn process(
(result, output)
}
fn capture_create(context: &Context<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
fn capture_create(context: &Context<'_>) -> (Arc<Capture>, Arc<SyncMutex<String>>) {
let env_config = &context.services.server.config.admin_log_capture;
let env_filter = EnvFilter::try_new(env_config).unwrap_or_else(|e| {
warn!("admin_log_capture filter invalid: {e:?}");
@@ -152,7 +146,7 @@ fn capture_create(context: &Context<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
data.level() <= log_level && data.our_modules() && data.scope.contains(&"admin")
};
let logs = Arc::new(Mutex::new(
let logs = Arc::new(SyncMutex::new(
collect_stream(|s| markdown_table_head(s)).expect("markdown table header"),
));
+32 -15
View File
@@ -291,20 +291,38 @@ pub(crate) async fn register_route(
}
// UIAA
let mut uiaainfo;
let skip_auth = if services.globals.registration_token.is_some() {
let mut uiaainfo = UiaaInfo {
flows: Vec::new(),
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
let skip_auth = body.appservice_info.is_some() || is_guest;
// Populate required UIAA flows
if services.globals.registration_token.is_some() {
// Registration token required
uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::RegistrationToken],
}],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
body.appservice_info.is_some()
} else {
uiaainfo.flows.push(AuthFlow {
stages: vec![AuthType::RegistrationToken],
});
}
if services.config.recaptcha_private_site_key.is_some() {
if let Some(pubkey) = &services.config.recaptcha_site_key {
// ReCaptcha required
uiaainfo
.flows
.push(AuthFlow { stages: vec![AuthType::ReCaptcha] });
uiaainfo.params = serde_json::value::to_raw_value(&serde_json::json!({
"m.login.recaptcha": {
"public_key": pubkey,
},
}))
.expect("Failed to serialize recaptcha params");
}
}
if uiaainfo.flows.is_empty() && !skip_auth {
// No registration token necessary, but clients must still go through the flow
uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
@@ -313,8 +331,7 @@ pub(crate) async fn register_route(
session: None,
auth_error: None,
};
body.appservice_info.is_some() || is_guest
};
}
if !skip_auth {
match &body.auth {
+126 -31
View File
@@ -2,10 +2,10 @@ use std::cmp::max;
use axum::extract::State;
use conduwuit::{
Err, Error, Event, Result, err, info,
Err, Error, Event, Result, debug, err, info,
matrix::{StateKey, pdu::PduBuilder},
};
use futures::StreamExt;
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, RoomId, RoomVersionId,
api::client::{error::ErrorKind, room::upgrade_room},
@@ -16,15 +16,16 @@ use ruma::{
power_levels::RoomPowerLevelsEventContent,
tombstone::RoomTombstoneEventContent,
},
space::child::{RedactedSpaceChildEventContent, SpaceChildEventContent},
},
int,
};
use serde_json::{json, value::to_raw_value};
use crate::Ruma;
use crate::router::Ruma;
/// Recommended transferable state events list from the spec
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 11] = &[
StateEventType::RoomAvatar,
StateEventType::RoomEncryption,
StateEventType::RoomGuestAccess,
@@ -34,6 +35,9 @@ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
StateEventType::RoomPowerLevels,
StateEventType::RoomServerAcl,
StateEventType::RoomTopic,
// Not explicitly recommended in spec, but very useful.
StateEventType::SpaceChild,
StateEventType::SpaceParent, // TODO: m.room.policy?
];
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
@@ -50,10 +54,7 @@ pub(crate) async fn upgrade_room_route(
State(services): State<crate::State>,
body: Ruma<upgrade_room::v3::Request>,
) -> Result<upgrade_room::v3::Response> {
debug_assert!(
TRANSFERABLE_STATE_EVENTS.is_sorted(),
"TRANSFERABLE_STATE_EVENTS is not sorted"
);
// TODO[v12]: Handle additional creators
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services.server.supported_room_version(&body.new_version) {
@@ -128,7 +129,7 @@ pub(crate) async fn upgrade_room_route(
);
},
| _ => {
// "creator" key no longer exists in V11+ rooms
// "creator" key no longer exists in V11 rooms
create_event_content.remove("creator");
},
}
@@ -175,6 +176,7 @@ pub(crate) async fn upgrade_room_route(
&replacement_room,
&state_lock,
)
.boxed()
.await?;
// Join the new room
@@ -205,35 +207,48 @@ pub(crate) async fn upgrade_room_route(
&replacement_room,
&state_lock,
)
.boxed()
.await?;
// Replicate transferable state events to the new room
for event_type in TRANSFERABLE_STATE_EVENTS {
let event_content = match services
let state_keys = services
.rooms
.state_accessor
.room_state_get(&body.room_id, event_type, "")
.await
{
| Ok(v) => v.content().to_owned(),
| Err(_) => continue, // Skipping missing events.
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: event_type.to_string().into(),
content: event_content,
state_key: Some(StateKey::new()),
..Default::default()
},
sender_user,
&replacement_room,
&state_lock,
)
.room_state_keys(&body.room_id, event_type)
.await?;
for state_key in state_keys {
let event_content = match services
.rooms
.state_accessor
.room_state_get(&body.room_id, event_type, &state_key)
.await
{
| Ok(v) => v.content().to_owned(),
| Err(_) => continue, // Skipping missing events.
};
if event_content.get() == "{}" {
// If the event content is empty, we skip it
continue;
}
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: event_type.to_string().into(),
content: event_content,
state_key: Some(StateKey::from(state_key)),
..Default::default()
},
sender_user,
&replacement_room,
&state_lock,
)
.boxed()
.await?;
}
}
// Moves any local aliases to the new room
@@ -290,10 +305,90 @@ pub(crate) async fn upgrade_room_route(
&body.room_id,
&state_lock,
)
.boxed()
.await?;
drop(state_lock);
// Check if the old room has a space parent, and if so, whether we should update
// it (m.space.parent, room_id)
let parents = services
.rooms
.state_accessor
.room_state_keys(&body.room_id, &StateEventType::SpaceParent)
.await?;
for raw_space_id in parents {
let space_id = RoomId::parse(&raw_space_id)?;
let Ok(child) = services
.rooms
.state_accessor
.room_state_get_content::<SpaceChildEventContent>(
space_id,
&StateEventType::SpaceChild,
body.room_id.as_str(),
)
.await
else {
// If the space does not have a child event for this room, we can skip it
continue;
};
debug!(
"Updating space {space_id} child event for room {} to {replacement_room}",
&body.room_id
);
// First, drop the space's child event
let state_lock = services.rooms.state.mutex.lock(space_id).await;
debug!("Removing space child event for room {} in space {space_id}", &body.room_id);
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&RedactedSpaceChildEventContent {})
.expect("event is valid, we just created it"),
state_key: Some(body.room_id.clone().as_str().into()),
..Default::default()
},
sender_user,
space_id,
&state_lock,
)
.boxed()
.await
.ok();
// Now, add a new child event for the replacement room
debug!("Adding space child event for room {replacement_room} in space {space_id}");
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: StateEventType::SpaceChild.into(),
content: to_raw_value(&SpaceChildEventContent {
via: vec![sender_user.server_name().to_owned()],
order: child.order,
suggested: child.suggested,
})
.expect("event is valid, we just created it"),
state_key: Some(replacement_room.as_str().into()),
..Default::default()
},
sender_user,
space_id,
&state_lock,
)
.boxed()
.await
.ok();
debug!(
"Finished updating space {space_id} child event for room {} to {replacement_room}",
&body.room_id
);
drop(state_lock);
}
// Return the replacement room id
Ok(upgrade_room::v3::Response { replacement_room })
}
+5 -2
View File
@@ -187,6 +187,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::well_known_support)
.ruma_route(&client::well_known_client)
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
.ruma_route(&client::room_initial_sync_route)
.route("/client/server.json", get(client::syncv3_client_server_json));
@@ -226,13 +227,15 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&server::well_known_server)
.ruma_route(&server::get_content_route)
.ruma_route(&server::get_content_thumbnail_route)
.route("/_conduwuit/local_user_count", get(client::conduwuit_local_user_count));
.route("/_conduwuit/local_user_count", get(client::conduwuit_local_user_count))
.route("/_continuwuity/local_user_count", get(client::conduwuit_local_user_count));
} else {
router = router
.route("/_matrix/federation/*path", any(federation_disabled))
.route("/.well-known/matrix/server", any(federation_disabled))
.route("/_matrix/key/*path", any(federation_disabled))
.route("/_conduwuit/local_user_count", any(federation_disabled));
.route("/_conduwuit/local_user_count", any(federation_disabled))
.route("/_continuwuity/local_user_count", any(federation_disabled));
}
if config.allow_legacy_media {
+2
View File
@@ -110,6 +110,8 @@ tracing-core.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
url.workspace = true
parking_lot.workspace = true
lock_api.workspace = true
[target.'cfg(unix)'.dependencies]
nix.workspace = true
+3 -4
View File
@@ -4,7 +4,6 @@ use std::{
cell::OnceCell,
ffi::{CStr, c_char, c_void},
fmt::Debug,
sync::RwLock,
};
use arrayvec::ArrayVec;
@@ -13,7 +12,7 @@ use tikv_jemalloc_sys as ffi;
use tikv_jemallocator as jemalloc;
use crate::{
Result, err, is_equal_to, is_nonzero,
Result, SyncRwLock, err, is_equal_to, is_nonzero,
utils::{math, math::Tried},
};
@@ -40,7 +39,7 @@ const MALLOC_CONF_PROF: &str = "";
#[global_allocator]
static JEMALLOC: jemalloc::Jemalloc = jemalloc::Jemalloc;
static CONTROL: RwLock<()> = RwLock::new(());
static CONTROL: SyncRwLock<()> = SyncRwLock::new(());
type Name = ArrayVec<u8, NAME_MAX>;
type Key = ArrayVec<usize, KEY_SEGS>;
@@ -332,7 +331,7 @@ fn set<T>(key: &Key, val: T) -> Result<T>
where
T: Copy + Debug,
{
let _lock = CONTROL.write()?;
let _lock = CONTROL.write();
let res = xchg(key, val)?;
inc_epoch()?;
+15 -6
View File
@@ -180,19 +180,28 @@ pub fn check(config: &Config) -> Result {
}
}
if config.recaptcha_site_key.is_some() && config.recaptcha_private_site_key.is_none() {
return Err!(Config(
"recaptcha_private_site_key",
"reCAPTCHA private site key is required when reCAPTCHA site key is set."
));
}
if config.allow_registration
&& !config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
&& config.registration_token.is_none()
&& config.registration_token_file.is_none()
&& config.recaptcha_site_key.is_none()
{
return Err!(Config(
"registration_token",
"!! You have `allow_registration` enabled without a token configured in your config \
which means you are allowing ANYONE to register on your conduwuit instance without \
any 2nd-step (e.g. registration token). If this is not the intended behaviour, \
please set a registration token. For security and safety reasons, conduwuit will \
shut down. If you are extra sure this is the desired behaviour you want, please \
set the following config option to true:
"!! You have `allow_registration` enabled without a token or captcha configured \
which means you are allowing ANYONE to register on your continuwuity instance \
without any 2nd-step (e.g. registration token, captcha), which is FREQUENTLY \
abused by malicious actors. If this is not the intended behaviour, please set a \
registration token. For security and safety reasons, continuwuity will shut down. \
If you are extra sure this is the desired behaviour you want, please set the \
following config option to true:
`yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`"
));
}
+50
View File
@@ -412,6 +412,17 @@ pub struct Config {
#[serde(default = "default_well_known_timeout")]
pub well_known_timeout: u64,
/// Federation client connection timeout (seconds). You should not set this
/// to high values, as dead homeservers can significantly slow down
/// federation, specifically key retrieval, which will take roughly the
/// amount of time you configure here given that a homeserver doesn't
/// respond. This will cause most clients to time out /keys/query, causing
/// E2EE and device verification to fail.
///
/// default: 10
#[serde(default = "default_federation_conn_timeout")]
pub federation_conn_timeout: u64,
/// Federation client request timeout (seconds). You most definitely want
/// this to be high to account for extremely large room joins, slow
/// homeservers, your own resources etc.
@@ -420,6 +431,23 @@ pub struct Config {
#[serde(default = "default_federation_timeout")]
pub federation_timeout: u64,
/// MSC4284 Policy server request timeout (seconds). Generally policy
/// servers should respond near instantly, however may slow down under
/// load. If a policy server doesn't respond in a short amount of time, the
/// room it is configured in may become unusable if this limit is set too
/// high. 10 seconds is a good default, however dropping this to 3-5 seconds
/// can be acceptable.
///
/// Please be aware that policy requests are *NOT* currently re-tried, so if
/// a spam check request fails, the event will be assumed to be not spam,
/// which in some cases may result in spam being sent to or received from
/// the room that would typically be prevented.
///
/// About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
/// default: 10
#[serde(default = "default_policy_server_request_timeout")]
pub policy_server_request_timeout: u64,
/// Federation client idle connection pool timeout (seconds).
///
/// default: 25
@@ -556,6 +584,24 @@ pub struct Config {
/// example: "/etc/continuwuity/.reg_token"
pub registration_token_file: Option<PathBuf>,
/// The public site key for reCaptcha. If this is provided, reCaptcha
/// becomes required during registration. If both captcha *and*
/// registration token are enabled, both will be required during
/// registration.
///
/// IMPORTANT: "Verify the origin of reCAPTCHA solutions" **MUST** BE
/// DISABLED IF YOU WANT THE CAPTCHA TO WORK IN 3RD PARTY CLIENTS, OR
/// CLIENTS HOSTED ON DOMAINS OTHER THAN YOUR OWN!
///
/// Registration must be enabled (`allow_registration` must be true) for
/// this to have any effect.
pub recaptcha_site_key: Option<String>,
/// The private site key for reCaptcha.
/// If this is omitted, captcha registration will not work,
/// even if `recaptcha_site_key` is set.
pub recaptcha_private_site_key: Option<String>,
/// Controls whether encrypted rooms and events are allowed.
#[serde(default = "true_fn")]
pub allow_encryption: bool,
@@ -2175,8 +2221,12 @@ fn default_well_known_conn_timeout() -> u64 { 6 }
fn default_well_known_timeout() -> u64 { 10 }
fn default_federation_conn_timeout() -> u64 { 10 }
fn default_federation_timeout() -> u64 { 25 }
fn default_policy_server_request_timeout() -> u64 { 10 }
fn default_federation_idle_timeout() -> u64 { 25 }
fn default_federation_idle_per_host() -> u16 { 1 }
+3 -7
View File
@@ -3,18 +3,15 @@
//! several crates, lower-level information is supplied from each crate during
//! static initialization.
use std::{
collections::BTreeMap,
sync::{Mutex, OnceLock},
};
use std::{collections::BTreeMap, sync::OnceLock};
use crate::utils::exchange;
use crate::{SyncMutex, utils::exchange};
/// Raw capture of rustc flags used to build each crate in the project. Informed
/// by rustc_flags_capture macro (one in each crate's mod.rs). This is
/// done during static initialization which is why it's mutex-protected and pub.
/// Should not be written to by anything other than our macro.
pub static FLAGS: Mutex<BTreeMap<&str, &[&str]>> = Mutex::new(BTreeMap::new());
pub static FLAGS: SyncMutex<BTreeMap<&str, &[&str]>> = SyncMutex::new(BTreeMap::new());
/// Processed list of enabled features across all project crates. This is
/// generated from the data in FLAGS.
@@ -27,7 +24,6 @@ fn init_features() -> Vec<&'static str> {
let mut features = Vec::new();
FLAGS
.lock()
.expect("locked")
.iter()
.for_each(|(_, flags)| append_features(&mut features, flags));
+1 -2
View File
@@ -40,7 +40,6 @@ where
self.state
.active
.read()
.expect("shared lock")
.iter()
.filter(|capture| filter(self, capture, event, &ctx))
.for_each(|capture| handle(self, capture, event, &ctx));
@@ -55,7 +54,7 @@ where
let mut visitor = Visitor { values: Values::new() };
event.record(&mut visitor);
let mut closure = capture.closure.lock().expect("exclusive lock");
let mut closure = capture.closure.lock();
closure(Data {
layer,
event,
+5 -3
View File
@@ -4,7 +4,7 @@ pub mod layer;
pub mod state;
pub mod util;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
pub use data::Data;
use guard::Guard;
@@ -12,6 +12,8 @@ pub use layer::{Layer, Value};
pub use state::State;
pub use util::*;
use crate::SyncMutex;
pub type Filter = dyn Fn(Data<'_>) -> bool + Send + Sync + 'static;
pub type Closure = dyn FnMut(Data<'_>) + Send + Sync + 'static;
@@ -19,7 +21,7 @@ pub type Closure = dyn FnMut(Data<'_>) + Send + Sync + 'static;
pub struct Capture {
state: Arc<State>,
filter: Option<Box<Filter>>,
closure: Mutex<Box<Closure>>,
closure: SyncMutex<Box<Closure>>,
}
impl Capture {
@@ -34,7 +36,7 @@ impl Capture {
Arc::new(Self {
state: state.clone(),
filter: filter.map(|p| -> Box<Filter> { Box::new(p) }),
closure: Mutex::new(Box::new(closure)),
closure: SyncMutex::new(Box::new(closure)),
})
}
+6 -8
View File
@@ -1,10 +1,11 @@
use std::sync::{Arc, RwLock};
use std::sync::Arc;
use super::Capture;
use crate::SyncRwLock;
/// Capture layer state.
pub struct State {
pub(super) active: RwLock<Vec<Arc<Capture>>>,
pub(super) active: SyncRwLock<Vec<Arc<Capture>>>,
}
impl Default for State {
@@ -13,17 +14,14 @@ impl Default for State {
impl State {
#[must_use]
pub fn new() -> Self { Self { active: RwLock::new(Vec::new()) } }
pub fn new() -> Self { Self { active: SyncRwLock::new(Vec::new()) } }
pub(super) fn add(&self, capture: &Arc<Capture>) {
self.active
.write()
.expect("locked for writing")
.push(capture.clone());
self.active.write().push(capture.clone());
}
pub(super) fn del(&self, capture: &Arc<Capture>) {
let mut vec = self.active.write().expect("locked for writing");
let mut vec = self.active.write();
if let Some(pos) = vec.iter().position(|v| Arc::ptr_eq(v, capture)) {
vec.swap_remove(pos);
}
+6 -6
View File
@@ -1,31 +1,31 @@
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use super::{
super::{Level, fmt},
Closure, Data,
};
use crate::Result;
use crate::{Result, SyncMutex};
pub fn fmt_html<S>(out: Arc<Mutex<S>>) -> Box<Closure>
pub fn fmt_html<S>(out: Arc<SyncMutex<S>>) -> Box<Closure>
where
S: std::fmt::Write + Send + 'static,
{
fmt(fmt::html, out)
}
pub fn fmt_markdown<S>(out: Arc<Mutex<S>>) -> Box<Closure>
pub fn fmt_markdown<S>(out: Arc<SyncMutex<S>>) -> Box<Closure>
where
S: std::fmt::Write + Send + 'static,
{
fmt(fmt::markdown, out)
}
pub fn fmt<F, S>(fun: F, out: Arc<Mutex<S>>) -> Box<Closure>
pub fn fmt<F, S>(fun: F, out: Arc<SyncMutex<S>>) -> Box<Closure>
where
F: Fn(&mut S, &Level, &str, &str) -> Result<()> + Send + Sync + Copy + 'static,
S: std::fmt::Write + Send + 'static,
{
Box::new(move |data| call(fun, &mut *out.lock().expect("locked"), &data))
Box::new(move |data| call(fun, &mut *out.lock(), &data))
}
fn call<F, S>(fun: F, out: &mut S, data: &Data<'_>)
+4 -12
View File
@@ -1,11 +1,8 @@
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::{collections::HashMap, sync::Arc};
use tracing_subscriber::{EnvFilter, reload};
use crate::{Result, error};
use crate::{Result, SyncMutex, error};
/// We need to store a reload::Handle value, but can't name it's type explicitly
/// because the S type parameter depends on the subscriber's previous layers. In
@@ -35,7 +32,7 @@ impl<L: Clone, S> ReloadHandle<L> for reload::Handle<L, S> {
#[derive(Clone)]
pub struct LogLevelReloadHandles {
handles: Arc<Mutex<HandleMap>>,
handles: Arc<SyncMutex<HandleMap>>,
}
type HandleMap = HashMap<String, Handle>;
@@ -43,16 +40,12 @@ type Handle = Box<dyn ReloadHandle<EnvFilter> + Send + Sync>;
impl LogLevelReloadHandles {
pub fn add(&self, name: &str, handle: Handle) {
self.handles
.lock()
.expect("locked")
.insert(name.into(), handle);
self.handles.lock().insert(name.into(), handle);
}
pub fn reload(&self, new_value: &EnvFilter, names: Option<&[&str]>) -> Result<()> {
self.handles
.lock()
.expect("locked")
.iter()
.filter(|(name, _)| names.is_some_and(|names| names.contains(&name.as_str())))
.for_each(|(_, handle)| {
@@ -66,7 +59,6 @@ impl LogLevelReloadHandles {
pub fn current(&self, name: &str) -> Option<EnvFilter> {
self.handles
.lock()
.expect("locked")
.get(name)
.map(|handle| handle.current())?
}
+22 -4
View File
@@ -149,8 +149,8 @@ where
for<'a> &'a E: Event + Send,
{
debug!(
event_id = format!("{}", incoming_event.event_id()),
event_type = format!("{}", incoming_event.event_type()),
event_id = %incoming_event.event_id(),
event_type = ?incoming_event.event_type(),
"auth_check beginning"
);
@@ -217,8 +217,9 @@ where
}
/*
// TODO: In the past this code caused problems federating with synapse, maybe this has been
// resolved already. Needs testing.
// TODO: In the past this code was commented as it caused problems with Synapse. This is no
// longer the case. This needs to be implemented.
// See also: https://github.com/ruma/ruma/pull/2064
//
// 2. Reject if auth_events
// a. auth_events cannot have duplicate keys since it's a BTree
@@ -256,6 +257,18 @@ where
| Some(e) => e,
};
if incoming_event.room_id() != room_create_event.room_id() {
warn!("room_id of incoming event does not match room_id of m.room.create event");
return Ok(false);
}
if let Some(ref pe) = power_levels_event {
if pe.room_id() != room_create_event.room_id() {
warn!("room_id of power levels event does not match room_id of m.room.create event");
return Ok(false);
}
}
// 3. If event does not have m.room.create in auth_events reject
if !incoming_event
.auth_events()
@@ -381,6 +394,11 @@ where
},
};
if sender_member_event.room_id() != room_create_event.room_id() {
warn!("room_id of incoming event does not match room_id of m.room.create event");
return Ok(false);
}
let sender_membership_event_content: RoomMemberContentFields =
from_json_str(sender_member_event.content().get())?;
let Some(membership_state) = sender_membership_event_content.membership else {
+1
View File
@@ -28,6 +28,7 @@ pub use info::{
pub use matrix::{
Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res,
};
pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock};
pub use server::Server;
pub use utils::{ctor, dtor, implement, result, result::Result};
+1
View File
@@ -19,6 +19,7 @@ pub mod sys;
#[cfg(test)]
mod tests;
pub mod time;
pub mod with_lock;
pub use ::conduwuit_macros::implement;
pub use ::ctor::{ctor, dtor};
+8 -17
View File
@@ -1,12 +1,8 @@
use std::{
fmt::Debug,
hash::Hash,
sync::{Arc, TryLockError::WouldBlock},
};
use std::{fmt::Debug, hash::Hash, sync::Arc};
use tokio::sync::OwnedMutexGuard as Omg;
use crate::{Result, err};
use crate::{Result, SyncMutex, err};
/// Map of Mutexes
pub struct MutexMap<Key, Val> {
@@ -19,7 +15,7 @@ pub struct Guard<Key, Val> {
}
type Map<Key, Val> = Arc<MapMutex<Key, Val>>;
type MapMutex<Key, Val> = std::sync::Mutex<HashMap<Key, Val>>;
type MapMutex<Key, Val> = SyncMutex<HashMap<Key, Val>>;
type HashMap<Key, Val> = std::collections::HashMap<Key, Value<Val>>;
type Value<Val> = Arc<tokio::sync::Mutex<Val>>;
@@ -45,7 +41,6 @@ where
let val = self
.map
.lock()
.expect("locked")
.entry(k.try_into().expect("failed to construct key"))
.or_default()
.clone();
@@ -66,7 +61,6 @@ where
let val = self
.map
.lock()
.expect("locked")
.entry(k.try_into().expect("failed to construct key"))
.or_default()
.clone();
@@ -87,10 +81,7 @@ where
let val = self
.map
.try_lock()
.map_err(|e| match e {
| WouldBlock => err!("would block"),
| _ => panic!("{e:?}"),
})?
.ok_or_else(|| err!("would block"))?
.entry(k.try_into().expect("failed to construct key"))
.or_default()
.clone();
@@ -102,13 +93,13 @@ where
}
#[must_use]
pub fn contains(&self, k: &Key) -> bool { self.map.lock().expect("locked").contains_key(k) }
pub fn contains(&self, k: &Key) -> bool { self.map.lock().contains_key(k) }
#[must_use]
pub fn is_empty(&self) -> bool { self.map.lock().expect("locked").is_empty() }
pub fn is_empty(&self) -> bool { self.map.lock().is_empty() }
#[must_use]
pub fn len(&self) -> usize { self.map.lock().expect("locked").len() }
pub fn len(&self) -> usize { self.map.lock().len() }
}
impl<Key, Val> Default for MutexMap<Key, Val>
@@ -123,7 +114,7 @@ impl<Key, Val> Drop for Guard<Key, Val> {
#[tracing::instrument(name = "unlock", level = "trace", skip_all)]
fn drop(&mut self) {
if Arc::strong_count(Omg::mutex(&self.val)) <= 2 {
self.map.lock().expect("locked").retain(|_, val| {
self.map.lock().retain(|_, val| {
!Arc::ptr_eq(val, Omg::mutex(&self.val)) || Arc::strong_count(val) > 2
});
}
+212
View File
@@ -0,0 +1,212 @@
//! Traits for explicitly scoping the lifetime of locks.
use std::{
future::Future,
sync::{Arc, Mutex},
};
pub trait WithLock<T: ?Sized> {
/// Acquires a lock and executes the given closure with the locked data,
/// returning the result.
fn with_lock<R, F>(&self, f: F) -> R
where
F: FnMut(&mut T) -> R;
}
impl<T> WithLock<T> for Mutex<T> {
fn with_lock<R, F>(&self, mut f: F) -> R
where
F: FnMut(&mut T) -> R,
{
// The locking and unlocking logic is hidden inside this function.
let mut data_guard = self.lock().unwrap();
f(&mut data_guard)
// Lock is released here when `data_guard` goes out of scope.
}
}
impl<T> WithLock<T> for Arc<Mutex<T>> {
fn with_lock<R, F>(&self, mut f: F) -> R
where
F: FnMut(&mut T) -> R,
{
// The locking and unlocking logic is hidden inside this function.
let mut data_guard = self.lock().unwrap();
f(&mut data_guard)
// Lock is released here when `data_guard` goes out of scope.
}
}
impl<R: lock_api::RawMutex, T: ?Sized> WithLock<T> for lock_api::Mutex<R, T> {
fn with_lock<Ret, F>(&self, mut f: F) -> Ret
where
F: FnMut(&mut T) -> Ret,
{
// The locking and unlocking logic is hidden inside this function.
let mut data_guard = self.lock();
f(&mut data_guard)
// Lock is released here when `data_guard` goes out of scope.
}
}
impl<R: lock_api::RawMutex, T: ?Sized> WithLock<T> for Arc<lock_api::Mutex<R, T>> {
fn with_lock<Ret, F>(&self, mut f: F) -> Ret
where
F: FnMut(&mut T) -> Ret,
{
// The locking and unlocking logic is hidden inside this function.
let mut data_guard = self.lock();
f(&mut data_guard)
// Lock is released here when `data_guard` goes out of scope.
}
}
pub trait WithLockAsync<T> {
/// Acquires a lock and executes the given closure with the locked data,
/// returning the result.
fn with_lock<R, F>(&self, f: F) -> impl Future<Output = R>
where
F: FnMut(&mut T) -> R;
/// Acquires a lock and executes the given async closure with the locked
/// data.
fn with_lock_async<R, F>(&self, f: F) -> impl std::future::Future<Output = R>
where
F: AsyncFnMut(&mut T) -> R;
}
impl<T> WithLockAsync<T> for futures::lock::Mutex<T> {
async fn with_lock<R, F>(&self, mut f: F) -> R
where
F: FnMut(&mut T) -> R,
{
// The locking and unlocking logic is hidden inside this function.
let mut data_guard = self.lock().await;
f(&mut data_guard)
// Lock is released here when `data_guard` goes out of scope.
}
async fn with_lock_async<R, F>(&self, mut f: F) -> R
where
F: AsyncFnMut(&mut T) -> R,
{
// The locking and unlocking logic is hidden inside this function.
let mut data_guard = self.lock().await;
f(&mut data_guard).await
// Lock is released here when `data_guard` goes out of scope.
}
}
impl<T> WithLockAsync<T> for Arc<futures::lock::Mutex<T>> {
async fn with_lock<R, F>(&self, mut f: F) -> R
where
F: FnMut(&mut T) -> R,
{
// The locking and unlocking logic is hidden inside this function.
let mut data_guard = self.lock().await;
f(&mut data_guard)
// Lock is released here when `data_guard` goes out of scope.
}
async fn with_lock_async<R, F>(&self, mut f: F) -> R
where
F: AsyncFnMut(&mut T) -> R,
{
// The locking and unlocking logic is hidden inside this function.
let mut data_guard = self.lock().await;
f(&mut data_guard).await
// Lock is released here when `data_guard` goes out of scope.
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_with_lock_return_value() {
let mutex = Mutex::new(5);
let result = mutex.with_lock(|v| {
*v += 1;
*v * 2
});
assert_eq!(result, 12);
let value = mutex.lock().unwrap();
assert_eq!(*value, 6);
}
#[test]
fn test_with_lock_unit_return() {
let mutex = Mutex::new(10);
mutex.with_lock(|v| {
*v += 2;
});
let value = mutex.lock().unwrap();
assert_eq!(*value, 12);
}
#[test]
fn test_with_lock_arc_mutex() {
let mutex = Arc::new(Mutex::new(1));
let result = mutex.with_lock(|v| {
*v *= 10;
*v
});
assert_eq!(result, 10);
assert_eq!(*mutex.lock().unwrap(), 10);
}
#[tokio::test]
async fn test_with_lock_async_return_value() {
use futures::lock::Mutex as AsyncMutex;
let mutex = AsyncMutex::new(7);
let result = mutex
.with_lock(|v| {
*v += 3;
*v * 2
})
.await;
assert_eq!(result, 20);
let value = mutex.lock().await;
assert_eq!(*value, 10);
}
#[tokio::test]
async fn test_with_lock_async_unit_return() {
use futures::lock::Mutex as AsyncMutex;
let mutex = AsyncMutex::new(100);
mutex
.with_lock(|v| {
*v -= 50;
})
.await;
let value = mutex.lock().await;
assert_eq!(*value, 50);
}
#[tokio::test]
async fn test_with_lock_async_closure() {
use futures::lock::Mutex as AsyncMutex;
let mutex = AsyncMutex::new(1);
mutex
.with_lock_async(async |v| {
*v += 9;
})
.await;
let value = mutex.lock().await;
assert_eq!(*value, 10);
}
#[tokio::test]
async fn test_with_lock_async_arc_mutex() {
use futures::lock::Mutex as AsyncMutex;
let mutex = Arc::new(AsyncMutex::new(2));
mutex
.with_lock_async(async |v: &mut i32| {
*v *= 5;
})
.await;
let value = mutex.lock().await;
assert_eq!(*value, 10);
}
}
+8
View File
@@ -44,6 +44,14 @@ zstd_compression = [
"conduwuit-core/zstd_compression",
"rust-rocksdb/zstd",
]
bindgen-static = [
# "bindgen/static"
# "clang-sys/static"
"rust-rocksdb/bindgen-static"
]
bindgen-runtime = [
"rust-rocksdb/bindgen-runtime"
]
[dependencies]
async-channel.workspace = true
+1 -1
View File
@@ -71,7 +71,7 @@ pub fn backup_count(&self) -> Result<usize> {
fn backup_engine(&self) -> Result<BackupEngine> {
let path = self.backup_path()?;
let options = BackupEngineOptions::new(path).map_err(map_err)?;
BackupEngine::open(&options, &*self.ctx.env.lock()?).map_err(map_err)
BackupEngine::open(&options, &self.ctx.env.lock()).map_err(map_err)
}
#[implement(Engine)]
+1 -1
View File
@@ -232,7 +232,7 @@ fn get_cache(ctx: &Context, desc: &Descriptor) -> Option<Cache> {
cache_opts.set_num_shard_bits(shard_bits);
cache_opts.set_capacity(size);
let mut caches = ctx.col_cache.lock().expect("locked");
let mut caches = ctx.col_cache.lock();
match desc.cache_disp {
| CacheDisp::Unique if desc.cache_size == 0 => None,
| CacheDisp::Unique => {
+6 -9
View File
@@ -1,9 +1,6 @@
use std::{
collections::BTreeMap,
sync::{Arc, Mutex},
};
use std::{collections::BTreeMap, sync::Arc};
use conduwuit::{Result, Server, debug, utils::math::usize_from_f64};
use conduwuit::{Result, Server, SyncMutex, debug, utils::math::usize_from_f64};
use rocksdb::{Cache, Env, LruCacheOptions};
use crate::{or_else, pool::Pool};
@@ -14,9 +11,9 @@ use crate::{or_else, pool::Pool};
/// These assets are housed in the shared Context.
pub(crate) struct Context {
pub(crate) pool: Arc<Pool>,
pub(crate) col_cache: Mutex<BTreeMap<String, Cache>>,
pub(crate) row_cache: Mutex<Cache>,
pub(crate) env: Mutex<Env>,
pub(crate) col_cache: SyncMutex<BTreeMap<String, Cache>>,
pub(crate) row_cache: SyncMutex<Cache>,
pub(crate) env: SyncMutex<Env>,
pub(crate) server: Arc<Server>,
}
@@ -68,7 +65,7 @@ impl Drop for Context {
debug!("Closing frontend pool");
self.pool.close();
let mut env = self.env.lock().expect("locked");
let mut env = self.env.lock();
debug!("Shutting down background threads");
env.set_high_priority_background_threads(0);
+3 -3
View File
@@ -9,7 +9,7 @@ use crate::or_else;
#[implement(Engine)]
pub fn memory_usage(&self) -> Result<String> {
let mut res = String::new();
let stats = get_memory_usage_stats(Some(&[&self.db]), Some(&[&*self.ctx.row_cache.lock()?]))
let stats = get_memory_usage_stats(Some(&[&self.db]), Some(&[&*self.ctx.row_cache.lock()]))
.or_else(or_else)?;
let mibs = |input| f64::from(u32::try_from(input / 1024).unwrap_or(0)) / 1024.0;
writeln!(
@@ -19,10 +19,10 @@ pub fn memory_usage(&self) -> Result<String> {
mibs(stats.mem_table_total),
mibs(stats.mem_table_unflushed),
mibs(stats.mem_table_readers_total),
mibs(u64::try_from(self.ctx.row_cache.lock()?.get_usage())?),
mibs(u64::try_from(self.ctx.row_cache.lock().get_usage())?),
)?;
for (name, cache) in &*self.ctx.col_cache.lock()? {
for (name, cache) in &*self.ctx.col_cache.lock() {
writeln!(res, "{name} cache: {:.2} MiB", mibs(u64::try_from(cache.get_usage())?))?;
}
+1 -5
View File
@@ -23,11 +23,7 @@ pub(crate) async fn open(ctx: Arc<Context>, desc: &[Descriptor]) -> Result<Arc<S
let config = &server.config;
let path = &config.database_path;
let db_opts = db_options(
config,
&ctx.env.lock().expect("environment locked"),
&ctx.row_cache.lock().expect("row cache locked"),
)?;
let db_opts = db_options(config, &ctx.env.lock(), &ctx.row_cache.lock())?;
let cfds = Self::configure_cfds(&ctx, &db_opts, desc)?;
let num_cfds = cfds.len();
+5 -5
View File
@@ -3,7 +3,7 @@ mod configure;
use std::{
mem::take,
sync::{
Arc, Mutex,
Arc,
atomic::{AtomicUsize, Ordering},
},
thread,
@@ -12,7 +12,7 @@ use std::{
use async_channel::{QueueStrategy, Receiver, RecvError, Sender};
use conduwuit::{
Error, Result, Server, debug, err, error, implement,
Error, Result, Server, SyncMutex, debug, err, error, implement,
result::DebugInspect,
smallvec::SmallVec,
trace,
@@ -31,7 +31,7 @@ use crate::{Handle, Map, keyval::KeyBuf, stream};
pub(crate) struct Pool {
server: Arc<Server>,
queues: Vec<Sender<Cmd>>,
workers: Mutex<Vec<JoinHandle<()>>>,
workers: SyncMutex<Vec<JoinHandle<()>>>,
topology: Vec<usize>,
busy: AtomicUsize,
queued_max: AtomicUsize,
@@ -115,7 +115,7 @@ impl Drop for Pool {
#[implement(Pool)]
#[tracing::instrument(skip_all)]
pub(crate) fn close(&self) {
let workers = take(&mut *self.workers.lock().expect("locked"));
let workers = take(&mut *self.workers.lock());
let senders = self.queues.iter().map(Sender::sender_count).sum::<usize>();
@@ -154,7 +154,7 @@ pub(crate) fn close(&self) {
#[implement(Pool)]
fn spawn_until(self: &Arc<Self>, recv: &[Receiver<Cmd>], count: usize) -> Result {
let mut workers = self.workers.lock().expect("locked");
let mut workers = self.workers.lock();
while workers.len() < count {
self.clone().spawn_one(&mut workers, recv)?;
}
+5 -5
View File
@@ -2,12 +2,12 @@ use std::{
collections::{HashMap, hash_map},
future::Future,
pin::Pin,
sync::RwLock,
};
use conduwuit::SyncRwLock;
use tokio::sync::watch;
type Watcher = RwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>;
type Watcher = SyncRwLock<HashMap<Vec<u8>, (watch::Sender<()>, watch::Receiver<()>)>>;
#[derive(Default)]
pub(crate) struct Watchers {
@@ -19,7 +19,7 @@ impl Watchers {
&'a self,
prefix: &[u8],
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
let mut rx = match self.watchers.write().unwrap().entry(prefix.to_vec()) {
let mut rx = match self.watchers.write().entry(prefix.to_vec()) {
| hash_map::Entry::Occupied(o) => o.get().1.clone(),
| hash_map::Entry::Vacant(v) => {
let (tx, rx) = watch::channel(());
@@ -35,7 +35,7 @@ impl Watchers {
}
pub(crate) fn wake(&self, key: &[u8]) {
let watchers = self.watchers.read().unwrap();
let watchers = self.watchers.read();
let mut triggered = Vec::new();
for length in 0..=key.len() {
if watchers.contains_key(&key[..length]) {
@@ -46,7 +46,7 @@ impl Watchers {
drop(watchers);
if !triggered.is_empty() {
let mut watchers = self.watchers.write().unwrap();
let mut watchers = self.watchers.write();
for prefix in triggered {
if let Some(tx) = watchers.remove(prefix) {
tx.0.send(()).expect("channel should still be open");
+2 -2
View File
@@ -15,13 +15,13 @@ pub(super) fn flags_capture(args: TokenStream) -> TokenStream {
#[conduwuit_core::ctor]
fn _set_rustc_flags() {
conduwuit_core::info::rustc::FLAGS.lock().expect("locked").insert(#crate_name, &RUSTC_FLAGS);
conduwuit_core::info::rustc::FLAGS.lock().insert(#crate_name, &RUSTC_FLAGS);
}
// static strings have to be yanked on module unload
#[conduwuit_core::dtor]
fn _unset_rustc_flags() {
conduwuit_core::info::rustc::FLAGS.lock().expect("locked").remove(#crate_name);
conduwuit_core::info::rustc::FLAGS.lock().remove(#crate_name);
}
};
+26 -2
View File
@@ -43,6 +43,11 @@ assets = [
[features]
default = [
"standard",
"release_max_log_level",
"bindgen-runtime", # replace with bindgen-static on alpine
]
standard = [
"blurhashing",
"brotli_compression",
"element_hacks",
@@ -52,10 +57,17 @@ default = [
"jemalloc_conf",
"journald",
"media_thumbnail",
"release_max_log_level",
"systemd",
"url_preview",
"zstd_compression",
"zstd_compression"
]
full = [
"standard",
"hardened_malloc",
"jemalloc_prof",
"perf_measurements",
"tokio_console"
# sentry_telemetry
]
blurhashing = [
@@ -161,6 +173,18 @@ zstd_compression = [
conduwuit_mods = [
"conduwuit-core/conduwuit_mods",
]
bindgen-static = [
# "bindgen/static"
# "clang-sys/static"
"conduwuit-database/bindgen-static"
]
bindgen-runtime = [
"conduwuit-database/bindgen-runtime"
]
[build-dependencies]
# bindgen = {version = "0.71.1", default-features = false}
# clang-sys = {version = "1", default-features = false}
[dependencies]
conduwuit-admin.workspace = true
+1
View File
@@ -111,6 +111,7 @@ webpage.workspace = true
webpage.optional = true
blurhash.workspace = true
blurhash.optional = true
recaptcha-verify = { version = "0.1.5", default-features = false }
[lints]
workspace = true
+22 -34
View File
@@ -1,11 +1,8 @@
#![cfg(feature = "console")]
use std::{
collections::VecDeque,
sync::{Arc, Mutex},
};
use std::{collections::VecDeque, sync::Arc};
use conduwuit::{Server, debug, defer, error, log, log::is_systemd_mode};
use conduwuit::{Server, SyncMutex, debug, defer, error, log, log::is_systemd_mode};
use futures::future::{AbortHandle, Abortable};
use ruma::events::room::message::RoomMessageEventContent;
use rustyline_async::{Readline, ReadlineError, ReadlineEvent};
@@ -17,10 +14,10 @@ use crate::{Dep, admin};
pub struct Console {
server: Arc<Server>,
admin: Dep<admin::Service>,
worker_join: Mutex<Option<JoinHandle<()>>>,
input_abort: Mutex<Option<AbortHandle>>,
command_abort: Mutex<Option<AbortHandle>>,
history: Mutex<VecDeque<String>>,
worker_join: SyncMutex<Option<JoinHandle<()>>>,
input_abort: SyncMutex<Option<AbortHandle>>,
command_abort: SyncMutex<Option<AbortHandle>>,
history: SyncMutex<VecDeque<String>>,
output: MadSkin,
}
@@ -50,7 +47,7 @@ impl Console {
}
pub async fn start(self: &Arc<Self>) {
let mut worker_join = self.worker_join.lock().expect("locked");
let mut worker_join = self.worker_join.lock();
if worker_join.is_none() {
let self_ = Arc::clone(self);
_ = worker_join.insert(self.server.runtime().spawn(self_.worker()));
@@ -60,7 +57,7 @@ impl Console {
pub async fn close(self: &Arc<Self>) {
self.interrupt();
let Some(worker_join) = self.worker_join.lock().expect("locked").take() else {
let Some(worker_join) = self.worker_join.lock().take() else {
return;
};
@@ -70,22 +67,18 @@ impl Console {
pub fn interrupt(self: &Arc<Self>) {
self.interrupt_command();
self.interrupt_readline();
self.worker_join
.lock()
.expect("locked")
.as_ref()
.map(JoinHandle::abort);
self.worker_join.lock().as_ref().map(JoinHandle::abort);
}
pub fn interrupt_readline(self: &Arc<Self>) {
if let Some(input_abort) = self.input_abort.lock().expect("locked").take() {
if let Some(input_abort) = self.input_abort.lock().take() {
debug!("Interrupting console readline...");
input_abort.abort();
}
}
pub fn interrupt_command(self: &Arc<Self>) {
if let Some(command_abort) = self.command_abort.lock().expect("locked").take() {
if let Some(command_abort) = self.command_abort.lock().take() {
debug!("Interrupting console command...");
command_abort.abort();
}
@@ -120,7 +113,7 @@ impl Console {
}
debug!("session ending");
self.worker_join.lock().expect("locked").take();
self.worker_join.lock().take();
}
async fn readline(self: &Arc<Self>) -> Result<ReadlineEvent, ReadlineError> {
@@ -135,9 +128,9 @@ impl Console {
let (abort, abort_reg) = AbortHandle::new_pair();
let future = Abortable::new(future, abort_reg);
_ = self.input_abort.lock().expect("locked").insert(abort);
_ = self.input_abort.lock().insert(abort);
defer! {{
_ = self.input_abort.lock().expect("locked").take();
_ = self.input_abort.lock().take();
}}
let Ok(result) = future.await else {
@@ -158,9 +151,9 @@ impl Console {
let (abort, abort_reg) = AbortHandle::new_pair();
let future = Abortable::new(future, abort_reg);
_ = self.command_abort.lock().expect("locked").insert(abort);
_ = self.command_abort.lock().insert(abort);
defer! {{
_ = self.command_abort.lock().expect("locked").take();
_ = self.command_abort.lock().take();
}}
_ = future.await;
@@ -184,20 +177,15 @@ impl Console {
}
fn set_history(&self, readline: &mut Readline) {
self.history
.lock()
.expect("locked")
.iter()
.rev()
.for_each(|entry| {
readline
.add_history_entry(entry.clone())
.expect("added history entry");
});
self.history.lock().iter().rev().for_each(|entry| {
readline
.add_history_entry(entry.clone())
.expect("added history entry");
});
}
fn add_history(&self, line: String) {
let mut history = self.history.lock().expect("locked");
let mut history = self.history.lock();
history.push_front(line);
history.truncate(HISTORY_LIMIT);
}
+3 -2
View File
@@ -192,8 +192,9 @@ pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
| Err(e) => return Err!(error!(?e, "Failure occurred while attempting revoke.")),
| Ok(event) if !matches!(event.membership, Invite | Knock | Join) =>
return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership),
| Ok(event) if !matches!(event.membership, Invite | Knock | Join) => {
return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership);
},
| Ok(event) => {
assert!(
+114 -22
View File
@@ -5,10 +5,11 @@ mod grant;
use std::{
pin::Pin,
sync::{Arc, RwLock as StdRwLock, Weak},
sync::{Arc, Weak},
};
use async_trait::async_trait;
use conduwuit::{Err, SyncRwLock, utils};
use conduwuit_core::{
Error, Event, Result, Server, debug, err, error, error::default_log, pdu::PduBuilder,
};
@@ -16,21 +17,26 @@ pub use create::create_admin_room;
use futures::{Future, FutureExt, TryFutureExt};
use loole::{Receiver, Sender};
use ruma::{
OwnedEventId, OwnedRoomId, RoomId, UserId,
Mxc, OwnedEventId, OwnedMxcUri, OwnedRoomId, RoomId, UInt, UserId,
events::{
Mentions,
room::message::{Relation, RoomMessageEventContent},
room::{
MediaSource,
message::{
FileInfo, FileMessageEventContent, MessageType, Relation, RoomMessageEventContent,
},
},
},
};
use tokio::sync::RwLock;
use crate::{Dep, account_data, globals, rooms, rooms::state::RoomMutexGuard};
use crate::{Dep, account_data, globals, media::MXC_LENGTH, rooms, rooms::state::RoomMutexGuard};
pub struct Service {
services: Services,
channel: (Sender<CommandInput>, Receiver<CommandInput>),
pub handle: RwLock<Option<Processor>>,
pub complete: StdRwLock<Option<Completer>>,
pub complete: SyncRwLock<Option<Completer>>,
#[cfg(feature = "console")]
pub console: Arc<console::Console>,
}
@@ -44,7 +50,8 @@ struct Services {
state_cache: Dep<rooms::state_cache::Service>,
state_accessor: Dep<rooms::state_accessor::Service>,
account_data: Dep<account_data::Service>,
services: StdRwLock<Option<Weak<crate::Services>>>,
services: SyncRwLock<Option<Weak<crate::Services>>>,
media: Dep<crate::media::Service>,
}
/// Inputs to a command are a multi-line string, optional reply_id, and optional
@@ -94,10 +101,11 @@ impl crate::Service for Service {
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
account_data: args.depend::<account_data::Service>("account_data"),
services: None.into(),
media: args.depend::<crate::media::Service>("media"),
},
channel: loole::bounded(COMMAND_QUEUE_LIMIT),
handle: RwLock::new(None),
complete: StdRwLock::new(None),
complete: SyncRwLock::new(None),
#[cfg(feature = "console")]
console: console::Console::new(&args),
}))
@@ -157,8 +165,65 @@ impl Service {
.ok();
}
/// Sends a message to the admin room as the admin user (see send_text() for
/// convenience).
/// Either returns a small-enough message, or converts a large message into
/// a file
pub async fn text_or_file(
&self,
message_content: RoomMessageEventContent,
) -> RoomMessageEventContent {
let body_len = Self::collate_msg_size(&message_content);
if body_len > 60000 {
// Intercept and send as file
let file = self
.text_to_file(message_content.body())
.await
.expect("failed to create text file");
let size_u64: u64 = message_content.body().len().try_into().map_or(0, |n| n);
let metadata = FileInfo {
mimetype: Some("text/markdown".to_owned()),
size: Some(UInt::new_saturating(size_u64)),
thumbnail_info: None,
thumbnail_source: None,
};
let content = FileMessageEventContent {
body: "Output was too large to send as text.".to_owned(),
formatted: None,
filename: Some("output.md".to_owned()),
source: MediaSource::Plain(file),
info: Some(Box::new(metadata)),
};
RoomMessageEventContent::new(MessageType::File(content))
} else {
message_content
}
}
#[must_use]
pub fn collate_msg_size(content: &RoomMessageEventContent) -> u64 {
content
.body()
.len()
.saturating_add(match &content.msgtype {
| MessageType::Text(t) =>
if t.formatted.is_some() {
t.formatted.as_ref().map_or(0, |f| f.body.len())
} else {
0
},
| MessageType::Notice(n) =>
if n.formatted.is_some() {
n.formatted.as_ref().map_or(0, |f| f.body.len())
} else {
0
},
| _ => 0,
})
.try_into()
.expect("size too large")
}
/// Sends a message to the admin room as the admin user (see send_text()
/// for convenience).
pub async fn send_message(&self, message_content: RoomMessageEventContent) -> Result<()> {
let user_id = &self.services.globals.server_user;
let room_id = self.get_admin_room().await?;
@@ -178,6 +243,36 @@ impl Service {
self.send_message(message_content).await
}
/// Casts a text body into a file and creates a file for it.
pub async fn text_to_file(&self, body: &str) -> Result<OwnedMxcUri> {
let mxc = Mxc {
server_name: self.services.globals.server_name(),
media_id: &utils::random_string(MXC_LENGTH),
};
match self
.services
.media
.create(
&mxc,
Some(self.services.globals.server_user.as_ref()),
Some(&utils::content_disposition::make_content_disposition(
None,
Some("text/markdown"),
Some("output.md"),
)),
Some("text/markdown"),
body.as_bytes(),
)
.await
{
| Ok(()) => Ok(mxc.to_string().into()),
| Err(e) => {
error!("Failed to upload text to file: {e}");
Err!(Request(Unknown("Failed to upload text to file")))
},
}
}
/// Posts a command to the command processor queue and returns. Processing
/// will take place on the service worker's task asynchronously. Errors if
/// the queue is full.
@@ -217,10 +312,7 @@ impl Service {
/// Invokes the tab-completer to complete the command. When unavailable,
/// None is returned.
pub fn complete_command(&self, command: &str) -> Option<String> {
self.complete
.read()
.expect("locked for reading")
.map(|complete| complete(command))
self.complete.read().map(|complete| complete(command))
}
async fn handle_signal(&self, sig: &'static str) {
@@ -243,17 +335,13 @@ impl Service {
}
async fn process_command(&self, command: CommandInput) -> ProcessorResult {
let handle = &self
.handle
.read()
.await
.expect("Admin module is not loaded");
let handle_guard = self.handle.read().await;
let handle = handle_guard.as_ref().expect("Admin module is not loaded");
let services = self
.services
.services
.read()
.expect("locked")
.as_ref()
.and_then(Weak::upgrade)
.expect("Services self-reference not initialized.");
@@ -325,11 +413,15 @@ impl Service {
assert!(self.user_is_admin(user_id).await, "sender is not admin");
let state_lock = self.services.state.mutex.lock(room_id).await;
if let Err(e) = self
.services
.timeline
.build_and_append_pdu(PduBuilder::timeline(&content), user_id, room_id, &state_lock)
.build_and_append_pdu(
PduBuilder::timeline(&self.text_or_file(content).await),
user_id,
room_id,
&state_lock,
)
.await
{
self.handle_response_error(e, room_id, user_id, &state_lock)
@@ -424,7 +516,7 @@ impl Service {
/// Sets the self-reference to crate::Services which will provide context to
/// the admin commands.
pub(super) fn set_services(&self, services: Option<&Arc<crate::Services>>) {
let receiver = &mut *self.services.services.write().expect("locked for writing");
let receiver = &mut *self.services.services.write();
let weak = services.map(Arc::downgrade);
*receiver = weak;
}
+3
View File
@@ -66,6 +66,7 @@ impl crate::Service for Service {
federation: base(config)?
.dns_resolver(resolver.resolver.hooked.clone())
.connect_timeout(Duration::from_secs(config.federation_conn_timeout))
.read_timeout(Duration::from_secs(config.federation_timeout))
.pool_max_idle_per_host(config.federation_idle_per_host.into())
.pool_idle_timeout(Duration::from_secs(config.federation_idle_timeout))
@@ -74,6 +75,7 @@ impl crate::Service for Service {
synapse: base(config)?
.dns_resolver(resolver.resolver.hooked.clone())
.connect_timeout(Duration::from_secs(config.federation_conn_timeout))
.read_timeout(Duration::from_secs(305))
.pool_max_idle_per_host(0)
.redirect(redirect::Policy::limited(3))
@@ -81,6 +83,7 @@ impl crate::Service for Service {
sender: base(config)?
.dns_resolver(resolver.resolver.hooked.clone())
.connect_timeout(Duration::from_secs(config.federation_conn_timeout))
.read_timeout(Duration::from_secs(config.sender_timeout))
.timeout(Duration::from_secs(config.sender_timeout))
.pool_max_idle_per_host(1)
+9 -13
View File
@@ -1,11 +1,11 @@
use std::sync::{Arc, RwLock};
use std::sync::Arc;
use conduwuit::{Result, utils};
use conduwuit::{Result, SyncRwLock, utils};
use database::{Database, Deserialized, Map};
pub struct Data {
global: Arc<Map>,
counter: RwLock<u64>,
counter: SyncRwLock<u64>,
pub(super) db: Arc<Database>,
}
@@ -16,25 +16,21 @@ impl Data {
let db = &args.db;
Self {
global: db["global"].clone(),
counter: RwLock::new(
Self::stored_count(&db["global"]).expect("initialized global counter"),
),
counter: SyncRwLock::new(Self::stored_count(&db["global"]).unwrap_or_default()),
db: args.db.clone(),
}
}
pub fn next_count(&self) -> Result<u64> {
let _cork = self.db.cork();
let mut lock = self.counter.write().expect("locked");
let mut lock = self.counter.write();
let counter: &mut u64 = &mut lock;
debug_assert!(
*counter == Self::stored_count(&self.global).expect("database failure"),
*counter == Self::stored_count(&self.global).unwrap_or_default(),
"counter mismatch"
);
*counter = counter
.checked_add(1)
.expect("counter must not overflow u64");
*counter = counter.checked_add(1).unwrap_or(*counter);
self.global.insert(COUNTER, counter.to_be_bytes());
@@ -43,10 +39,10 @@ impl Data {
#[inline]
pub fn current_count(&self) -> u64 {
let lock = self.counter.read().expect("locked");
let lock = self.counter.read();
let counter: &u64 = &lock;
debug_assert!(
*counter == Self::stored_count(&self.global).expect("database failure"),
*counter == Self::stored_count(&self.global).unwrap_or_default(),
"counter mismatch"
);
+6 -16
View File
@@ -1,14 +1,9 @@
mod data;
use std::{
collections::HashMap,
fmt::Write,
sync::{Arc, RwLock},
time::Instant,
};
use std::{collections::HashMap, fmt::Write, sync::Arc, time::Instant};
use async_trait::async_trait;
use conduwuit::{Result, Server, error, utils::bytes::pretty};
use conduwuit::{Result, Server, SyncRwLock, error, utils::bytes::pretty};
use data::Data;
use regex::RegexSet;
use ruma::{OwnedEventId, OwnedRoomAliasId, OwnedServerName, OwnedUserId, ServerName, UserId};
@@ -19,7 +14,7 @@ pub struct Service {
pub db: Data,
server: Arc<Server>,
pub bad_event_ratelimiter: Arc<RwLock<HashMap<OwnedEventId, RateLimitState>>>,
pub bad_event_ratelimiter: Arc<SyncRwLock<HashMap<OwnedEventId, RateLimitState>>>,
pub server_user: OwnedUserId,
pub admin_alias: OwnedRoomAliasId,
pub turn_secret: String,
@@ -62,7 +57,7 @@ impl crate::Service for Service {
Ok(Arc::new(Self {
db,
server: args.server.clone(),
bad_event_ratelimiter: Arc::new(RwLock::new(HashMap::new())),
bad_event_ratelimiter: Arc::new(SyncRwLock::new(HashMap::new())),
admin_alias: OwnedRoomAliasId::try_from(format!("#admins:{}", &args.server.name))
.expect("#admins:server_name is valid alias name"),
server_user: UserId::parse_with_server_name(
@@ -76,7 +71,7 @@ impl crate::Service for Service {
}
async fn memory_usage(&self, out: &mut (dyn Write + Send)) -> Result {
let (ber_count, ber_bytes) = self.bad_event_ratelimiter.read()?.iter().fold(
let (ber_count, ber_bytes) = self.bad_event_ratelimiter.read().iter().fold(
(0_usize, 0_usize),
|(mut count, mut bytes), (event_id, _)| {
bytes = bytes.saturating_add(event_id.capacity());
@@ -91,12 +86,7 @@ impl crate::Service for Service {
Ok(())
}
async fn clear_cache(&self) {
self.bad_event_ratelimiter
.write()
.expect("locked for writing")
.clear();
}
async fn clear_cache(&self) { self.bad_event_ratelimiter.write().clear(); }
fn name(&self) -> &str { service::make_name(std::module_path!()) }
}
-1
View File
@@ -58,7 +58,6 @@ impl Manager {
let services: Vec<Arc<dyn Service>> = self
.service
.read()
.expect("locked for reading")
.values()
.map(|val| val.0.upgrade())
.map(|arc| arc.expect("services available for manager startup"))
+6 -18
View File
@@ -1,9 +1,6 @@
use std::{
mem::size_of,
sync::{Arc, Mutex},
};
use std::{mem::size_of, sync::Arc};
use conduwuit::{Err, Result, err, utils, utils::math::usize_from_f64};
use conduwuit::{Err, Result, SyncMutex, err, utils, utils::math::usize_from_f64};
use database::Map;
use lru_cache::LruCache;
@@ -11,7 +8,7 @@ use crate::rooms::short::ShortEventId;
pub(super) struct Data {
shorteventid_authchain: Arc<Map>,
pub(super) auth_chain_cache: Mutex<LruCache<Vec<u64>, Arc<[ShortEventId]>>>,
pub(super) auth_chain_cache: SyncMutex<LruCache<Vec<u64>, Arc<[ShortEventId]>>>,
}
impl Data {
@@ -23,7 +20,7 @@ impl Data {
.expect("valid cache size");
Self {
shorteventid_authchain: db["shorteventid_authchain"].clone(),
auth_chain_cache: Mutex::new(LruCache::new(cache_size)),
auth_chain_cache: SyncMutex::new(LruCache::new(cache_size)),
}
}
@@ -34,12 +31,7 @@ impl Data {
debug_assert!(!key.is_empty(), "auth_chain key must not be empty");
// Check RAM cache
if let Some(result) = self
.auth_chain_cache
.lock()
.expect("cache locked")
.get_mut(key)
{
if let Some(result) = self.auth_chain_cache.lock().get_mut(key) {
return Ok(Arc::clone(result));
}
@@ -63,7 +55,6 @@ impl Data {
// Cache in RAM
self.auth_chain_cache
.lock()
.expect("cache locked")
.insert(vec![key[0]], Arc::clone(&chain));
Ok(chain)
@@ -84,9 +75,6 @@ impl Data {
}
// Cache in RAM
self.auth_chain_cache
.lock()
.expect("cache locked")
.insert(key, auth_chain);
self.auth_chain_cache.lock().insert(key, auth_chain);
}
}
+2 -2
View File
@@ -248,10 +248,10 @@ pub fn cache_auth_chain_vec(&self, key: Vec<u64>, auth_chain: &[ShortEventId]) {
#[implement(Service)]
pub fn get_cache_usage(&self) -> (usize, usize) {
let cache = self.db.auth_chain_cache.lock().expect("locked");
let cache = self.db.auth_chain_cache.lock();
(cache.len(), cache.capacity())
}
#[implement(Service)]
pub fn clear_cache(&self) { self.db.auth_chain_cache.lock().expect("locked").clear(); }
pub fn clear_cache(&self) { self.db.auth_chain_cache.lock().clear(); }
@@ -41,7 +41,6 @@ where
.globals
.bad_event_ratelimiter
.write()
.expect("locked")
.entry(id)
{
| hash_map::Entry::Vacant(e) => {
@@ -76,7 +75,6 @@ where
.globals
.bad_event_ratelimiter
.read()
.expect("locked")
.get(&*next_id)
{
// Exponential backoff
@@ -187,7 +185,6 @@ where
.globals
.bad_event_ratelimiter
.read()
.expect("locked")
.get(&*next_id)
{
// Exponential backoff
@@ -160,7 +160,6 @@ pub async fn handle_incoming_pdu<'a>(
.globals
.bad_event_ratelimiter
.write()
.expect("locked")
.entry(prev_id.into())
{
| hash_map::Entry::Vacant(e) => {
@@ -181,13 +180,11 @@ pub async fn handle_incoming_pdu<'a>(
let start_time = Instant::now();
self.federation_handletime
.write()
.expect("locked")
.insert(room_id.into(), (event_id.to_owned(), start_time));
defer! {{
self.federation_handletime
.write()
.expect("locked")
.remove(room_id);
}};
@@ -42,7 +42,6 @@ where
.globals
.bad_event_ratelimiter
.read()
.expect("locked")
.get(prev_id)
{
// Exponential backoff
@@ -70,13 +69,11 @@ where
let start_time = Instant::now();
self.federation_handletime
.write()
.expect("locked")
.insert(room_id.into(), ((*prev_id).to_owned(), start_time));
defer! {{
self.federation_handletime
.write()
.expect("locked")
.remove(room_id);
}};
+7 -13
View File
@@ -6,19 +6,15 @@ mod handle_incoming_pdu;
mod handle_outlier_pdu;
mod handle_prev_pdu;
mod parse_incoming_pdu;
mod policy_server;
mod resolve_state;
mod state_at_incoming;
mod upgrade_outlier_pdu;
use std::{
collections::HashMap,
fmt::Write,
sync::{Arc, RwLock as StdRwLock},
time::Instant,
};
use std::{collections::HashMap, fmt::Write, sync::Arc, time::Instant};
use async_trait::async_trait;
use conduwuit::{Err, Event, PduEvent, Result, RoomVersion, Server, utils::MutexMap};
use conduwuit::{Err, Event, PduEvent, Result, RoomVersion, Server, SyncRwLock, utils::MutexMap};
use ruma::{
OwnedEventId, OwnedRoomId, RoomId, RoomVersionId,
events::room::create::RoomCreateEventContent,
@@ -28,7 +24,7 @@ use crate::{Dep, globals, rooms, sending, server_keys};
pub struct Service {
pub mutex_federation: RoomMutexMap,
pub federation_handletime: StdRwLock<HandleTimeMap>,
pub federation_handletime: SyncRwLock<HandleTimeMap>,
services: Services,
}
@@ -42,6 +38,7 @@ struct Services {
server_keys: Dep<server_keys::Service>,
short: Dep<rooms::short::Service>,
state: Dep<rooms::state::Service>,
state_cache: Dep<rooms::state_cache::Service>,
state_accessor: Dep<rooms::state_accessor::Service>,
state_compressor: Dep<rooms::state_compressor::Service>,
timeline: Dep<rooms::timeline::Service>,
@@ -67,6 +64,7 @@ impl crate::Service for Service {
pdu_metadata: args.depend::<rooms::pdu_metadata::Service>("rooms::pdu_metadata"),
short: args.depend::<rooms::short::Service>("rooms::short"),
state: args.depend::<rooms::state::Service>("rooms::state"),
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
state_accessor: args
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
state_compressor: args
@@ -81,11 +79,7 @@ impl crate::Service for Service {
let mutex_federation = self.mutex_federation.len();
writeln!(out, "federation_mutex: {mutex_federation}")?;
let federation_handletime = self
.federation_handletime
.read()
.expect("locked for reading")
.len();
let federation_handletime = self.federation_handletime.read().len();
writeln!(out, "federation_handletime: {federation_handletime}")?;
Ok(())
@@ -0,0 +1,121 @@
//! Policy server integration for event spam checking in Matrix rooms.
//!
//! This module implements a check against a room-specific policy server, as
//! described in the relevant Matrix spec proposal (see: https://github.com/matrix-org/matrix-spec-proposals/pull/4284).
use std::time::Duration;
use conduwuit::{Err, Event, PduEvent, Result, debug, implement, warn};
use ruma::{
RoomId, ServerName,
api::federation::room::policy::v1::Request as PolicyRequest,
events::{StateEventType, room::policy::RoomPolicyEventContent},
};
/// Asks a remote policy server if the event is allowed.
///
/// If the event is the `org.matrix.msc4284.policy` configuration state event,
/// this check is skipped. Similarly, if there is no policy server configured in
/// the PDU's room, or the configured server is not present in the room, the
/// check is also skipped.
///
/// If the policy server marks the event as spam, Ok(false) is returned,
/// otherwise Ok(true) allows the event. If the policy server cannot be
/// contacted for whatever reason, Err(e) is returned, which generally is a
/// fail-open operation.
#[implement(super::Service)]
#[tracing::instrument(skip_all, level = "debug")]
pub async fn ask_policy_server(&self, pdu: &PduEvent, room_id: &RoomId) -> Result<bool> {
if *pdu.event_type() == StateEventType::RoomPolicy.into() {
debug!(
room_id = %room_id,
event_type = ?pdu.event_type(),
"Skipping spam check for policy server meta-event"
);
return Ok(true);
}
let Ok(policyserver) = self
.services
.state_accessor
.room_state_get_content(room_id, &StateEventType::RoomPolicy, "")
.await
.map(|c: RoomPolicyEventContent| c)
else {
return Ok(true);
};
let via = match policyserver.via {
| Some(ref via) => ServerName::parse(via)?,
| None => {
debug!("No policy server configured for room {room_id}");
return Ok(true);
},
};
if via.is_empty() {
debug!("Policy server is empty for room {room_id}, skipping spam check");
return Ok(true);
}
if !self.services.state_cache.server_in_room(via, room_id).await {
debug!(
room_id = %room_id,
via = %via,
"Policy server is not in the room, skipping spam check"
);
return Ok(true);
}
let outgoing = self
.services
.sending
.convert_to_outgoing_federation_event(pdu.to_canonical_object())
.await;
debug!(
room_id = %room_id,
via = %via,
outgoing = ?outgoing,
"Checking event for spam with policy server"
);
let response = tokio::time::timeout(
Duration::from_secs(self.services.server.config.policy_server_request_timeout),
self.services
.sending
.send_federation_request(via, PolicyRequest {
event_id: pdu.event_id().to_owned(),
pdu: Some(outgoing),
}),
)
.await;
let response = match response {
| Ok(Ok(response)) => response,
| Ok(Err(e)) => {
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Failed to contact policy server: {e}"
);
// Network or policy server errors are treated as non-fatal: event is allowed by
// default.
return Err(e);
},
| Err(_) => {
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Policy server request timed out after 10 seconds"
);
return Err!("Request to policy server timed out");
},
};
if response.recommendation == "spam" {
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Event was marked as spam by policy server",
);
return Ok(false);
}
Ok(true)
}
@@ -1,7 +1,7 @@
use std::{borrow::Borrow, collections::BTreeMap, iter::once, sync::Arc, time::Instant};
use conduwuit::{
Err, Result, debug, debug_info, err, implement, is_equal_to,
Err, Result, debug, debug_info, err, implement, info, is_equal_to,
matrix::{Event, EventTypeExt, PduEvent, StateKey, state_res},
trace,
utils::stream::{BroadbandExt, ReadyExt},
@@ -47,7 +47,10 @@ where
return Err!(Request(InvalidParam("Event has been soft failed")));
}
debug!("Upgrading to timeline pdu");
debug!(
event_id = %incoming_pdu.event_id,
"Upgrading PDU from outlier to timeline"
);
let timer = Instant::now();
let room_version_id = get_room_version_id(create_event)?;
@@ -55,7 +58,10 @@ where
// backwards extremities doing all the checks in this list starting at 1.
// These are not timeline events.
debug!("Resolving state at event");
debug!(
event_id = %incoming_pdu.event_id,
"Resolving state at event"
);
let mut state_at_incoming_event = if incoming_pdu.prev_events().count() == 1 {
self.state_at_incoming_degree_one(&incoming_pdu).await?
} else {
@@ -74,7 +80,10 @@ where
let room_version = to_room_version(&room_version_id);
debug!("Performing auth check");
debug!(
event_id = %incoming_pdu.event_id,
"Performing auth check to upgrade"
);
// 11. Check the auth of the event passes based on the state of the event
let state_fetch_state = &state_at_incoming_event;
let state_fetch = |k: StateEventType, s: StateKey| async move {
@@ -84,6 +93,10 @@ where
self.services.timeline.get_pdu(event_id).await.ok()
};
debug!(
event_id = %incoming_pdu.event_id,
"Running initial auth check"
);
let auth_check = state_res::event_auth::auth_check(
&room_version,
&incoming_pdu,
@@ -97,7 +110,10 @@ where
return Err!(Request(Forbidden("Event has failed auth check with state at the event.")));
}
debug!("Gathering auth events");
debug!(
event_id = %incoming_pdu.event_id,
"Gathering auth events"
);
let auth_events = self
.services
.state
@@ -115,6 +131,10 @@ where
ready(auth_events.get(&key).map(ToOwned::to_owned))
};
debug!(
event_id = %incoming_pdu.event_id,
"Running auth check with claimed state auth"
);
let auth_check = state_res::event_auth::auth_check(
&room_version,
&incoming_pdu,
@@ -125,8 +145,11 @@ where
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
// Soft fail check before doing state res
debug!("Performing soft-fail check");
let soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_id)) {
debug!(
event_id = %incoming_pdu.event_id,
"Performing soft-fail check"
);
let mut soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_id)) {
| (false, _) => true,
| (true, None) => false,
| (true, Some(redact_id)) =>
@@ -140,7 +163,10 @@ where
// 13. Use state resolution to find new room state
// We start looking at current room state now, so lets lock the room
trace!("Locking the room");
trace!(
room_id = %room_id,
"Locking the room"
);
let state_lock = self.services.state.mutex.lock(room_id).await;
// Now we calculate the set of extremities this room has after the incoming
@@ -219,10 +245,63 @@ where
.await?;
}
if !soft_fail {
// Don't call the below checks on events that have already soft-failed, there's
// no reason to re-calculate that.
// 14-pre. If the event is not a state event, ask the policy server about it
if incoming_pdu.state_key.is_none() {
debug!(event_id = %incoming_pdu.event_id, "Checking policy server for event");
match self.ask_policy_server(&incoming_pdu, room_id).await {
| Ok(false) => {
warn!(
event_id = %incoming_pdu.event_id,
"Event has been marked as spam by policy server"
);
soft_fail = true;
},
| _ => {
debug!(
event_id = %incoming_pdu.event_id,
"Event has passed policy server check or the policy server was unavailable."
);
},
}
}
// Additionally, if this is a redaction for a soft-failed event, we soft-fail it
// also.
// TODO: this is supposed to hide redactions from policy servers, however, for
// full efficacy it also needs to hide redactions for unknown events. This
// needs to be investigated at a later time.
if let Some(redact_id) = incoming_pdu.redacts_id(&room_version_id) {
debug!(
redact_id = %redact_id,
"Checking if redaction is for a soft-failed event"
);
if self
.services
.pdu_metadata
.is_event_soft_failed(&redact_id)
.await
{
warn!(
redact_id = %redact_id,
"Redaction is for a soft-failed event, soft failing the redaction"
);
soft_fail = true;
}
}
}
// 14. Check if the event passes auth based on the "current state" of the room,
// if not soft fail it
if soft_fail {
debug!("Soft failing event");
info!(
event_id = %incoming_pdu.event_id,
"Soft failing event"
);
// assert!(extremities.is_empty(), "soft_fail extremities empty");
let extremities = extremities.iter().map(Borrow::borrow);
self.services
@@ -242,7 +321,10 @@ where
.pdu_metadata
.mark_event_soft_failed(incoming_pdu.event_id());
warn!("Event was soft failed: {:?}", incoming_pdu.event_id());
warn!(
event_id = %incoming_pdu.event_id,
"Event was soft failed"
);
return Err!(Request(InvalidParam("Event has been soft failed")));
}
@@ -91,3 +91,22 @@ pub async fn room_state_get(
.and_then(|shortstatehash| self.state_get(shortstatehash, event_type, state_key))
.await
}
/// Returns all state keys for the given `room_id` and `event_type`.
#[implement(super::Service)]
#[tracing::instrument(skip(self), level = "debug")]
pub async fn room_state_keys(
&self,
room_id: &RoomId,
event_type: &StateEventType,
) -> Result<Vec<String>> {
let shortstatehash = self.services.state.get_room_shortstatehash(room_id).await?;
let state_keys: Vec<String> = self
.state_keys(shortstatehash, event_type)
.map(|state_key| state_key.to_string())
.collect()
.await;
Ok(state_keys)
}
+6 -16
View File
@@ -1,13 +1,10 @@
mod update;
mod via;
use std::{
collections::HashMap,
sync::{Arc, RwLock},
};
use std::{collections::HashMap, sync::Arc};
use conduwuit::{
Result, implement,
Result, SyncRwLock, implement,
result::LogErr,
utils::{ReadyExt, stream::TryIgnore},
warn,
@@ -54,14 +51,14 @@ struct Data {
userroomid_knockedstate: Arc<Map>,
}
type AppServiceInRoomCache = RwLock<HashMap<OwnedRoomId, HashMap<String, bool>>>;
type AppServiceInRoomCache = SyncRwLock<HashMap<OwnedRoomId, HashMap<String, bool>>>;
type StrippedStateEventItem = (OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>);
type SyncStateEventItem = (OwnedRoomId, Vec<Raw<AnySyncStateEvent>>);
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
appservice_in_room_cache: RwLock::new(HashMap::new()),
appservice_in_room_cache: SyncRwLock::new(HashMap::new()),
services: Services {
account_data: args.depend::<account_data::Service>("account_data"),
config: args.depend::<config::Service>("config"),
@@ -99,7 +96,6 @@ pub async fn appservice_in_room(&self, room_id: &RoomId, appservice: &Registrati
if let Some(cached) = self
.appservice_in_room_cache
.read()
.expect("locked")
.get(room_id)
.and_then(|map| map.get(&appservice.registration.id))
.copied()
@@ -124,7 +120,6 @@ pub async fn appservice_in_room(&self, room_id: &RoomId, appservice: &Registrati
self.appservice_in_room_cache
.write()
.expect("locked")
.entry(room_id.into())
.or_default()
.insert(appservice.registration.id.clone(), in_room);
@@ -134,19 +129,14 @@ pub async fn appservice_in_room(&self, room_id: &RoomId, appservice: &Registrati
#[implement(Service)]
pub fn get_appservice_in_room_cache_usage(&self) -> (usize, usize) {
let cache = self.appservice_in_room_cache.read().expect("locked");
let cache = self.appservice_in_room_cache.read();
(cache.len(), cache.capacity())
}
#[implement(Service)]
#[tracing::instrument(level = "debug", skip_all)]
pub fn clear_appservice_in_room_cache(&self) {
self.appservice_in_room_cache
.write()
.expect("locked")
.clear();
}
pub fn clear_appservice_in_room_cache(&self) { self.appservice_in_room_cache.write().clear(); }
/// Returns an iterator of all servers participating in this room.
#[implement(Service)]
+1 -4
View File
@@ -211,10 +211,7 @@ pub async fn update_joined_count(&self, room_id: &RoomId) {
self.db.serverroomids.put_raw(serverroom_id, []);
}
self.appservice_in_room_cache
.write()
.expect("locked")
.remove(room_id);
self.appservice_in_room_cache.write().remove(room_id);
}
/// Direct DB function to directly mark a user as joined. It is not
+7 -7
View File
@@ -2,12 +2,12 @@ use std::{
collections::{BTreeSet, HashMap},
fmt::{Debug, Write},
mem::size_of,
sync::{Arc, Mutex},
sync::Arc,
};
use async_trait::async_trait;
use conduwuit::{
Result,
Result, SyncMutex,
arrayvec::ArrayVec,
at, checked, err, expected, implement, utils,
utils::{bytes, math::usize_from_f64, stream::IterStream},
@@ -23,7 +23,7 @@ use crate::{
};
pub struct Service {
pub stateinfo_cache: Mutex<StateInfoLruCache>,
pub stateinfo_cache: SyncMutex<StateInfoLruCache>,
db: Data,
services: Services,
}
@@ -86,7 +86,7 @@ impl crate::Service for Service {
async fn memory_usage(&self, out: &mut (dyn Write + Send)) -> Result {
let (cache_len, ents) = {
let cache = self.stateinfo_cache.lock().expect("locked");
let cache = self.stateinfo_cache.lock();
let ents = cache.iter().map(at!(1)).flat_map(|vec| vec.iter()).fold(
HashMap::new(),
|mut ents, ssi| {
@@ -110,7 +110,7 @@ impl crate::Service for Service {
Ok(())
}
async fn clear_cache(&self) { self.stateinfo_cache.lock().expect("locked").clear(); }
async fn clear_cache(&self) { self.stateinfo_cache.lock().clear(); }
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}
@@ -123,7 +123,7 @@ pub async fn load_shortstatehash_info(
&self,
shortstatehash: ShortStateHash,
) -> Result<ShortStateInfoVec> {
if let Some(r) = self.stateinfo_cache.lock()?.get_mut(&shortstatehash) {
if let Some(r) = self.stateinfo_cache.lock().get_mut(&shortstatehash) {
return Ok(r.clone());
}
@@ -152,7 +152,7 @@ async fn cache_shortstatehash_info(
shortstatehash: ShortStateHash,
stack: ShortStateInfoVec,
) -> Result {
self.stateinfo_cache.lock()?.insert(shortstatehash, stack);
self.stateinfo_cache.lock().insert(shortstatehash, stack);
Ok(())
}
+19
View File
@@ -165,6 +165,25 @@ pub async fn create_hash_and_sign_event(
return Err!(Request(Forbidden("Event is not authorized.")));
}
// Check with the policy server
match self
.services
.event_handler
.ask_policy_server(&pdu, room_id)
.await
{
| Ok(true) => {},
| Ok(false) => {
return Err!(Request(Forbidden(debug_warn!(
"Policy server marked this event as spam"
))));
},
| Err(e) => {
// fail open
warn!("Failed to check event with policy server: {e}");
},
}
// Hash and sign
let mut pdu_json = utils::to_canonical_object(&pdu).map_err(|e| {
err!(Request(BadJson(warn!("Failed to convert PDU to canonical JSON: {e}"))))
+23 -27
View File
@@ -3,11 +3,13 @@ use std::{
collections::BTreeMap,
fmt::Write,
ops::Deref,
sync::{Arc, OnceLock, RwLock, Weak},
sync::{Arc, OnceLock, Weak},
};
use async_trait::async_trait;
use conduwuit::{Err, Result, Server, err, error::inspect_log, utils::string::SplitInfallible};
use conduwuit::{
Err, Result, Server, SyncRwLock, err, error::inspect_log, utils::string::SplitInfallible,
};
use database::Database;
/// Abstract interface for a Service
@@ -62,7 +64,7 @@ pub(crate) struct Dep<T: Service + Send + Sync> {
name: &'static str,
}
pub(crate) type Map = RwLock<MapType>;
pub(crate) type Map = SyncRwLock<MapType>;
pub(crate) type MapType = BTreeMap<MapKey, MapVal>;
pub(crate) type MapVal = (Weak<dyn Service>, Weak<dyn Any + Send + Sync>);
pub(crate) type MapKey = String;
@@ -143,15 +145,12 @@ pub(crate) fn get<T>(map: &Map, name: &str) -> Option<Arc<T>>
where
T: Any + Send + Sync + Sized,
{
map.read()
.expect("locked for reading")
.get(name)
.map(|(_, s)| {
s.upgrade().map(|s| {
s.downcast::<T>()
.expect("Service must be correctly downcast.")
})
})?
map.read().get(name).map(|(_, s)| {
s.upgrade().map(|s| {
s.downcast::<T>()
.expect("Service must be correctly downcast.")
})
})?
}
/// Reference a Service by name. Returns Err if the Service does not exist or
@@ -160,21 +159,18 @@ pub(crate) fn try_get<T>(map: &Map, name: &str) -> Result<Arc<T>>
where
T: Any + Send + Sync + Sized,
{
map.read()
.expect("locked for reading")
.get(name)
.map_or_else(
|| Err!("Service {name:?} does not exist or has not been built yet."),
|(_, s)| {
s.upgrade().map_or_else(
|| Err!("Service {name:?} no longer exists."),
|s| {
s.downcast::<T>()
.map_err(|_| err!("Service {name:?} must be correctly downcast."))
},
)
},
)
map.read().get(name).map_or_else(
|| Err!("Service {name:?} does not exist or has not been built yet."),
|(_, s)| {
s.upgrade().map_or_else(
|| Err!("Service {name:?} no longer exists."),
|s| {
s.downcast::<T>()
.map_err(|_| err!("Service {name:?} must be correctly downcast."))
},
)
},
)
}
/// Utility for service implementations; see Service::name() in the trait.
+7 -11
View File
@@ -1,10 +1,8 @@
use std::{
any::Any,
collections::BTreeMap,
sync::{Arc, RwLock},
};
use std::{any::Any, collections::BTreeMap, sync::Arc};
use conduwuit::{Result, Server, debug, debug_info, info, trace, utils::stream::IterStream};
use conduwuit::{
Result, Server, SyncRwLock, debug, debug_info, info, trace, utils::stream::IterStream,
};
use database::Database;
use futures::{Stream, StreamExt, TryStreamExt};
use tokio::sync::Mutex;
@@ -52,7 +50,7 @@ impl Services {
#[allow(clippy::cognitive_complexity)]
pub async fn build(server: Arc<Server>) -> Result<Arc<Self>> {
let db = Database::open(&server).await?;
let service: Arc<Map> = Arc::new(RwLock::new(BTreeMap::new()));
let service: Arc<Map> = Arc::new(SyncRwLock::new(BTreeMap::new()));
macro_rules! build {
($tyname:ty) => {{
let built = <$tyname>::build(Args {
@@ -193,7 +191,7 @@ impl Services {
fn interrupt(&self) {
debug!("Interrupting services...");
for (name, (service, ..)) in self.service.read().expect("locked for reading").iter() {
for (name, (service, ..)) in self.service.read().iter() {
if let Some(service) = service.upgrade() {
trace!("Interrupting {name}");
service.interrupt();
@@ -205,7 +203,6 @@ impl Services {
fn services(&self) -> impl Stream<Item = Arc<dyn Service>> + Send {
self.service
.read()
.expect("locked for reading")
.values()
.filter_map(|val| val.0.upgrade())
.collect::<Vec<_>>()
@@ -233,10 +230,9 @@ impl Services {
#[allow(clippy::needless_pass_by_value)]
fn add_service(map: &Arc<Map>, s: Arc<dyn Service>, a: Arc<dyn Any + Send + Sync>) {
let name = s.name();
let len = map.read().expect("locked for reading").len();
let len = map.read().len();
trace!("built service #{len}: {name:?}");
map.write()
.expect("locked for writing")
.insert(name.to_owned(), (Arc::downgrade(&s), Arc::downgrade(&a)));
}
+29 -32
View File
@@ -2,10 +2,10 @@ mod watch;
use std::{
collections::{BTreeMap, BTreeSet},
sync::{Arc, Mutex, Mutex as StdMutex},
sync::Arc,
};
use conduwuit::{Result, Server};
use conduwuit::{Result, Server, SyncMutex};
use database::Map;
use ruma::{
OwnedDeviceId, OwnedRoomId, OwnedUserId,
@@ -62,11 +62,11 @@ struct SnakeSyncCache {
extensions: v5::request::Extensions,
}
type DbConnections<K, V> = Mutex<BTreeMap<K, V>>;
type DbConnections<K, V> = SyncMutex<BTreeMap<K, V>>;
type DbConnectionsKey = (OwnedUserId, OwnedDeviceId, String);
type DbConnectionsVal = Arc<Mutex<SlidingSyncCache>>;
type DbConnectionsVal = Arc<SyncMutex<SlidingSyncCache>>;
type SnakeConnectionsKey = (OwnedUserId, OwnedDeviceId, Option<String>);
type SnakeConnectionsVal = Arc<Mutex<SnakeSyncCache>>;
type SnakeConnectionsVal = Arc<SyncMutex<SnakeSyncCache>>;
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
@@ -90,8 +90,8 @@ impl crate::Service for Service {
state_cache: args.depend::<rooms::state_cache::Service>("rooms::state_cache"),
typing: args.depend::<rooms::typing::Service>("rooms::typing"),
},
connections: StdMutex::new(BTreeMap::new()),
snake_connections: StdMutex::new(BTreeMap::new()),
connections: SyncMutex::new(BTreeMap::new()),
snake_connections: SyncMutex::new(BTreeMap::new()),
}))
}
@@ -100,22 +100,19 @@ impl crate::Service for Service {
impl Service {
pub fn snake_connection_cached(&self, key: &SnakeConnectionsKey) -> bool {
self.snake_connections
.lock()
.expect("locked")
.contains_key(key)
self.snake_connections.lock().contains_key(key)
}
pub fn forget_snake_sync_connection(&self, key: &SnakeConnectionsKey) {
self.snake_connections.lock().expect("locked").remove(key);
self.snake_connections.lock().remove(key);
}
pub fn remembered(&self, key: &DbConnectionsKey) -> bool {
self.connections.lock().expect("locked").contains_key(key)
self.connections.lock().contains_key(key)
}
pub fn forget_sync_request_connection(&self, key: &DbConnectionsKey) {
self.connections.lock().expect("locked").remove(key);
self.connections.lock().remove(key);
}
pub fn update_snake_sync_request_with_cache(
@@ -123,13 +120,13 @@ impl Service {
snake_key: &SnakeConnectionsKey,
request: &mut v5::Request,
) -> BTreeMap<String, BTreeMap<OwnedRoomId, u64>> {
let mut cache = self.snake_connections.lock().expect("locked");
let mut cache = self.snake_connections.lock();
let cached = Arc::clone(
cache
.entry(snake_key.clone())
.or_insert_with(|| Arc::new(Mutex::new(SnakeSyncCache::default()))),
.or_insert_with(|| Arc::new(SyncMutex::new(SnakeSyncCache::default()))),
);
let cached = &mut cached.lock().expect("locked");
let cached = &mut cached.lock();
drop(cache);
//v5::Request::try_from_http_request(req, path_args);
@@ -232,16 +229,16 @@ impl Service {
};
let key = into_db_key(key.0.clone(), key.1.clone(), conn_id);
let mut cache = self.connections.lock().expect("locked");
let mut cache = self.connections.lock();
let cached = Arc::clone(cache.entry(key).or_insert_with(|| {
Arc::new(Mutex::new(SlidingSyncCache {
Arc::new(SyncMutex::new(SlidingSyncCache {
lists: BTreeMap::new(),
subscriptions: BTreeMap::new(),
known_rooms: BTreeMap::new(),
extensions: ExtensionsConfig::default(),
}))
}));
let cached = &mut cached.lock().expect("locked");
let cached = &mut cached.lock();
drop(cache);
for (list_id, list) in &mut request.lists {
@@ -328,16 +325,16 @@ impl Service {
key: &DbConnectionsKey,
subscriptions: BTreeMap<OwnedRoomId, sync_events::v4::RoomSubscription>,
) {
let mut cache = self.connections.lock().expect("locked");
let mut cache = self.connections.lock();
let cached = Arc::clone(cache.entry(key.clone()).or_insert_with(|| {
Arc::new(Mutex::new(SlidingSyncCache {
Arc::new(SyncMutex::new(SlidingSyncCache {
lists: BTreeMap::new(),
subscriptions: BTreeMap::new(),
known_rooms: BTreeMap::new(),
extensions: ExtensionsConfig::default(),
}))
}));
let cached = &mut cached.lock().expect("locked");
let cached = &mut cached.lock();
drop(cache);
cached.subscriptions = subscriptions;
@@ -350,16 +347,16 @@ impl Service {
new_cached_rooms: BTreeSet<OwnedRoomId>,
globalsince: u64,
) {
let mut cache = self.connections.lock().expect("locked");
let mut cache = self.connections.lock();
let cached = Arc::clone(cache.entry(key.clone()).or_insert_with(|| {
Arc::new(Mutex::new(SlidingSyncCache {
Arc::new(SyncMutex::new(SlidingSyncCache {
lists: BTreeMap::new(),
subscriptions: BTreeMap::new(),
known_rooms: BTreeMap::new(),
extensions: ExtensionsConfig::default(),
}))
}));
let cached = &mut cached.lock().expect("locked");
let cached = &mut cached.lock();
drop(cache);
for (room_id, lastsince) in cached
@@ -386,13 +383,13 @@ impl Service {
globalsince: u64,
) {
assert!(key.2.is_some(), "Some(conn_id) required for this call");
let mut cache = self.snake_connections.lock().expect("locked");
let mut cache = self.snake_connections.lock();
let cached = Arc::clone(
cache
.entry(key.clone())
.or_insert_with(|| Arc::new(Mutex::new(SnakeSyncCache::default()))),
.or_insert_with(|| Arc::new(SyncMutex::new(SnakeSyncCache::default()))),
);
let cached = &mut cached.lock().expect("locked");
let cached = &mut cached.lock();
drop(cache);
for (room_id, lastsince) in cached
@@ -416,13 +413,13 @@ impl Service {
key: &SnakeConnectionsKey,
subscriptions: BTreeMap<OwnedRoomId, v5::request::RoomSubscription>,
) {
let mut cache = self.snake_connections.lock().expect("locked");
let mut cache = self.snake_connections.lock();
let cached = Arc::clone(
cache
.entry(key.clone())
.or_insert_with(|| Arc::new(Mutex::new(SnakeSyncCache::default()))),
.or_insert_with(|| Arc::new(SyncMutex::new(SnakeSyncCache::default()))),
);
let cached = &mut cached.lock().expect("locked");
let cached = &mut cached.lock();
drop(cache);
cached.subscriptions = subscriptions;
+32 -6
View File
@@ -1,10 +1,10 @@
use std::{
collections::{BTreeMap, HashSet},
sync::{Arc, RwLock},
sync::Arc,
};
use conduwuit::{
Err, Error, Result, err, error, implement, utils,
Err, Error, Result, SyncRwLock, err, error, implement, utils,
utils::{hash, string::EMPTY},
};
use database::{Deserialized, Json, Map};
@@ -19,7 +19,7 @@ use ruma::{
use crate::{Dep, config, globals, users};
pub struct Service {
userdevicesessionid_uiaarequest: RwLock<RequestMap>,
userdevicesessionid_uiaarequest: SyncRwLock<RequestMap>,
db: Data,
services: Services,
}
@@ -42,7 +42,7 @@ pub const SESSION_ID_LENGTH: usize = 32;
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
userdevicesessionid_uiaarequest: RwLock::new(RequestMap::new()),
userdevicesessionid_uiaarequest: SyncRwLock::new(RequestMap::new()),
db: Data {
userdevicesessionid_uiaainfo: args.db["userdevicesessionid_uiaainfo"].clone(),
},
@@ -177,6 +177,34 @@ pub async fn try_auth(
// Password was correct! Let's add it to `completed`
uiaainfo.completed.push(AuthType::Password);
},
| AuthData::ReCaptcha(r) => {
if self.services.config.recaptcha_private_site_key.is_none() {
return Err!(Request(Forbidden("ReCaptcha is not configured.")));
}
match recaptcha_verify::verify(
self.services
.config
.recaptcha_private_site_key
.as_ref()
.unwrap(),
r.response.as_str(),
None,
)
.await
{
| Ok(()) => {
uiaainfo.completed.push(AuthType::ReCaptcha);
},
| Err(e) => {
error!("ReCaptcha verification failed: {e:?}");
uiaainfo.auth_error = Some(ruma::api::client::error::StandardErrorBody {
kind: ErrorKind::forbidden(),
message: "ReCaptcha verification failed.".to_owned(),
});
return Ok((false, uiaainfo));
},
}
},
| AuthData::RegistrationToken(t) => {
let tokens = self.read_tokens().await?;
if tokens.contains(t.token.trim()) {
@@ -240,7 +268,6 @@ fn set_uiaa_request(
let key = (user_id.to_owned(), device_id.to_owned(), session.to_owned());
self.userdevicesessionid_uiaarequest
.write()
.expect("locked for writing")
.insert(key, request.to_owned());
}
@@ -259,7 +286,6 @@ pub fn get_uiaa_request(
self.userdevicesessionid_uiaarequest
.read()
.expect("locked for reading")
.get(&key)
.cloned()
}
+1 -1
View File
@@ -7,7 +7,7 @@
<p>To get started, you can:</p>
<ul>
<li>Read the <a href="https://continuwuity.org/introduction">documentation</a></li>
<li>Join the <a href="https://matrix.to/#/#continuwuity:continuwuity.org">Continuwuity Matrix room</a> or <a href="https://matrix.to/#/#space:continuwuity.org">space</a></li>
<li>Join the <a href="https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org">Continuwuity Matrix room</a> or <a href="https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org">space</a></li>
<li>Log in with a <a href="https://matrix.org/ecosystem/clients/">client</a></li>
<li>Ensure <a href="https://federationtester.matrix.org/#{{ server_name }}">federation</a> works</li>
</ul>
+1 -1
View File
@@ -48,7 +48,7 @@ vector () {
VECTOR_OPTS=$@
element "$TOOLCHAIN" $VECTOR_OPTS --no-default-features
element "$TOOLCHAIN" $VECTOR_OPTS --features=default
element "$TOOLCHAIN" $VECTOR_OPTS --all-features
element "$TOOLCHAIN" $VECTOR_OPTS --features full
}
matrix () {
+5
View File
@@ -605,3 +605,8 @@ ul#searchresults span.teaser em {
margin-inline-start: -14px;
width: 14px;
}
/* HACK: Stop the keyboard shortcuts from adding a giant space at the top */
#mdbook-help-container {
display: none;
}