Compare commits

..

134 Commits

Author SHA1 Message Date
Jason Volk b8b93a2e86 Bump 0.4.1
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk 29d69b7688 update complement test results
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
strawberry bd07fb61e0 add hot_reload.md to SUMMARY.md
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry a41a60ef07 media: dont ignore requested filename on /download for Content-Disposition
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
Jason Volk ec7a9ab726 add toolchain and build/check shortcut to smoketest
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk 25f598ce6c enable http2 feature for reqwest.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk dbcb3be0ab fix duplicate output; increase wait in smoketest.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk a537462d51 replace num_cpus dependency with available_parallelism()
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk d2aef071bc add possibly referenced rocksdb symbol to export list.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk d68b11e8ff fix rustflags for release-max-perf
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk 9cf5b0926e fix regressed jemalloc stats feature
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk ff0b57c89c remove unused jemalloc dep in main module.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk b94045a468 dissolve key_value/*
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk 3122648767 split ruma_wrapper from_request() related.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk 3f5349ad76 simplify RumaHandler for Router building.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk 27dcf213f1 tweak error strings.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk a1b526b3b7 tweak log levels
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk dc614e11d6 check invite target is our server.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk c5569b4c6e dedup acl checks
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
Jason Volk 71a1285c7b hoist receipt ACL check
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-27 18:16:23 -04:00
strawberry abdda6cf32 check invited user's server against ACLs on /invite
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 4d21f9d962 use ok_or_else instead of ok_or for function calls in server_server.rs
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 1013fe5a42 check for membership join state at /send_join
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry f31b7b9420 ignore inbound EDUs for users that dont belong to origin server
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry e5e358cc68 compare X-Matrix origin + body origin and check PDU/EDU length at /send txn
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 50bc7cc005 check state_key matches sender user at /send_leave
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 445015e9ea check user ID server against ACLs at /send_leave
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 7a38c12e5d check for member event type at /send_leave
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 2a77951152 check for membership leave state at /send_leave
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 0256c27363 check if we know about room at /make_leave
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 826edc0a3a check state_key matches sender user at /send_join
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry a5043a38e1 only allow membership event types at /send_join
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry bfd471a863 check user ID server against ACLs for /send_join
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 3981e77ec6 check user ID server against ACLs for /make_join
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 81bf4b7150 check user ID server against ACLs for /make_leave
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry b8ec763a7c ignore read receipts from ACL'd servers and users not joined
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 003d4edbfa debug log receiving typing EDUs for users not in room
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 4f0006d18a ignore typing EDUs from ACL'd user's servers
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry b822e3a94c listen on IPv6 localhost by default
this is dual-stack by default on linux, resolves
issues with nginx using `localhost` and randomly
choosing between 127.0.0.1 and [::1], causing
intermittent upstream issues

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 68fffe8e96 check room ACLs on sender user's server for incoming PDUs
`handle_incoming_pdu`

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
strawberry 7328ed7509 rename misleading sender_servername to origin
this is the X-Matrix origin/server, NOT the `"sender"``
user's server name.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-27 18:16:23 -04:00
Benjamin Lee 6ccf578437 bump rocksdb input
Nedded to pull in [1], which is rared for dynamic rocksdb builds with
liburing.

[1]: https://github.com/girlbossceo/rocksdb/commit/c8a1450231e9c608edf535538dbe8ca1a8d2f3bc
2024-05-27 04:54:57 -04:00
Benjamin Lee 8a1848a814 Revert "nix: default output to scopeHostStatic instead of scopeHost"
This reverts commit a37b2b9e64.

Dynamic builds are working again, so we'd prefer having that be the
default output for consistency with nixpkgs.
2024-05-27 04:54:57 -04:00
Benjamin Lee b4cd8e9140 fix dynamic builds with liburing
The original implementation of this was really weird, so I restructed it
a lot while debugging, and am just gonna leave the restructured version.

Root cause of the segfault seems to be that upstream nixpkgs liburing
derivation is generating both static and dynamic libraries, causing
rocksdb to statically link liburing in a dynamic build, pulling in some
allocator stuff at the same time. I created a PR[1] to fix this upstream,
but it probably won't be available on nixos-unstable for quite a while,
so we can also patch it locally.

[1]: https://github.com/NixOS/nixpkgs/pull/314945
2024-05-27 04:54:57 -04:00
Benjamin Lee a08f90b161 add a smoke-test to CI for the nix 'default' output
I talked to somebody yesterday in #conduwuit:puppygock.gay that was using
this output in their system config. The dynamically-linked jemalloc build
is quite fragile, and is not tested by anything else in CI. We want to
make sure we don't break it again in the future.
2024-05-27 04:54:57 -04:00
Benjamin Lee 207979579c fix dynamically linked devshell
This failed to inherit the fix from bec507d739
because the crane package's buildInputs become propagatedBuildInputs in
a static stdenv, but become normal buildInputs in a dynamic stdenv. Since
we were only pulling propagatedBuildInputs into the devshell, dynamically
linked devshells did not include the rust-jemalloc-sys package. This
causes tikv-jemalloc-sys to build it's own static jemalloc package, and
we end up loading libc before jemalloc at runtime.
2024-05-27 04:54:57 -04:00
Benjamin Lee 68b96026ec unmark dynamically-linked jemalloc builds as broken
It turns out that this was actually fixed by
bec507d739 and
857ac42aac, but we didn't identify it at the
time. Notably, the `dynamic` devshell is still broken.
2024-05-27 04:54:57 -04:00
strawberry 30beb20230 conditionally static link rust-rocksdb-uwu by hot reload cfg
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 19e7779693 update complement test results
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 6269822613 actually fix all let_underscore_must_use lints
CI caught some more

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 0877ee6191 allow let underscore use lint for rocksdb create cf for now
the workaround needs to be extended to rocksdb caches, but
i dont know that part of code

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry a37b2b9e64 nix: default output to scopeHostStatic instead of scopeHost
defaults to static builds instead of dynamically linked builds

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 29fe960efa bump hyper-util and libz-sys
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 6bf2e73830 ci: run cache dependencies in ci.yml as well
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 630760b5da bump rocksdb to v9.2.1
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 61e7f1e614 remove rpath = true from dev profile as the rustflags have it
needed for hot reloading but rpath being true by default
causes linker errors on lld because of the sad rpath bug

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 7ebed7aa3e clarify disable-room message after banning room
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry ad3eeaf4c1 delete audit.toml
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 5215fbe695 drop redaction calculated hash log to debug_info
this is normal redactions. no valid reason this needs to be
warn as it just causes confusion.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry dc9fe657d5 fix guest accounts being logged still
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 1c7c5bc09c feat: add /_conduwuit/local_user_count endpoint
only enabled if federation is enabled

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 32161801ed use/enable let_underscore_must_use lint
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 71bdcb958a fix: dont drop remote federation error on 4xx responses
for a very long time, if a remote server responded to us with
a valid but unsuccessful (HTTP 4xx) response and the caller was the
`send_federation_request` function, we may find ourselves
with a warning message only containing the destination's
server name which was very unhelpful. the true error was
buried away in trace logs. this would primarily be noticed
with server key fetch requests from us.

conduit has been throwing away the ruma request error: https://gitlab.com/famedly/conduit/-/blame/next/src/utils/error.rs#L62

before: 2024-05-23T04:45:02.930224Z  WARN router:{path=/_matrix/client/v3/publicRooms}:handle: conduit_api::client_server::directory: Failed to return our /publicRooms: matrix.org
after: 2024-05-23T05:05:02.435272Z  WARN router:{path=/_matrix/client/v3/publicRooms}:handle: conduit_api::client_server::directory: Failed to return our /publicRooms: matrix.org: [401 / M_UNAUTHORIZED] Failed to find any key to satisfy: _FetchKeyRequest(server_name='your.server.name', minimum_valid_until_ts=1716440702337, key_ids=['ed25519:RQB3XPQX'])

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry d3db0ad4e2 renovate: label PRs as dependencies and github_actions
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry e098448b9d init a few vecs in event_handler using with_capacity
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry d49507bc21 media: decomplexify get_all_media_keys for deleting all MXC URIs
wow this was terrible, early strawberry code

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry cb73ae3732 add registration token validity endpoint as per matrix 1.2
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 06bec40591 fix: add missing fetch_required_signing_keys for remote send_leave
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 9a7ba94ccf explicity define unstable support for sliding sync
this matrix-react-sdk PR (and the cited sliding sync MSC)
says that they will intend on checking sliding sync support
from this unstable feature flag at /versions until the CORS
header stuff is specced

https://github.com/matrix-org/matrix-react-sdk/pull/12498

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 2990c30ac9 nix: bump rocksdb input
• Updated input 'rocksdb':
    'github:girlbossceo/rocksdb/db6df0b185774778457dabfcbd822cb81760cade' (2024-05-03)
  → 'github:girlbossceo/rocksdb/be68b3c95ccd225f3121ba33a67cfaf3c3596afc' (2024-05-23)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry d9c575d96f bump deps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry c32406aa0e replace deprecated config option for complement
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 03d12cb44e update docs a tad
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry bef7dbd1cb finally error on complement diff mismatch, remove jemalloc builds from
CI

jemalloc is now a default feature

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 08577873b4 update complement test results
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry a3931b0f1f nix: bump flake.lock
• Updated input 'crane':
    'github:ipetkov/crane/27025ab71bdca30e7ed0a16c88fd74c5970fc7f5' (2024-05-09)
  → 'github:ipetkov/crane/7443df1c478947bf96a2e699209f53b2db26209d' (2024-05-19)
• Updated input 'fenix':
    'github:nix-community/fenix/297c756ba6249d483c1dafe42378560458842173' (2024-05-10)
  → 'github:nix-community/fenix/063d7e5fac454edd35b7e2cedb6ca9fb1410c79b' (2024-05-21)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/5bf2f85c8054d80424899fa581db1b192230efb5' (2024-05-09)
  → 'github:rust-lang/rust-analyzer/21ec8f523812b88418b2bfc64240c62b3dd967bd' (2024-05-19)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/f1010e0469db743d14519a1efd37e23f8513d714' (2024-05-09)
  → 'github:NixOS/nixpkgs/3eaeaeb6b1e08a016380c279f8846e0bd8808916' (2024-05-21)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry ba2f22b5d3 nix: remove jemalloc (now default) targets, add jq input for default
jq input change was from https://gitlab.computer.surgery/matrix/grapevine-fork/-/commit/17eb3545906d21f2ed18f0f0f917a4638f12ef6c
to prevent unnecessary bindgen rebuilds

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry 0914aaa1b6 skip a few known flaky/unreliable complement tests
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-25 22:28:43 -04:00
strawberry f3427afc7f nix: use new public keys for binary caches due to attic issues
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-24 18:13:02 -04:00
morguldir 9aa372d83b nix: Allow excluding features, allow disabling release_max_log_level 2024-05-24 15:12:23 -04:00
morguldir 5893901a75 Explicitly include snappy as well
Not sure what changed that we need this

Signed-off-by: morguldir <morguldir@protonmail.com>
2024-05-24 12:46:15 -04:00
morguldir 8ba9b33a95 Make sure we use the liburing of the platform we're building for
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-05-24 12:46:15 -04:00
morguldir 70047ff26d Make rocksdb include liburing, and tell gcc the path during the build
With: strings /nix/store/9skicdac6xs4yww1nd3h7m6xydv4hxlj-rocksdb-9.1.1/lib/librocksdb.so.9|rg io_uring|wc -l
112
With: strings result/bin/conduit |rg io_uring|wc -l
5

Without: strings static-x86_64-unknown-linux-musl-jemalloc |rg io_uring | wc -l
0

Signed-off-by: morguldir <morguldir@protonmail.com>
2024-05-24 12:46:15 -04:00
Benjamin Lee 1d57e14dc0 set C/LDFLAGS for complement dependencies directly
Previously we were relying on NIX_CFLAGS_COMPILE, but this is not being
set in static devshells. A cleaner solution for complement would likely
be to build the tests in their own nix derivation instead of building
them in the devshell, but this change unblocks CI for now.
2024-05-24 10:53:47 -04:00
Benjamin Lee 5d81203277 use a statically-linked binary for complement
Dynamically-linked jemalloc is broken.
2024-05-24 10:53:47 -04:00
Benjamin Lee ad39a34c16 add a dynamically-linked devshell
This is broken on linux, but can be used by darwin users for development,
since static/jemalloc/darwin is broken.
2024-05-24 10:53:47 -04:00
Benjamin Lee a007338b34 mark dynamic jemalloc builds as broken on linux 2024-05-24 10:53:47 -04:00
Benjamin Lee 3d1507e6dd mark static rocksdb broken on darwin 2024-05-24 10:53:47 -04:00
Benjamin Lee 4cb7c0b982 don't use prefixed jemalloc with rocksdb
This is causing build failures on Mac:

> In file included from /tmp/nix-build-rocksdb-static-aarch64-apple-darwin-9.1.1.drv-0/source/memory/memory_allocator.cc:8:
> In file included from /tmp/nix-build-rocksdb-static-aarch64-apple-darwin-9.1.1.drv-0/source/memory/jemalloc_nodump_allocator.h:11:
> /tmp/nix-build-rocksdb-static-aarch64-apple-darwin-9.1.1.drv-0/source/port/jemalloc_helper.h:63:36: warning: unknown attribute '_rjem_malloc' ignored [-Wunknown-attributes]
> mallocx(size_t, int) JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1)
>                                    ^~~~~~
> /nix/store/3bix0kzy670dyhhizri3dwb1qfj3sdpa-jemalloc-static-aarch64-apple-darwin-5.3.0/include/jemalloc/jemalloc.h:412:18: note: expanded from macro 'malloc'
> #  define malloc je_malloc
>                  ^~~~~~~~~
> /nix/store/3bix0kzy670dyhhizri3dwb1qfj3sdpa-jemalloc-static-aarch64-apple-darwin-5.3.0/include/jemalloc/jemalloc.h:75:21: note: expanded from macro 'je_malloc'
> #  define je_malloc _rjem_malloc
>                     ^~~~~~~~~~~~
> /nix/store/3bix0kzy670dyhhizri3dwb1qfj3sdpa-jemalloc-static-aarch64-apple-darwin-5.3.0/include/jemalloc/jemalloc.h:183:43: note: expanded from macro 'JEMALLOC_ATTR'
> #  define JEMALLOC_ATTR(s) __attribute__((s))

Full build log at <https://girlboss.ceo/~strawberry/pb/ygJ3>. This is
likely fixable with patches to rocksdb, but not worth it since darwin is
only a dev platform.
2024-05-24 10:53:47 -04:00
Benjamin Lee 0c34cf95ce set show-trace for nix in CI 2024-05-24 10:53:47 -04:00
Benjamin Lee 17cc02ff99 add a 'no-features' devshell for local testing 2024-05-24 10:53:47 -04:00
Benjamin Lee c0f8253fc5 enable all-features in nix for CI builds
CI is running `cargo build --all-features`, so we should be passing all
the features to nix as well.

The only thing this currently affects is the jemalloc_prof feature, but if
we add any non-default features that affect nix in the future they should
also be handled correctly now.
2024-05-24 10:53:47 -04:00
Benjamin Lee 0fd0a5d73c switch default devshell to static linking
Dynamically-linked jemalloc doesn't work due to link-order issues, and we
want CI to be testing a static binary anyway since that's what we're
publishing in releases.
2024-05-24 10:53:47 -04:00
Benjamin Lee 4e6fc2f2df factor devshell out into a helper function
We're planning to add a second devshell with `all-features` for CI.
2024-05-24 10:53:47 -04:00
Benjamin Lee a6742ce8a7 remove liburing from devshell
This doesn't seem to be necessary to build, and the derivation is broken
in pkgsStatic.
2024-05-24 10:53:47 -04:00
Benjamin Lee 188dea13e0 do default-feature unification in nix
Some of the features affect nix dependencies, so we need to have a
full feature list available when constructing the nix derivation. This
incidentally fixes the bug where we weren't enabling jemalloc on rocksdb
in CI/devshells, because jemalloc is now a default feature. It does not
fix the more general class of that issue, where CI is performing an
`--all-features` build in a nix devshell built for default-features.

I am now passing `--no-default-features` to cargo, and having it use our
unified feature list rather than duplicating the unification inside cargo.
2024-05-24 10:53:47 -04:00
Benjamin Lee a7fe434086 only link to one jemalloc build
Without setting JEMALLOC_OVERRIDE, we end up linking to two different
jemalloc builds. Once dynamically, as a transitive dependency through
rocksdb, and a second time to the static jemalloc that tikv-jemalloc-sys
builds.
2024-05-24 10:53:47 -04:00
renovate[bot] eb8dd9cb44 chore(deps): update aquasecurity/trivy-action action to v0.21.0 2024-05-23 01:30:36 -04:00
strawberry 474d50d10c bump conduwuit version to 0.4.0
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-21 20:22:17 -04:00
Jason Volk 2e732c711c docs: Update docs for hot-reloading.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-21 20:22:17 -04:00
strawberry 981ec51ec0 docs: add initial docs for hot reload
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-21 20:22:17 -04:00
Jason Volk 2dd5cf8c68 move clap; fix version
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-21 20:22:17 -04:00
Jason Volk 74832bdc47 fix smoke from builds produced by --all-features
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-21 20:22:17 -04:00
Jason Volk fdc9a9a1b8 add cargo smoketest
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-21 20:22:17 -04:00
Jason Volk 1f3a9a40e5 lint clippy::collapsible_match (nightly)
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-21 20:22:17 -04:00
Jason Volk 362649ff87 rename src/bin to src/main
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-21 20:22:17 -04:00
strawberry 4aeec78ab4 debian: remove old symlink on postrm
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-21 20:22:17 -04:00
strawberry 9bfa89a555 adjust debian metadata, set crane workspace name
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-21 20:22:17 -04:00
Jason Volk 6c1434c165 Hot-Reloading Refactor
Signed-off-by: Jason Volk <jason@zemos.net>
2024-05-21 20:22:17 -04:00
slonkazoid ae1a4fd283 add modification time fallback if birth time is not supported on this platform 2024-05-21 16:58:30 -04:00
Benjamin Lee 9eb0784f6f don't return extra member count or e2ee device updates from sync
Previously, we were returning redundant member count updates or encrypted
device updates from the /sync endpoint in some cases. The extra member
count updates are spec-compliant, but unnecessary, while the extra
encrypted device updates violate the spec.

The refactor necessary to fix this bug is also necessary to support
filtering on state events in sync.

Details:

Joined room incremental sync needs to examine state events for four
purposes:

 1. determining whether we need to return an update to room member counts
 2. determining the set of left/joined devices for encrypted rooms
    (returned in `device_lists`)
 3. returning state events to the client (in `rooms.joined.*.state`)
 4. tracking which member events we have sent to the client, so they can
    be omitted on future requests when lazy-loading is enabled.

The state events that we need to examine for the first two cases is member
events in the delta between `since` and the end of `timeline`. For the
second two cases, we need the delta between `since` and the start of
`timeline`, plus contextual member events for any senders that occur in
`timeline`. The second list is subject to filtering, while the first is
not.

Before this change, we were using the same set of state events that we are
returning to the client (cases 3/4) to do the analysis for cases 1/2.
In a compliant implementation, this would result in us missing some
relevant member events in 1/2 in addition to seeing redundant member
events. In current conduwuit this is not the case because the set of
events that we return to the client is always a superset of the set that
is needed for cases 1/2. This is because we don't support filtering, and
we have an existing bug[1] where we are returning the delta between
`since` and the end of `timeline` rather than the start.

[1]: https://github.com/girlbossceo/conduwuit/issues/361

Fixing this is necessary to implement filtering because otherwise
we would start missing some member events for member count or encrypted
device updates if the relevant member events are rejected by the filter.
This would be much worse than our current behavior.
2024-05-20 20:55:56 -04:00
Benjamin Lee 8bffcfe82b remove sync response cache
This cache can serve invalid responses, and has an extremely low hit
rate.

It serves invalid responses because because it's only keyed off
the `since` parameter, but many of the other request parameters also
affect the response or it's side effects. This will become worse once we
implement filtering, because there will be a wider space of parameters
with different responses. This problem is fixable, but not worth it
because of the low hit rate.

The low hit rate is because normal clients will always issue the next
sync request with `since` set to the `prev_batch` value of the previous
response. The only time we expect to see multiple requests with the same
`since` is when the response is empty, but we don't cache empty
responses.

This was confirmed experimentally by logging cache hits and misses over
15 minutes with a wide variety of clients. This test was run on
matrix.computer.surgery, which has only a few active users, but a
large volume of sync traffic from many rooms. Over the test period, we
had 3 hits and 5309 misses. All hits occurred in the first minute, so I
suspect that they had something to do with client recovery from an
offline state. The clients that were connected during the test are:

 - element web
 - schildichat web
 - iamb
 - gomuks
 - nheko
 - fractal
 - fluffychat web
 - fluffychat android
 - cinny web
 - element android
 - element X android

Fixes: #336
2024-05-17 18:13:11 -04:00
strawberry 6ef4781050 downgrade zlib/libz-sys to 1.1.16 as it breaks nix
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-17 03:42:25 -04:00
strawberry 302592f219 bump conduwuit version to 0.3.4
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-17 03:17:27 -04:00
Benjamin Lee 7cd72d8447 bump lockfile 2024-05-17 03:08:56 -04:00
renovate[bot] 4389e08686 chore(deps): update cachix/install-nix-action action to v27 2024-05-15 14:39:21 -04:00
strawberry 91064fe873 fix up systemd unit file, remove chown on config file for debian
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 004354353a docker-compose: slight cleanups, correct database paths, fix branding
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry c64a507691 correct default database path to /var/lib/conduwuit
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 81d2078cdb debian: dont start service immediately, add postinst instructions
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry f5864afb52 remove namespace check on username login, code simplification on login route
the namespace check on username login is unnecessary, hashes aren't ever
going to match, and axum auth handles this kind of stuff already

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 9a63e7cc9b flip order of complement diff checking, update test results
we now pass all Content-Disposition checks/tests

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 296d7c58ee nix: bump complement input for conduwuit support
https://github.com/matrix-org/complement/pull/723

• Updated input 'complement':
    'github:matrix-org/complement/370a014dca0f720614e0c8f68b9a3e66ecf7f516' (2024-05-02)
  → 'github:matrix-org/complement/8587fb3cbe746754b2c883ff6c818ca4d987d0a5' (2024-05-14)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry a8446f910a debian: fix config permissions, delete debconf support
debconf support needs to be done in a way that does not duplicate
the config file like upstream does.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry a063a6d088 debian: make the docs actually coherent and understandable, and update it
the language here is very poor and i'm not sure why it was written like this.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 5069c88f77 ci: correct paths for debian package creation, use conduwuit
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 53974320e5 debian: create system account verbosely
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 1c6ef66e3e fix gitlab ci
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry ffb63c9c8d ci: regex out the cargo/rustc target for cargo-deb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry de6b296eb5 ci: use verbose for mv operations
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 4c11c9f048 ci: use target-specific dirs for cargo-deb, fix cargo-deb paths
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 6074298426 ci: allow build job to be ran for all events except for draft PRs
this allows build to be ran for workflow_dispatch

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry 6e9f68bf81 chore: update complement test results
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
strawberry edd67a102a ci(debian): add missing --target= for arm64 debs, add --verbose
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-05-15 14:31:35 -04:00
252 changed files with 12314 additions and 10519 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
use flake
use flake ".#${DIRENV_DEVSHELL:-default}"
PATH_add bin
+31 -19
View File
@@ -35,6 +35,11 @@ env:
# Custom nix binary cache if fork is being used
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
# Use the all-features devshell instead of default, to ensure that features
# match between nix and cargo
DIRENV_DEVSHELL: all-features
# Get error output from nix that we can actually use
NIX_CONFIG: show-trace = true
permissions:
packages: write
@@ -76,7 +81,7 @@ jobs:
run: |
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg= conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=
EOF
- name: Use alternative Nix binary caches if specified
@@ -92,7 +97,11 @@ jobs:
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
direnv allow
nix develop --command true
nix develop .#all-features --command true
- name: Cache CI dependencies
run: |
bin/nix-build-and-cache ci
- name: Run CI tests
run: |
@@ -131,10 +140,8 @@ jobs:
if-no-files-found: error
- name: Diff Complement results with checked-in repo results
# TODO: figure out why our complement results are not 100% consistent so we don't need to allow failures
continue-on-error: true
run: |
diff -u --color=always complement_test_results.jsonl tests/test_results/complement/test_results.jsonl > >(tee -a complement_test_output.log)
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_test_output.log)
- name: Add Complement diff result to Job Summary
run: |
@@ -158,14 +165,12 @@ jobs:
name: Build
runs-on: ubuntu-latest
needs: tests
if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false)
if: github.event.pull_request.draft != true
strategy:
matrix:
include:
- target: aarch64-unknown-linux-musl
- target: aarch64-unknown-linux-musl-jemalloc
- target: x86_64-unknown-linux-musl
- target: x86_64-unknown-linux-musl-jemalloc
steps:
- name: Sync repository
uses: actions/checkout@v4
@@ -185,8 +190,8 @@ jobs:
- name: Apply Nix binary cache configuration
run: |
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg= conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=
EOF
- name: Use alternative Nix binary caches if specified
@@ -202,15 +207,21 @@ jobs:
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
direnv allow
nix develop --command true
nix develop .#all-features --command true
- name: Build static ${{ matrix.target }}
run: |
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
bin/nix-build-and-cache just .#static-${{ matrix.target }}
mkdir -p target/release
cp -v -f result/bin/conduit target/release/
direnv exec . cargo deb --no-build --no-strip --output target/debian/${{ matrix.target }}.deb
mv target/release/conduit static-${{ matrix.target }}
mkdir -v -p target/release/
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
cp -v -f result/bin/conduit target/release/conduwuit
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
# -p conduit is the main crate name
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}.deb
mv -v target/release/conduwuit static-${{ matrix.target }}
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
- name: Upload static-${{ matrix.target }}
uses: actions/upload-artifact@v4
@@ -223,8 +234,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: deb-${{ matrix.target }}
path: target/debian/${{ matrix.target }}.deb
path: ${{ matrix.target }}.deb
if-no-files-found: error
compression-level: 0
- name: Build OCI image ${{ matrix.target }}
run: |
@@ -243,7 +255,7 @@ jobs:
name: Docker publish
runs-on: ubuntu-latest
needs: build
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false)) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '')
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '')
env:
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
@@ -289,8 +301,8 @@ jobs:
- name: Move OCI images into position
run: |
mv oci-image-x86_64-*-jemalloc/*.tar.gz oci-image-amd64.tar.gz
mv oci-image-aarch64-*-jemalloc/*.tar.gz oci-image-arm64v8.tar.gz
mv -v oci-image-x86_64-*/*.tar.gz oci-image-amd64.tar.gz
mv -v oci-image-aarch64-*/*.tar.gz oci-image-arm64v8.tar.gz
- name: Load and push amd64 image
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
+3 -3
View File
@@ -49,7 +49,7 @@ jobs:
uses: actions/configure-pages@v5
- name: Install Nix (with flakes and nix-command enabled)
uses: cachix/install-nix-action@v26
uses: cachix/install-nix-action@v27
with:
nix_path: nixpkgs=channel:nixos-unstable
@@ -61,9 +61,9 @@ jobs:
extra-substituters = https://crane.cachix.org
extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=
extra-substituters = https://attic.kennel.juneis.dog/conduit
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
extra-substituters = https://attic.kennel.juneis.dog/conduwuit
extra-trusted-public-keys = conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
extra-trusted-public-keys = conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=
- name: Add alternative Nix binary caches if specified
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
+2 -2
View File
@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v4
- name: Run Trivy code and vulnerability scanner on repo
uses: aquasecurity/trivy-action@0.20.0
uses: aquasecurity/trivy-action@0.21.0
with:
scan-type: repo
format: sarif
@@ -34,7 +34,7 @@ jobs:
severity: CRITICAL,HIGH,MEDIUM,LOW
- name: Run Trivy code and vulnerability scanner on filesystem
uses: aquasecurity/trivy-action@0.20.0
uses: aquasecurity/trivy-action@0.21.0
with:
scan-type: fs
format: sarif
+3 -6
View File
@@ -26,10 +26,10 @@ before_script:
# Add conduwuit binary cache
- if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduwuit" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduit" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=" >> /etc/nix/nix.conf; fi
# Add alternate binary cache
- if command -v nix > /dev/null && [ -n "$ATTIC_ENDPOINT" ]; then echo "extra-substituters = $ATTIC_ENDPOINT" >> /etc/nix/nix.conf; fi
@@ -53,9 +53,6 @@ before_script:
# Allow .envrc
- if command -v nix > /dev/null; then direnv allow; fi
# Cache attic client
- if command -v nix > /dev/null; then ./bin/nix-build-and-cache --inputs-from . attic; fi
# Set CARGO_HOME to a cacheable path
- export CARGO_HOME="$(git rev-parse --show-toplevel)/.gitlab-ci.d/cargo"
@@ -86,7 +83,7 @@ ci:
artifacts:
stage: artifacts
image: nixos/nix:2.22.0
image: nixos/nix:2.22.1
script:
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl
- cp result/bin/conduit x86_64-unknown-linux-musl
Generated
+340 -330
View File
File diff suppressed because it is too large Load Diff
+470 -347
View File
@@ -1,112 +1,94 @@
[package]
# TODO: when can we rename to conduwuit?
name = "conduit"
#cargo-features = ["profile-rustflags"]
[workspace]
resolver = "2"
members = ["src/*"]
default-members = ["src/*"]
[workspace.package]
description = "a very cool fork of Conduit, a Matrix homeserver written in Rust"
license = "Apache-2.0"
authors = [
"strawberry <strawberry@puppygock.gay>",
"timokoesters <timo@koesters.xyz>",
]
version = "0.4.1"
edition = "2021"
# See also `rust-toolchain.toml`
rust-version = "1.77.0"
homepage = "https://conduwuit.puppyirl.gay/"
repository = "https://github.com/girlbossceo/conduwuit"
readme = "README.md"
version = "0.3.3"
edition = "2021"
# See also `rust-toolchain.toml`
rust-version = "1.77.0"
[workspace.metadata.crane]
name = "conduit"
[dependencies]
console-subscriber = { version = "0.2", optional = true }
[workspace.dependencies.sanitize-filename]
version = "0.5.0"
infer = { version = "0.15", default-features = false }
[workspace.dependencies.infer]
version = "0.15"
default-features = false
# for hot lib reload
hot-lib-reloader = { version = "^0.7", optional = true }
[workspace.dependencies.jsonwebtoken]
version = "9.3.0"
# Used for secure identifiers
rand = "0.8.5"
# Used for conduit::Error type
thiserror = "1.0.60"
# Used to encode server public key
base64 = "0.22.1"
# Used when hashing the state
ring = "0.17.8"
# Used to find matching events for appservices
regex = "1.10.4"
# Used to load forbidden room/user regex from config
serde_regex = "1.1.0"
# Used to make working with iterators easier, was already a transitive depdendency
itertools = "0.12.1"
# jwt jsonwebtokens
jsonwebtoken = "9.3.0"
# Used for ruma wrapper
serde_html_form = "0.2.6"
[workspace.dependencies.base64]
version = "0.22.1"
# used for TURN server authentication
hmac = "0.12.1"
sha-1 = "0.10.1"
[workspace.dependencies.hmac]
version = "0.12.1"
[workspace.dependencies.sha-1]
version = "0.10.1"
# used for checking if an IP is in specific subnets / CIDR ranges easier
ipaddress = "0.1.3"
[workspace.dependencies.ipaddress]
version = "0.1.3"
# to get the client IP address of requests
#axum-client-ip = "0.4.2"
[workspace.dependencies.rand]
version = "0.8.5"
# to parse user-friendly time durations in admin commands
cyborgtime = "2.1.1"
# all the web/HTTP dependencies
# Used for the http request / response body type for Ruma endpoints used with reqwest
bytes = "1.6.0"
http = "1.1.0"
http-body-util = "0.1.1"
[workspace.dependencies.bytes]
version = "1.6.0"
# used to replace the channels of the tokio runtime
loole = "0.3.0"
[workspace.dependencies.http-body-util]
version = "0.1.1"
# Validating urls in config, was already a transitive dependency
url = { version = "2.5.0", features = ["serde"] }
[workspace.dependencies.http]
version = "1.1.0"
async-trait = "0.1.80"
[workspace.dependencies.regex]
version = "1.10.4"
lru-cache = "0.1.2"
sanitize-filename = "0.5.0"
# standard date and time tools
[dependencies.chrono]
version = "0.4.38"
features = ["alloc"]
default-features = false
# Web framework
[dependencies.axum]
[workspace.dependencies.axum]
version = "0.7.5"
default-features = false
features = ["form", "http1", "http2", "json", "matched-path"]
features = [
"form",
"http1",
"http2",
"json",
"matched-path",
"tokio",
]
[dependencies.axum-extra]
[workspace.dependencies.axum-extra]
version = "0.9.3"
default-features = false
features = ["typed-header"]
[dependencies.axum-server]
[workspace.dependencies.axum-server]
version = "0.6.0"
features = ["tls-rustls"]
[dependencies.tower]
[workspace.dependencies.tower]
version = "0.4.13"
features = ["util"]
[dependencies.tower-http]
[workspace.dependencies.tower-http]
version = "0.5.2"
features = [
"add-extension",
@@ -118,153 +100,168 @@ features = [
"catch-panic",
]
[dependencies.hyper]
version = "1.3.1"
features = ["server", "http1", "http2"]
[dependencies.hyper-util]
version = "0.1.3"
[dependencies.reqwest]
[workspace.dependencies.reqwest]
version = "0.12.4"
default-features = false
features = ["rustls-tls-native-roots", "socks", "hickory-dns"]
features = [
"rustls-tls-native-roots",
"socks",
"hickory-dns",
"http2",
]
# all the serde stuff
# Used for pdu definition
[dependencies.serde]
[workspace.dependencies.serde]
version = "1.0.201"
features = ["rc"]
# Used for appservice registration files
[dependencies.serde_yaml]
version = "0.9.34"
# Used for ruma wrapper
[dependencies.serde_json]
[workspace.dependencies.serde_json]
version = "1.0.117"
features = ["raw_value"]
# Used for appservice registration files
[workspace.dependencies.serde_yaml]
version = "0.9.34"
# Used to load forbidden room/user regex from config
[workspace.dependencies.serde_regex]
version = "1.1.0"
# Used for ruma wrapper
[workspace.dependencies.serde_html_form]
version = "0.2.6"
# Used for password hashing
[dependencies.argon2]
[workspace.dependencies.argon2]
version = "0.5.3"
features = ["alloc", "rand"]
default-features = false
# Used to generate thumbnails for images
[dependencies.image]
[workspace.dependencies.image]
version = "0.25.1"
default-features = false
features = ["jpeg", "png", "gif", "webp"]
features = [
"jpeg",
"png",
"gif",
"webp",
]
# logging
[dependencies.log]
[workspace.dependencies.log]
version = "0.4.21"
default-features = false
[dependencies.tracing]
[workspace.dependencies.tracing]
version = "0.1.40"
default-features = false
[dependencies.tracing-subscriber]
[workspace.dependencies.tracing-subscriber]
version = "0.3.18"
features = ["env-filter"]
# optional SHA256 media keys feature
[dependencies.sha2]
version = "0.10.8"
optional = true
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
[dependencies.opentelemetry]
version = "0.21.0"
optional = true
[dependencies.tracing-flame]
version = "0.2.0"
optional = true
[dependencies.tracing-opentelemetry]
version = "0.22.0"
optional = true
[dependencies.opentelemetry_sdk]
version = "0.21.2"
optional = true
features = ["rt-tokio"]
[dependencies.opentelemetry-jaeger]
version = "0.20.0"
optional = true
features = ["rt-tokio"]
# optional sentry metrics for crash/panic reporting
[dependencies.sentry]
version = "0.32.3"
optional = true
default-features = false
features = [
"backtrace",
"contexts",
"debug-images",
"panic",
"rustls",
"tower",
"tower-http",
"tracing",
"reqwest",
"log",
]
[dependencies.sentry-tracing]
version = "0.32.3"
optional = true
[dependencies.sentry-tower]
version = "0.32.3"
optional = true
# optional jemalloc usage
[dependencies.tikv-jemalloc-sys]
version = "0.5.4"
optional = true
default-features = false
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
[dependencies.tikv-jemallocator]
version = "0.5.4"
optional = true
default-features = false
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
[dependencies.tikv-jemalloc-ctl]
version = "0.5.4"
optional = true
default-features = false
features = ["use_std"]
# for URL previews
[dependencies.webpage]
[workspace.dependencies.webpage]
version = "2.0.1"
default-features = false
# to support multiple variations of setting a config option
[dependencies.either]
version = "1.11.0"
features = ["serde"]
# to listen on both HTTP and HTTPS if listening on TLS dierctly from conduwuit for complement or sytest
[dependencies.axum-server-dual-protocol]
version = "0.6"
optional = true
# used for conduit's CLI and admin room command parsing
[dependencies.clap]
[workspace.dependencies.clap]
version = "4.5.4"
default-features = false
features = ["std", "derive", "help", "usage", "error-context", "string"]
features = [
"std",
"derive",
"help",
"usage",
"error-context",
"string",
]
[dependencies.futures-util]
[workspace.dependencies.futures-util]
version = "0.3.30"
default-features = false
[workspace.dependencies.tokio]
version = "1.37.0"
features = [
"fs",
"net",
"macros",
"sync",
"signal",
"time",
"rt-multi-thread",
"io-util",
]
[workspace.dependencies.libloading]
version = "0.8.3"
# Validating urls in config, was already a transitive dependency
[workspace.dependencies.url]
version = "2.5.0"
features = ["serde"]
# standard date and time tools
[workspace.dependencies.chrono]
version = "0.4.38"
features = ["alloc"]
default-features = false
[workspace.dependencies.hyper]
version = "1.3.1"
features = [
"server",
"http1",
"http2",
]
[workspace.dependencies.hyper-util]
version = "0.1.4"
# to support multiple variations of setting a config option
[workspace.dependencies.either]
version = "1.11.0"
features = ["serde"]
# Used for reading the configuration from conduwuit.toml & environment variables
[dependencies.figment]
[workspace.dependencies.figment]
version = "0.10.18"
features = ["env", "toml"]
[workspace.dependencies.hickory-resolver]
version = "0.24.1"
default-features = false
# Used for conduit::Error type
[workspace.dependencies.thiserror]
version = "1.0.61"
# Used when hashing the state
[workspace.dependencies.ring]
version = "0.17.8"
# Used to make working with iterators easier, was already a transitive depdendency
[workspace.dependencies.itertools]
version = "0.13.0"
# to parse user-friendly time durations in admin commands
#TODO: overlaps chrono?
[workspace.dependencies.cyborgtime]
version = "2.1.1"
# used to replace the channels of the tokio runtime
[workspace.dependencies.loole]
version = "0.3.0"
[workspace.dependencies.async-trait]
version = "0.1.80"
[workspace.dependencies.lru-cache]
version = "0.1.2"
# Used for matrix spec type definitions and helpers
[dependencies.ruma]
git = "https://github.com/girlbossceo/ruma"
[workspace.dependencies.ruma]
git = "https://github.com/girlbossceo/ruwuma"
branch = "conduwuit-changes"
features = [
"compat",
@@ -289,60 +286,128 @@ features = [
"unstable-extensible-events",
]
[dependencies.ruma-identifiers-validation]
git = "https://github.com/girlbossceo/ruma"
[workspace.dependencies.ruma-identifiers-validation]
git = "https://github.com/girlbossceo/ruwuma"
branch = "conduwuit-changes"
[dependencies.hickory-resolver]
version = "0.24.1"
[workspace.dependencies.rust-rocksdb]
path = "deps/rust-rocksdb"
package = "rust-rocksdb-uwu"
features = [
"multi-threaded-cf",
"mt_static",
"snappy",
"lz4",
"zstd",
"zlib",
"bzip2",
]
[workspace.dependencies.zstd]
version = "0.13.1"
# to listen on both HTTP and HTTPS if listening on TLS dierctly from conduwuit for complement or sytest
[workspace.dependencies.axum-server-dual-protocol]
version = "0.6"
# optional SHA256 media keys feature
[workspace.dependencies.sha2]
version = "0.10.8"
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
[workspace.dependencies.opentelemetry]
version = "0.21.0"
[workspace.dependencies.tracing-flame]
version = "0.2.0"
[workspace.dependencies.tracing-opentelemetry]
version = "0.22.0"
[workspace.dependencies.opentelemetry_sdk]
version = "0.21.2"
features = ["rt-tokio"]
[workspace.dependencies.opentelemetry-jaeger]
version = "0.20.0"
features = ["rt-tokio"]
# optional sentry metrics for crash/panic reporting
[workspace.dependencies.sentry]
version = "0.32.3"
default-features = false
features = [
"backtrace",
"contexts",
"debug-images",
"panic",
"rustls",
"tower",
"tower-http",
"tracing",
"reqwest",
"log",
]
[dependencies.rust-rocksdb]
git = "https://github.com/zaidoon1/rust-rocksdb"
branch = "master"
optional = true
default-features = true
features = ["multi-threaded-cf", "zstd"]
[workspace.dependencies.sentry-tracing]
version = "0.32.3"
[workspace.dependencies.sentry-tower]
version = "0.32.3"
[dependencies.rusqlite]
# jemalloc usage
[workspace.dependencies.tikv-jemalloc-sys]
version = "0.5.4"
default-features = false
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
[workspace.dependencies.tikv-jemallocator]
version = "0.5.4"
default-features = false
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
[workspace.dependencies.tikv-jemalloc-ctl]
version = "0.5.4"
default-features = false
features = ["use_std"]
[workspace.dependencies.rusqlite]
git = "https://github.com/rusqlite/rusqlite"
#branch = "master"
rev = "e00b626e2b1c67347d789fb7f600281705c89381"
optional = true
features = ["bundled"]
# used only by rusqlite
[dependencies.parking_lot]
[workspace.dependencies.parking_lot]
version = "0.12.2"
optional = true
# used only by rusqlite
[dependencies.thread_local]
[workspace.dependencies.thread_local]
version = "1.1.8"
optional = true
# used only by rusqlite and rust-rocksdb
[dependencies.num_cpus]
version = "1.16.0"
[workspace.dependencies.tokio-metrics]
version = "0.3.1"
default-features = false
[dependencies.tokio]
version = "1.37.0"
features = ["fs", "macros", "sync", "signal"]
[workspace.dependencies.console-subscriber]
version = "0.2"
# *nix-specific dependencies
[target.'cfg(unix)'.dependencies]
nix = { version = "0.28.0", features = ["resource"] }
sd-notify = { version = "0.4.1", optional = true } # systemd is only available/relevant on *nix platforms
[workspace.dependencies.nix]
version = "0.28.0"
features = ["resource"]
[workspace.dependencies.sd-notify]
version = "0.4.1"
[target.'cfg(all(not(target_env = "msvc"), target_os = "linux"))'.dependencies]
hardened_malloc-rs = { version = "0.1.2", optional = true, features = [
"static",
"gcc",
"light",
], default-features = false }
#hardened_malloc-rs = { optional = true, features = ["static","clang","light"], path = "../hardened_malloc-rs", default-features = false }
[workspace.dependencies.hardened_malloc-rs]
version = "0.1.2"
default-features = false
features = [
"static",
"gcc",
"light",
]
#
# Patches
#
# backport of [https://github.com/tokio-rs/tracing/pull/2956] to the 0.1.x branch of tracing.
# we can switch back to upstream if #2956 is merged and backported in the upstream repo.
@@ -356,166 +421,219 @@ branch = "tracing-subscriber/env-filter-clone-0.1.x-backport"
git = "https://github.com/girlbossceo/tracing"
branch = "tracing-subscriber/env-filter-clone-0.1.x-backport"
[features]
default = [
"backend_rocksdb",
"systemd",
"element_hacks",
"sentry_telemetry",
"gzip_compression",
"brotli_compression",
"zstd_compression",
"release_max_log_level",
"io_uring",
]
backend_sqlite = ["sqlite"]
backend_rocksdb = ["rocksdb"]
rocksdb = ["dep:rust-rocksdb"]
jemalloc = [
"dep:tikv-jemalloc-sys",
"dep:tikv-jemalloc-ctl",
"dep:tikv-jemallocator",
"rust-rocksdb/jemalloc",
]
jemalloc_prof = ["tikv-jemalloc-sys/profiling"]
sqlite = ["dep:rusqlite", "dep:parking_lot", "dep:thread_local"]
systemd = ["dep:sd-notify"]
sentry_telemetry = ["dep:sentry", "dep:sentry-tracing", "dep:sentry-tower"]
gzip_compression = ["tower-http/compression-gzip", "reqwest/gzip"]
zstd_compression = ["tower-http/compression-zstd"]
brotli_compression = ["tower-http/compression-br", "reqwest/brotli"]
sha256_media = ["dep:sha2"]
io_uring = ["rust-rocksdb/io-uring"]
axum_dual_protocol = ["dep:axum-server-dual-protocol"]
perf_measurements = [
"dep:opentelemetry",
"dep:tracing-flame",
"dep:tracing-opentelemetry",
"dep:opentelemetry_sdk",
"dep:opentelemetry-jaeger",
]
# enable the tokio_console server
# incompatible with release_max_log_level
tokio_console = ["dep:console-subscriber", "tokio/tracing"]
hot_reload = ["dep:hot-lib-reloader"]
hardened_malloc = ["dep:hardened_malloc-rs"]
# increases performance, reduces build times, and reduces binary size by not compiling or
# genreating code for log level filters that users will generally not use (debug and trace) only in release builds
#
# the expense is obviously losing those log level filters for usage at runtime. debug builds will still have all log levels
release_max_log_level = [
"tracing/max_level_trace",
"tracing/release_max_level_info",
"log/max_level_trace",
"log/release_max_level_info",
]
# developer feature useful only in debug builds.
dev_release_log_level = []
# client/server interopability hacks
# Our crates
#
## element has various non-spec compliant behaviour
element_hacks = []
[workspace.dependencies.conduit-router]
package = "conduit_router"
path = "src/router"
default-features = false
[package.metadata.deb]
name = "conduwuit"
maintainer = "strawberry <strawberry@puppygock.gay>"
copyright = "2024, strawberry <strawberry@puppygock.gay>"
license-file = ["LICENSE", "3"]
depends = "$auto, ca-certificates"
extended-description = """\
a cool hard fork of Conduit, a Matrix homeserver written in Rust"""
section = "net"
priority = "optional"
assets = [
[
"debian/README.md",
"usr/share/doc/conduwuit/README.Debian",
"644",
],
[
"README.md",
"usr/share/doc/conduwuit/",
"644",
],
[
"target/release/conduit",
"usr/sbin/conduwuit",
"755",
],
[
"conduwuit-example.toml",
"etc/conduwuit/conduwuit.toml",
"640",
],
]
conf-files = ["/etc/conduwuit/conduwuit.toml"]
maintainer-scripts = "debian/"
systemd-units = { unit-name = "conduwuit" }
[workspace.dependencies.conduit-admin]
package = "conduit_admin"
path = "src/admin"
default-features = false
[workspace.dependencies.conduit-api]
package = "conduit_api"
path = "src/api"
default-features = false
[profile.dev]
#debug = 0
lto = 'off'
codegen-units = 512
incremental = true
overflow-checks = true
#panic = "abort"
[workspace.dependencies.conduit-service]
package = "conduit_service"
path = "src/service"
default-features = false
# seems to speed up continuous debug compilations
[profile.dev.build-override]
opt-level = 3
[profile.dev.package."*"] # external dependencies
opt-level = 1
[profile.dev.package."tokio"]
opt-level = 3
[workspace.dependencies.conduit-database]
package = "conduit_database"
path = "src/database"
default-features = false
[workspace.dependencies.conduit-core]
package = "conduit_core"
path = "src/core"
default-features = false
###############################################################################
#
# Release profiles
#
# default release profile
[profile.release]
lto = 'thin'
incremental = false
opt-level = 3
strip = "symbols"
control-flow-guard = true # Windows only
debug = 0
lto = "thin"
# release profile with debug symbols
[profile.release-debuginfo]
inherits = "release"
strip = "none"
debug = "full"
strip = "none"
# high performance release profile which uses fat LTO across all crates, 1 codegen unit, max opt-level, and optimises across all crates
[profile.release-high-perf]
inherits = "release"
lto = 'fat'
lto = "fat"
codegen-units = 1
panic = "abort"
# For releases also try to max optimizations for dependencies:
[profile.release-high-perf.build-override]
debug = 0
opt-level = 3
# do not use without profile-rustflags enabled
[profile.release-max-perf]
inherits = "release"
strip = "symbols"
lto = "fat"
#rustflags = [
# '-Ctarget-cpu=native',
# '-Ztune-cpu=native',
# '-Ctarget-feature=+crt-static',
# '-Crelocation-model=static',
# '-Ztls-model=local-exec',
# '-Zinline-in-all-cgus=true',
# '-Zinline-mir=true',
# '-Zmir-opt-level=3',
# '-Clink-arg=-fuse-ld=gold',
# '-Clink-arg=-Wl,--threads',
# '-Clink-arg=-Wl,--gc-sections',
# '-Clink-arg=-luring',
# '-Clink-arg=-lstdc++',
# '-Clink-arg=-lc',
# '-Ztime-passes',
# '-Ztime-llvm-passes',
#]
[profile.release-max-perf.build-override]
inherits = "release-max-perf"
opt-level = 0
#rustflags = [
# '-Ctarget-feature=-crt-static',
#]
[profile.bench]
inherits = "release"
#rustflags = [
# "-Cremark=all",
# '-Ztime-passes',
# '-Ztime-llvm-passes',
#]
###############################################################################
#
# Developer profile
#
# To enable hot-reloading:
# 1. Uncomment all of the rustflags here.
# 2. Uncomment crate-type=dylib in src/*/Cargo.toml and deps/rust-rocksdb/Cargo.toml
#
# opt-level, mir-opt-level, validate-mir are not known to interfere with reloading
# and can be raised if build times are tolerable.
[profile.dev]
debug = 1
opt-level = 0
panic = "unwind"
debug-assertions = true
incremental = true
codegen-units = 64
#rustflags = [
# '--cfg', 'conduit_mods',
# '-Ztime-passes',
# '-Zmir-opt-level=0',
# '-Zvalidate-mir=false',
# '-Ztls-model=global-dynamic',
# '-Cprefer-dynamic=true',
# '-Zstaticlib-prefer-dynamic=true',
# '-Zstaticlib-allow-rdylib-deps=true',
# '-Zpacked-bundled-libs=false',
# '-Zplt=true',
# '-Crpath=true',
# '-Clink-arg=-Wl,--as-needed',
# '-Clink-arg=-Wl,--allow-shlib-undefined',
# '-Clink-arg=-Wl,-z,keep-text-section-prefix',
# '-Clink-arg=-Wl,-z,lazy',
#]
[profile.dev.package.conduit_core]
inherits = "dev"
incremental = false
#rustflags = [
# '--cfg', 'conduit_mods',
# '-Ztime-passes',
# '-Zmir-opt-level=0',
# '-Ztls-model=initial-exec',
# '-Cprefer-dynamic=true',
# '-Zstaticlib-prefer-dynamic=true',
# '-Zstaticlib-allow-rdylib-deps=true',
# '-Zpacked-bundled-libs=false',
# '-Zplt=true',
# '-Clink-arg=-Wl,--as-needed',
# '-Clink-arg=-Wl,--allow-shlib-undefined',
# '-Clink-arg=-Wl,-z,lazy',
# '-Clink-arg=-Wl,-z,unique',
# '-Clink-arg=-Wl,-z,nodlopen',
# '-Clink-arg=-Wl,-z,nodelete',
#]
[profile.dev.package.conduit]
inherits = "dev"
incremental = false
#rustflags = [
# '--cfg', 'conduit_mods',
# '-Ztime-passes',
# '-Zmir-opt-level=0',
# '-Zvalidate-mir=false',
# '-Ztls-model=global-dynamic',
# '-Cprefer-dynamic=true',
# '-Zexport-executable-symbols=true',
# '-Zplt=true',
# '-Crpath=true',
# '-Clink-arg=-Wl,--as-needed',
# '-Clink-arg=-Wl,--allow-shlib-undefined',
# '-Clink-arg=-Wl,--export-dynamic',
# '-Clink-arg=-Wl,-z,lazy',
#]
[profile.dev.package.rust-rocksdb-uwu]
inherits = "dev"
debug = 'limited'
incremental = false
codegen-units = 1
opt-level = 'z'
#rustflags = [
# '--cfg', 'conduit_mods',
# '-Ztls-model=initial-exec',
# '-Cprefer-dynamic=true',
# '-Zstaticlib-prefer-dynamic=true',
# '-Zstaticlib-allow-rdylib-deps=true',
# '-Zpacked-bundled-libs=true',
# '-Zplt=true',
# '-Clink-arg=-Wl,--no-as-needed',
# '-Clink-arg=-Wl,--allow-shlib-undefined',
# '-Clink-arg=-Wl,-z,lazy',
# '-Clink-arg=-Wl,-z,nodlopen',
# '-Clink-arg=-Wl,-z,nodelete',
#]
[profile.release-high-perf.package."*"]
debug = 0
opt-level = 3
[profile.dev.package.'*']
inherits = "dev"
debug = 'limited'
incremental = false
codegen-units = 1
opt-level = 'z'
#rustflags = [
# '--cfg', 'conduit_mods',
# '-Ztls-model=global-dynamic',
# '-Cprefer-dynamic=true',
# '-Zstaticlib-prefer-dynamic=true',
# '-Zstaticlib-allow-rdylib-deps=true',
# '-Zpacked-bundled-libs=true',
# '-Zplt=true',
# '-Clink-arg=-Wl,--as-needed',
# '-Clink-arg=-Wl,-z,lazy',
# '-Clink-arg=-Wl,-z,nodelete',
#]
[lints]
workspace = true
[profile.test]
incremental = false
[workspace.lints.rust]
missing_abi = "warn"
@@ -537,10 +655,14 @@ unreachable_pub = "warn"
# this seems to suggest broken code and is not working correctly
unused_braces = "allow"
# cfgs cannot be limited to features or cargo build --all-features panics for unsuspecting users.
# cfgs cannot be limited to expected cfgs or their de facto non-transitive/opt-in use-case e.g.
# tokio_unstable will warn.
unexpected_cfgs = "allow"
# some sadness
missing_docs = "allow"
[workspace.lints.clippy]
# pedantic = "warn"
@@ -612,7 +734,6 @@ unnecessary_box_returns = "warn"
map_unwrap_or = "warn"
implicit_clone = "warn"
match_wildcard_for_single_variants = "warn"
unnecessary_wraps = "warn"
match_same_arms = "warn"
ignored_unit_patterns = "warn"
redundant_else = "warn"
@@ -629,6 +750,7 @@ manual_let_else = "warn"
trivially_copy_pass_by_ref = "warn"
wildcard_imports = "warn"
checked_conversions = "warn"
let_underscore_must_use = "warn"
#integer_arithmetic = "warn"
#as_conversions = "warn"
@@ -646,7 +768,7 @@ mod_module_files = "allow"
unwrap_used = "allow"
expect_used = "allow"
if_then_some_else_none = "allow"
let_underscore_must_use = "allow"
let_underscore_future = "allow"
map_err_ignore = "allow"
missing_docs_in_private_items = "allow"
multiple_inherent_impl = "allow"
@@ -654,3 +776,4 @@ error_impl_error = "allow"
string_add = "allow"
string_slice = "allow"
ref_patterns = "allow"
unnecessary_wraps = "allow"
-2
View File
@@ -1,2 +0,0 @@
[advisories]
ignore = ["RUSTSEC-2020-0016"]
+8 -2
View File
@@ -17,11 +17,17 @@ RESULTS_FILE="$3"
OCI_IMAGE="complement-conduit:main"
# Complement tests that are skipped due to flakiness/reliability issues (likely
# Complement itself induced based on various open issues)
#
# According to Go docs, these are separated by forward slashes and not pipes (why)
SKIPPED_COMPLEMENT_TESTS='-skip=TestJumpToDateEndpoint.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestFederationRoomsInvite.*|TestClientSpacesSummary.*'
toplevel="$(git rev-parse --show-toplevel)"
pushd "$toplevel" > /dev/null
bin/nix-build-and-cache just .#complement
bin/nix-build-and-cache just .#static-complement
docker load < result
popd > /dev/null
@@ -31,7 +37,7 @@ set +o pipefail
env \
-C "$COMPLEMENT_SRC" \
COMPLEMENT_BASE_IMAGE="$OCI_IMAGE" \
go test -tags="conduwuit_blacklist" -v -timeout 1h -json ./tests | tee "$LOG_FILE"
go test -tags="conduwuit_blacklist" "$SKIPPED_COMPLEMENT_TESTS" -v -timeout 1h -json ./tests | tee "$LOG_FILE"
set -o pipefail
# Post-process the results into an easy-to-compare format, sorted by Test name for reproducible results
+1 -1
View File
@@ -70,7 +70,7 @@ ci() {
--inputs-from "$toplevel"
# Keep sorted
"$toplevel#devShells.x86_64-linux.default"
"$toplevel#devShells.x86_64-linux.all-features"
attic#default
nixpkgs#direnv
nixpkgs#jq
+3 -2
View File
@@ -60,8 +60,9 @@
### Database configuration
# This is the only directory where conduwuit will save its data, including media
database_path = "/var/lib/matrix-conduit/"
# This is the only directory where conduwuit will save its data, including media.
# Note: this was previously "/var/lib/matrix-conduit"
database_path = "/var/lib/conduwuit"
# Database backend: Only rocksdb and sqlite are supported. Please note that sqlite
# will perform significantly worse than rocksdb as it is not intended to be used the
+13 -24
View File
@@ -1,33 +1,22 @@
# conduwuit for Debian
Installation
------------
Information about downloading and deploying the Debian package. This may also be referenced for other `apt`-based distros such as Ubuntu.
Information about downloading, building and deploying the Debian package, see
the "Installing conduwuit" section in the Deploying docs.
All following sections until "Setting up the Reverse Proxy" be ignored because
this is handled automatically by the packaging.
### Installation
Configuration
-------------
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.
When installed, Debconf generates the configuration of the homeserver
(host)name, the address and port it listens on. This configuration ends up in
`/etc/conduwuit/conduwuit.toml`.
### Configuration
You can tweak more detailed settings by uncommenting and setting the variables
in `/etc/conduwuit/conduwuit.toml`. This involves settings such as the maximum
file size for download/upload, enabling federation, etc.
When installed, the example config is placed at `/etc/conduwuit/conduwuit.toml` as the default config. At the minimum, you will need to change your `server_name` here.
Running
-------
You can tweak more detailed settings by uncommenting and setting the config options
in `/etc/conduwuit/conduwuit.toml`.
The package uses the [`conduwuit.service`](../configuration.md#example-systemd-unit-file) systemd unit file to start and
stop conduwuit. It loads the configuration file mentioned above to set up the
environment before running the server.
### Running
This package assumes by default that conduwuit will be placed behind a reverse
proxy. This default deployment entails just listening
on `127.0.0.1` and the free port `6167` and is reachable via a client using the URL
<http://localhost:6167>. Matrix federation requires TLS, so you will need to set up
some certificates and renewal, for it to work properly.
The package uses the [`conduwuit.service`](../configuration.md#example-systemd-unit-file) systemd unit file to start and stop conduwuit. The binary is installed 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.
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.
+6 -4
View File
@@ -13,6 +13,8 @@ Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
ExecStart=/usr/sbin/conduwuit
ReadWritePaths=/var/lib/conduwuit /etc/conduwuit
AmbientCapabilities=
CapabilityBoundingSet=
@@ -44,16 +46,16 @@ SystemCallArchitectures=native
SystemCallFilter=@system-service @resources
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
SystemCallErrorNumber=EPERM
StateDirectory=conduwuit
#StateDirectory=conduwuit
RuntimeDirectory=conduit
RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750
Restart=on-failure
RestartSec=5
TimeoutStopSec=4m
TimeoutStartSec=4m
TimeoutStopSec=2m
TimeoutStartSec=2m
StartLimitInterval=1m
StartLimitBurst=5
+12 -11
View File
@@ -1,17 +1,18 @@
#!/bin/sh
set -e
# TODO: implement debconf support that is maintainable without duplicating the config
# Source debconf library.
. /usr/share/debconf/confmodule
# Ask for the Matrix homeserver name, address and port.
db_input high conduwuit/hostname || true
db_go
db_input low conduwuit/address || true
db_go
db_input medium conduwuit/port || true
db_go
#. /usr/share/debconf/confmodule
#
## Ask for the Matrix homeserver name, address and port.
#db_input high conduwuit/hostname || true
#db_go
#
#db_input low conduwuit/address || true
#db_go
#
#db_input medium conduwuit/port || true
#db_go
exit 0
+22 -7
View File
@@ -1,9 +1,12 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
# TODO: implement debconf support that is maintainable without duplicating the config
#. /usr/share/debconf/confmodule
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit/
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
CONDUWUIT_CONFIG_FILE="${CONDUWUIT_CONFIG_PATH}/conduwuit.toml"
case "$1" in
configure)
@@ -14,15 +17,27 @@ case "$1" in
--home "$CONDUWUIT_DATABASE_PATH" \
--disabled-login \
--shell "/usr/sbin/nologin" \
--force-badname \
--verbose \
conduwuit
fi
# Create the database path if it does not exist yet and fix up ownership
# and permissions.
mkdir -p "$CONDUWUIT_DATABASE_PATH"
chown conduwuit:conduwuit -R "$CONDUWUIT_DATABASE_PATH"
chmod 700 "$CONDUWUIT_DATABASE_PATH"
# and permissions for the config.
mkdir -v -p "$CONDUWUIT_DATABASE_PATH"
# symlink the previous location for compatibility
ln -s -v "$CONDUWUIT_DATABASE_PATH" "/var/lib/matrix-conduit"
chown -v conduwuit:conduwuit -R "$CONDUWUIT_DATABASE_PATH"
chown -v conduwuit:conduwuit -R "$CONDUWUIT_CONFIG_PATH"
chmod -v 740 "$CONDUWUIT_DATABASE_PATH"
echo ''
echo 'Make sure you edit the example config at /etc/conduwuit/conduwuit.toml before starting!'
echo 'To start the server, run: systemctl start conduwuit.service'
echo ''
;;
esac
+8 -3
View File
@@ -1,10 +1,11 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
#. /usr/share/debconf/confmodule
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
CONDUWUIT_DATABASE_PATH_SYMLINK=/var/lib/matrix-conduit
case $1 in
purge)
@@ -15,11 +16,15 @@ case $1 in
# "configuration files must be preserved when the package is removed, and
# only deleted when the package is purged."
if [ -d "$CONDUWUIT_CONFIG_PATH" ]; then
rm -r "$CONDUWUIT_CONFIG_PATH"
rm -v -r "$CONDUWUIT_CONFIG_PATH"
fi
if [ -d "$CONDUWUIT_DATABASE_PATH" ]; then
rm -r "$CONDUWUIT_DATABASE_PATH"
rm -v -r "$CONDUWUIT_DATABASE_PATH"
fi
if [ -d "$CONDUWUIT_DATABASE_PATH_SYMLINK" ]; then
rm -v -r "$CONDUWUIT_DATABASE_PATH_SYMLINK"
fi
;;
esac
-21
View File
@@ -1,21 +0,0 @@
Template: conduwuit/hostname
Type: string
Default: localhost
Description: The server (host)name of the Matrix homeserver
This is the hostname the homeserver will be reachable at via a client.
.
If set to "localhost", you can connect with a client locally and clients
from other hosts and also other homeservers will not be able to reach you!
Template: conduwuit/address
Type: string
Default: 127.0.0.1
Description: The listen address of the Matrix homeserver
This is the address the homeserver will listen on. Leave it set to 127.0.0.1
when using a reverse proxy.
Template: conduwuit/port
Type: string
Default: 6167
Description: The port of the Matrix homeserver
This port is most often just accessed by a reverse proxy.
+35
View File
@@ -0,0 +1,35 @@
[package]
name = "rust-rocksdb-uwu"
version = "0.0.1"
edition = "2021"
[features]
default = ["snappy", "lz4", "zstd", "zlib", "bzip2"]
jemalloc = ["rust-rocksdb/jemalloc"]
io-uring = ["rust-rocksdb/io-uring"]
valgrind = ["rust-rocksdb/valgrind"]
snappy = ["rust-rocksdb/snappy"]
lz4 = ["rust-rocksdb/lz4"]
zstd = ["rust-rocksdb/zstd"]
zlib = ["rust-rocksdb/zlib"]
bzip2 = ["rust-rocksdb/bzip2"]
rtti = ["rust-rocksdb/rtti"]
mt_static = ["rust-rocksdb/mt_static"]
multi-threaded-cf = ["rust-rocksdb/multi-threaded-cf"]
serde1 = ["rust-rocksdb/serde1"]
malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
[dependencies.rust-rocksdb]
git = "https://github.com/zaidoon1/rust-rocksdb"
branch = "master"
default-features = false
[lib]
path = "lib.rs"
crate-type = [
"rlib",
# "dylib"
]
[lints]
workspace = true
+61
View File
@@ -0,0 +1,61 @@
pub use rust_rocksdb::*;
#[cfg_attr(not(conduit_mods), link(name = "rocksdb"))]
#[cfg_attr(conduit_mods, link(name = "rocksdb", kind = "static"))]
extern "C" {
pub fn rocksdb_list_column_families();
pub fn rocksdb_logger_create_stderr_logger();
pub fn rocksdb_options_set_info_log();
pub fn rocksdb_get_options_from_string();
pub fn rocksdb_writebatch_create();
pub fn rocksdb_writebatch_destroy();
pub fn rocksdb_writebatch_put_cf();
pub fn rocksdb_writebatch_delete_cf();
pub fn rocksdb_iter_value();
pub fn rocksdb_iter_seek_to_last();
pub fn rocksdb_iter_seek_for_prev();
pub fn rocksdb_iter_seek_to_first();
pub fn rocksdb_iter_next();
pub fn rocksdb_iter_prev();
pub fn rocksdb_iter_seek();
pub fn rocksdb_iter_valid();
pub fn rocksdb_iter_get_error();
pub fn rocksdb_iter_key();
pub fn rocksdb_iter_destroy();
pub fn rocksdb_livefiles();
pub fn rocksdb_livefiles_count();
pub fn rocksdb_livefiles_destroy();
pub fn rocksdb_livefiles_column_family_name();
pub fn rocksdb_livefiles_name();
pub fn rocksdb_livefiles_size();
pub fn rocksdb_livefiles_level();
pub fn rocksdb_livefiles_smallestkey();
pub fn rocksdb_livefiles_largestkey();
pub fn rocksdb_livefiles_entries();
pub fn rocksdb_livefiles_deletions();
pub fn rocksdb_put_cf();
pub fn rocksdb_delete_cf();
pub fn rocksdb_get_pinned_cf();
pub fn rocksdb_create_column_family();
pub fn rocksdb_get_latest_sequence_number();
pub fn rocksdb_batched_multi_get_cf();
pub fn rocksdb_cancel_all_background_work();
pub fn rocksdb_repair_db();
pub fn rocksdb_list_column_families_destroy();
pub fn rocksdb_flush();
pub fn rocksdb_flush_wal();
pub fn rocksdb_open_column_families();
pub fn rocksdb_open_for_read_only_column_families();
pub fn rocksdb_open_as_secondary_column_families();
pub fn rocksdb_open_column_families_with_ttl();
pub fn rocksdb_open();
pub fn rocksdb_open_for_read_only();
pub fn rocksdb_open_with_ttl();
pub fn rocksdb_open_as_secondary();
pub fn rocksdb_write();
pub fn rocksdb_create_iterator_cf();
pub fn rocksdb_backup_engine_create_new_backup_flush();
pub fn rocksdb_backup_engine_options_create();
pub fn rocksdb_write_buffer_manager_destroy();
pub fn rocksdb_options_set_ttl();
}
+1
View File
@@ -16,3 +16,4 @@
- [Development](development.md)
- [Contributing](contributing.md)
- [Testing](development/testing.md)
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
+17 -27
View File
@@ -1,40 +1,30 @@
# Conduit - Behind Traefik Reverse Proxy
# conduwuit - Behind Traefik Reverse Proxy
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
### If you already built the conduduwit image with 'docker build' or want to use the Docker Hub image,
### then you are ready to go.
image: girlbossceo/conduwuit:latest
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
# build:
# context: .
# args:
# CREATED: '2021-03-16T08:18:27Z'
# VERSION: '0.1.0'
# LOCAL: 'false'
# GIT_REF: origin/master
restart: unless-stopped
volumes:
- db:/var/lib/matrix-conduit
#- ./conduwuit.toml:/etc/conduit.toml
- db:/var/lib/conduwuit
#- ./conduwuit.toml:/etc/conduwuit.toml
networks:
- proxy
environment:
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit
CONDUIT_DATABASE_BACKEND: rocksdb
CONDUIT_PORT: 6167
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
CONDUIT_ALLOW_REGISTRATION: 'true'
CONDUIT_ALLOW_FEDERATION: 'true'
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUIT_LOG: warn,state_res=warn
CONDUIT_ADDRESS: 0.0.0.0
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
CONDUWUIT_DATABASE_BACKEND: rocksdb
CONDUWUIT_PORT: 6167
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
CONDUWUIT_ALLOW_REGISTRATION: 'true'
CONDUWUIT_ALLOW_FEDERATION: 'true'
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUWUIT_LOG: warn,state_res=warn
CONDUWUIT_ADDRESS: 0.0.0.0
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
@@ -48,7 +38,7 @@ services:
- ./nginx/www:/var/www/ # location of the client and server .well-known-files
### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second
### Domain or Subdomain for the communication between Element and Conduit
### Domain or Subdomain for the communication between Element and conduwuit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web:
# image: vectorim/element-web:latest
+5 -5
View File
@@ -1,4 +1,4 @@
# Conduit - Traefik Reverse Proxy Labels
# conduwuit - Traefik Reverse Proxy Labels
version: '2.4' # uses '2.4' for cpuset
services:
@@ -7,10 +7,10 @@ services:
- "traefik.enable=true"
- "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
- "traefik.http.routers.to-conduit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which Conduit is hosted
- "traefik.http.routers.to-conduit.tls=true"
- "traefik.http.routers.to-conduit.tls.certresolver=letsencrypt"
- "traefik.http.routers.to-conduit.middlewares=cors-headers@docker"
- "traefik.http.routers.to-conduwuit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which conduwuit is hosted
- "traefik.http.routers.to-conduwuit.tls=true"
- "traefik.http.routers.to-conduwuit.tls.certresolver=letsencrypt"
- "traefik.http.routers.to-conduwuit.middlewares=cors-headers@docker"
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
+19 -30
View File
@@ -1,44 +1,33 @@
# Conduit - Behind Traefik Reverse Proxy
# conduwuit - Behind Traefik Reverse Proxy
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
### If you already built the conduwuit image with 'docker build' or want to use the Docker Hub image,
### then you are ready to go.
image: girlbossceo/conduwuit:latest
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
# build:
# context: .
# args:
# CREATED: '2021-03-16T08:18:27Z'
# VERSION: '0.1.0'
# LOCAL: 'false'
# GIT_REF: origin/master
restart: unless-stopped
volumes:
- db:/srv/conduit/.local/share/conduit
#- ./conduwuit.toml:/etc/conduit.toml
- db:/srv/conduwuit/.local/share/conduwuit
#- ./conduwuit.toml:/etc/conduwuit.toml
networks:
- proxy
environment:
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
CONDUIT_ALLOW_REGISTRATION : 'true'
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
CONDUWUIT_ALLOW_REGISTRATION : 'true'
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
### Uncomment and change values as desired
# CONDUIT_ADDRESS: 0.0.0.0
# CONDUIT_PORT: 6167
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
# CONDUIT_LOG: info # default is: "warn,state_res=warn"
# CONDUIT_ALLOW_JAEGER: 'false'
# CONDUIT_ALLOW_ENCRYPTION: 'true'
# CONDUIT_ALLOW_FEDERATION: 'true'
# CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
# CONDUIT_WORKERS: 10
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
# CONDUWUIT_ADDRESS: 0.0.0.0
# CONDUWUIT_PORT: 6167
# CONDUWUIT_LOG: info # default is: "warn,state_res=warn"
# CONDUWUIT_ALLOW_JAEGER: 'false'
# CONDUWUIT_ALLOW_ENCRYPTION: 'true'
# CONDUWUIT_ALLOW_FEDERATION: 'true'
# CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
# CONDUWUIT_DATABASE_PATH: /srv/conduwuit/.local/share/conduwuit
# CONDUWUIT_WORKERS: 10
# CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
@@ -53,7 +42,7 @@ services:
### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second
### Domain or Subdomain for the communication between Element and Conduit
### Domain or Subdomain for the communication between Element and conduwuit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web:
# image: vectorim/element-web:latest
+17 -27
View File
@@ -1,45 +1,35 @@
# Conduit
# conduwuit
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
### If you already built the Conduit image with 'docker build' or want to use a registry image,
### If you already built the conduwuit image with 'docker build' or want to use a registry image,
### then you are ready to go.
image: girlbossceo/conduwuit:latest
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
# build:
# context: .
# args:
# CREATED: '2021-03-16T08:18:27Z'
# VERSION: '0.1.0'
# LOCAL: 'false'
# GIT_REF: origin/master
restart: unless-stopped
ports:
- 8448:6167
volumes:
- db:/var/lib/matrix-conduit
#- ./conduwuit.toml:/etc/conduit.toml
- db:/var/lib/conduwuit
#- ./conduwuit.toml:/etc/conduwuit.toml
environment:
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit
CONDUIT_DATABASE_BACKEND: rocksdb
CONDUIT_PORT: 6167
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
CONDUIT_ALLOW_REGISTRATION: 'true'
CONDUIT_ALLOW_FEDERATION: 'true'
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUIT_LOG: warn,state_res=warn
CONDUIT_ADDRESS: 0.0.0.0
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
CONDUWUIT_DATABASE_BACKEND: rocksdb
CONDUWUIT_PORT: 6167
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
CONDUWUIT_ALLOW_REGISTRATION: 'true'
CONDUWUIT_ALLOW_FEDERATION: 'true'
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUWUIT_LOG: warn,state_res=warn
CONDUWUIT_ADDRESS: 0.0.0.0
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
#
### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second
### Domain or Subdomain for the communication between Element and Conduit
### Domain or Subdomain for the communication between Element and conduwuit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web:
# image: vectorim/element-web:latest
+9 -1
View File
@@ -10,9 +10,17 @@ A binary cache for conduwuit that the CI/CD publishes to is available at the
following places (both are the same just different names):
```
https://attic.kennel.juneis.dog/conduit
conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
https://attic.kennel.juneis.dog/conduwuit
conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=
```
The binary caches have been recreated recently due to attic issues. The old public keys were:
```
conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
```
+1 -2
View File
@@ -16,8 +16,7 @@ look like this:
RUSTFLAGS="--cfg tokio_unstable" cargo build \
--release \
--no-default-features \
--features
backend_rocksdb,systemd,element_hacks,sentry_telemetry,gzip_compression,brotli_compression,zstd_compression,tokio_console
--features=rocksdb,systemd,element_hacks,sentry_telemetry,gzip_compression,brotli_compression,zstd_compression,tokio_console
```
[1]: https://docs.rs/tokio-console/latest/tokio_console/
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

+93
View File
@@ -0,0 +1,93 @@
# Hot Reloading ("Live" Development)
### Summary
When developing in debug-builds with the nightly toolchain, conduwuit is modular using dynamic libraries and various parts of the application are hot-reloadable while the server is running: http api handlers, admin commands, services, database, etc. These are all split up into individual workspace crates as seen in the `src/` directory. Changes to sourcecode in a crate rebuild that crate and subsequent crates depending on it. Reloading then occurs for the changed crates.
Release builds still produce static binaries which are unaffected. Rust's soundness guarantees are in full force. Thus you cannot hot-reload release binaries.
### Requirements
Currently, this development setup only works on x86_64 and aarch64 Linux glibc. [musl explicitly does not support hot reloadable libraries, and does not implement `dlclose`][2]. macOS does not fully support our usage of `RTLD_GLOBAL` possibly due to some thread-local issues. [This Rust issue][3] may be of relevance, specifically [this comment][4]. It may be possible to get it working on only very modern macOS versions such as at least Sonoma, as currently loading dylibs is supported, but not unloading them in our setup, and the cited comment mentions an Apple WWDC confirming there have been TLS changes to somewhat make this possible.
As mentioned above this requires the nightly toolchain. This is due to reliance on various Cargo.toml features that are only available on nightly, most specifically `RUSTFLAGS` in Cargo.toml. Some of the implementation could also be simpler based on other various nightly features. We hope lots of nightly features start making it out of nightly sooner as there have been dozens of very helpful features that have been stuck in nightly ("unstable") for at least 5+ years that would make this simpler. We encourage greater community consensus to move these features into stability.
This currently only works on x86_64/aarch64 Linux with a glibc C library. musl C library, macOS, and likely other host architectures are not supported (if other architectures work, feel free to let us know and/or make a PR updating this). This should work on GNU ld and lld (rust-lld) and gcc/clang, however if you happen to have linker issues it's recommended to try using `mold` or `gold` linkers, and please let us know in the [conduwuit Matrix room][7] the linker error and what linker solved this issue so we can figure out a solution. Ideally there should be minimal friction to using this, and in the future a build script (`build.rs`) may be suitable to making this easier to use if the capabilities allow us.
### Usage
As of 19 May 2024, the instructions for using this are:
0. Have patience. Don't hesitate to join the [conduwuit Matrix room][7] to receive help using this. As indicated by the various rustflags used and some of the interesting issues linked at the bottom, this is definitely not something the Rust ecosystem or toolchain is used to doing.
1. Install the nightly toolchain using rustup. You may need to use `rustup override set nightly` in your local conduwuit directory, or use `cargo +nightly` for all actions.
2. Uncomment `cargo-features` at the top level / root Cargo.toml
3. Scroll down to the `# Developer profile` section and uncomment ALL the rustflags for each dev profile and their respective packages.
4. In each workspace crate's Cargo.toml (everything under `src/*` AND `deps/rust-rocksdb/Cargo.toml`), uncomment the `dylib` crate type under `[lib]`.
5. Due to [this rpath issue][5], you must export the `LD_LIBRARY_PATH` environment variable to your nightly Rust toolchain library directory. If using rustup (hopefully), use this: `export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/`
6. Start the server. You can use `cargo +nightly run` for this along with the standard.
7. Make some changes where you need to.
8. In a separate terminal window in the same directory (or using a terminal multiplexer like tmux), run the *build* Cargo command `cargo +nightly build`. Cargo should only rebuild what was changed / what's necessary, so it should not be rebuilding all the crates.
9. In your conduwuit server terminal, hit/send `CTRL+C` signal. This will tell conduwuit to find which libraries need to be reloaded, and reloads them as necessary.
10. If there were no errors, it will tell you it successfully reloaded `#` modules, and your changes should now be visible. Repeat 7 - 9 as needed.
To shutdown conduwuit in this setup, hit/send `CTRL+\`. Normal builds still shutdown with `CTRL+C` as usual.
Steps 1 - 5 are the initial first-time steps for using this. To remove the hot reload setup, revert/comment all the Cargo.toml changes.
As mentioned in the requirements section, if you happen to have some linker issues, try using the `-fuse-ld=` rustflag and specify mold or gold in all the `rustflags` definitions in the top level Cargo.toml, and please let us know in the [conduwuit Matrix room][7] the problem. mold can be installed typically through your distro, and gold is provided by the binutils package.
It's possible a helper script can be made to do all of this, or most preferably a specially made build script (build.rs). `cargo watch` support will be implemented soon which will eliminate the need to manually run `cargo build` all together.
### Addendum
Conduit was inherited as a single crate without modularity or reloading in its design. Reasonable partitioning and abstraction allowed a split into several crates, though many circular dependencies had to be corrected. The resulting crates now form a directed graph as depicted in figures below. The interfacing between these crates is still extremely broad which is not mitigable.
Initially [hot_lib_reload][6] was investigated but found appropriate for a project designed with modularity through limited interfaces, not a large and complex existing codebase. Instead a bespoke solution built directly on [libloading][8] satisfied our constraints. This required relatively minimal modifications and zero maintenance burden compared to what would be required otherwise. The technical difference lies with relocation processing: we leverage global bindings (`RTLD_GLOBAL`) in a very intentional way. Most libraries and off-the-shelf module systems (such as [hot_lib_reload][6]) restrict themselves to local bindings (`RTLD_LOCAL`). This allows them to release software to multiple platforms with much greater consistency, but at the cost of burdening applications to explicitly manage these bindings. In our case with an optional feature for developers, we shrug any such requirement to enjoy the cost/benefit on platforms where global relocations are properly cooperative.
To make use of `RTLD_GLOBAL` the application has to be oriented as a directed acyclic graph. The primary rule is simple and illustrated in the figure below: **no crate is allowed to call a function or use a variable from a crate below it.**
![conduwuit's dynamic library setup diagram - created by Jason Volk](assets/libraries.png)
When a symbol is referenced between crates they become bound: **crates cannot be unloaded until their calling crates are first unloaded.** Thus we start the reloading process from the crate which has no callers. There is a small problem though: the first crate is called by the base executable itself! This is solved by using an `RTLD_LOCAL` binding for just one link between the main executable and the first crate, freeing the executable from all modules as no global binding ever occurs between them.
![conduwuit's reload and load order diagram - created by Jason Volk](assets/reload_order.png)
Proper resource management is essential for reliable reloading to occur. This is a very basic ask in RAII-idiomatic Rust and the exposure to reloading hazards is remarkably low, generally stemming from poor patterns and practices. Unfortunately static analysis doesn't enforce reload-safety programmatically (though it could one day), for now hazards can be avoided by knowing a few basic do's and dont's:
1. Understand that code is memory. Just like one is forbidden from referencing free'd memory, one must not transfer control to free'd code. Exposure to this is primarily from two things:
- Callbacks, which this project makes very little use of.
- Async tasks, which are addressed below.
2. Tie all resources to a scope or object lifetime with greatest possible symmetry (locality). For our purposes this applies to code resources, which means async blocks and tokio tasks.
- **Never spawn a task without receiving and storing its JoinHandle**.
- **Always wait on join handles** before leaving a scope or in another cleanup function called by an owning scope.
3. Know any minor specific quirks documented in code or here:
- Don't use `tokio::spawn`, instead use our `Handle` in `core/server.rs`, which is reachable in most of the codebase via `services()` or other state. This is due to some bugs or assumptions made in tokio, as it happens in `unsafe {}` blocks, which are mitigated by circumventing some thread-local variables. Using runtime handles is good practice in any case.
The initial implementation PR is available [here][1].
### Interesting related issues/bugs
- [DT_RUNPATH produced in binary with rpath = true is wrong (cargo)][5]
- [Disabling MIR Optimization in Rust Compilation (cargo)](https://internals.rust-lang.org/t/disabling-mir-optimization-in-rust-compilation/19066/5)
- [Workspace-level metadata (cargo-deb)](https://github.com/kornelski/cargo-deb/issues/68)
[1]: https://github.com/girlbossceo/conduwuit/pull/387
[2]: https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading-libraries
[3]: https://github.com/rust-lang/rust/issues/28794
[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/#/#conduwuit:puppygock.gay
[8]: https://crates.io/crates/libloading
+1
View File
@@ -153,6 +153,7 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
- Interest in supporting other operating systems such as macOS, BSDs, and Windows, and getting them added into CI and doing builds for them
- Add config option for disabling RocksDB Direct IO if needed
- Add various documentation on maintaining conduwuit, using RocksDB online backups, some troubleshooting, using admin commands, etc
- (Developers): Add support for [hot reloadable/"live" modular development](development/hot_reload.md)
- (Developers): Add support for tokio-console
- (Developers): Add support for tracing flame graphs
- Add `release-debuginfo` Cargo build profile
+1 -1
View File
@@ -59,4 +59,4 @@ conduwuit can ping other servers using `!admin debug ping`. This takes a server
#### Allocator memory stats
If using jemalloc (for now) and built with jemallocator's `stats` feature, you can see conduwuit's jemalloc memory stats by using `!admin debug memory-stats`
When using jemalloc with jemallocator's `stats` feature, you can see conduwuit's jemalloc memory stats by using `!admin debug memory-stats`
+13 -1
View File
@@ -58,7 +58,7 @@ script = "lychee --version"
[[task]]
name = "cargo-audit"
group = "security"
script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked --ignore RUSTSEC-2020-0016"
script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked"
[[task]]
name = "cargo-fmt"
@@ -145,3 +145,15 @@ cargo test \
-- \
--color=always
"""
# Ensure that the flake's default output can build and run without crashing
#
# This is a dynamically-linked jemalloc build, which is a case not covered by
# our other tests. We've had linking problems in the past with dynamic
# jemalloc builds that usually show up as an immediate segfault or "invalid free"
[[task]]
name = "nix-default"
group = "tests"
script = """
nix run .#default -- --help
"""
Generated
+37 -19
View File
@@ -26,11 +26,11 @@
"complement": {
"flake": false,
"locked": {
"lastModified": 1714661560,
"narHash": "sha256-E1ZiUbOgo7rWo8zt2M2vzCVSykCxK0Ot2dUAxTL6cpU=",
"lastModified": 1715700731,
"narHash": "sha256-cie+b5N/TQAFD8vF/XbqfyFJkFU0qUPDbtJQDm/TfQc=",
"owner": "matrix-org",
"repo": "complement",
"rev": "370a014dca0f720614e0c8f68b9a3e66ecf7f516",
"rev": "8587fb3cbe746754b2c883ff6c818ca4d987d0a5",
"type": "github"
},
"original": {
@@ -68,11 +68,11 @@
]
},
"locked": {
"lastModified": 1715274763,
"narHash": "sha256-3Iv1PGHJn9sV3HO4FlOVaaztOxa9uGLfOmUWrH7v7+A=",
"lastModified": 1716569590,
"narHash": "sha256-5eDbq8TuXFGGO3mqJFzhUbt5zHVTf5zilQoyW5jnJwo=",
"owner": "ipetkov",
"repo": "crane",
"rev": "27025ab71bdca30e7ed0a16c88fd74c5970fc7f5",
"rev": "109987da061a1bf452f435f1653c47511587d919",
"type": "github"
},
"original": {
@@ -90,11 +90,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1715322226,
"narHash": "sha256-ezoe/FwfJpA7sskLoLP2iwfwkYnscEFCP6Vk5kPwh9k=",
"lastModified": 1716359173,
"narHash": "sha256-pYcjP6Gy7i6jPWrjiWAVV0BCQp+DdmGaI/k65lBb/kM=",
"owner": "nix-community",
"repo": "fenix",
"rev": "297c756ba6249d483c1dafe42378560458842173",
"rev": "b6fc5035b28e36a98370d0eac44f4ef3fd323df6",
"type": "github"
},
"original": {
@@ -171,6 +171,23 @@
"type": "github"
}
},
"liburing": {
"flake": false,
"locked": {
"lastModified": 1716565485,
"narHash": "sha256-4R19aJNQYs6vb0/Hz4bWT56YN1P1DkFL/sxdE4Yj0CE=",
"owner": "axboe",
"repo": "liburing",
"rev": "b90c0e670a93caabbebe2d9e24ff85cece4cfe0e",
"type": "github"
},
"original": {
"owner": "axboe",
"ref": "master",
"repo": "liburing",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1710156097,
@@ -221,11 +238,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1715266358,
"narHash": "sha256-doPgfj+7FFe9rfzWo1siAV2mVCasW+Bh8I1cToAXEE4=",
"lastModified": 1716330097,
"narHash": "sha256-8BO3B7e3BiyIDsaKA0tY8O88rClYRTjvAp66y+VBUeU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f1010e0469db743d14519a1efd37e23f8513d714",
"rev": "5710852ba686cc1fd0d3b8e22b3117d43ba374c2",
"type": "github"
},
"original": {
@@ -238,16 +255,16 @@
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1714770052,
"narHash": "sha256-NCPYF2wYBsB9OHEkZSOYoPlxjC9BBMhJp8EM5M1o3Mc=",
"lastModified": 1716773462,
"narHash": "sha256-5kUH+XK+2lbFfUgbxuNy3YMLHbp6scfWPdtc8za1wDM=",
"owner": "girlbossceo",
"repo": "rocksdb",
"rev": "db6df0b185774778457dabfcbd822cb81760cade",
"rev": "c8a1450231e9c608edf535538dbe8ca1a8d2f3bc",
"type": "github"
},
"original": {
"owner": "girlbossceo",
"ref": "v9.1.1",
"ref": "v9.2.1",
"repo": "rocksdb",
"type": "github"
}
@@ -260,6 +277,7 @@
"fenix": "fenix",
"flake-compat": "flake-compat_2",
"flake-utils": "flake-utils_2",
"liburing": "liburing",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_2",
"rocksdb": "rocksdb"
@@ -268,11 +286,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1715255944,
"narHash": "sha256-vLLgYpdtKBaGYTamNLg1rbRo1bPXp4Jgded/gnprPVw=",
"lastModified": 1716107283,
"narHash": "sha256-NJgrwLiLGHDrCia5AeIvZUHUY7xYGVryee0/9D3Ir1I=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "5bf2f85c8054d80424899fa581db1b192230efb5",
"rev": "21ec8f523812b88418b2bfc64240c62b3dd967bd",
"type": "github"
},
"original": {
+89 -77
View File
@@ -9,13 +9,15 @@
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
# https://github.com/girlbossceo/rocksdb/commit/db6df0b185774778457dabfcbd822cb81760cade
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.1.1"; flake = false; };
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.2.1"; flake = false; };
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
};
outputs = inputs:
inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgsHost = inputs.nixpkgs.legacyPackages.${system};
pkgsHostStatic = pkgsHost.pkgsStatic;
# The Rust toolchain to use
toolchain = inputs.fenix.packages.${system}.fromToolchainFile {
@@ -25,7 +27,8 @@
sha256 = "sha256-+syqAd2kX8KVa8/U2gz3blIQTTsYYt3U63xBWaGOSc8";
};
scope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
inherit pkgs;
book = self.callPackage ./nix/pkgs/book {};
complement = self.callPackage ./nix/pkgs/complement {};
craneLib = ((inputs.crane.mkLib pkgs).overrideToolchain toolchain);
@@ -39,22 +42,87 @@
(builtins.fromJSON (builtins.readFile ./flake.lock))
.nodes.rocksdb.original.ref;
});
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
liburing = pkgs.liburing.overrideAttrs (old: {
# the configure script doesn't support these, and unconditionally
# builds both static and dynamic libraries.
configureFlags = pkgs.lib.subtractLists
[ "--enable-static" "--disable-shared" ]
old.configureFlags;
postInstall = old.postInstall + ''
# we remove the extra outputs
#
# we need to do this to prevent rocksdb from trying to link the
# static library in a dynamic stdenv
rm $out/lib/liburing*${
if pkgs.stdenv.hostPlatform.isStatic then ".so*" else ".a"
}
'';
});
});
scopeHost = (scope pkgsHost);
scopeHost = mkScope pkgsHost;
scopeHostStatic = mkScope pkgsHostStatic;
mkDevShell = scope: scope.pkgs.mkShell {
env = scope.main.env // {
# Rust Analyzer needs to be able to find the path to default crate
# sources, and it can read this environment variable to do so. The
# `rust-src` component is required in order for this to work.
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
# Convenient way to access a pinned version of Complement's source
# code.
COMPLEMENT_SRC = inputs.complement.outPath;
# Needed for Complement
CGO_CFLAGS = "-I${scope.pkgs.olm}/include";
CGO_LDFLAGS = "-L${scope.pkgs.olm}/lib";
};
# Development tools
packages = [
# Always use nightly rustfmt because most of its options are unstable
#
# This needs to come before `toolchain` in this list, otherwise
# `$PATH` will have stable rustfmt instead.
inputs.fenix.packages.${system}.latest.rustfmt
toolchain
]
++ (with pkgsHost.pkgs; [
engage
cargo-audit
# Needed for producing Debian packages
cargo-deb
# Needed for Complement
go
# Needed for our script for Complement
jq
# Needed for finding broken markdown links
lychee
# Useful for editing the book locally
mdbook
])
++ scope.main.buildInputs
++ scope.main.propagatedBuildInputs
++ scope.main.nativeBuildInputs;
meta.broken = scope.main.meta.broken;
};
in
{
packages = {
default = scopeHost.main;
jemalloc = scopeHost.main.override { features = ["jemalloc"]; };
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
oci-image = scopeHost.oci-image;
oci-image-jemalloc = scopeHost.oci-image.override {
main = scopeHost.main.override {
features = ["jemalloc"];
};
};
oci-image-hmalloc = scopeHost.oci-image.override {
main = scopeHost.main.override {
features = ["hardened_malloc"];
@@ -64,6 +132,7 @@
book = scopeHost.book;
complement = scopeHost.complement;
static-complement = scopeHostStatic.complement;
}
//
builtins.listToAttrs
@@ -79,7 +148,7 @@
config = crossSystem;
};
}).pkgsStatic;
scopeCrossStatic = scope pkgsCrossStatic;
scopeCrossStatic = mkScope pkgsCrossStatic;
in
[
# An output for a statically-linked binary
@@ -88,14 +157,6 @@
value = scopeCrossStatic.main;
}
# An output for a statically-linked binary with jemalloc
{
name = "${binaryName}-jemalloc";
value = scopeCrossStatic.main.override {
features = ["jemalloc"];
};
}
# An output for a statically-linked binary with hardened_malloc
{
name = "${binaryName}-hmalloc";
@@ -110,16 +171,6 @@
value = scopeCrossStatic.oci-image;
}
# An output for an OCI image based on that binary with jemalloc
{
name = "oci-image-${crossSystem}-jemalloc";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
features = ["jemalloc"];
};
};
}
# An output for an OCI image based on that binary with hardened_malloc
{
name = "oci-image-${crossSystem}-hmalloc";
@@ -138,54 +189,15 @@
)
);
devShells.default = pkgsHost.mkShell {
env = scopeHost.main.env // {
# Rust Analyzer needs to be able to find the path to default crate
# sources, and it can read this environment variable to do so. The
# `rust-src` component is required in order for this to work.
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
# Convenient way to access a pinned version of Complement's source
# code.
COMPLEMENT_SRC = inputs.complement.outPath;
};
# Development tools
packages = [
# Always use nightly rustfmt because most of its options are unstable
#
# This needs to come before `toolchain` in this list, otherwise
# `$PATH` will have stable rustfmt instead.
inputs.fenix.packages.${system}.latest.rustfmt
toolchain
]
++ (with pkgsHost; [
engage
cargo-audit
# Needed for producing Debian packages
cargo-deb
# Needed for Complement
go
olm
# Needed for our script for Complement
jq
# Needed for finding broken markdown links
lychee
# Useful for editing the book locally
mdbook
])
++ (if !pkgsHost.stdenv.isDarwin then [
# Needed for building with io_uring
pkgsHost.liburing
] else [])
++
scopeHost.main.nativeBuildInputs;
};
devShells.default = mkDevShell scopeHostStatic;
devShells.all-features = mkDevShell
(scopeHostStatic.overrideScope (final: prev: {
main = prev.main.override { all_features = true; };
}));
devShells.no-features = mkDevShell
(scopeHostStatic.overrideScope (final: prev: {
main = prev.main.override { default_features = false; };
}));
devShells.dynamic = mkDevShell scopeHost;
});
}
+1 -3
View File
@@ -44,9 +44,7 @@ let
-sha256
${lib.getExe' coreutils "env"} \
CONDUIT_SERVER_NAME="$SERVER_NAME" \
CONDUIT_WELL_KNOWN_SERVER="$SERVER_NAME:8448" \
CONDUIT_WELL_KNOWN_SERVER="$SERVER_NAME:8008" \
CONDUWUIT_SERVER_NAME="$SERVER_NAME" \
${lib.getExe main'}
'';
in
+87 -14
View File
@@ -1,27 +1,91 @@
# Dependencies (keep sorted)
{ craneLib
, inputs
, jq
, lib
, libiconv
, liburing
, pkgsBuildHost
, rocksdb
, rust
, rust-jemalloc-sys
, stdenv
# Options (keep sorted)
, default_features ? true
, disable_release_max_log_level ? false
, all_features ? false
, disable_features ? []
, features ? []
, profile ? "release"
}:
let
# We perform default-feature unification in nix, because some of the dependencies
# on the nix side depend on feature values.
workspaceMembers = builtins.map (member: "${inputs.self}/src/${member}")
(builtins.attrNames (builtins.readDir "${inputs.self}/src"));
crateFeatures = path:
let manifest = lib.importTOML "${path}/Cargo.toml"; in
lib.remove "default" (lib.attrNames manifest.features) ++
lib.attrNames
(lib.filterAttrs
(_: dependency: dependency.optional or false)
manifest.dependencies);
crateDefaultFeatures = path:
(lib.importTOML "${path}/Cargo.toml").features.default;
allDefaultFeatures = lib.unique
(lib.flatten (builtins.map crateDefaultFeatures workspaceMembers));
allFeatures = lib.unique
(lib.flatten (builtins.map crateFeatures workspaceMembers));
features' = lib.unique
(features ++
lib.optionals default_features allDefaultFeatures ++
lib.optionals all_features allFeatures);
disable_features' = disable_features ++ lib.optionals disable_release_max_log_level ["release_max_log_level"];
features'' = lib.subtractLists disable_features' features';
featureEnabled = feature : builtins.elem feature features'';
enableLiburing = featureEnabled "io_uring" && stdenv.isLinux;
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
# own. In order for this to work, we need to set flags on the build that match
# whatever flags tikv-jemalloc-sys was going to use. These are dependent on
# which features we enable in tikv-jemalloc-sys.
rust-jemalloc-sys' = (rust-jemalloc-sys.override {
# tikv-jemalloc-sys/unprefixed_malloc_on_supported_platforms feature
unprefixed = true;
}).overrideAttrs (old: {
configureFlags = old.configureFlags ++
# tikv-jemalloc-sys/profiling feature
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof";
});
buildDepsOnlyEnv =
let
rocksdb' = rocksdb.override {
enableJemalloc = builtins.elem "jemalloc" features;
};
rocksdb' = (rocksdb.override {
jemalloc = rust-jemalloc-sys';
# rocksdb fails to build with prefixed jemalloc, which is required on
# darwin due to [1]. In this case, fall back to building rocksdb with
# libc malloc. This should not cause conflicts, because all of the
# jemalloc symbols are prefixed.
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
enableJemalloc = featureEnabled "jemalloc" && !stdenv.isDarwin;
}).overrideAttrs (old: {
# TODO: static rocksdb fails to build on darwin
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
# TODO: switch to enableUring option once https://github.com/NixOS/nixpkgs/pull/314945 is available
buildInputs = old.buildInputs ++ lib.optional enableLiburing liburing;
});
in
{
# https://crane.dev/faq/rebuilds-bindgen.html
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
CARGO_PROFILE = profile;
ROCKSDB_INCLUDE_DIR = "${rocksdb'}/include";
ROCKSDB_LIB_DIR = "${rocksdb'}/lib";
@@ -38,7 +102,14 @@ buildDepsOnlyEnv =
buildPackageEnv = {
CONDUWUIT_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
} // buildDepsOnlyEnv;
} // buildDepsOnlyEnv // {
# Only needed in static stdenv because these are transitive dependencies of rocksdb
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
" -L${lib.getLib liburing}/lib -luring";
};
commonAttrs = {
inherit
@@ -55,16 +126,24 @@ commonAttrs = {
include = [
"Cargo.lock"
"Cargo.toml"
"hot_lib"
"deps"
"src"
];
};
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
nativeBuildInputs = [
# bindgen needs the build platform's libclang. Apparently due to "splicing
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
# right thing here.
pkgsBuildHost.rustPlatform.bindgenHook
# We don't actually depend on `jq`, but crane's `buildPackage` does, but
# its `buildDepsOnly` doesn't. This causes those two derivations to have
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
# rebuilds of bindgen and its depedents.
jq
]
++ lib.optionals stdenv.isDarwin [
# https://github.com/NixOS/nixpkgs/issues/206242
@@ -82,22 +161,16 @@ craneLib.buildPackage ( commonAttrs // {
env = buildDepsOnlyEnv;
});
cargoExtraArgs = ""
cargoExtraArgs = "--no-default-features "
+ lib.optionalString
(!default_features)
"--no-default-features "
+ lib.optionalString
(features != [])
"--features " + (builtins.concatStringsSep "," features);
(features'' != [])
"--features " + (builtins.concatStringsSep "," features'');
# This is redundant with CI
cargoTestCommand = "";
cargoCheckCommand = "";
doCheck = false;
# https://crane.dev/faq/rebuilds-bindgen.html
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
env = buildPackageEnv;
passthru = {
+2 -1
View File
@@ -11,5 +11,6 @@
},
"nix": {
"enabled": true
}
},
"labels": ["dependencies", "github_actions"]
}
+63
View File
@@ -0,0 +1,63 @@
[package]
name = "conduit_admin"
version.workspace = true
edition.workspace = true
[lib]
path = "mod.rs"
crate-type = [
"rlib",
# "dylib",
]
[features]
default = [
"rocksdb",
"io_uring",
"jemalloc",
"zstd_compression",
"release_max_log_level",
]
dev_release_log_level = []
release_max_log_level = [
"tracing/max_level_trace",
"tracing/release_max_level_info",
"log/max_level_trace",
"log/release_max_level_info",
]
rocksdb = [
"dep:rust-rocksdb",
]
jemalloc = [
"rust-rocksdb/jemalloc",
]
io_uring = [
"rust-rocksdb/io-uring",
]
zstd_compression = [
"rust-rocksdb/zstd",
]
[dependencies]
clap.workspace = true
conduit-api.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
conduit-service.workspace = true
futures-util.workspace = true
log.workspace = true
loole.workspace = true
regex.workspace = true
ruma.workspace = true
rust-rocksdb.optional = true
rust-rocksdb.workspace = true
serde_json.workspace = true
serde.workspace = true
serde_yaml.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
[lints]
workspace = true
@@ -1,6 +1,6 @@
use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent};
use crate::{service::admin::escape_html, services, Result};
use crate::{escape_html, services, Result};
pub(crate) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
@@ -1,8 +1,8 @@
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::appservice_command::{list, register, show, unregister};
use crate::Result;
pub(crate) mod appservice_command;
@@ -1,18 +1,15 @@
use std::{collections::BTreeMap, sync::Arc, time::Instant};
use conduit::{utils::HtmlEscape, Error, Result};
use ruma::{
api::client::error::ErrorKind, events::room::message::RoomMessageEventContent, CanonicalJsonObject, EventId,
RoomId, RoomVersionId, ServerName,
};
use service::{rooms::event_handler::parse_incoming_pdu, sending::send::resolve_actual_dest, services, PduEvent};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};
use tracing_subscriber::EnvFilter;
use crate::{
api::server_server::parse_incoming_pdu, service::sending::send::resolve_actual_dest, services, utils::HtmlEscape,
Error, PduEvent, Result,
};
pub(crate) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
let event_id = Arc::<EventId>::from(event_id);
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? {
@@ -127,7 +124,9 @@ pub(crate) async fn get_remote_pdu_list(
for pdu in list {
if force {
_ = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await;
if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await {
warn!(%e, "Failed to get remote PDU, ignoring error");
}
} else {
get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?;
}
@@ -332,7 +331,7 @@ pub(crate) async fn change_log_level(
};
match services()
.globals
.server
.tracing_reload_handle
.reload(&old_filter_layer)
{
@@ -361,7 +360,7 @@ pub(crate) async fn change_log_level(
};
match services()
.globals
.server
.tracing_reload_handle
.reload(&new_filter_layer)
{
@@ -447,15 +446,16 @@ pub(crate) async fn resolve_true_destination(
));
}
let (actual_dest, hostname_uri) = resolve_actual_dest(&server_name, no_cache, true).await?;
let (actual_dest, hostname_uri) = resolve_actual_dest(&server_name, no_cache).await?;
Ok(RoomMessageEventContent::text_plain(format!(
"Actual destination: {actual_dest:?} | Hostname URI: {hostname_uri}"
)))
}
#[must_use]
pub(crate) fn memory_stats() -> RoomMessageEventContent {
let html_body = crate::alloc::memory_stats();
let html_body = conduit::alloc::memory_stats();
if html_body.is_empty() {
return RoomMessageEventContent::text_plain("malloc stats are not supported on your compiled malloc.");
@@ -1,13 +1,8 @@
use std::fmt::Write as _;
use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
use crate::{
service::admin::{escape_html, get_room_info},
services,
utils::HtmlEscape,
Result,
};
use crate::{escape_html, get_room_info, services, utils::HtmlEscape, Result};
pub(crate) async fn disable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
services().rooms.metadata.disable_room(&room_id, true)?;
@@ -25,7 +20,8 @@ pub(crate) async fn incoming_federeation(_body: Vec<&str>) -> Result<RoomMessage
for (r, (e, i)) in map.iter() {
let elapsed = i.elapsed();
let _ = writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60);
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60,)
.expect("should be able to write to string buffer");
}
Ok(RoomMessageEventContent::text_plain(&msg))
}
@@ -125,7 +121,7 @@ pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>)
members,
escape_html(name)
)
.unwrap();
.expect("should be able to write to string buffer");
output
})
);
+305
View File
@@ -0,0 +1,305 @@
use std::sync::Arc;
use clap::Parser;
use regex::Regex;
use ruma::{
events::{
relation::InReplyTo,
room::message::{Relation::Reply, RoomMessageEventContent},
TimelineEventType,
},
OwnedRoomId, OwnedUserId, ServerName, UserId,
};
use serde_json::value::to_raw_value;
use tokio::sync::MutexGuard;
use tracing::error;
extern crate conduit_service as service;
use conduit::{Error, Result};
pub(crate) use service::admin::{AdminRoomEvent, Service};
use service::{admin::HandlerResult, pdu::PduBuilder};
use self::{fsck::FsckCommand, tester::TesterCommands};
use crate::{
appservice, appservice::AppserviceCommand, debug, debug::DebugCommand, escape_html, federation,
federation::FederationCommand, fsck, media, media::MediaCommand, query, query::QueryCommand, room,
room::RoomCommand, server, server::ServerCommand, services, tester, user, user::UserCommand,
};
pub(crate) const PAGE_SIZE: usize = 100;
#[cfg_attr(test, derive(Debug))]
#[derive(Parser)]
#[command(name = "@conduit:server.name:", version = env!("CARGO_PKG_VERSION"))]
pub(crate) enum AdminCommand {
#[command(subcommand)]
/// - Commands for managing appservices
Appservices(AppserviceCommand),
#[command(subcommand)]
/// - Commands for managing local users
Users(UserCommand),
#[command(subcommand)]
/// - Commands for managing rooms
Rooms(RoomCommand),
#[command(subcommand)]
/// - Commands for managing federation
Federation(FederationCommand),
#[command(subcommand)]
/// - Commands for managing the server
Server(ServerCommand),
#[command(subcommand)]
/// - Commands for managing media
Media(MediaCommand),
#[command(subcommand)]
/// - Commands for debugging things
Debug(DebugCommand),
#[command(subcommand)]
/// - Query all the database getters and iterators
Query(QueryCommand),
#[command(subcommand)]
/// - Query all the database getters and iterators
Fsck(FsckCommand),
#[command(subcommand)]
Tester(TesterCommands),
}
#[must_use]
pub fn handle(event: AdminRoomEvent, room: OwnedRoomId, user: OwnedUserId) -> HandlerResult {
Box::pin(handle_event(event, room, user))
}
async fn handle_event(event: AdminRoomEvent, admin_room: OwnedRoomId, server_user: OwnedUserId) -> Result<()> {
let (mut message_content, reply) = match event {
AdminRoomEvent::SendMessage(content) => (content, None),
AdminRoomEvent::ProcessMessage(room_message, reply_id) => {
(process_admin_message(room_message).await, Some(reply_id))
},
};
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(admin_room.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
if let Some(reply) = reply {
message_content.relates_to = Some(Reply {
in_reply_to: InReplyTo {
event_id: reply.into(),
},
});
}
let response_pdu = PduBuilder {
event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&message_content).expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
};
if let Err(e) = services()
.rooms
.timeline
.build_and_append_pdu(response_pdu, &server_user, &admin_room, &state_lock)
.await
{
handle_response_error(&e, &admin_room, &server_user, &state_lock).await?;
}
Ok(())
}
async fn handle_response_error(
e: &Error, admin_room: &OwnedRoomId, server_user: &UserId, state_lock: &MutexGuard<'_, ()>,
) -> Result<()> {
error!("Failed to build and append admin room response PDU: \"{e}\"");
let error_room_message = RoomMessageEventContent::text_plain(format!(
"Failed to build and append admin room PDU: \"{e}\"\n\nThe original admin command may have finished \
successfully, but we could not return the output."
));
let response_pdu = PduBuilder {
event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&error_room_message).expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
};
services()
.rooms
.timeline
.build_and_append_pdu(response_pdu, server_user, admin_room, state_lock)
.await?;
Ok(())
}
// Parse and process a message from the admin room
async fn process_admin_message(room_message: String) -> RoomMessageEventContent {
let mut lines = room_message.lines().filter(|l| !l.trim().is_empty());
let command_line = lines.next().expect("each string has at least one line");
let body = lines.collect::<Vec<_>>();
let admin_command = match parse_admin_command(command_line) {
Ok(command) => command,
Err(error) => {
let server_name = services().globals.server_name();
let message = error.replace("server.name", server_name.as_str());
let html_message = usage_to_html(&message, server_name);
return RoomMessageEventContent::text_html(message, html_message);
},
};
match process_admin_command(admin_command, body).await {
Ok(reply_message) => reply_message,
Err(error) => {
let markdown_message = format!("Encountered an error while handling the command:\n```\n{error}\n```",);
let html_message = format!("Encountered an error while handling the command:\n<pre>\n{error}\n</pre>",);
RoomMessageEventContent::text_html(markdown_message, html_message)
},
}
}
// Parse chat messages from the admin room into an AdminCommand object
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> {
// Note: argv[0] is `@conduit:servername:`, which is treated as the main command
let mut argv = command_line.split_whitespace().collect::<Vec<_>>();
// Replace `help command` with `command --help`
// Clap has a help subcommand, but it omits the long help description.
if argv.len() > 1 && argv[1] == "help" {
argv.remove(1);
argv.push("--help");
}
// Backwards compatibility with `register_appservice`-style commands
let command_with_dashes_argv1;
if argv.len() > 1 && argv[1].contains('_') {
command_with_dashes_argv1 = argv[1].replace('_', "-");
argv[1] = &command_with_dashes_argv1;
}
// Backwards compatibility with `register_appservice`-style commands
let command_with_dashes_argv2;
if argv.len() > 2 && argv[2].contains('_') {
command_with_dashes_argv2 = argv[2].replace('_', "-");
argv[2] = &command_with_dashes_argv2;
}
// if the user is using the `query` command (argv[1]), replace the database
// function/table calls with underscores to match the codebase
let command_with_dashes_argv3;
if argv.len() > 3 && argv[1].eq("query") {
command_with_dashes_argv3 = argv[3].replace('_', "-");
argv[3] = &command_with_dashes_argv3;
}
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
}
async fn process_admin_command(command: AdminCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
let reply_message_content = match command {
AdminCommand::Appservices(command) => appservice::process(command, body).await?,
AdminCommand::Media(command) => media::process(command, body).await?,
AdminCommand::Users(command) => user::process(command, body).await?,
AdminCommand::Rooms(command) => room::process(command, body).await?,
AdminCommand::Federation(command) => federation::process(command, body).await?,
AdminCommand::Server(command) => server::process(command, body).await?,
AdminCommand::Debug(command) => debug::process(command, body).await?,
AdminCommand::Query(command) => query::process(command, body).await?,
AdminCommand::Fsck(command) => fsck::process(command, body).await?,
AdminCommand::Tester(command) => tester::process(command, body).await?,
};
Ok(reply_message_content)
}
// Utility to turn clap's `--help` text to HTML.
fn usage_to_html(text: &str, server_name: &ServerName) -> String {
// Replace `@conduit:servername:-subcmdname` with `@conduit:servername:
// subcmdname`
let text = text.replace(&format!("@conduit:{server_name}:-"), &format!("@conduit:{server_name}: "));
// For the conduit admin room, subcommands become main commands
let text = text.replace("SUBCOMMAND", "COMMAND");
let text = text.replace("subcommand", "command");
// Escape option names (e.g. `<element-id>`) since they look like HTML tags
let text = escape_html(&text);
// Italicize the first line (command name and version text)
let re = Regex::new("^(.*?)\n").expect("Regex compilation should not fail");
let text = re.replace_all(&text, "<em>$1</em>\n");
// Unmerge wrapped lines
let text = text.replace("\n ", " ");
// Wrap option names in backticks. The lines look like:
// -V, --version Prints version information
// And are converted to:
// <code>-V, --version</code>: Prints version information
// (?m) enables multi-line mode for ^ and $
let re = Regex::new("(?m)^ {4}(([a-zA-Z_&;-]+(, )?)+) +(.*)$").expect("Regex compilation should not fail");
let text = re.replace_all(&text, "<code>$1</code>: $4");
// Look for a `[commandbody]` tag. If it exists, use all lines below it that
// start with a `#` in the USAGE section.
let mut text_lines = text.lines().collect::<Vec<&str>>();
let mut command_body = String::new();
if let Some(line_index) = text_lines.iter().position(|line| *line == "[commandbody]") {
text_lines.remove(line_index);
while text_lines
.get(line_index)
.is_some_and(|line| line.starts_with('#'))
{
command_body += if text_lines[line_index].starts_with("# ") {
&text_lines[line_index][2..]
} else {
&text_lines[line_index][1..]
};
command_body += "[nobr]\n";
text_lines.remove(line_index);
}
}
let text = text_lines.join("\n");
// Improve the usage section
let text = if command_body.is_empty() {
// Wrap the usage line in code tags
let re = Regex::new("(?m)^USAGE:\n {4}(@conduit:.*)$").expect("Regex compilation should not fail");
re.replace_all(&text, "USAGE:\n<code>$1</code>").to_string()
} else {
// Wrap the usage line in a code block, and add a yaml block example
// This makes the usage of e.g. `register-appservice` more accurate
let re = Regex::new("(?m)^USAGE:\n {4}(.*?)\n\n").expect("Regex compilation should not fail");
re.replace_all(&text, "USAGE:\n<pre>$1[nobr]\n[commandbodyblock]</pre>")
.replace("[commandbodyblock]", &command_body)
};
// Add HTML line-breaks
text.replace("\n\n\n", "\n\n")
.replace('\n', "<br>\n")
.replace("[nobr]<br>", "")
}
@@ -1,7 +1,7 @@
use ruma::{events::room::message::RoomMessageEventContent, EventId};
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
use tracing::{debug, info};
use crate::{service::admin::MxcUri, services, Result};
use crate::{services, Result};
pub(crate) async fn delete(
_body: Vec<&str>, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
@@ -1,8 +1,8 @@
use clap::Subcommand;
use ruma::{events::room::message::RoomMessageEventContent, EventId};
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
use self::media_commands::{delete, delete_list, delete_past_remote_media};
use crate::{service::admin::MxcUri, Result};
use crate::Result;
pub(crate) mod media_commands;
+55
View File
@@ -0,0 +1,55 @@
pub(crate) mod appservice;
pub(crate) mod debug;
pub(crate) mod federation;
pub(crate) mod fsck;
pub(crate) mod handler;
pub(crate) mod media;
pub(crate) mod query;
pub(crate) mod room;
pub(crate) mod server;
pub(crate) mod tester;
pub(crate) mod user;
pub(crate) mod utils;
extern crate conduit_api as api;
extern crate conduit_core as conduit;
extern crate conduit_service as service;
pub(crate) use conduit::{mod_ctor, mod_dtor, Result};
pub use handler::handle;
pub(crate) use service::{services, user_is_local};
pub(crate) use crate::{
handler::Service,
utils::{escape_html, get_room_info},
};
mod_ctor! {}
mod_dtor! {}
#[cfg(test)]
mod test {
use clap::Parser;
use crate::handler::AdminCommand;
#[test]
fn get_help_short() { get_help_inner("-h"); }
#[test]
fn get_help_long() { get_help_inner("--help"); }
#[test]
fn get_help_subcommand() { get_help_inner("help"); }
fn get_help_inner(input: &str) {
let error = AdminCommand::try_parse_from(["argv[0] doesn't matter", input])
.unwrap_err()
.to_string();
// Search for a handful of keywords that suggest the help printed properly
assert!(error.contains("Usage:"));
assert!(error.contains("Commands:"));
assert!(error.contains("Options:"));
}
}
@@ -46,7 +46,7 @@ pub(crate) enum RoomAliasCommand {
room_alias_localpart: String,
},
/// - Remove an alias
/// - Remove a local alias
Remove {
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
@@ -1,9 +1,9 @@
use std::fmt::Write as _;
use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId};
use super::RoomAliasCommand;
use crate::{service::admin::escape_html, services, Result};
use crate::{escape_html, services, Result};
pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
@@ -79,12 +79,13 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
match aliases {
Ok(aliases) => {
let plain_list = aliases.iter().fold(String::new(), |mut output, alias| {
writeln!(output, "- {alias}").unwrap();
writeln!(output, "- {alias}").expect("should be able to write to string buffer");
output
});
let html_list = aliases.iter().fold(String::new(), |mut output, alias| {
writeln!(output, "<li>{}</li>", escape_html(alias.as_ref())).unwrap();
writeln!(output, "<li>{}</li>", escape_html(alias.as_ref()))
.expect("should be able to write to string buffer");
output
});
@@ -106,7 +107,8 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
let plain_list = aliases
.iter()
.fold(String::new(), |mut output, (alias, id)| {
writeln!(output, "- `{alias}` -> #{id}:{server_name}").unwrap();
writeln!(output, "- `{alias}` -> #{id}:{server_name}")
.expect("should be able to write to string buffer");
output
});
@@ -120,7 +122,7 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
escape_html(id.as_ref()),
server_name
)
.unwrap();
.expect("should be able to write to string buffer");
output
});
@@ -1,11 +1,8 @@
use std::fmt::Write as _;
use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
use crate::{
service::admin::{escape_html, get_room_info, PAGE_SIZE},
services, Result,
};
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMessageEventContent> {
// TODO: i know there's a way to do this with clap, but i can't seem to find it
@@ -51,7 +48,7 @@ pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMe
members,
escape_html(name)
)
.unwrap();
.expect("should be able to write to string buffer");
output
})
);
@@ -1,12 +1,9 @@
use std::fmt::Write as _;
use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
use super::RoomDirectoryCommand;
use crate::{
service::admin::{escape_html, get_room_info, PAGE_SIZE},
services, Result,
};
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
@@ -68,7 +65,7 @@ pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
members,
escape_html(name.as_ref())
)
.unwrap();
.expect("should be able to write to string buffer");
output
})
);
@@ -1,18 +1,16 @@
use std::fmt::Write as _;
use std::fmt::Write;
use api::client_server::{get_alias_helper, leave_room};
use ruma::{
events::room::message::RoomMessageEventContent, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId,
};
use tracing::{debug, error, info, warn};
use super::RoomModerationCommand;
use crate::{
api::client_server::{get_alias_helper, leave_room},
service::admin::{escape_html, Service},
services,
utils::user_id::user_is_local,
Result,
use super::{
super::{escape_html, Service},
RoomModerationCommand,
};
use crate::{services, user_is_local, Result};
pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
@@ -105,16 +103,16 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) ->
.filter_map(|user| {
user.ok().filter(|local_user| {
user_is_local(local_user)
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
&& (user_is_local(local_user)
&& services()
.users
.is_admin(local_user)
.unwrap_or(true)) // since this is a force
// operation, assume user
// is an admin if somehow
// this fails
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
&& (user_is_local(local_user)
&& services()
.users
.is_admin(local_user)
.unwrap_or(true)) // since this is a force
// operation, assume user
// is an admin if somehow
// this fails
})
})
.collect::<Vec<OwnedUserId>>()
@@ -124,7 +122,9 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) ->
&local_user, &room_id
);
_ = leave_room(&local_user, &room_id, None).await;
if let Err(e) = leave_room(&local_user, &room_id, None).await {
warn!(%e, "Failed to leave room");
}
}
} else {
for local_user in services()
@@ -134,14 +134,14 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) ->
.filter_map(|user| {
user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name()
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& !services()
.users
.is_admin(local_user)
.unwrap_or(false))
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& !services()
.users
.is_admin(local_user)
.unwrap_or(false))
})
})
.collect::<Vec<OwnedUserId>>()
@@ -170,8 +170,8 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) ->
}
Ok(RoomMessageEventContent::text_plain(
"Room banned and removed all our local users, use disable-room to stop receiving new inbound \
federation events as well if needed.",
"Room banned and removed all our local users, use `!admin federation disable-room` to stop receiving \
new inbound federation events as well if needed.",
))
},
RoomModerationCommand::BanListOfRooms {
@@ -309,19 +309,19 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) ->
.filter_map(|user| {
user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name()
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& services()
.users
.is_admin(local_user)
.unwrap_or(true)) // since this is a
// force operation,
// assume user is
// an admin if
// somehow this
// fails
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& services()
.users
.is_admin(local_user)
.unwrap_or(true)) // since this is a
// force operation,
// assume user is
// an admin if
// somehow this
// fails
})
})
.collect::<Vec<OwnedUserId>>()
@@ -331,7 +331,9 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) ->
admins too)",
&local_user, room_id
);
_ = leave_room(&local_user, &room_id, None).await;
if let Err(e) = leave_room(&local_user, &room_id, None).await {
warn!(%e, "Failed to leave room");
}
}
} else {
for local_user in services()
@@ -341,14 +343,14 @@ pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) ->
.filter_map(|user| {
user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name()
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& !services()
.users
.is_admin(local_user)
.unwrap_or(false))
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& !services()
.users
.is_admin(local_user)
.unwrap_or(false))
})
})
.collect::<Vec<OwnedUserId>>()
@@ -4,7 +4,7 @@ use crate::{services, Result};
pub(crate) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let seconds = services()
.globals
.server
.started
.elapsed()
.expect("standard duration")
@@ -28,7 +28,7 @@ pub(crate) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventCont
pub(crate) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let response0 = services().memory_usage().await;
let response1 = services().globals.db.memory_usage();
let response2 = crate::alloc::memory_usage();
let response2 = conduit::alloc::memory_usage();
Ok(RoomMessageEventContent::text_plain(format!(
"Services:\n{response0}\n\nDatabase:\n{response1}\n{}",
@@ -69,12 +69,15 @@ pub(crate) async fn backup_database(_body: Vec<&str>) -> Result<RoomMessageEvent
));
}
let mut result = tokio::task::spawn_blocking(move || match services().globals.db.backup() {
Ok(()) => String::new(),
Err(e) => (*e).to_string(),
})
.await
.unwrap();
let mut result = services()
.server
.runtime()
.spawn_blocking(move || match services().globals.db.backup() {
Ok(()) => String::new(),
Err(e) => (*e).to_string(),
})
.await
.unwrap();
if result.is_empty() {
result = services().globals.db.backup_list()?;
@@ -9,6 +9,6 @@ pub(crate) enum TesterCommands {
}
pub(crate) async fn process(command: TesterCommands, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
TesterCommands::Tester => RoomMessageEventContent::notice_plain(String::from("complete")),
TesterCommands::Tester => RoomMessageEventContent::notice_plain(String::from("completed")),
})
}
@@ -1,15 +1,13 @@
use std::{fmt::Write as _, sync::Arc};
use api::client_server::{join_room_by_id_helper, leave_all_rooms};
use conduit::utils;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, UserId};
use tracing::{error, info, warn};
use crate::{
api::client_server::{join_room_by_id_helper, leave_all_rooms, AUTO_GEN_PASSWORD_LENGTH},
service::admin::{escape_html, get_room_info},
services,
utils::{self, user_id::user_is_local},
Result,
};
use crate::{escape_html, get_room_info, services, user_is_local, Result};
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
pub(crate) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
match services().users.list_local_users() {
@@ -67,7 +65,8 @@ pub(crate) async fn create(
.new_user_displayname_suffix
.is_empty()
{
_ = write!(displayname, " {}", services().globals.config.new_user_displayname_suffix);
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer");
}
services()
@@ -111,7 +110,7 @@ pub(crate) async fn create(
)
.await
{
Ok(_) => {
Ok(_response) => {
info!("Automatically joined room {room} for user {user_id}");
},
Err(e) => {
+30
View File
@@ -0,0 +1,30 @@
pub(crate) use conduit::utils::HtmlEscape;
use ruma::OwnedRoomId;
use crate::services;
pub(crate) fn escape_html(s: &str) -> String {
s.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
pub(crate) fn get_room_info(id: &OwnedRoomId) -> (OwnedRoomId, u64, String) {
(
id.clone(),
services()
.rooms
.state_cache
.room_joined_count(id)
.ok()
.flatten()
.unwrap_or(0),
services()
.rooms
.state_accessor
.get_name(id)
.ok()
.flatten()
.unwrap_or_else(|| id.to_string()),
)
}
-7
View File
@@ -1,7 +0,0 @@
//! Default allocator with no special features
/// Always returns the empty string
pub(crate) fn memory_stats() -> String { Default::default() }
/// Always returns the empty string
pub(crate) fn memory_usage() -> String { Default::default() }
-8
View File
@@ -1,8 +0,0 @@
#[global_allocator]
static HMALLOC: hardened_malloc_rs::HardenedMalloc = hardened_malloc_rs::HardenedMalloc;
pub(crate) fn memory_usage() -> String {
String::default() //TODO: get usage
}
pub(crate) fn memory_stats() -> String { "Extended statistics are not available from hardened_malloc.".to_owned() }
+66
View File
@@ -0,0 +1,66 @@
[package]
name = "conduit_api"
version.workspace = true
edition.workspace = true
[lib]
path = "mod.rs"
crate-type = [
"rlib",
# "dylib",
]
[features]
default = [
"element_hacks",
"gzip_compression",
"brotli_compression",
"release_max_log_level",
]
element_hacks = []
dev_release_log_level = []
release_max_log_level = [
"tracing/max_level_trace",
"tracing/release_max_level_info",
"log/max_level_trace",
"log/release_max_level_info",
]
gzip_compression = [
"reqwest/gzip",
]
brotli_compression = [
"reqwest/brotli",
]
[dependencies]
argon2.workspace = true
axum-extra.workspace = true
axum.workspace = true
base64.workspace = true
bytes.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
conduit-service.workspace = true
futures-util.workspace = true
hmac.workspace = true
http.workspace = true
hyper.workspace = true
image.workspace = true
ipaddress.workspace = true
jsonwebtoken.workspace = true
log.workspace = true
rand.workspace = true
reqwest.workspace = true
ruma.workspace = true
serde_html_form.workspace = true
serde_json.workspace = true
serde.workspace = true
sha-1.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
webpage.workspace = true
[lints]
workspace = true
+34 -7
View File
@@ -1,10 +1,11 @@
use std::fmt::Write as _;
use std::fmt::Write;
use conduit::debug_info;
use register::RegistrationKind;
use ruma::{
api::client::{
account::{
change_password, deactivate, get_3pids, get_username_availability,
change_password, check_registration_token_validity, deactivate, get_3pids, get_username_availability,
register::{self, LoginType},
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, whoami,
ThirdPartyIdRemovalStatus,
@@ -19,9 +20,10 @@ use tracing::{error, info, warn};
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{
api::client_server::{self, join_room_by_id_helper},
service, services,
utils::{self, user_id::user_is_local},
client_server::{self, join_room_by_id_helper},
service::user_is_local,
services,
utils::{self},
Error, Result, Ruma,
};
@@ -240,7 +242,8 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
// If `new_user_displayname_suffix` is set, registration will push whatever
// content is set to the user's display name with a space before it
if !services().globals.new_user_displayname_suffix().is_empty() {
_ = write!(displayname, " {}", services().globals.config.new_user_displayname_suffix);
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer");
}
services()
@@ -288,10 +291,11 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
.users
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())?;
info!("New user \"{}\" registered on this server.", user_id);
debug_info!(%user_id, %device_id, "User account was created");
// log in conduit admin channel if a non-guest user registered
if body.appservice_info.is_none() && !is_guest {
info!("New user \"{user_id}\" registered on this server.");
services()
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
@@ -302,6 +306,8 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
// log in conduit admin channel if a guest registered
if body.appservice_info.is_none() && is_guest && services().globals.log_guest_registrations() {
info!("New guest user \"{user_id}\" registered on this server.");
if let Some(device_display_name) = &body.initial_device_display_name {
if body
.initial_device_display_name
@@ -593,3 +599,24 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
"Third party identifier is not allowed",
))
}
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
///
/// Checks if the provided registration token is valid at the time of checking
///
/// Currently does not have any ratelimiting, and this isn't very practical as
/// there is only one registration token allowed.
pub(crate) async fn check_registration_token_validity(
body: Ruma<check_registration_token_validity::v1::Request>,
) -> Result<check_registration_token_validity::v1::Response> {
let Some(reg_token) = services().globals.config.registration_token.clone() else {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Server does not allow token registration.",
));
};
Ok(check_registration_token_validity::v1::Response {
valid: reg_token == body.token,
})
}
+4 -4
View File
@@ -13,8 +13,9 @@ use ruma::{
use tracing::debug;
use crate::{
debug_info, debug_warn, service::appservice::RegistrationInfo, services, utils::server_name::server_is_ours, Error,
Result, Ruma,
debug_info, debug_warn,
service::{appservice::RegistrationInfo, server_is_ours},
services, Error, Result, Ruma,
};
/// # `PUT /_matrix/client/v3/directory/room/{roomAlias}`
@@ -65,7 +66,6 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
/// - TODO: Update canonical alias event
pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) -> Result<delete_alias::v3::Response> {
alias_checks(&body.room_alias, &body.appservice_info).await?;
if services()
.rooms
.alias
@@ -99,7 +99,7 @@ pub(crate) async fn get_alias_route(body: Ruma<get_alias::v3::Request>) -> Resul
get_alias_helper(body.body.room_alias, None).await
}
pub(crate) async fn get_alias_helper(
pub async fn get_alias_helper(
room_alias: OwnedRoomAliasId, servers: Option<Vec<OwnedServerName>>,
) -> Result<get_alias::v3::Response> {
debug!("get_alias_helper servers: {servers:?}");
+1 -1
View File
@@ -163,7 +163,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
.map(|(_, pdu)| pdu.to_room_event())
.collect();
let mut state = Vec::new();
let mut state = Vec::with_capacity(state_ids.len());
for (shortstatekey, id) in state_ids {
let (event_type, state_key) = services()
+1 -1
View File
@@ -24,7 +24,7 @@ use ruma::{
};
use tracing::{error, info, warn};
use crate::{services, utils::server_name::server_is_ours, Error, Result, Ruma};
use crate::{service::server_is_ours, services, Error, Result, Ruma};
/// # `POST /_matrix/client/v3/publicRooms`
///
+2 -1
View File
@@ -22,8 +22,9 @@ use tracing::debug;
use super::SESSION_ID_LENGTH;
use crate::{
service::user_is_local,
services,
utils::{self, user_id::user_is_local},
utils::{self},
Error, Result, Ruma,
};
+17 -29
View File
@@ -6,7 +6,7 @@ use reqwest::Url;
use ruma::api::client::{
error::{ErrorKind, RetryAfter},
media::{
create_content, create_mxc_uri, get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
get_media_preview,
},
};
@@ -15,14 +15,16 @@ use webpage::HTML;
use crate::{
debug_warn,
service::media::{FileMeta, UrlPreviewData},
service::{
media::{FileMeta, UrlPreviewData},
server_is_ours,
},
services,
utils::{
self,
content_disposition::{
content_disposition_type, make_content_disposition, make_content_type, sanitise_filename,
},
server_name::server_is_ours,
},
Error, Result, Ruma, RumaResponse,
};
@@ -59,29 +61,6 @@ pub(crate) async fn get_media_config_v1_route(
get_media_config_route(body).await.map(RumaResponse)
}
/// # `POST /_matrix/media/v1/create`
///
/// Creates a MXC URI.
///
/// <https://spec.matrix.org/latest/client-server-api/#post_matrixmediav1create>
///
/// TODO: implement `unused_expires_at`, prevent MXC URI creation spam by
/// keeping track of created MXC URIs with no content pushed to them per-user
pub(crate) async fn create_mxc_uri(body: Ruma<create_mxc_uri::v1::Request>) -> Result<create_mxc_uri::v1::Response> {
let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mxc = format!(
"mxc://{}/{}",
services().globals.server_name(),
utils::random_string(MXC_LENGTH)
);
Ok(create_mxc_uri::v1::Response {
content_uri: mxc.into(),
unused_expires_at: None,
})
}
/// # `GET /_matrix/media/v3/preview_url`
///
/// Returns URL preview.
@@ -213,7 +192,7 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
content_disposition,
}) = services().media.get(mxc.clone()).await?
{
let content_disposition = Some(make_content_disposition(&file, &content_type, content_disposition));
let content_disposition = Some(make_content_disposition(&file, &content_type, content_disposition, None));
let content_type = Some(make_content_type(&file, &content_type).to_owned());
Ok(get_content::v3::Response {
@@ -241,6 +220,7 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
&response.file,
&response.content_type,
response.content_disposition,
None,
));
let content_type = Some(make_content_type(&response.file, &response.content_type).to_owned());
@@ -293,7 +273,12 @@ pub(crate) async fn get_content_as_filename_route(
content_disposition,
}) = services().media.get(mxc.clone()).await?
{
let content_disposition = Some(make_content_disposition(&file, &content_type, content_disposition));
let content_disposition = Some(make_content_disposition(
&file,
&content_type,
content_disposition,
Some(body.filename.clone()),
));
let content_type = Some(make_content_type(&file, &content_type).to_owned());
Ok(get_content_as_filename::v3::Response {
@@ -318,6 +303,7 @@ pub(crate) async fn get_content_as_filename_route(
&remote_content_response.file,
&remote_content_response.content_type,
remote_content_response.content_disposition,
None,
));
let content_type = Some(
make_content_type(&remote_content_response.file, &remote_content_response.content_type).to_owned(),
@@ -389,7 +375,7 @@ pub(crate) async fn get_content_thumbnail_route(
)
.await?
{
let content_disposition = Some(make_content_disposition(&file, &content_type, content_disposition));
let content_disposition = Some(make_content_disposition(&file, &content_type, content_disposition, None));
let content_type = Some(make_content_type(&file, &content_type).to_owned());
Ok(get_content_thumbnail::v3::Response {
@@ -446,6 +432,7 @@ pub(crate) async fn get_content_thumbnail_route(
&get_thumbnail_response.file,
&get_thumbnail_response.content_type,
get_thumbnail_response.content_disposition,
None,
));
let content_type = Some(
make_content_type(&get_thumbnail_response.file, &get_thumbnail_response.content_type).to_owned(),
@@ -519,6 +506,7 @@ async fn get_remote_content(
&content_response.file,
&content_response.content_type,
content_response.content_disposition,
None,
));
let content_type = Some(make_content_type(&content_response.file, &content_response.content_type).to_owned());
+20 -9
View File
@@ -35,9 +35,12 @@ use tracing::{debug, error, info, trace, warn};
use super::get_alias_helper;
use crate::{
service::pdu::{gen_event_id_canonical_json, PduBuilder},
service::{
pdu::{gen_event_id_canonical_json, PduBuilder},
server_is_ours, user_is_local,
},
services,
utils::{self, server_name::server_is_ours, user_id::user_is_local},
utils::{self},
Error, PduEvent, Result, Ruma,
};
@@ -77,7 +80,9 @@ async fn banned_room_check(user_id: &UserId, room_id: Option<&RoomId>, server_na
// ignore errors
leave_all_rooms(user_id).await;
_ = services().users.deactivate_account(user_id);
if let Err(e) = services().users.deactivate_account(user_id) {
warn!(%e, "Failed to deactivate account");
}
}
return Err(Error::BadRequest(
@@ -112,7 +117,9 @@ async fn banned_room_check(user_id: &UserId, room_id: Option<&RoomId>, server_na
// ignore errors
leave_all_rooms(user_id).await;
_ = services().users.deactivate_account(user_id);
if let Err(e) = services().users.deactivate_account(user_id) {
warn!(%e, "Failed to deactivate account");
}
}
return Err(Error::BadRequest(
@@ -607,7 +614,7 @@ pub(crate) async fn joined_members_route(
})
}
pub(crate) async fn join_room_by_id_helper(
pub async fn join_room_by_id_helper(
sender_user: Option<&UserId>, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>,
) -> Result<join_room_by_id::v3::Response> {
@@ -1525,7 +1532,7 @@ pub(crate) async fn invite_helper(
// Make a user leave all their joined rooms, forgets all rooms, and ignores
// errors
pub(crate) async fn leave_all_rooms(user_id: &UserId) {
pub async fn leave_all_rooms(user_id: &UserId) {
let all_rooms = services()
.rooms
.state_cache
@@ -1545,12 +1552,16 @@ pub(crate) async fn leave_all_rooms(user_id: &UserId) {
};
// ignore errors
_ = services().rooms.state_cache.forget(&room_id, user_id);
_ = leave_room(user_id, &room_id, None).await;
if let Err(e) = services().rooms.state_cache.forget(&room_id, user_id) {
warn!(%e, "Failed to forget room");
}
if let Err(e) = leave_room(user_id, &room_id, None).await {
warn!(%e, "Failed to leave room");
}
}
}
pub(crate) async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {
pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {
// Ask a remote server if we don't have this room
if !services()
.rooms
+2 -4
View File
@@ -3,6 +3,7 @@ use std::{
sync::Arc,
};
use conduit::PduCount;
use ruma::{
api::client::{
error::ErrorKind,
@@ -14,10 +15,7 @@ use ruma::{
};
use serde_json::{from_str, Value};
use crate::{
service::{pdu::PduBuilder, rooms::timeline::PduCount},
services, utils, Error, PduEvent, Result, Ruma,
};
use crate::{service::pdu::PduBuilder, services, utils, Error, PduEvent, Result, Ruma};
/// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
///
+38 -39
View File
@@ -1,40 +1,41 @@
mod account;
mod alias;
mod backup;
mod capabilities;
mod config;
mod context;
mod device;
mod directory;
mod filter;
mod keys;
mod media;
mod membership;
mod message;
mod presence;
mod profile;
mod push;
mod read_marker;
mod redact;
mod relations;
mod report;
mod room;
mod search;
mod session;
mod space;
mod state;
mod sync;
mod tag;
mod thirdparty;
mod threads;
mod to_device;
mod typing;
mod unstable;
mod unversioned;
mod user_directory;
mod voip;
pub(crate) mod account;
pub(crate) mod alias;
pub(crate) mod backup;
pub(crate) mod capabilities;
pub(crate) mod config;
pub(crate) mod context;
pub(crate) mod device;
pub(crate) mod directory;
pub(crate) mod filter;
pub(crate) mod keys;
pub(crate) mod media;
pub(crate) mod membership;
pub(crate) mod message;
pub(crate) mod presence;
pub(crate) mod profile;
pub(crate) mod push;
pub(crate) mod read_marker;
pub(crate) mod redact;
pub(crate) mod relations;
pub(crate) mod report;
pub(crate) mod room;
pub(crate) mod search;
pub(crate) mod session;
pub(crate) mod space;
pub(crate) mod state;
pub(crate) mod sync;
pub(crate) mod tag;
pub(crate) mod thirdparty;
pub(crate) mod threads;
pub(crate) mod to_device;
pub(crate) mod typing;
pub(crate) mod unstable;
pub(crate) mod unversioned;
pub(crate) mod user_directory;
pub(crate) mod voip;
pub(crate) use account::*;
pub use alias::get_alias_helper;
pub(crate) use alias::*;
pub(crate) use backup::*;
pub(crate) use capabilities::*;
@@ -46,6 +47,7 @@ pub(crate) use filter::*;
pub(crate) use keys::*;
pub(crate) use media::*;
pub(crate) use membership::*;
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room};
pub(crate) use message::*;
pub(crate) use presence::*;
pub(crate) use profile::*;
@@ -77,7 +79,4 @@ const DEVICE_ID_LENGTH: usize = 10;
const TOKEN_LENGTH: usize = 32;
/// generated user session ID length
pub(crate) const SESSION_ID_LENGTH: usize = 32;
/// auto-generated password length
pub(crate) const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
const SESSION_ID_LENGTH: usize = service::uiaa::SESSION_ID_LENGTH;
+15 -5
View File
@@ -12,8 +12,12 @@ use ruma::{
presence::PresenceState,
};
use serde_json::value::to_raw_value;
use tracing::warn;
use crate::{service::pdu::PduBuilder, services, utils::user_id::user_is_local, Error, Result, Ruma};
use crate::{
service::{pdu::PduBuilder, user_is_local},
services, Error, Result, Ruma,
};
/// # `PUT /_matrix/client/r0/profile/{userId}/displayname`
///
@@ -79,11 +83,14 @@ pub(crate) async fn set_displayname_route(
);
let state_lock = mutex_state.lock().await;
_ = services()
if let Err(e) = services()
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
.await;
.await
{
warn!(%e, "Failed to update/send new display name in room");
}
}
if services().globals.allow_local_presence() {
@@ -221,11 +228,14 @@ pub(crate) async fn set_avatar_url_route(
);
let state_lock = mutex_state.lock().await;
_ = services()
if let Err(e) = services()
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
.await;
.await
{
warn!(%e, "Failed to set/update room with new avatar URL / pfp");
}
}
if services().globals.allow_local_presence() {
+2 -1
View File
@@ -1,5 +1,6 @@
use std::collections::BTreeMap;
use conduit::PduCount;
use ruma::{
api::client::{error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt},
events::{
@@ -9,7 +10,7 @@ use ruma::{
MilliSecondsSinceUnixEpoch,
};
use crate::{service::rooms::timeline::PduCount, services, Error, Result, Ruma};
use crate::{services, Error, Result, Ruma};
/// # `POST /_matrix/client/r0/rooms/{roomId}/read_markers`
///
+7 -5
View File
@@ -1,5 +1,6 @@
use std::{cmp::max, collections::BTreeMap, sync::Arc};
use conduit::{debug_info, debug_warn};
use ruma::{
api::client::{
error::ErrorKind,
@@ -28,8 +29,7 @@ use serde_json::{json, value::to_raw_value};
use tracing::{error, info, warn};
use crate::{
api::client_server::invite_helper,
debug_info, debug_warn,
client_server::invite_helper,
service::{appservice::RegistrationInfo, pdu::PduBuilder},
services, Error, Result, Ruma,
};
@@ -388,7 +388,7 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
Error::BadRequest(ErrorKind::InvalidParam, "Invalid initial state event.")
})?;
debug_warn!("initial state event: {event:?}");
debug_info!("Room creation initial state event: {event:?}");
// client/appservice workaround: if a user sends an initial_state event with a
// state event in there with the content of literally `{}` (not null or empty
@@ -460,7 +460,9 @@ pub(crate) async fn create_room_route(body: Ruma<create_room::v3::Request>) -> R
// 8. Events implied by invite (and TODO: invite_3pid)
drop(state_lock);
for user_id in &body.invite {
_ = invite_helper(sender_user, user_id, &room_id, None, body.is_direct).await;
if let Err(e) = invite_helper(sender_user, user_id, &room_id, None, body.is_direct).await {
warn!(%e, "Failed to send invite");
}
}
// Homeserver specific stuff
@@ -815,7 +817,7 @@ pub(crate) async fn upgrade_room_route(body: Ruma<upgrade_room::v3::Request>) ->
// Modify the power levels in the old room to prevent sending of events and
// inviting new users
_ = services()
services()
.rooms
.timeline
.build_and_append_pdu(
+11 -28
View File
@@ -18,7 +18,7 @@ use ruma::{
UserId,
};
use serde::Deserialize;
use tracing::{debug, error, info, warn};
use tracing::{debug, info, warn};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::{services, utils, Error, Result, Ruma};
@@ -76,14 +76,7 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
warn!("Bad login type: {:?}", &body.login_info);
return Err(Error::BadRequest(ErrorKind::forbidden(), "Bad login type."));
}
.map_err(|e| {
warn!("Failed to parse username from user logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
})?;
if services().appservice.is_exclusive_user_id(&user_id).await {
return Err(Error::BadRequest(ErrorKind::Exclusive, "User ID reserved by appservice."));
}
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
let hash = services()
.users
@@ -94,18 +87,15 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
return Err(Error::BadRequest(ErrorKind::UserDeactivated, "The user has been deactivated"));
}
let Ok(parsed_hash) = PasswordHash::new(&hash) else {
error!("error while hashing user {}", user_id);
return Err(Error::BadServerResponse("could not hash"));
};
let parsed_hash = PasswordHash::new(&hash)
.map_err(|_| Error::BadServerResponse("Unknown error occurred hashing password."))?;
let hash_matches = services()
if services()
.globals
.argon
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok();
if !hash_matches {
.is_err()
{
return Err(Error::BadRequest(ErrorKind::forbidden(), "Wrong username or password."));
}
@@ -125,17 +115,10 @@ pub(crate) async fn login_route(body: Ruma<login::v3::Request>) -> Result<login:
let username = token.claims.sub.to_lowercase();
let user_id =
UserId::parse_with_server_name(username, services().globals.server_name()).map_err(|e| {
warn!("Failed to parse username from user logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
})?;
if services().appservice.is_exclusive_user_id(&user_id).await {
return Err(Error::BadRequest(ErrorKind::Exclusive, "User ID reserved by appservice."));
}
user_id
UserId::parse_with_server_name(username, services().globals.server_name()).map_err(|e| {
warn!("Failed to parse username from user logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
})?
} else {
return Err(Error::BadRequest(
ErrorKind::Unknown,
+2 -4
View File
@@ -19,10 +19,8 @@ use ruma::{
use tracing::{error, log::warn};
use crate::{
service::{self, pdu::PduBuilder},
services,
utils::server_name::server_is_ours,
Error, Result, Ruma, RumaResponse,
service::{pdu::PduBuilder, server_is_ours},
services, Error, Result, Ruma, RumaResponse,
};
/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}/{stateKey}`
+71 -154
View File
@@ -5,6 +5,7 @@ use std::{
time::Duration,
};
use conduit::PduCount;
use ruma::{
api::client::{
filter::{FilterDefinition, LazyLoadOptions},
@@ -25,15 +26,11 @@ use ruma::{
StateEventType, TimelineEventType,
},
serde::Raw,
uint, DeviceId, EventId, OwnedDeviceId, OwnedUserId, RoomId, UInt, UserId,
uint, DeviceId, EventId, OwnedUserId, RoomId, UInt, UserId,
};
use tokio::sync::watch::Sender;
use tracing::{debug, error, Instrument as _, Span};
use tracing::{error, Instrument as _, Span};
use crate::{
service::{pdu::EventHash, rooms::timeline::PduCount},
services, utils, Error, PduEvent, Result, Ruma, RumaResponse,
};
use crate::{service::pdu::EventHash, services, utils, Error, PduEvent, Result, Ruma, RumaResponse};
/// # `GET /_matrix/client/r0/sync`
///
@@ -73,10 +70,6 @@ use crate::{
/// For left rooms:
/// - If the user left after `since`: `prev_batch` token, empty state (TODO:
/// subset of the state at the point of the leave)
///
/// - Sync is handled in an async task, multiple requests from the same device
/// with the same
/// `since` will be cached
pub(crate) async fn sync_events_route(
body: Ruma<sync_events::v3::Request>,
) -> Result<sync_events::v3::Response, RumaResponse<UiaaResponse>> {
@@ -84,95 +77,6 @@ pub(crate) async fn sync_events_route(
let sender_device = body.sender_device.expect("user is authenticated");
let body = body.body;
let mut rx = match services()
.globals
.sync_receivers
.write()
.await
.entry((sender_user.clone(), sender_device.clone()))
{
Entry::Vacant(v) => {
let (tx, rx) = tokio::sync::watch::channel(None);
v.insert((body.since.clone(), rx.clone()));
tokio::spawn(sync_helper_wrapper(sender_user.clone(), sender_device.clone(), body, tx));
rx
},
Entry::Occupied(mut o) => {
if o.get().0 != body.since {
let (tx, rx) = tokio::sync::watch::channel(None);
o.insert((body.since.clone(), rx.clone()));
debug!("Sync started for {sender_user}");
tokio::spawn(sync_helper_wrapper(sender_user.clone(), sender_device.clone(), body, tx));
rx
} else {
o.get().1.clone()
}
},
};
let we_have_to_wait = rx.borrow().is_none();
if we_have_to_wait {
if let Err(e) = rx.changed().await {
error!("Error waiting for sync: {}", e);
}
}
let result = match rx
.borrow()
.as_ref()
.expect("When sync channel changes it's always set to some")
{
Ok(response) => Ok(response.clone()),
Err(error) => Err(error.to_response()),
};
result
}
async fn sync_helper_wrapper(
sender_user: OwnedUserId, sender_device: OwnedDeviceId, body: sync_events::v3::Request,
tx: Sender<Option<Result<sync_events::v3::Response>>>,
) {
let since = body.since.clone();
let r = sync_helper(sender_user.clone(), sender_device.clone(), body).await;
if let Ok((_, caching_allowed)) = r {
if !caching_allowed {
match services()
.globals
.sync_receivers
.write()
.await
.entry((sender_user, sender_device))
{
Entry::Occupied(o) => {
// Only remove if the device didn't start a different /sync already
if o.get().0 == since {
o.remove();
}
},
Entry::Vacant(_) => {},
}
}
}
_ = tx.send(Some(r.map(|(r, _)| r)));
}
async fn sync_helper(
sender_user: OwnedUserId,
sender_device: OwnedDeviceId,
body: sync_events::v3::Request,
// bool = caching allowed
) -> Result<(sync_events::v3::Response, bool), Error> {
// Presence update
if services().globals.allow_local_presence() {
services()
@@ -238,7 +142,7 @@ async fn sync_helper(
.collect::<Vec<_>>();
// Coalesce database writes for the remainder of this scope.
let _cork = services().globals.db.cork_and_flush()?;
let _cork = services().globals.cork_and_flush()?;
for room_id in all_joined_rooms {
let room_id = room_id?;
@@ -413,11 +317,14 @@ async fn sync_helper(
if duration.as_secs() > 30 {
duration = Duration::from_secs(30);
}
_ = tokio::time::timeout(duration, watcher).await;
Ok((response, false))
} else {
Ok((response, since != next_batch)) // Only cache if we made progress
#[allow(clippy::let_underscore_must_use)]
{
_ = tokio::time::timeout(duration, watcher).await;
}
}
Ok(response)
}
#[tracing::instrument(skip_all, fields(user_id = %sender_user, room_id = %room_id))]
@@ -845,8 +752,7 @@ async fn load_joined_room(
// Incremental /sync
let since_shortstatehash = since_shortstatehash.unwrap();
let mut state_events = Vec::new();
let mut lazy_loaded = HashSet::new();
let mut delta_state_events = Vec::new();
if since_shortstatehash != current_shortstatehash {
let current_state_ids = services()
@@ -867,55 +773,12 @@ async fn load_joined_room(
continue;
};
if pdu.kind == TimelineEventType::RoomMember {
match UserId::parse(
pdu.state_key
.as_ref()
.expect("State event has state key")
.clone(),
) {
Ok(state_key_userid) => {
lazy_loaded.insert(state_key_userid);
},
Err(e) => error!("Invalid state key for member event: {}", e),
}
}
state_events.push(pdu);
delta_state_events.push(pdu);
tokio::task::yield_now().await;
}
}
}
for (_, event) in &timeline_pdus {
if lazy_loaded.contains(&event.sender) {
continue;
}
if !services().rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
room_id,
&event.sender,
)? || lazy_load_send_redundant
{
if let Some(member_event) = services().rooms.state_accessor.room_state_get(
room_id,
&StateEventType::RoomMember,
event.sender.as_str(),
)? {
lazy_loaded.insert(event.sender.clone());
state_events.push(member_event);
}
}
}
services()
.rooms
.lazy_loading
.lazy_load_mark_sent(sender_user, sender_device, room_id, lazy_loaded, next_batchcount)
.await;
let encrypted_room = services()
.rooms
.state_accessor
@@ -931,12 +794,12 @@ async fn load_joined_room(
// Calculations:
let new_encrypted_room = encrypted_room && since_encryption.is_none();
let send_member_count = state_events
let send_member_count = delta_state_events
.iter()
.any(|event| event.kind == TimelineEventType::RoomMember);
if encrypted_room {
for state_event in &state_events {
for state_event in &delta_state_events {
if state_event.kind != TimelineEventType::RoomMember {
continue;
}
@@ -997,6 +860,57 @@ async fn load_joined_room(
(None, None, Vec::new())
};
let mut state_events = delta_state_events;
let mut lazy_loaded = HashSet::new();
// Mark all member events we're returning as lazy-loaded
for pdu in &state_events {
if pdu.kind == TimelineEventType::RoomMember {
match UserId::parse(
pdu.state_key
.as_ref()
.expect("State event has state key")
.clone(),
) {
Ok(state_key_userid) => {
lazy_loaded.insert(state_key_userid);
},
Err(e) => error!("Invalid state key for member event: {}", e),
}
}
}
// Fetch contextual member state events for events from the timeline, and
// mark them as lazy-loaded as well.
for (_, event) in &timeline_pdus {
if lazy_loaded.contains(&event.sender) {
continue;
}
if !services().rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
room_id,
&event.sender,
)? || lazy_load_send_redundant
{
if let Some(member_event) = services().rooms.state_accessor.room_state_get(
room_id,
&StateEventType::RoomMember,
event.sender.as_str(),
)? {
lazy_loaded.insert(event.sender.clone());
state_events.push(member_event);
}
}
}
services()
.rooms
.lazy_loading
.lazy_load_mark_sent(sender_user, sender_device, room_id, lazy_loaded, next_batchcount)
.await;
(
heroes,
joined_member_count,
@@ -1684,7 +1598,10 @@ pub(crate) async fn sync_events_v4_route(
if duration.as_secs() > 30 {
duration = Duration::from_secs(30);
}
_ = tokio::time::timeout(duration, watcher).await;
#[allow(clippy::let_underscore_must_use)]
{
_ = tokio::time::timeout(duration, watcher).await;
}
}
Ok(sync_events::v4::Response {
+1 -1
View File
@@ -8,7 +8,7 @@ use ruma::{
to_device::DeviceIdOrAllDevices,
};
use crate::{services, utils::user_id::user_is_local, Error, Result, Ruma};
use crate::{services, user_is_local, Error, Result, Ruma};
/// # `PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}`
///
+21 -7
View File
@@ -45,12 +45,13 @@ pub(crate) async fn get_supported_versions_route(
],
unstable_features: BTreeMap::from_iter([
("org.matrix.e2e_cross_signing".to_owned(), true),
("org.matrix.msc2285.stable".to_owned(), true),
("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true),
("org.matrix.msc2836".to_owned(), true),
("org.matrix.msc2946".to_owned(), true),
("org.matrix.msc3026.busy_presence".to_owned(), true),
("org.matrix.msc3827".to_owned(), true),
("org.matrix.msc2285.stable".to_owned(), true), /* private read receipts (https://github.com/matrix-org/matrix-spec-proposals/pull/2285) */
("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true), /* query mutual rooms (https://github.com/matrix-org/matrix-spec-proposals/pull/2666) */
("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */
("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */
("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
("org.matrix.msc3575".to_owned(), true), /* sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1588877046) */
]),
};
@@ -154,7 +155,20 @@ pub(crate) async fn syncv3_client_server_json() -> Result<impl IntoResponse> {
/// `/_matrix/federation/v1/version`
pub(crate) async fn conduwuit_server_version() -> Result<impl IntoResponse> {
Ok(Json(serde_json::json!({
"name": "Conduwuit",
"name": "conduwuit",
"version": conduwuit_version(),
})))
}
/// # `GET /_conduwuit/local_user_count`
///
/// conduwuit-specific API to return the amount of users registered on this
/// homeserver. Endpoint is disabled if federation is disabled for privacy. This
/// only includes active users (not deactivated, no guests, etc)
pub(crate) async fn conduwuit_local_user_count() -> Result<impl IntoResponse> {
let user_count = services().users.list_local_users()?.len();
Ok(Json(serde_json::json!({
"count": user_count
})))
}
+15 -3
View File
@@ -1,3 +1,15 @@
pub(crate) mod client_server;
pub(crate) mod ruma_wrapper;
pub(crate) mod server_server;
pub mod client_server;
pub mod router;
mod ruma_wrapper;
pub mod server_server;
extern crate conduit_core as conduit;
extern crate conduit_service as service;
pub use client_server::membership::{join_room_by_id_helper, leave_all_rooms};
pub(crate) use conduit::{debug_error, debug_info, debug_warn, error::RumaResponse, utils, Error, Result};
pub(crate) use ruma_wrapper::Ruma;
pub(crate) use service::{pdu::PduEvent, services, user_is_local};
conduit::mod_ctor! {}
conduit::mod_dtor! {}
+37 -61
View File
@@ -1,21 +1,19 @@
use std::future::Future;
use axum::{
extract::FromRequestParts,
response::IntoResponse,
routing::{any, get, on, post, MethodFilter},
Router,
};
use conduit::{Error, Result, Server};
use http::{Method, Uri};
use ruma::api::{client::error::ErrorKind, IncomingRequest};
use crate::{
api::{client_server, server_server},
Config, Error, Result, Ruma, RumaResponse,
};
use crate::{client_server, server_server, Ruma, RumaResponse};
pub(crate) fn routes(config: &Config) -> Router {
let router = Router::new()
pub fn build(router: Router, server: &Server) -> Router {
let config = &server.config;
let router = router
.ruma_route(client_server::get_supported_versions_route)
.ruma_route(client_server::get_register_available_route)
.ruma_route(client_server::register_route)
@@ -29,6 +27,7 @@ pub(crate) fn routes(config: &Config) -> Router {
.ruma_route(client_server::third_party_route)
.ruma_route(client_server::request_3pid_management_token_via_email_route)
.ruma_route(client_server::request_3pid_management_token_via_msisdn_route)
.ruma_route(client_server::check_registration_token_validity)
.ruma_route(client_server::get_capabilities_route)
.ruma_route(client_server::get_pushrules_all_route)
.ruma_route(client_server::set_pushrule_route)
@@ -133,7 +132,6 @@ pub(crate) fn routes(config: &Config) -> Router {
.ruma_route(client_server::get_media_config_route)
.ruma_route(client_server::get_media_preview_route)
.ruma_route(client_server::create_content_route)
.ruma_route(client_server::create_mxc_uri)
// legacy v1 media routes
.route(
"/_matrix/media/v1/preview_url",
@@ -188,9 +186,7 @@ pub(crate) fn routes(config: &Config) -> Router {
.route("/_conduwuit/server_version", get(client_server::conduwuit_server_version))
.route("/_matrix/client/r0/rooms/:room_id/initialSync", get(initial_sync))
.route("/_matrix/client/v3/rooms/:room_id/initialSync", get(initial_sync))
.route("/client/server.json", get(client_server::syncv3_client_server_json))
.route("/", get(it_works))
.fallback(not_found);
.route("/client/server.json", get(client_server::syncv3_client_server_json));
if config.allow_federation {
router
@@ -223,24 +219,20 @@ pub(crate) fn routes(config: &Config) -> Router {
.ruma_route(server_server::claim_keys_route)
.ruma_route(server_server::get_hierarchy_route)
.ruma_route(server_server::well_known_server)
.route("/_conduwuit/local_user_count", get(client_server::conduwuit_local_user_count))
} else {
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))
}
}
async fn not_found(_uri: Uri) -> impl IntoResponse {
Error::BadRequest(ErrorKind::Unrecognized, "Unrecognized request")
}
async fn initial_sync(_uri: Uri) -> impl IntoResponse {
Error::BadRequest(ErrorKind::GuestAccessForbidden, "Guest access not implemented")
}
async fn it_works() -> &'static str { "hewwo from conduwuit woof!" }
async fn federation_disabled() -> impl IntoResponse { Error::bad_config("Federation is disabled.") }
trait RouterExt {
@@ -251,63 +243,47 @@ trait RouterExt {
}
impl RouterExt for Router {
#[inline(always)]
fn ruma_route<H, T>(self, handler: H) -> Self
where
H: RumaHandler<T>,
T: 'static,
{
handler.add_to_router(self)
handler.add_routes(self)
}
}
pub(crate) trait RumaHandler<T> {
// Can't transform to a handler without boxing or relying on the nightly-only
// impl-trait-in-traits feature. Moving a small amount of extra logic into the
// trait allows bypassing both.
fn add_to_router(self, router: Router) -> Router;
trait RumaHandler<T> {
fn add_routes(&self, router: Router) -> Router;
fn add_route(&self, router: Router, path: &str) -> Router;
}
macro_rules! impl_ruma_handler {
( $($ty:ident),* $(,)? ) => {
#[axum::async_trait]
#[allow(non_snake_case)]
impl<Req, E, F, Fut, $($ty,)*> RumaHandler<($($ty,)* Ruma<Req>,)> for F
where
Req: IncomingRequest + Send + 'static,
F: FnOnce($($ty,)* Ruma<Req>) -> Fut + Clone + Send + 'static,
Fut: Future<Output = Result<Req::OutgoingResponse, E>>
+ Send,
E: IntoResponse,
$( $ty: FromRequestParts<()> + Send + 'static, )*
{
fn add_to_router(self, mut router: Router) -> Router {
let meta = Req::METADATA;
let method_filter = method_to_filter(meta.method);
impl<Req, E, F, Fut> RumaHandler<Ruma<Req>> for F
where
Req: IncomingRequest + Send + 'static,
F: FnOnce(Ruma<Req>) -> Fut + Clone + Send + Sync + 'static,
Fut: Future<Output = Result<Req::OutgoingResponse, E>> + Send,
E: IntoResponse,
{
#[inline(always)]
fn add_routes(&self, router: Router) -> Router {
Req::METADATA
.history
.all_paths()
.fold(router, |router, path| self.add_route(router, path))
}
for path in meta.history.all_paths() {
let handler = self.clone();
router = router.route(path, on(method_filter, |$( $ty: $ty, )* req| async move {
handler($($ty,)* req).await.map(RumaResponse)
}))
}
router
}
}
};
#[inline(always)]
fn add_route(&self, router: Router, path: &str) -> Router {
let handle = self.clone();
let method = method_to_filter(Req::METADATA.method);
let action = |req| async { handle(req).await.map(RumaResponse) };
router.route(path, on(method, action))
}
}
impl_ruma_handler!();
impl_ruma_handler!(T1);
impl_ruma_handler!(T1, T2);
impl_ruma_handler!(T1, T2, T3);
impl_ruma_handler!(T1, T2, T3, T4);
impl_ruma_handler!(T1, T2, T3, T4, T5);
impl_ruma_handler!(T1, T2, T3, T4, T5, T6);
impl_ruma_handler!(T1, T2, T3, T4, T5, T6, T7);
impl_ruma_handler!(T1, T2, T3, T4, T5, T6, T7, T8);
#[inline]
fn method_to_filter(method: Method) -> MethodFilter {
match method {
Method::DELETE => MethodFilter::DELETE,
+252
View File
@@ -0,0 +1,252 @@
use std::collections::BTreeMap;
use axum::RequestPartsExt;
use axum_extra::{headers::Authorization, typed_header::TypedHeaderRejectionReason, TypedHeader};
use http::uri::PathAndQuery;
use ruma::{
api::{client::error::ErrorKind, AuthScheme, IncomingRequest},
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
};
use tracing::warn;
use super::{request::Request, xmatrix::XMatrix};
use crate::{service::appservice::RegistrationInfo, services, Error, Result};
enum Token {
Appservice(Box<RegistrationInfo>),
User((OwnedUserId, OwnedDeviceId)),
Invalid,
None,
}
pub(super) struct Auth {
pub(super) sender_user: Option<OwnedUserId>,
pub(super) sender_device: Option<OwnedDeviceId>,
pub(super) origin: Option<OwnedServerName>,
pub(super) appservice_info: Option<RegistrationInfo>,
}
pub(super) async fn auth<T>(request: &mut Request) -> Result<Auth>
where
T: IncomingRequest,
{
let metadata = T::METADATA;
let token = match &request.auth {
Some(TypedHeader(Authorization(bearer))) => Some(bearer.token()),
None => request.query.access_token.as_deref(),
};
let token = if let Some(token) = token {
if let Some(reg_info) = services().appservice.find_from_token(token).await {
Token::Appservice(Box::new(reg_info))
} else if let Some((user_id, device_id)) = services().users.find_from_token(token)? {
Token::User((user_id, OwnedDeviceId::from(device_id)))
} else {
Token::Invalid
}
} else {
Token::None
};
if metadata.authentication == AuthScheme::None {
match request.parts.uri.path() {
// TODO: can we check this better?
"/_matrix/client/v3/publicRooms" | "/_matrix/client/r0/publicRooms" => {
if !services()
.globals
.config
.allow_public_room_directory_without_auth
{
match token {
Token::Appservice(_) | Token::User(_) => {
// we should have validated the token above
// already
},
Token::None | Token::Invalid => {
return Err(Error::BadRequest(ErrorKind::MissingToken, "Missing or invalid access token."));
},
}
}
},
_ => {},
};
}
match (metadata.authentication, token) {
(_, Token::Invalid) => Err(Error::BadRequest(
ErrorKind::UnknownToken {
soft_logout: false,
},
"Unknown access token.",
)),
(AuthScheme::AccessToken, Token::Appservice(info)) => Ok(auth_appservice(request, info)?),
(AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken, Token::Appservice(info)) => {
Ok(Auth {
sender_user: None,
sender_device: None,
origin: None,
appservice_info: Some(*info),
})
},
(AuthScheme::AccessToken, Token::None) => {
Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))
},
(
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
Token::User((user_id, device_id)),
) => Ok(Auth {
sender_user: Some(user_id),
sender_device: Some(device_id),
origin: None,
appservice_info: None,
}),
(AuthScheme::ServerSignatures, Token::None) => Ok(auth_server(request).await?),
(AuthScheme::None | AuthScheme::AppserviceToken | AuthScheme::AccessTokenOptional, Token::None) => Ok(Auth {
sender_user: None,
sender_device: None,
origin: None,
appservice_info: None,
}),
(AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) => Err(Error::BadRequest(
ErrorKind::Unauthorized,
"Only server signatures should be used on this endpoint.",
)),
(AuthScheme::AppserviceToken, Token::User(_)) => Err(Error::BadRequest(
ErrorKind::Unauthorized,
"Only appservice access tokens should be used on this endpoint.",
)),
}
}
fn auth_appservice(request: &mut Request, info: Box<RegistrationInfo>) -> Result<Auth> {
let user_id = request
.query
.user_id
.clone()
.map_or_else(
|| {
UserId::parse_with_server_name(
info.registration.sender_localpart.as_str(),
services().globals.server_name(),
)
},
UserId::parse,
)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
if !info.is_user_match(&user_id) {
return Err(Error::BadRequest(ErrorKind::Exclusive, "User is not in namespace."));
}
if !services().users.exists(&user_id)? {
return Err(Error::BadRequest(ErrorKind::forbidden(), "User does not exist."));
}
Ok(Auth {
sender_user: Some(user_id),
sender_device: None,
origin: None,
appservice_info: Some(*info),
})
}
async fn auth_server(request: &mut Request) -> Result<Auth> {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let TypedHeader(Authorization(x_matrix)) = request
.parts
.extract::<TypedHeader<Authorization<XMatrix>>>()
.await
.map_err(|e| {
warn!("Missing or invalid Authorization header: {e}");
let msg = match e.reason() {
TypedHeaderRejectionReason::Missing => "Missing Authorization header.",
TypedHeaderRejectionReason::Error(_) => "Invalid X-Matrix signatures.",
_ => "Unknown header-related error",
};
Error::BadRequest(ErrorKind::forbidden(), msg)
})?;
let origin_signatures = BTreeMap::from_iter([(x_matrix.key.clone(), CanonicalJsonValue::String(x_matrix.sig))]);
let signatures = BTreeMap::from_iter([(
x_matrix.origin.as_str().to_owned(),
CanonicalJsonValue::Object(origin_signatures),
)]);
let server_destination = services().globals.server_name().as_str().to_owned();
if let Some(destination) = x_matrix.destination.as_ref() {
if destination != &server_destination {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Invalid authorization."));
}
}
let signature_uri = CanonicalJsonValue::String(
request
.parts
.uri
.path_and_query()
.unwrap_or(&PathAndQuery::from_static("/"))
.to_string(),
);
let mut request_map = BTreeMap::from_iter([
(
"method".to_owned(),
CanonicalJsonValue::String(request.parts.method.to_string()),
),
("uri".to_owned(), signature_uri),
(
"origin".to_owned(),
CanonicalJsonValue::String(x_matrix.origin.as_str().to_owned()),
),
("destination".to_owned(), CanonicalJsonValue::String(server_destination)),
("signatures".to_owned(), CanonicalJsonValue::Object(signatures)),
]);
if let Some(json_body) = &request.json {
request_map.insert("content".to_owned(), json_body.clone());
};
let keys_result = services()
.rooms
.event_handler
.fetch_signing_keys_for_server(&x_matrix.origin, vec![x_matrix.key.clone()])
.await;
let keys = keys_result.map_err(|e| {
warn!("Failed to fetch signing keys: {e}");
Error::BadRequest(ErrorKind::forbidden(), "Failed to fetch signing keys.")
})?;
let pub_key_map = BTreeMap::from_iter([(x_matrix.origin.as_str().to_owned(), keys)]);
match ruma::signatures::verify_json(&pub_key_map, &request_map) {
Ok(()) => Ok(Auth {
sender_user: None,
sender_device: None,
origin: Some(x_matrix.origin),
appservice_info: None,
}),
Err(e) => {
warn!("Failed to verify json request from {}: {e}\n{request_map:?}", x_matrix.origin);
if request.parts.uri.to_string().contains('@') {
warn!(
"Request uri contained '@' character. Make sure your reverse proxy gives Conduit the raw uri \
(apache: use nocanon)"
);
}
Err(Error::BadRequest(
ErrorKind::forbidden(),
"Failed to verify X-Matrix signatures.",
))
},
}
}
-399
View File
@@ -1,399 +0,0 @@
use std::{collections::BTreeMap, str};
use axum::{
async_trait,
extract::{FromRequest, Path},
response::{IntoResponse, Response},
RequestExt, RequestPartsExt,
};
use axum_extra::{
headers::{
authorization::{Bearer, Credentials},
Authorization,
},
typed_header::TypedHeaderRejectionReason,
TypedHeader,
};
use bytes::{BufMut, BytesMut};
use http::{uri::PathAndQuery, StatusCode};
use http_body_util::Full;
use hyper::Request;
use ruma::{
api::{client::error::ErrorKind, AuthScheme, IncomingRequest, OutgoingResponse},
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
};
use serde::Deserialize;
use tracing::{debug, error, trace, warn};
use super::{Ruma, RumaResponse};
use crate::{debug_warn, service::appservice::RegistrationInfo, services, Error, Result};
enum Token {
Appservice(Box<RegistrationInfo>),
User((OwnedUserId, OwnedDeviceId)),
Invalid,
None,
}
#[derive(Deserialize)]
struct QueryParams {
access_token: Option<String>,
user_id: Option<String>,
}
#[async_trait]
impl<T, S> FromRequest<S, axum::body::Body> for Ruma<T>
where
T: IncomingRequest,
{
type Rejection = Error;
#[allow(unused_qualifications)] // async traits
async fn from_request(req: Request<axum::body::Body>, _state: &S) -> Result<Self, Self::Rejection> {
let limited = req.with_limited_body();
let (mut parts, body) = limited.into_parts();
let mut body = axum::body::to_bytes(
body,
services()
.globals
.config
.max_request_size
.try_into()
.expect("failed to convert max request size"),
)
.await
.map_err(|_| Error::BadRequest(ErrorKind::MissingToken, "Missing token."))?;
let metadata = T::METADATA;
let auth_header: Option<TypedHeader<Authorization<Bearer>>> = parts.extract().await?;
let path_params: Path<Vec<String>> = parts.extract().await?;
let query = parts.uri.query().unwrap_or_default();
let query_params: QueryParams = match serde_html_form::from_str(query) {
Ok(params) => params,
Err(e) => {
error!(%query, "Failed to deserialize query parameters: {e}");
return Err(Error::BadRequest(ErrorKind::Unknown, "Failed to read query parameters"));
},
};
let token = match &auth_header {
Some(TypedHeader(Authorization(bearer))) => Some(bearer.token()),
None => query_params.access_token.as_deref(),
};
let token = if let Some(token) = token {
if let Some(reg_info) = services().appservice.find_from_token(token).await {
Token::Appservice(Box::new(reg_info))
} else if let Some((user_id, device_id)) = services().users.find_from_token(token)? {
Token::User((user_id, OwnedDeviceId::from(device_id)))
} else {
Token::Invalid
}
} else {
Token::None
};
if metadata.authentication == AuthScheme::None {
match parts.uri.path() {
// TODO: can we check this better?
"/_matrix/client/v3/publicRooms" | "/_matrix/client/r0/publicRooms" => {
if !services()
.globals
.config
.allow_public_room_directory_without_auth
{
match token {
Token::Appservice(_) | Token::User(_) => {
// we should have validated the token above
// already
},
Token::None | Token::Invalid => {
return Err(Error::BadRequest(
ErrorKind::MissingToken,
"Missing or invalid access token.",
));
},
}
}
},
_ => {},
};
}
let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&body).ok();
let (sender_user, sender_device, sender_servername, appservice_info) = match (metadata.authentication, token) {
(_, Token::Invalid) => {
return Err(Error::BadRequest(
ErrorKind::UnknownToken {
soft_logout: false,
},
"Unknown access token.",
))
},
(AuthScheme::AccessToken, Token::Appservice(info)) => {
let user_id = query_params
.user_id
.map_or_else(
|| {
UserId::parse_with_server_name(
info.registration.sender_localpart.as_str(),
services().globals.server_name(),
)
},
UserId::parse,
)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
if !info.is_user_match(&user_id) {
return Err(Error::BadRequest(ErrorKind::Exclusive, "User is not in namespace."));
}
if !services().users.exists(&user_id)? {
return Err(Error::BadRequest(ErrorKind::forbidden(), "User does not exist."));
}
(Some(user_id), None, None, Some(*info))
},
(
AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken,
Token::Appservice(info),
) => (None, None, None, Some(*info)),
(AuthScheme::AccessToken, Token::None) => {
return Err(Error::BadRequest(ErrorKind::MissingToken, "Missing access token."));
},
(
AuthScheme::AccessToken | AuthScheme::AccessTokenOptional | AuthScheme::None,
Token::User((user_id, device_id)),
) => (Some(user_id), Some(device_id), None, None),
(AuthScheme::ServerSignatures, Token::None) => {
if !services().globals.allow_federation() {
return Err(Error::bad_config("Federation is disabled."));
}
let TypedHeader(Authorization(x_matrix)) = parts
.extract::<TypedHeader<Authorization<XMatrix>>>()
.await
.map_err(|e| {
warn!("Missing or invalid Authorization header: {e}");
let msg = match e.reason() {
TypedHeaderRejectionReason::Missing => "Missing Authorization header.",
TypedHeaderRejectionReason::Error(_) => "Invalid X-Matrix signatures.",
_ => "Unknown header-related error",
};
Error::BadRequest(ErrorKind::forbidden(), msg)
})?;
let origin_signatures =
BTreeMap::from_iter([(x_matrix.key.clone(), CanonicalJsonValue::String(x_matrix.sig))]);
let signatures = BTreeMap::from_iter([(
x_matrix.origin.as_str().to_owned(),
CanonicalJsonValue::Object(origin_signatures),
)]);
let server_destination = services().globals.server_name().as_str().to_owned();
if let Some(destination) = x_matrix.destination.as_ref() {
if destination != &server_destination {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Invalid authorization."));
}
}
let signature_uri = CanonicalJsonValue::String(
parts
.uri
.path_and_query()
.unwrap_or(&PathAndQuery::from_static("/"))
.to_string(),
);
let mut request_map = BTreeMap::from_iter([
("method".to_owned(), CanonicalJsonValue::String(parts.method.to_string())),
("uri".to_owned(), signature_uri),
(
"origin".to_owned(),
CanonicalJsonValue::String(x_matrix.origin.as_str().to_owned()),
),
("destination".to_owned(), CanonicalJsonValue::String(server_destination)),
("signatures".to_owned(), CanonicalJsonValue::Object(signatures)),
]);
if let Some(json_body) = &json_body {
request_map.insert("content".to_owned(), json_body.clone());
};
let keys_result = services()
.rooms
.event_handler
.fetch_signing_keys_for_server(&x_matrix.origin, vec![x_matrix.key.clone()])
.await;
let keys = keys_result.map_err(|e| {
warn!("Failed to fetch signing keys: {e}");
Error::BadRequest(ErrorKind::forbidden(), "Failed to fetch signing keys.")
})?;
let pub_key_map = BTreeMap::from_iter([(x_matrix.origin.as_str().to_owned(), keys)]);
match ruma::signatures::verify_json(&pub_key_map, &request_map) {
Ok(()) => (None, None, Some(x_matrix.origin), None),
Err(e) => {
warn!("Failed to verify json request from {}: {e}\n{request_map:?}", x_matrix.origin);
if parts.uri.to_string().contains('@') {
warn!(
"Request uri contained '@' character. Make sure your reverse proxy gives Conduit the \
raw uri (apache: use nocanon)"
);
}
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Failed to verify X-Matrix signatures.",
));
},
}
},
(AuthScheme::None | AuthScheme::AppserviceToken | AuthScheme::AccessTokenOptional, Token::None) => {
(None, None, None, None)
},
(AuthScheme::ServerSignatures, Token::Appservice(_) | Token::User(_)) => {
return Err(Error::BadRequest(
ErrorKind::Unauthorized,
"Only server signatures should be used on this endpoint.",
));
},
(AuthScheme::AppserviceToken, Token::User(_)) => {
return Err(Error::BadRequest(
ErrorKind::Unauthorized,
"Only appservice access tokens should be used on this endpoint.",
));
},
};
let mut http_request = Request::builder().uri(parts.uri).method(parts.method);
*http_request.headers_mut().unwrap() = parts.headers;
if let Some(CanonicalJsonValue::Object(json_body)) = &mut json_body {
let user_id = sender_user.clone().unwrap_or_else(|| {
UserId::parse_with_server_name("", services().globals.server_name()).expect("we know this is valid")
});
let uiaa_request = json_body
.get("auth")
.and_then(|auth| auth.as_object())
.and_then(|auth| auth.get("session"))
.and_then(|session| session.as_str())
.and_then(|session| {
services().uiaa.get_uiaa_request(
&user_id,
&sender_device.clone().unwrap_or_else(|| "".into()),
session,
)
});
if let Some(CanonicalJsonValue::Object(initial_request)) = uiaa_request {
for (key, value) in initial_request {
json_body.entry(key).or_insert(value);
}
}
let mut buf = BytesMut::new().writer();
serde_json::to_writer(&mut buf, json_body).expect("value serialization can't fail");
body = buf.into_inner().freeze();
}
let http_request = http_request.body(&*body).unwrap();
debug!(
"{:?} {:?} {:?}",
http_request.method(),
http_request.uri(),
http_request.headers()
);
trace!("{:?} {:?} {:?}", http_request.method(), http_request.uri(), json_body);
let body = T::try_from_http_request(http_request, &path_params).map_err(|e| {
warn!("try_from_http_request failed: {e:?}",);
debug_warn!("JSON body: {:?}", json_body);
Error::BadRequest(ErrorKind::BadJson, "Failed to deserialize request.")
})?;
Ok(Ruma {
body,
sender_user,
sender_device,
sender_servername,
json_body,
appservice_info,
})
}
}
struct XMatrix {
origin: OwnedServerName,
destination: Option<String>,
key: String, // KeyName?
sig: String,
}
impl Credentials for XMatrix {
const SCHEME: &'static str = "X-Matrix";
fn decode(value: &http::HeaderValue) -> Option<Self> {
debug_assert!(
value.as_bytes().starts_with(b"X-Matrix "),
"HeaderValue to decode should start with \"X-Matrix ..\", received = {value:?}",
);
let parameters = str::from_utf8(&value.as_bytes()["X-Matrix ".len()..])
.ok()?
.trim_start();
let mut origin = None;
let mut destination = None;
let mut key = None;
let mut sig = None;
for entry in parameters.split_terminator(',') {
let (name, value) = entry.split_once('=')?;
// It's not at all clear why some fields are quoted and others not in the spec,
// let's simply accept either form for every field.
let value = value
.strip_prefix('"')
.and_then(|rest| rest.strip_suffix('"'))
.unwrap_or(value);
// FIXME: Catch multiple fields of the same name
match name {
"origin" => origin = Some(value.try_into().ok()?),
"key" => key = Some(value.to_owned()),
"sig" => sig = Some(value.to_owned()),
"destination" => destination = Some(value.to_owned()),
_ => debug!("Unexpected field `{name}` in X-Matrix Authorization header"),
}
}
Some(Self {
origin: origin?,
key: key?,
sig: sig?,
destination,
})
}
fn encode(&self) -> http::HeaderValue { todo!() }
}
impl<T: OutgoingResponse> IntoResponse for RumaResponse<T> {
fn into_response(self) -> Response {
match self.0.try_into_http_response::<BytesMut>() {
Ok(res) => res.map(BytesMut::freeze).map(Full::new).into_response(),
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
}
}
}
+9 -16
View File
@@ -1,17 +1,21 @@
mod auth;
mod request;
mod xmatrix;
use std::ops::Deref;
use ruma::{api::client::uiaa::UiaaResponse, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId};
use ruma::{CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId};
use crate::{service::appservice::RegistrationInfo, Error};
mod axum;
use crate::service::appservice::RegistrationInfo;
/// Extractor for Ruma request structs
pub(crate) struct Ruma<T> {
/// Request struct body
pub(crate) body: T,
pub(crate) sender_user: Option<OwnedUserId>,
pub(crate) sender_device: Option<OwnedDeviceId>,
pub(crate) sender_servername: Option<OwnedServerName>,
/// X-Matrix origin/server
pub(crate) origin: Option<OwnedServerName>,
pub(crate) json_body: Option<CanonicalJsonValue>, // This is None when body is not a valid string
pub(crate) appservice_info: Option<RegistrationInfo>,
}
@@ -21,14 +25,3 @@ impl<T> Deref for Ruma<T> {
fn deref(&self) -> &Self::Target { &self.body }
}
#[derive(Clone)]
pub(crate) struct RumaResponse<T>(pub(crate) T);
impl<T> From<T> for RumaResponse<T> {
fn from(t: T) -> Self { Self(t) }
}
impl From<Error> for RumaResponse<UiaaResponse> {
fn from(t: Error) -> Self { t.to_response() }
}
+149
View File
@@ -0,0 +1,149 @@
use std::{mem, str};
use axum::{
async_trait,
extract::{FromRequest, Path},
RequestExt, RequestPartsExt,
};
use axum_extra::{
headers::{authorization::Bearer, Authorization},
TypedHeader,
};
use bytes::{BufMut, Bytes, BytesMut};
use conduit::debug_warn;
use http::request::Parts;
use ruma::{
api::{client::error::ErrorKind, IncomingRequest},
CanonicalJsonValue, UserId,
};
use serde::Deserialize;
use tracing::{debug, trace, warn};
use super::{auth, auth::Auth, Ruma};
use crate::{services, Error, Result};
#[derive(Deserialize)]
pub(super) struct QueryParams {
pub(super) access_token: Option<String>,
pub(super) user_id: Option<String>,
}
pub(super) struct Request {
pub(super) auth: Option<TypedHeader<Authorization<Bearer>>>,
pub(super) path: Path<Vec<String>>,
pub(super) query: QueryParams,
pub(super) json: Option<CanonicalJsonValue>,
pub(super) body: Bytes,
pub(super) parts: Parts,
}
#[async_trait]
impl<T, S> FromRequest<S, axum::body::Body> for Ruma<T>
where
T: IncomingRequest,
{
type Rejection = Error;
async fn from_request(request: hyper::Request<axum::body::Body>, _state: &S) -> Result<Self, Self::Rejection> {
let mut request: Request = extract(request).await?;
let auth: Auth = auth::auth::<T>(&mut request).await?;
let body = make_body::<T>(&mut request, &auth)?;
Ok(Ruma {
body,
sender_user: auth.sender_user,
sender_device: auth.sender_device,
origin: auth.origin,
json_body: request.json,
appservice_info: auth.appservice_info,
})
}
}
fn make_body<T>(request: &mut Request, auth: &Auth) -> Result<T>
where
T: IncomingRequest,
{
let body = if let Some(CanonicalJsonValue::Object(json_body)) = &mut request.json {
let user_id = auth.sender_user.clone().unwrap_or_else(|| {
UserId::parse_with_server_name("", services().globals.server_name()).expect("we know this is valid")
});
let uiaa_request = json_body
.get("auth")
.and_then(|auth| auth.as_object())
.and_then(|auth| auth.get("session"))
.and_then(|session| session.as_str())
.and_then(|session| {
services().uiaa.get_uiaa_request(
&user_id,
&auth.sender_device.clone().unwrap_or_else(|| "".into()),
session,
)
});
if let Some(CanonicalJsonValue::Object(initial_request)) = uiaa_request {
for (key, value) in initial_request {
json_body.entry(key).or_insert(value);
}
}
let mut buf = BytesMut::new().writer();
serde_json::to_writer(&mut buf, &request.json).expect("value serialization can't fail");
buf.into_inner().freeze()
} else {
mem::take(&mut request.body)
};
let mut http_request = hyper::Request::builder()
.uri(request.parts.uri.clone())
.method(request.parts.method.clone());
*http_request.headers_mut().unwrap() = request.parts.headers.clone();
let http_request = http_request.body(body).unwrap();
debug!(
"{:?} {:?} {:?}",
http_request.method(),
http_request.uri(),
http_request.headers()
);
trace!("{:?} {:?} {:?}", http_request.method(), http_request.uri(), request.json);
let body = T::try_from_http_request(http_request, &request.path).map_err(|e| {
warn!("try_from_http_request failed: {e:?}",);
debug_warn!("JSON body: {:?}", request.json);
Error::BadRequest(ErrorKind::BadJson, "Failed to deserialize request.")
})?;
Ok(body)
}
async fn extract(request: hyper::Request<axum::body::Body>) -> Result<Request> {
let limited = request.with_limited_body();
let (mut parts, body) = limited.into_parts();
let auth = parts.extract().await?;
let path = parts.extract().await?;
let query = serde_html_form::from_str(parts.uri.query().unwrap_or_default())
.map_err(|_| Error::BadRequest(ErrorKind::Unknown, "Failed to read query parameters"))?;
let max_body_size = services()
.globals
.config
.max_request_size
.try_into()
.expect("failed to convert max request size");
let body = axum::body::to_bytes(body, max_body_size)
.await
.map_err(|_| Error::BadRequest(ErrorKind::TooLarge, "Request body too large"))?;
let json = serde_json::from_slice::<CanonicalJsonValue>(&body).ok();
Ok(Request {
auth,
path,
query,
json,
body,
parts,
})
}
+61
View File
@@ -0,0 +1,61 @@
use std::str;
use axum_extra::headers::authorization::Credentials;
use ruma::OwnedServerName;
use tracing::debug;
pub(crate) struct XMatrix {
pub(crate) origin: OwnedServerName,
pub(crate) destination: Option<String>,
pub(crate) key: String, // KeyName?
pub(crate) sig: String,
}
impl Credentials for XMatrix {
const SCHEME: &'static str = "X-Matrix";
fn decode(value: &http::HeaderValue) -> Option<Self> {
debug_assert!(
value.as_bytes().starts_with(b"X-Matrix "),
"HeaderValue to decode should start with \"X-Matrix ..\", received = {value:?}",
);
let parameters = str::from_utf8(&value.as_bytes()["X-Matrix ".len()..])
.ok()?
.trim_start();
let mut origin = None;
let mut destination = None;
let mut key = None;
let mut sig = None;
for entry in parameters.split_terminator(',') {
let (name, value) = entry.split_once('=')?;
// It's not at all clear why some fields are quoted and others not in the spec,
// let's simply accept either form for every field.
let value = value
.strip_prefix('"')
.and_then(|rest| rest.strip_suffix('"'))
.unwrap_or(value);
// FIXME: Catch multiple fields of the same name
match name {
"origin" => origin = Some(value.try_into().ok()?),
"key" => key = Some(value.to_owned()),
"sig" => sig = Some(value.to_owned()),
"destination" => destination = Some(value.to_owned()),
_ => debug!("Unexpected field `{name}` in X-Matrix Authorization header"),
}
}
Some(Self {
origin: origin?,
key: key?,
sig: sig?,
destination,
})
}
fn encode(&self) -> http::HeaderValue { todo!() }
}
+412 -222
View File
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
[package]
name = "conduit_core"
version.workspace = true
edition.workspace = true
[lib]
path = "mod.rs"
crate-type = [
"rlib",
# "dylib",
]
[features]
default = [
"rocksdb",
"io_uring",
"jemalloc",
"gzip_compression",
"zstd_compression",
"brotli_compression",
"sentry_telemetry",
"release_max_log_level",
]
dev_release_log_level = []
release_max_log_level = [
"tracing/max_level_trace",
"tracing/release_max_level_info",
"log/max_level_trace",
"log/release_max_level_info",
]
sqlite = [
"dep:rusqlite",
"dep:parking_lot",
"dep:thread_local",
]
rocksdb = [
"dep:rust-rocksdb",
]
jemalloc = [
"dep:tikv-jemalloc-sys",
"dep:tikv-jemalloc-ctl",
"dep:tikv-jemallocator",
"rust-rocksdb/jemalloc",
]
jemalloc_prof = [
"tikv-jemalloc-sys/profiling",
]
hardened_malloc = [
"dep:hardened_malloc-rs"
]
io_uring = [
"rust-rocksdb/io-uring",
]
zstd_compression = [
"rust-rocksdb/zstd",
]
gzip_compression = [
"reqwest/gzip",
]
brotli_compression = [
"reqwest/brotli",
]
perf_measurements = []
sentry_telemetry = []
[dependencies]
async-trait.workspace = true
axum-server.workspace = true
axum.workspace = true
base64.workspace = true
bytes.workspace = true
clap.workspace = true
cyborgtime.workspace = true
either.workspace = true
figment.workspace = true
futures-util.workspace = true
http-body-util.workspace = true
http.workspace = true
image.workspace = true
infer.workspace = true
ipaddress.workspace = true
itertools.workspace = true
libloading.workspace = true
log.workspace = true
lru-cache.workspace = true
parking_lot.optional = true
parking_lot.workspace = true
rand.workspace = true
regex.workspace = true
reqwest.workspace = true
ring.workspace = true
ruma.workspace = true
rusqlite.optional = true
rusqlite.workspace = true
rust-rocksdb.optional = true
rust-rocksdb.workspace = true
sanitize-filename.workspace = true
serde_json.workspace = true
serde_regex.workspace = true
serde.workspace = true
serde_yaml.workspace = true
sha-1.workspace = true
thiserror.workspace = true
thread_local.optional = true
thread_local.workspace = true
tikv-jemallocator.optional = true
tikv-jemallocator.workspace = true
tikv-jemalloc-ctl.optional = true
tikv-jemalloc-ctl.workspace = true
tikv-jemalloc-sys.optional = true
tikv-jemalloc-sys.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
url.workspace = true
zstd.optional = true
zstd.workspace = true
[target.'cfg(unix)'.dependencies]
nix.workspace = true
[target.'cfg(all(not(target_env = "msvc"), target_os = "linux"))'.dependencies]
hardened_malloc-rs.workspace = true
hardened_malloc-rs.optional = true
[lints]
workspace = true
+9
View File
@@ -0,0 +1,9 @@
//! Default allocator with no special features
/// Always returns the empty string
#[must_use]
pub fn memory_stats() -> String { Default::default() }
/// Always returns the empty string
#[must_use]
pub fn memory_usage() -> String { Default::default() }
+10
View File
@@ -0,0 +1,10 @@
#[global_allocator]
static HMALLOC: hardened_malloc_rs::HardenedMalloc = hardened_malloc_rs::HardenedMalloc;
#[must_use]
pub fn memory_usage() -> String {
String::default() //TODO: get usage
}
#[must_use]
pub fn memory_stats() -> String { "Extended statistics are not available from hardened_malloc.".to_owned() }

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