Compare commits

...

182 Commits

Author SHA1 Message Date
strawberry b6e9dc3d98 comment out borked ci thing for now
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-09 10:17:28 -05:00
strawberry cfcd6eb1a6 bump ruwuma to stop erroring on empty push response body
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-07 18:00:58 -05:00
strawberry 88e7e50daf add missing source OCI image label metadata
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-07 11:49:00 -05:00
strawberry 8345ea2cd3 add --locked and --no-fail-fast to cargo test, add other feature test
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-06 19:02:14 -05:00
strawberry add2e0e9ee bump rust-rocksdb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-06 18:27:36 -05:00
strawberry 43e6c27bb7 misc nix tweaks to maybe speedup ci
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-06 18:27:36 -05:00
strawberry c7c9f0e4a6 catch clippy lints for --no-default-features builds
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-06 18:27:36 -05:00
strawberry ef2d307c15 fix warnings and errors when building with no features
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-06 18:27:32 -05:00
strawberry f761d4d5c9 bump db version to 17, cleanup, rerun old migrations for users who downgraded
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-06 16:48:19 -05:00
strawberry 16b07ae3ec add default systemd support for a TTY to use console mode from
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-06 16:47:10 -05:00
Jason Volk 62d80b97e6 add systemd unit logging mode
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-06 03:16:56 +00:00
strawberry fda8b36809 add more systemd notify integration with stopping/reloading/ready states
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-05 07:32:00 +00:00
strawberry f6dfc9538f bump ruwuma to stop erroring on duplicate yaml values on appservice EDUs (we dont implement this atm anyways)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-05 07:32:00 +00:00
strawberry f80d85e107 add SIGUSR1 systemctl reload config support to systemd units
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-02-05 07:32:00 +00:00
Jason Volk 9158edfb7c fix empty join timeline bug
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-05 07:32:00 +00:00
Jason Volk 04656a7886 fix spaces pagination bug
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-05 07:32:00 +00:00
Jason Volk 442bb9889c improvements on blurhashing feature
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-04 21:47:03 +00:00
Niko 62180897c0 Added blurhash.rs to fascilitate blurhashing.
Signed-off-by: Niko <cnotsomark@gmail.com>
2025-02-04 21:47:03 +00:00
Nineko 80277f6aa2 Adds .gitattributes to the projects to prevent LN and CLRF conflicts. (#681) 2025-02-04 16:46:00 -05:00
Jason Volk d32534164c fix soft-failed redaction regression (ff8bbd4cfa)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-04 21:00:12 +00:00
Jason Volk b3271e0d65 split state_accessor
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-04 00:02:00 +00:00
Jason Volk 106bcd30b7 optimize incremental sync state diff
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-04 00:02:00 +00:00
Jason Volk da4b94d80d trap panics when running in gdb
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-04 00:02:00 +00:00
Jason Volk 32f990fc72 fix the panic counter in the tower layer
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-03 01:16:09 +00:00
Jason Volk 5e59ce37c4 snapshot sync results at next_batch upper-bound
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-02 16:30:55 +00:00
Jason Volk a774afe837 modernize remove_to_device_events
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-02 16:30:55 +00:00
Jason Volk ffe3b0faf2 make shutdown grace periods configurable
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-02 16:30:55 +00:00
Jason Volk bd6d4bc58f enforce timeout on request layers
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-02 16:30:55 +00:00
Jason Volk b4d22bd05e remove unnecessary cf arc refcnt workaround
log errors and panics propagating through the request task join

Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-02 10:08:30 +00:00
Jason Volk 7ce782ddf4 fix jemalloc cfgs lacking msvc conditions
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-02 10:08:30 +00:00
Jason Volk 4add39d0fe cache compressed state in a sorted structure for logarithmic queries with partial keys
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-02 10:08:30 +00:00
Jason Volk ea49b60273 add Option support to database deserializer
Signed-off-by: Jason Volk <jason@zemos.net>
2025-02-02 00:09:18 +00:00
Jason Volk 2fa9621f3a flatten state_full_shortids
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-31 22:46:26 +00:00
Jason Volk 09bc71caab fix missed concurrent fetch opportunities in sender (ffd0fd4242)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-31 22:46:26 +00:00
Jason Volk 6983798487 implement lazy-loading for incremental sync
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-31 22:46:26 +00:00
Jason Volk a4ef04cd14 fix room join completion taking wrong sync branch
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-31 22:46:26 +00:00
Jason Volk 4e0cedbe51 simplify v3 sync presence collecting
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-31 22:46:26 +00:00
Jason Volk 4ff1155bf0 reroll encrypted_room branch in incremental sync state
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-31 22:46:26 +00:00
Jason Volk e161e5dd61 add pair_of! macro
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-31 22:45:37 +00:00
morguldir f698254c41 make registration tokens reloadable, and allow configuring multiple
Signed-off-by: morguldir <morguldir@protonmail.com>
2025-01-31 03:09:02 +01:00
Jason Volk 69837671bb simplify request handler task base
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-30 18:58:32 +00:00
Jason Volk ff8bbd4cfa untwist the redaction check stanza
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-30 05:26:30 +00:00
Jason Volk 1a8482b3b4 refactor incoming extremities retention; broad filter, single pass
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-30 05:26:30 +00:00
Jason Volk 31c2968bb2 move db files command w/ filter args; misc related cleanup
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 23:21:08 +00:00
Jason Volk 3c8376d897 parallelize state-res pre-gathering
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 23:21:07 +00:00
Jason Volk 50acfe7832 flatten auth chain iterations
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 21:28:26 +00:00
Jason Volk eb7d893c86 fix malloc_conf feature-awareness
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 06:37:30 +00:00
Jason Volk 936161d89e reduce bottommost compression underrides
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 03:09:13 +00:00
Jason Volk 329925c661 additional info level span adjustments
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 03:09:13 +00:00
Jason Volk af399fd517 flatten state accessor iterations
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk ad0b0af955 combine state_accessor data into mod
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk 2c5af902a3 support executing configurable admin commands via SIGUSR2
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk 2f449ba47d support reloading config via SIGUSR1
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk a567e314e9 simplify shutdown signal handlers
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk ed3cd99781 abstract the config reload checks
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk 99fe88c21e use smallvec for the edu sending event buffer
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk ffd0fd4242 pipeline pdu fetch for federation sending destination
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk b2a565b0b4 propagate better error from server.check_running() 2025-01-29 01:18:08 +00:00
Jason Volk c516a8df3e fanout edu processing
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk 94d786ac12 process rooms and edus concurrently
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk 677316631a pipeline prologue of handle_incoming_pdu
simplify room_version/first_pdu_in_room argument passing

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk 2b730a30ad add broad_flat_map
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-29 01:18:08 +00:00
Jason Volk 98f9570547 add option to disable rocksdb checksums
reference runtime state for default option initialization

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-28 18:38:47 +00:00
Jason Volk 13335042b7 enable the paranoid-checks options in debug mode
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-28 18:36:00 +00:00
Jason Volk 6db8df5e23 skip redundant acl check when sender is origin
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-28 18:36:00 +00:00
Jason Volk d0b4a619af furnish batch interface with trait
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-28 18:36:00 +00:00
Jason Volk 4a2d0d35bc split federation request from sending service
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-28 18:36:00 +00:00
Jason Volk 3e0ff2dc84 simplify references to server_name
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-28 18:36:00 +00:00
June Clementine Strawberry 71a3855af6 fix couple more docker manifest typos
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-26 21:30:15 -05:00
Jade Ellis db7d23e780 fix creating manifest on wrong repo 2025-01-26 20:38:26 -05:00
Jade Ellis 1c585ab1b6 create manifests for unique docker tags 2025-01-26 20:38:26 -05:00
Jade Ellis 24e6086f12 load correct image file 2025-01-26 20:38:26 -05:00
Jade Ellis ee63f720c9 revert incorrect tags 2025-01-26 20:38:26 -05:00
June Clementine Strawberry 4b3c54bbfa check if DOCKER_USERNAME is empty as well in dockerhub desc publish
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-26 13:11:47 -05:00
Jason Volk 68856645ee refactor lazy-loading
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-26 17:48:53 +00:00
Jason Volk 9ad4f20da4 propagate underflow as error result, not index bounds panic
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-26 17:48:53 +00:00
Jason Volk 186c459584 use scalar for file shape; increase shape for small-type columns
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-26 17:48:53 +00:00
Jason Volk 29a19ba437 add write_to_cache to descriptor
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-26 17:48:53 +00:00
June Clementine Strawberry 3b0195e6b3 misc various github actions ci fixes
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-26 01:10:25 -05:00
June Clementine Strawberry 4b331fe50e update README.md, crate metadata, and OCI image metadata
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-26 01:08:39 -05:00
June Clementine Strawberry c323894497 use test in postrm deb script before deleting
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-25 21:27:21 -05:00
June Clementine Strawberry 5b5ccba64e make conduwuit lowercase in the user-agent again
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-25 21:20:55 -05:00
bumpsoo 9dcf289c7a (doc): Update docker-compose.yml and conduwuit-example.toml
The server cannot start without a registration token when registration is configured

Signed-off-by: bumpsoo <bumpsoo063@protonmail.ch>
2025-01-25 20:08:46 -05:00
Jade Ellis d86061084c Publish haswell images to image registries (#674)
* push haswell images to docker repos

* Add OCI labels to image

* fixup! Add OCI labels to image

* fixup! push haswell images to docker repos

* fixup! Add OCI labels to image

* fixup! Add OCI labels to image

* fixup! Add OCI labels to image
2025-01-25 20:05:41 -05:00
Jade Ellis 1d26eec82d publish README to docker hub 2025-01-25 19:34:10 -05:00
June Clementine Strawberry 9514064c1c use --locked for macOS builds
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-25 13:40:31 -05:00
June Clementine Strawberry 2abf15b9e9 revert back to v9.9.3 due to upstream build issue with macos
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-25 13:35:22 -05:00
June Clementine Strawberry cd5d4f48be add mau.dev mirror of conduwuit
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-25 03:08:35 -05:00
morguldir eed3291625 ci: set variable after ssh has been configured, mainly for draft pull requests 2025-01-25 02:01:55 -05:00
morguldir 6a7fe3ab7c limit wal archive size to 1gb
Signed-off-by: morguldir <morguldir@protonmail.com>
2025-01-25 02:01:36 -05:00
June Clementine Strawberry 72daf7ea68 bump rocksdb to v9.10.0, reverts upstream rocksdb regression causing deadlocks on corrupt WAL files
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-25 02:01:20 -05:00
Jason Volk 94f2384fb0 fix missing iteration-optimized read options on several stream types
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk d59f68a51a add sensitive-field directives to config display
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk b1b6dc0479 reloadable configuration
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk 184a3b0f0c reduce some tracing span levels; bump ruma
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk b5c167de12 call decay prior to purge for trim-memory
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk 5be07ebc0f eliminate references to services.globals.config
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk 7c6b8b132a add config reloading indirector
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk 1351d07735 improve path argument to Config::load and constructions
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk 6e7c73336c move room version config check out of services.globals
make available_room_versions() non-member associated

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk 52adae7553 add sequence method to db engine; improve engine interface/tracing
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-25 01:35:18 +00:00
Jason Volk a5520e8b1b fix SRV override loss on cache expiration
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-23 01:48:53 +00:00
Jason Volk 265802d546 additional prof/stats jemalloc related interface tweaks
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-23 01:48:53 +00:00
Jason Volk da9f1ae5d7 expire resolver cache entries
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-22 08:51:56 +00:00
Jason Volk 607e338ac2 cache result of resolution at completion of resolution
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-22 07:56:24 +00:00
Jason Volk f75d9fa79e deduplicate name resolutions
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-22 06:57:18 +00:00
Jason Volk 7c0c029a4a add try_lock to MutexMap; allow TryFrom constructions
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-22 06:40:07 +00:00
Jason Volk 49023aa295 use database for resolver caches
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-22 02:16:51 +00:00
Jason Volk 0c96891008 add CBOR support to database schema
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-22 00:52:48 +00:00
Jason Volk 1f31e74024 add del to raw suite
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-22 03:50:51 +00:00
Jason Volk 9ab381e4eb generate fmt::Display for Config
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-18 07:12:20 +00:00
Jason Volk dda27ffcb1 add some compaction related interfaces
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-18 12:05:07 +00:00
Jason Volk 8ab825b12c add stream parallelism extension combinators
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-21 20:43:38 +00:00
Jason Volk 19f6d9d0e1 add index-compression and auto-readahead to descriptor
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 12:12:44 +00:00
Jason Volk 277b4951e8 add compression-shaping; tweak default compression levels
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 11:50:17 +00:00
Jason Volk 610129d162 outdent auth_chain Service impl
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 09:05:49 +00:00
Jason Volk 4c0ae8c2f7 parallelize get_auth_chain outer
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 09:02:50 +00:00
Jason Volk ea25dc04b2 parallelize current and incoming fork-state fetch
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 07:38:32 +00:00
Jason Volk 388730d6dd add TryWideband trait to similar to TryBroadband
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 07:38:19 +00:00
Jason Volk ac944496c1 optimize statekey-from-short loopsite
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-14 19:17:45 +00:00
Jason Volk 3dae02b886 add preferred jemalloc config
add muzzy/dirty configuration mallctl interface

add program argument for --gc-muzzy=false

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-18 01:30:41 +00:00
Jason Volk 3eed408b29 additional util macros and reorg
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk 4fbbfe5d30 add alt argument format for detecting cargo build phase
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk df3eb95d4f additional affinity utils
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk 7045481fae add from_errno construction to Error
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk c6ae6adc80 pre-allocate some amount of media read buffer
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk afdf5a07b5 abstract hidden line related in config generator macro 2025-01-20 04:48:47 +00:00
Jason Volk f9e76d6239 improve debug memory-stats options
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk 8141ca3444 refactor admin outputs to asyncwrite
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk abf33013e3 check-in additional database test related
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk 96e85adc32 use cache builder for row and table cache options
add cache check using multi-get path

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:48:47 +00:00
Jason Volk fc1170e12a additional tracing span tweaks
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:17:04 +00:00
Jason Volk 819e35f81f remove mutex lock/unlock during sync iteration.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:17:04 +00:00
Jason Volk bab40a3747 enable hashing on large-block indexes
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:17:04 +00:00
Jason Volk aad42bdaa0 reduce block size on small tables
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:17:04 +00:00
Jason Volk 3759d1be6c tweak per-column write_buffer down from default
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:17:04 +00:00
Jason Volk 77d8e26efe integrate trim-on-park memory reclamation to runtime
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:17:04 +00:00
Jason Volk 7a8ca8842a add jemallctl base; add trim to interface w/ console cmd
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:17:02 +00:00
Jason Volk 80832cb0bb add checked math wrapper
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:16:17 +00:00
Jason Volk 98d8e5c63c add standard error trait and thread access error conversions
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-20 04:16:17 +00:00
Jason Volk 5167e1f06d add option to disable listeners
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-18 01:43:58 +00:00
Jason Volk e56d3c6cb3 add multi_get_statekey_from_short
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-18 01:43:58 +00:00
Jason Volk afcd0bfeef add deref_at macro util
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-18 01:43:58 +00:00
strawberry 5b8464252c cleanup+fix login get_token code, use db ser/deser instead
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-17 17:40:53 -05:00
Jade Ellis 2cc6ad8df3 implement /login/get_token (MSC3882) 2025-01-17 17:40:53 -05:00
Jade Ellis afe9e5536b remove undocumented jwt token login 2025-01-17 17:40:53 -05:00
Jason Volk 9ebb39ca4f add missing cfg_attr on deserialisation db test
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-15 14:34:50 -05:00
June Clementine Strawberry f59e3d8850 bump nix lockfile, cargo.lock, rust to 1.84, and fix tracing fork
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-14 13:05:25 -05:00
morguldir 6cb3275be0 Add initial MSC4186 (Simplified Sliding Sync) implementation
Signed-off-by: morguldir <morguldir@protonmail.com>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-14 15:09:51 +01:00
morguldir be16f84410 syncv3: use a function for repeated pattern of fetching sticky params 2025-01-13 22:31:21 -05:00
strawberry 9dd058de60 update complement results
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-13 14:15:16 -05:00
strawberry 5a1c41e66b knocking implementation
Signed-off-by: strawberry <strawberry@puppygock.gay>

add sync bit of knocking

Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-13 14:15:16 -05:00
strawberry fabd3cf567 ci: set binary as executable before uploading to webserver
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-13 14:15:16 -05:00
strawberry 5e21b43f25 run direnv exec in engage default steps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-13 14:15:16 -05:00
strawberry 9bda5a43e5 fix /kick endpoint unbanning banned users
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-13 14:15:16 -05:00
strawberry 8c18481d1d optimise resetting all user presences
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-13 14:09:53 -05:00
Holger Huo fde1b94e26 fix: logic mistake 2025-01-11 21:43:58 -05:00
Holger Huo b71201cf19 add: clear online status on server boot 2025-01-11 21:43:57 -05:00
Holger Huo 8451ea3bc3 update: refresh timeout greater than idle timeout 2025-01-11 21:43:57 -05:00
Holger Huo 6f15c9b3f4 fix: presence timer not working 2025-01-11 21:43:57 -05:00
strawberry 0074f903d8 delete lix binary cache because it has terrible reliability
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-11 21:43:57 -05:00
strawberry 1852eeebf2 disable more unnecessary features in various build outputs
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-11 20:48:36 -05:00
strawberry 5b6279b1c5 ci: require docker publishing to pass tests
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-11 20:48:36 -05:00
strawberry 4c2999ccd1 gate libloading to conduwuit_mods feature and cfg only
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-11 20:48:36 -05:00
strawberry 53d03bbb1f gate sd_notify to linux target_os only
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-11 19:37:44 -05:00
Jason Volk 66231676f1 gracefully ignore unknown columns; add dropped flag in descriptor
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-10 22:29:05 +00:00
Jason Volk 16fa2eca87 add conf item for write buffer size
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-10 07:03:34 +00:00
Jason Volk 6a0f9add0c refactor database engine/options; add column descriptors
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-10 07:03:34 +00:00
Jason Volk 02f19cf951 tweak tracing spans; inlines
db deserializer tracing instrument cover

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-10 07:03:34 +00:00
Jason Volk 685b127f99 simplify iterator state constructor arguments
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-10 07:03:34 +00:00
Jason Volk cc1889d135 Add default-enabled feature-gates for url_preview and media_thumbnail
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-10 07:03:34 +00:00
Jason Volk 0238f27605 prevent example-config generating in test builds
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-10 07:03:34 +00:00
Jason Volk 5dae086197 exclude config item from doctest
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-10 06:53:15 +00:00
Jason Volk 44e6b1af3c fixes for tests to be run in release-mode
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-09 19:56:42 +00:00
Jason Volk 94c8683836 improve db pool topology configuration
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-09 18:14:23 +00:00
Jason Volk d36167ab64 partially revert 9a9c071e82; use std threads for db pool.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-09 18:14:23 +00:00
Jason Volk 925061b92d flatten timeline pdus iterations; increase concurrency
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-09 18:14:23 +00:00
Jason Volk 27328cbc01 additional futures extension utils
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-09 18:14:23 +00:00
Jason Volk a3f9432da8 eliminate the state-res mutex hazard
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-09 18:14:23 +00:00
Jason Volk 82168b972a fix heroes calculation regression
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-09 18:14:23 +00:00
Neil Svedberg 7526ba9d6f Add header to console
When the console is launched, it now prints this message:

    conduwuit VERSION admin console
    "help" for help, ^D to exit the console, ^\ to stop the server
2025-01-09 10:18:49 -05:00
Jade Ellis 8c74e35e76 automatically retry returning data in syncv3 (#652)
* automatically retry returning data in syncv3

* reference service

* clippy fixes
2025-01-03 22:15:48 -05:00
264 changed files with 13349 additions and 7002 deletions
+87
View File
@@ -0,0 +1,87 @@
# taken from https://github.com/gitattributes/gitattributes/blob/46a8961ad73f5bd4d8d193708840fbc9e851d702/Rust.gitattributes
# Auto detect text files and perform normalization
* text=auto
*.rs text diff=rust
*.toml text diff=toml
Cargo.lock text
# taken from https://github.com/gitattributes/gitattributes/blob/46a8961ad73f5bd4d8d193708840fbc9e851d702/Common.gitattributes
# Documents
*.bibtex text diff=bibtex
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.md text diff=markdown
*.mdx text diff=markdown
*.tex text diff=tex
*.adoc text
*.textile text
*.mustache text
*.csv text eol=crlf
*.tab text
*.tsv text
*.txt text
*.sql text
*.epub diff=astextplain
# Graphics
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.tif binary
*.tiff binary
*.ico binary
# SVG treated as text by default.
*.svg text
*.eps binary
# Scripts
*.bash text eol=lf
*.fish text eol=lf
*.ksh text eol=lf
*.sh text eol=lf
*.zsh text eol=lf
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# Serialisation
*.json text
*.toml text
*.xml text
*.yaml text
*.yml text
# Archives
*.7z binary
*.bz binary
*.bz2 binary
*.bzip2 binary
*.gz binary
*.lz binary
*.lzma binary
*.rar binary
*.tar binary
*.taz binary
*.tbz binary
*.tbz2 binary
*.tgz binary
*.tlz binary
*.txz binary
*.xz binary
*.Z binary
*.zip binary
*.zst binary
# Text files where line endings should be preserved
*.patch -text
-8
View File
@@ -1,8 +0,0 @@
<!-- Please describe your changes here -->
-----------------------------------------------------------------------------
- [ ] I ran `cargo fmt`, `cargo clippy`, and `cargo test`
- [ ] I agree to release my code and all other changes of this MR under the Apache-2.0 license
-264
View File
@@ -1,264 +0,0 @@
name: CI and Artifacts
on:
pull_request:
push:
# documentation workflow deals with this or is not relevant for this workflow
paths-ignore:
- '*.md'
- 'conduwuit-example.toml'
- 'book.toml'
- '.gitlab-ci.yml'
- '.gitignore'
- 'renovate.json'
- 'docs/**'
- 'debian/**'
- 'docker/**'
branches:
- main
tags:
- '*'
# Allows you to run this workflow manually from the Actions tab
#workflow_dispatch:
#concurrency:
# group: ${{ gitea.head_ref || gitea.ref_name }}
# cancel-in-progress: true
env:
# Required to make some things output color
TERM: ansi
# Publishing to my nix binary cache
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
# conduwuit.cachix.org
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
# Just in case incremental is still being set to true, speeds up CI
CARGO_INCREMENTAL: 0
# Custom nix binary cache if fork is being used
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
# Get error output from nix that we can actually use
NIX_CONFIG: show-trace = true
#permissions:
# packages: write
# contents: read
jobs:
tests:
name: Test
runs-on: ubuntu-latest
steps:
- name: Sync repository
uses: https://github.com/actions/checkout@v4
- name: Tag comparison check
if: startsWith(gitea.ref, 'refs/tags/v')
run: |
# Tag mismatch with latest repo tag check to prevent potential downgrades
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
if [ $LATEST_TAG != ${{ gitea.ref_name }} ]; then
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.'
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.' >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Install Nix
uses: https://github.com/DeterminateSystems/nix-installer-action@main
with:
diagnostic-endpoint: ""
extra-conf: |
experimental-features = nix-command flakes
accept-flake-config = true
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Configure Magic Nix Cache
uses: https://github.com/DeterminateSystems/magic-nix-cache-action@main
with:
diagnostic-endpoint: ""
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
- 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 https://cache.lix.systems https://conduwuit.cachix.org
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
EOF
- name: Use alternative Nix binary caches if specified
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
run: |
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
EOF
- name: Prepare build environment
run: |
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 .#all-features --command true
- name: Cache CI dependencies
run: |
bin/nix-build-and-cache ci
- name: Run CI tests
run: |
direnv exec . engage > >(tee -a test_output.log)
- name: Sync Complement repository
uses: https://github.com/actions/checkout@v4
with:
repository: 'matrix-org/complement'
path: complement_src
- name: Run Complement tests
run: |
direnv exec . bin/complement 'complement_src' 'complement_test_logs.jsonl' 'complement_test_results.jsonl'
cp -v -f result complement_oci_image.tar.gz
- name: Upload Complement OCI image
uses: https://github.com/actions/upload-artifact@v4
with:
name: complement_oci_image.tar.gz
path: complement_oci_image.tar.gz
if-no-files-found: error
- name: Upload Complement logs
uses: https://github.com/actions/upload-artifact@v4
with:
name: complement_test_logs.jsonl
path: complement_test_logs.jsonl
if-no-files-found: error
- name: Upload Complement results
uses: https://github.com/actions/upload-artifact@v4
with:
name: complement_test_results.jsonl
path: complement_test_results.jsonl
if-no-files-found: error
- name: Diff Complement results with checked-in repo results
run: |
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_test_output.log)
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
echo '```diff' >> $GITHUB_STEP_SUMMARY
tail -n 100 complement_test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Update Job Summary
if: success() || failure()
run: |
if [ ${{ job.status }} == 'success' ]; then
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
else
echo '```' >> $GITHUB_STEP_SUMMARY
tail -n 40 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
build:
name: Build
runs-on: ubuntu-latest
needs: tests
strategy:
matrix:
include:
- target: aarch64-unknown-linux-musl
- target: x86_64-unknown-linux-musl
steps:
- name: Sync repository
uses: https://github.com/actions/checkout@v4
- name: Install Nix
uses: https://github.com/DeterminateSystems/nix-installer-action@main
with:
diagnostic-endpoint: ""
extra-conf: |
experimental-features = nix-command flakes
accept-flake-config = true
- name: Install and enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Configure Magic Nix Cache
uses: https://github.com/DeterminateSystems/magic-nix-cache-action@main
with:
diagnostic-endpoint: ""
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
- 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 https://cache.lix.systems https://conduwuit.cachix.org
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
EOF
- name: Use alternative Nix binary caches if specified
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
run: |
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
EOF
- name: Prepare build environment
run: |
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 .#all-features --command true
- name: Build static ${{ matrix.target }}
run: |
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
bin/nix-build-and-cache just .#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: https://github.com/actions/upload-artifact@v4
with:
name: static-${{ matrix.target }}
path: static-${{ matrix.target }}
if-no-files-found: error
- name: Upload deb ${{ matrix.target }}
uses: https://github.com/actions/upload-artifact@v4
with:
name: deb-${{ matrix.target }}
path: ${{ matrix.target }}.deb
if-no-files-found: error
compression-level: 0
- name: Build OCI image ${{ matrix.target }}
run: |
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
- name: Upload OCI image ${{ matrix.target }}
uses: https://github.com/actions/upload-artifact@v4
with:
name: oci-image-${{ matrix.target }}
path: oci-image-${{ matrix.target }}.tar.gz
if-no-files-found: error
compression-level: 0
+181 -114
View File
@@ -22,8 +22,8 @@ concurrency:
env:
# sccache only on main repo
SCCACHE_GHA_ENABLED: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'true' || 'false' }}"
RUSTC_WRAPPER: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
SCCACHE_GHA_ENABLED: "${{ !startsWith(github.ref, 'refs/tags/') && (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'true' || 'false' }}"
RUSTC_WRAPPER: "${{ !startsWith(github.ref, 'refs/tags/') && (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
SCCACHE_BUCKET: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
SCCACHE_S3_USE_SSL: ${{ vars.SCCACHE_S3_USE_SSL }}
SCCACHE_REGION: ${{ vars.SCCACHE_REGION }}
@@ -45,16 +45,14 @@ env:
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
NIX_CONFIG: |
show-trace = true
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
accept-flake-config = true
# complement uses libolm
NIXPKGS_ALLOW_INSECURE: 1
WEB_UPLOAD_SSH_USERNAME: ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
GH_SHA: ${{ github.sha }}
GH_REF_NAME: ${{ github.ref_name }}
WEBSERVER_DIR_NAME: ${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
permissions: {}
@@ -87,11 +85,13 @@ jobs:
END
echo "Checking connection"
ssh -q website "echo test"
ssh -q website "echo test" || ssh -q website "echo test"
echo "Creating commit rev directory on web server"
ssh -q website "rm -rf /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/$GITHUB_SHA/"
ssh -q website "mkdir -v /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/$GITHUB_SHA/"
ssh -q website "rm -rf /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/" || ssh -q website "rm -rf /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/"
ssh -q website "mkdir -v /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/" || ssh -q website "mkdir -v /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/"
echo "SSH_WEBSITE=1" >> "$GITHUB_ENV"
- name: Install liburing
run: |
@@ -126,6 +126,9 @@ jobs:
- uses: nixbuild/nix-quick-install-action@master
- name: Restore and cache Nix store
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
# releases and tags
#if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: nix-community/cache-nix-action@v5.1.0
with:
# restore and save a cache using this key
@@ -155,7 +158,7 @@ jobs:
- name: Apply Nix binary cache configuration
run: |
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
@@ -186,11 +189,16 @@ jobs:
# use sccache for Rust
- name: Run sccache-cache
if: (env.SCCACHE_GHA_ENABLED == 'true')
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
# releases and tags
#if: ${{ (env.SCCACHE_GHA_ENABLED == 'true') && !startsWith(github.ref, 'refs/tags/') }}
uses: mozilla-actions/sccache-action@main
# use rust-cache
- uses: Swatinem/rust-cache@v2
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
# releases and tags
#if: ${{ !startsWith(github.ref, 'refs/tags/') }}
with:
cache-all-crates: "true"
cache-on-failure: "true"
@@ -306,11 +314,16 @@ jobs:
END
echo "Checking connection"
ssh -q website "echo test"
ssh -q website "echo test" || ssh -q website "echo test"
echo "SSH_WEBSITE=1" >> "$GITHUB_ENV"
- uses: nixbuild/nix-quick-install-action@master
- name: Restore and cache Nix store
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
# releases and tags
#if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: nix-community/cache-nix-action@v5.1.0
with:
# restore and save a cache using this key
@@ -340,7 +353,7 @@ jobs:
- name: Apply Nix binary cache configuration
run: |
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
@@ -364,11 +377,16 @@ jobs:
# use sccache for Rust
- name: Run sccache-cache
if: (env.SCCACHE_GHA_ENABLED == 'true')
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
# releases and tags
#if: ${{ (env.SCCACHE_GHA_ENABLED == 'true') && !startsWith(github.ref, 'refs/tags/') }}
uses: mozilla-actions/sccache-action@main
# use rust-cache
- uses: Swatinem/rust-cache@v2
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
# releases and tags
#if: ${{ !startsWith(github.ref, 'refs/tags/') }}
with:
cache-all-crates: "true"
cache-on-failure: "true"
@@ -491,29 +509,29 @@ jobs:
- name: Upload static-x86_64-linux-musl-all-features-x86_64-haswell-optimised to webserver
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp static-x86_64-linux-musl-x86_64-haswell-optimised website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/static-x86_64-linux-musl-x86_64-haswell-optimised
if [ ! -z $SSH_WEBSITE ]; then
chmod +x static-x86_64-linux-musl-x86_64-haswell-optimised
scp static-x86_64-linux-musl-x86_64-haswell-optimised website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/static-x86_64-linux-musl-x86_64-haswell-optimised
fi
- name: Upload static-${{ matrix.target }}-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp static-${{ matrix.target }} website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/static-${{ matrix.target }}
if [ ! -z $SSH_WEBSITE ]; then
chmod +x static-${{ matrix.target }}
scp static-${{ matrix.target }} website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/static-${{ matrix.target }}
fi
- name: Upload static deb x86_64-linux-musl-all-features-x86_64-haswell-optimised to webserver
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp x86_64-linux-musl-x86_64-haswell-optimised.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/x86_64-linux-musl-x86_64-haswell-optimised.deb
if [ ! -z $SSH_WEBSITE ]; then
scp x86_64-linux-musl-x86_64-haswell-optimised.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/x86_64-linux-musl-x86_64-haswell-optimised.deb
fi
- name: Upload static deb ${{ matrix.target }}-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp ${{ matrix.target }}.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/${{ matrix.target }}.deb
if [ ! -z $SSH_WEBSITE ]; then
scp ${{ matrix.target }}.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/${{ matrix.target }}.deb
fi
- name: Upload static-${{ matrix.target }}-debug-all-features to GitHub
@@ -532,17 +550,15 @@ jobs:
compression-level: 0
- name: Upload static-${{ matrix.target }}-debug-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp static-${{ matrix.target }}-debug website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/static-${{ matrix.target }}-debug
if [ ! -z $SSH_WEBSITE ]; then
scp static-${{ matrix.target }}-debug website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/static-${{ matrix.target }}-debug
fi
- name: Upload static deb ${{ matrix.target }}-debug-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp ${{ matrix.target }}-debug.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/${{ matrix.target }}-debug.deb
if [ ! -z $SSH_WEBSITE ]; then
scp ${{ matrix.target }}-debug.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/${{ matrix.target }}-debug.deb
fi
- name: Build OCI image ${{ matrix.target }}-all-features
@@ -564,6 +580,14 @@ jobs:
cp -v -f result oci-image-${{ matrix.target }}-debug.tar.gz
- name: Upload OCI image x86_64-linux-musl-all-features-x86_64-haswell-optimised to GitHub
if: ${{ matrix.target == 'x86_64-linux-musl' }}
uses: actions/upload-artifact@v4
with:
name: oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised
path: oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz
if-no-files-found: error
compression-level: 0
- name: Upload OCI image ${{ matrix.target }}-all-features to GitHub
uses: actions/upload-artifact@v4
with:
@@ -583,22 +607,20 @@ jobs:
- name: Upload OCI image x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz to webserver
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz
if [ ! -z $SSH_WEBSITE ]; then
scp oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz
fi
- name: Upload OCI image ${{ matrix.target }}-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp oci-image-${{ matrix.target }}.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/oci-image-${{ matrix.target }}.tar.gz
if [ ! -z $SSH_WEBSITE ]; then
scp oci-image-${{ matrix.target }}.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/oci-image-${{ matrix.target }}.tar.gz
fi
- name: Upload OCI image ${{ matrix.target }}-debug-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp oci-image-${{ matrix.target }}-debug.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/oci-image-${{ matrix.target }}-debug.tar.gz
if [ ! -z $SSH_WEBSITE ]; then
scp oci-image-${{ matrix.target }}-debug.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/oci-image-${{ matrix.target }}-debug.tar.gz
fi
build_mac_binaries:
@@ -637,7 +659,9 @@ jobs:
END
echo "Checking connection"
ssh -q website "echo test"
ssh -q website "echo test" || ssh -q website "echo test"
echo "SSH_WEBSITE=1" >> "$GITHUB_ENV"
- name: Tag comparison check
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
@@ -653,7 +677,9 @@ jobs:
# use sccache for Rust
- name: Run sccache-cache
if: (env.SCCACHE_GHA_ENABLED == 'true')
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
# releases and tags
#if: ${{ (env.SCCACHE_GHA_ENABLED == 'true') && !startsWith(github.ref, 'refs/tags/') }}
uses: mozilla-actions/sccache-action@main
# use rust-cache
@@ -667,7 +693,7 @@ jobs:
- name: Build macOS x86_64 binary
if: ${{ matrix.os == 'macos-13' }}
run: |
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short ${{ github.sha }})" cargo build --release
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short ${{ github.sha }})" cargo build --release --locked --features=perf_measurements,sentry_telemetry,direct_tls
cp -v -f target/release/conduwuit conduwuit-macos-x86_64
otool -L conduwuit-macos-x86_64
@@ -675,12 +701,13 @@ jobs:
- name: Run x86_64 macOS release binary
if: ${{ matrix.os == 'macos-13' }}
run: |
./conduwuit-macos-x86_64 --help
./conduwuit-macos-x86_64 --version
- name: Build macOS arm64 binary
if: ${{ matrix.os == 'macos-latest' }}
run: |
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short ${{ github.sha }})" cargo build --release
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short ${{ github.sha }})" cargo build --release --locked --features=perf_measurements,sentry_telemetry,direct_tls
cp -v -f target/release/conduwuit conduwuit-macos-arm64
otool -L conduwuit-macos-arm64
@@ -688,20 +715,23 @@ jobs:
- name: Run arm64 macOS release binary
if: ${{ matrix.os == 'macos-latest' }}
run: |
./conduwuit-macos-arm64 --help
./conduwuit-macos-arm64 --version
- name: Upload macOS x86_64 binary to webserver
if: ${{ matrix.os == 'macos-13' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp conduwuit-macos-x86_64 website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/conduwuit-macos-x86_64
if [ ! -z $SSH_WEBSITE ]; then
chmod +x conduwuit-macos-x86_64
scp conduwuit-macos-x86_64 website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/conduwuit-macos-x86_64
fi
- name: Upload macOS arm64 binary to webserver
if: ${{ matrix.os == 'macos-latest' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp conduwuit-macos-arm64 website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/conduwuit-macos-arm64
if [ ! -z $SSH_WEBSITE ]; then
chmod +x conduwuit-macos-arm64
scp conduwuit-macos-arm64 website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/conduwuit-macos-arm64
fi
- name: Upload macOS x86_64 binary
@@ -733,24 +763,17 @@ jobs:
docker:
name: Docker publish
runs-on: ubuntu-24.04
needs: [build, variables]
needs: [build, variables, tests]
permissions:
packages: write
contents: read
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && github.event.pull_request.user.login != 'renovate[bot]'
env:
DOCKER_ARM64: docker.io/${{ needs.variables.outputs.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/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
DOCKER_TAG: docker.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
DOCKER_BRANCH: docker.io/${{ needs.variables.outputs.github_repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GHCR_ARM64: ghcr.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
GHCR_AMD64: ghcr.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
GHCR_TAG: ghcr.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
GHCR_BRANCH: ghcr.io/${{ needs.variables.outputs.github_repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GLCR_ARM64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
GLCR_AMD64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
GLCR_TAG: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
GLCR_BRANCH: registry.gitlab.com/conduwuit/conduwuit:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
DOCKER_HUB_REPO: docker.io/${{ needs.variables.outputs.github_repository }}
GHCR_REPO: ghcr.io/${{ needs.variables.outputs.github_repository }}
GLCR_REPO: registry.gitlab.com/conduwuit/conduwuit
UNIQUE_TAG: ${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
BRANCH_TAG: ${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
@@ -784,143 +807,187 @@ jobs:
- name: Move OCI images into position
run: |
mv -v oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised/*.tar.gz oci-image-amd64-haswell-optimised.tar.gz
mv -v oci-image-x86_64-linux-musl/*.tar.gz oci-image-amd64.tar.gz
mv -v oci-image-aarch64-linux-musl/*.tar.gz oci-image-arm64v8.tar.gz
mv -v oci-image-x86_64-linux-musl-debug/*.tar.gz oci-image-amd64-debug.tar.gz
mv -v oci-image-aarch64-linux-musl-debug/*.tar.gz oci-image-arm64v8-debug.tar.gz
- name: Load and push amd64 haswell image
run: |
docker load -i oci-image-amd64-haswell-optimised.tar.gz
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-haswell
docker push ${GHCR_REPO}:${UNIQUE_TAG}-haswell
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-haswell
docker push ${GLCR_REPO}:${UNIQUE_TAG}-haswell
fi
- name: Load and push amd64 image
run: |
docker load -i oci-image-amd64.tar.gz
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_AMD64}
docker push ${DOCKER_AMD64}
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_AMD64}
docker push ${GHCR_AMD64}
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-amd64
docker push ${GHCR_REPO}:${UNIQUE_TAG}-amd64
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_AMD64}
docker push ${GLCR_AMD64}
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-amd64
docker push ${GLCR_REPO}:${UNIQUE_TAG}-amd64
fi
- name: Load and push arm64 image
run: |
docker load -i oci-image-arm64v8.tar.gz
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_ARM64}
docker push ${DOCKER_ARM64}
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_ARM64}
docker push ${GHCR_ARM64}
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8
docker push ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_ARM64}
docker push ${GLCR_ARM64}
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8
docker push ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8
fi
- name: Load and push amd64 debug image
run: |
docker load -i oci-image-amd64-debug.tar.gz
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_AMD64}-debug
docker push ${DOCKER_AMD64}-debug
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64-debug
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64-debug
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_AMD64}-debug
docker push ${GHCR_AMD64}-debug
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-amd64-debug
docker push ${GHCR_REPO}:${UNIQUE_TAG}-amd64-debug
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_AMD64}-debug
docker push ${GLCR_AMD64}-debug
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-amd64-debug
docker push ${GLCR_REPO}:${UNIQUE_TAG}-amd64-debug
fi
- name: Load and push arm64 debug image
run: |
docker load -i oci-image-arm64v8-debug.tar.gz
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_ARM64}-debug
docker push ${DOCKER_ARM64}-debug
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8-debug
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8-debug
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_ARM64}-debug
docker push ${GHCR_ARM64}-debug
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8-debug
docker push ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8-debug
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_ARM64}-debug
docker push ${GLCR_ARM64}-debug
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8-debug
docker push ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8-debug
fi
- name: Create Docker haswell manifests
run: |
# Dockerhub Container Registry
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker manifest create ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
docker manifest create ${DOCKER_HUB_REPO}:${BRANCH_TAG}-haswell --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
fi
# GitHub Container Registry
if [ $GHCR_ENABLED = "true" ]; then
docker manifest create ${GHCR_REPO}:${UNIQUE_TAG}-haswell --amend ${GHCR_REPO}:${UNIQUE_TAG}-haswell
docker manifest create ${GHCR_REPO}:${BRANCH_TAG}-haswell --amend ${GHCR_REPO}:${UNIQUE_TAG}-haswell
fi
# GitLab Container Registry
if [ ! -z $GITLAB_TOKEN ]; then
docker manifest create ${GLCR_REPO}:${UNIQUE_TAG}-haswell --amend ${GLCR_REPO}:${UNIQUE_TAG}-haswell
docker manifest create ${GLCR_REPO}:${BRANCH_TAG}-haswell --amend ${GLCR_REPO}:${UNIQUE_TAG}-haswell
fi
- name: Create Docker combined manifests
run: |
# Dockerhub Container Registry
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker manifest create ${DOCKER_TAG} --amend ${DOCKER_ARM64} --amend ${DOCKER_AMD64}
docker manifest create ${DOCKER_BRANCH} --amend ${DOCKER_ARM64} --amend ${DOCKER_AMD64}
docker manifest create ${DOCKER_HUB_REPO}:${UNIQUE_TAG} --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64
docker manifest create ${DOCKER_HUB_REPO}:${BRANCH_TAG} --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64
fi
# GitHub Container Registry
if [ $GHCR_ENABLED = "true" ]; then
docker manifest create ${GHCR_TAG} --amend ${GHCR_ARM64} --amend ${GHCR_AMD64}
docker manifest create ${GHCR_BRANCH} --amend ${GHCR_ARM64} --amend ${GHCR_AMD64}
docker manifest create ${GHCR_REPO}:${UNIQUE_TAG} --amend ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${GHCR_REPO}:${UNIQUE_TAG}-amd64
docker manifest create ${GHCR_REPO}:${BRANCH_TAG} --amend ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${GHCR_REPO}:${UNIQUE_TAG}-amd64
fi
# GitLab Container Registry
if [ ! -z $GITLAB_TOKEN ]; then
docker manifest create ${GLCR_TAG} --amend ${GLCR_ARM64} --amend ${GLCR_AMD64}
docker manifest create ${GLCR_BRANCH} --amend ${GLCR_ARM64} --amend ${GLCR_AMD64}
docker manifest create ${GLCR_REPO}:${UNIQUE_TAG} --amend ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${GLCR_REPO}:${UNIQUE_TAG}-amd64
docker manifest create ${GLCR_REPO}:${BRANCH_TAG} --amend ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${GLCR_REPO}:${UNIQUE_TAG}-amd64
fi
- name: Create Docker combined debug manifests
run: |
# Dockerhub Container Registry
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker manifest create ${DOCKER_TAG}-debug --amend ${DOCKER_ARM64}-debug --amend ${DOCKER_AMD64}-debug
docker manifest create ${DOCKER_BRANCH}-debug --amend ${DOCKER_ARM64}-debug --amend ${DOCKER_AMD64}-debug
docker manifest create ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-debug --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64-debug
docker manifest create ${DOCKER_HUB_REPO}:${BRANCH_TAG}-debug --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64-debug
fi
# GitHub Container Registry
if [ $GHCR_ENABLED = "true" ]; then
docker manifest create ${GHCR_TAG}-debug --amend ${GHCR_ARM64}-debug --amend ${GHCR_AMD64}-debug
docker manifest create ${GHCR_BRANCH}-debug --amend ${GHCR_ARM64}-debug --amend ${GHCR_AMD64}-debug
docker manifest create ${GHCR_REPO}:${UNIQUE_TAG}-debug --amend ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${GHCR_REPO}:${UNIQUE_TAG}-amd64-debug
docker manifest create ${GHCR_REPO}:${BRANCH_TAG}-debug --amend ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${GHCR_REPO}:${UNIQUE_TAG}-amd64-debug
fi
# GitLab Container Registry
if [ ! -z $GITLAB_TOKEN ]; then
docker manifest create ${GLCR_TAG}-debug --amend ${GLCR_ARM64}-debug --amend ${GLCR_AMD64}-debug
docker manifest create ${GLCR_BRANCH}-debug --amend ${GLCR_ARM64}-debug --amend ${GLCR_AMD64}-debug
docker manifest create ${GLCR_REPO}:${UNIQUE_TAG}-debug --amend ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${GLCR_REPO}:${UNIQUE_TAG}-amd64-debug
docker manifest create ${GLCR_REPO}:${BRANCH_TAG}-debug --amend ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${GLCR_REPO}:${UNIQUE_TAG}-amd64-debug
fi
- name: Push manifests to Docker registries
run: |
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker manifest push ${DOCKER_TAG}
docker manifest push ${DOCKER_BRANCH}
docker manifest push ${DOCKER_TAG}-debug
docker manifest push ${DOCKER_BRANCH}-debug
docker manifest push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}
docker manifest push ${DOCKER_HUB_REPO}:${BRANCH_TAG}
docker manifest push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-debug
docker manifest push ${DOCKER_HUB_REPO}:${BRANCH_TAG}-debug
docker manifest push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
docker manifest push ${DOCKER_HUB_REPO}:${BRANCH_TAG}-haswell
fi
if [ $GHCR_ENABLED = "true" ]; then
docker manifest push ${GHCR_TAG}
docker manifest push ${GHCR_BRANCH}
docker manifest push ${GHCR_TAG}-debug
docker manifest push ${GHCR_BRANCH}-debug
docker manifest push ${GHCR_REPO}:${UNIQUE_TAG}
docker manifest push ${GHCR_REPO}:${BRANCH_TAG}
docker manifest push ${GHCR_REPO}:${UNIQUE_TAG}-debug
docker manifest push ${GHCR_REPO}:${BRANCH_TAG}-debug
docker manifest push ${GHCR_REPO}:${UNIQUE_TAG}-haswell
docker manifest push ${GHCR_REPO}:${BRANCH_TAG}-haswell
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker manifest push ${GLCR_TAG}
docker manifest push ${GLCR_BRANCH}
docker manifest push ${GLCR_TAG}-debug
docker manifest push ${GLCR_BRANCH}-debug
docker manifest push ${GLCR_REPO}:${UNIQUE_TAG}
docker manifest push ${GLCR_REPO}:${BRANCH_TAG}
docker manifest push ${GLCR_REPO}:${UNIQUE_TAG}-debug
docker manifest push ${GLCR_REPO}:${BRANCH_TAG}-debug
docker manifest push ${GLCR_REPO}:${UNIQUE_TAG}-haswell
docker manifest push ${GLCR_REPO}:${BRANCH_TAG}-haswell
fi
- name: Add Image Links to Job Summary
run: |
if [ ! -z $DOCKERHUB_TOKEN ]; then
echo "- \`docker pull ${DOCKER_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${DOCKER_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${DOCKER_HUB_REPO}:${UNIQUE_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell\`" >> $GITHUB_STEP_SUMMARY
fi
if [ $GHCR_ENABLED = "true" ]; then
echo "- \`docker pull ${GHCR_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GHCR_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GHCR_REPO}:${UNIQUE_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GHCR_REPO}:${UNIQUE_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GHCR_REPO}:${UNIQUE_TAG}-haswell\`" >> $GITHUB_STEP_SUMMARY
fi
if [ ! -z $GITLAB_TOKEN ]; then
echo "- \`docker pull ${GLCR_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GLCR_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GLCR_REPO}:${UNIQUE_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GLCR_REPO}:${UNIQUE_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GLCR_REPO}:${UNIQUE_TAG}-haswell\`" >> $GITHUB_STEP_SUMMARY
fi
@@ -0,0 +1,41 @@
name: Update Docker Hub Description
on:
push:
branches:
- main
paths:
- README.md
- .github/workflows/docker-hub-description.yml
workflow_dispatch:
jobs:
dockerHubDescription:
runs-on: ubuntu-latest
if: ${{ (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && github.event.pull_request.user.login != 'renovate[bot]' && (vars.DOCKER_USERNAME != '') }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setting variables
uses: actions/github-script@v7
id: var
with:
script: |
const githubRepo = '${{ github.repository }}'.toLowerCase()
const repoId = githubRepo.split('/')[1]
core.setOutput('github_repository', githubRepo)
const dockerRepo = '${{ vars.DOCKER_USERNAME }}'.toLowerCase() + '/' + repoId
core.setOutput('docker_repo', dockerRepo)
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ vars.DOCKER_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ steps.var.outputs.docker_repo }}
short-description: ${{ github.event.repository.description }}
enable-url-completion: true
+5 -2
View File
@@ -24,7 +24,7 @@ env:
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
NIX_CONFIG: |
show-trace = true
extra-substituters = extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-substituters = extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
@@ -73,6 +73,9 @@ jobs:
- uses: nixbuild/nix-quick-install-action@master
- name: Restore and cache Nix store
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
# releases and tags
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
uses: nix-community/cache-nix-action@v5.1.0
with:
# restore and save a cache using this key
@@ -102,7 +105,7 @@ jobs:
- name: Apply Nix binary cache configuration
run: |
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
+2 -6
View File
@@ -12,8 +12,8 @@ variables:
TRANSFER_METER_FREQUENCY: 5s
NIX_CONFIG: |
show-trace = true
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://conduwuit.cachix.org
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
accept-flake-config = true
@@ -45,10 +45,6 @@ before_script:
- if command -v nix > /dev/null && [ -n "$ATTIC_ENDPOINT" ]; then echo "extra-substituters = $ATTIC_ENDPOINT" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null && [ -n "$ATTIC_PUBLIC_KEY" ]; then echo "extra-trusted-public-keys = $ATTIC_PUBLIC_KEY" >> /etc/nix/nix.conf; fi
# Add Lix binary cache
- if command -v nix > /dev/null; then echo "extra-substituters = https://cache.lix.systems" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=" >> /etc/nix/nix.conf; fi
# Add crane binary cache
- if command -v nix > /dev/null; then echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=" >> /etc/nix/nix.conf; fi
Generated
+663 -274
View File
File diff suppressed because it is too large Load Diff
+42 -21
View File
@@ -7,19 +7,20 @@ default-members = ["src/*"]
[workspace.package]
authors = [
"strawberry <strawberry@puppygock.gay>",
"timokoesters <timo@koesters.xyz>",
"June Clementine Strawberry <june@girlboss.ceo>",
"strawberry <strawberry@puppygock.gay>", # woof
"Jason Volk <jason@zemos.net>",
]
categories = ["network-programming"]
description = "a very cool fork of Conduit, a Matrix homeserver written in Rust"
description = "a very cool Matrix chat homeserver written in Rust"
edition = "2021"
homepage = "https://conduwuit.puppyirl.gay/"
keywords = ["chat", "matrix", "server", "uwu"]
keywords = ["chat", "matrix", "networking", "server", "uwu"]
license = "Apache-2.0"
# See also `rust-toolchain.toml`
readme = "README.md"
repository = "https://github.com/girlbossceo/conduwuit"
rust-version = "1.83.0"
rust-version = "1.84.0"
version = "0.5.0"
[workspace.metadata.crane]
@@ -58,10 +59,6 @@ features = ["parse"]
[workspace.dependencies.sanitize-filename]
version = "0.6.0"
[workspace.dependencies.jsonwebtoken]
version = "9.3.0"
default-features = false
[workspace.dependencies.base64]
version = "0.22.1"
default-features = false
@@ -130,12 +127,13 @@ version = "0.6.2"
default-features = false
features = [
"add-extension",
"catch-panic",
"cors",
"sensitive-headers",
"set-header",
"timeout",
"trace",
"util",
"catch-panic",
]
[workspace.dependencies.rustls]
@@ -181,7 +179,7 @@ version = "0.5.3"
features = ["alloc", "rand"]
default-features = false
# Used to generate thumbnails for images
# Used to generate thumbnails for images & blurhashes
[workspace.dependencies.image]
version = "0.25.5"
default-features = false
@@ -192,6 +190,14 @@ features = [
"webp",
]
[workspace.dependencies.blurhash]
version = "0.2.3"
default-features = false
features = [
"fast-linear-to-srgb",
"image",
]
# logging
[workspace.dependencies.log]
version = "0.4.22"
@@ -336,7 +342,7 @@ version = "0.1.2"
[workspace.dependencies.ruma]
git = "https://github.com/girlbossceo/ruwuma"
#branch = "conduwuit-changes"
rev = "c4f55b39900b33b2d443dd12a6a2dab50961fdfb"
rev = "f5667c6292adb43fbe4725d31d6b5127a0cf60ce"
features = [
"compat",
"rand",
@@ -435,17 +441,23 @@ version = "0.35.0"
# jemalloc usage
[workspace.dependencies.tikv-jemalloc-sys]
git = "https://github.com/girlbossceo/jemallocator"
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
default-features = false
features = ["unprefixed_malloc_on_supported_platforms"]
features = [
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
]
[workspace.dependencies.tikv-jemallocator]
git = "https://github.com/girlbossceo/jemallocator"
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
default-features = false
features = ["unprefixed_malloc_on_supported_platforms"]
features = [
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
]
[workspace.dependencies.tikv-jemalloc-ctl]
git = "https://github.com/girlbossceo/jemallocator"
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
default-features = false
features = ["use_std"]
@@ -504,6 +516,14 @@ version = "0.2"
[workspace.dependencies.num-traits]
version = "0.2"
[workspace.dependencies.minicbor]
version = "0.25.1"
features = ["std"]
[workspace.dependencies.minicbor-serde]
version = "0.3.2"
features = ["std"]
#
# Patches
#
@@ -513,16 +533,16 @@ version = "0.2"
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
[patch.crates-io.tracing-subscriber]
git = "https://github.com/girlbossceo/tracing"
rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
rev = "05825066a6d0e9ad6b80dcf29457eb179ff4768c"
[patch.crates-io.tracing]
git = "https://github.com/girlbossceo/tracing"
rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
rev = "05825066a6d0e9ad6b80dcf29457eb179ff4768c"
[patch.crates-io.tracing-core]
git = "https://github.com/girlbossceo/tracing"
rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
rev = "05825066a6d0e9ad6b80dcf29457eb179ff4768c"
[patch.crates-io.tracing-log]
git = "https://github.com/girlbossceo/tracing"
rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
rev = "05825066a6d0e9ad6b80dcf29457eb179ff4768c"
# adds a tab completion callback: https://github.com/girlbossceo/rustyline-async/commit/de26100b0db03e419a3d8e1dd26895d170d1fe50
# adds event for CTRL+\: https://github.com/girlbossceo/rustyline-async/commit/67d8c49aeac03a5ef4e818f663eaa94dd7bf339b
@@ -873,6 +893,7 @@ enum_glob_use = { level = "allow", priority = 1 }
if_not_else = { level = "allow", priority = 1 }
if_then_some_else_none = { level = "allow", priority = 1 }
inline_always = { level = "allow", priority = 1 }
match_bool = { level = "allow", priority = 1 }
missing_docs_in_private_items = { level = "allow", priority = 1 }
missing_errors_doc = { level = "allow", priority = 1 }
missing_panics_doc = { level = "allow", priority = 1 }
+53 -22
View File
@@ -4,7 +4,7 @@
<!-- ANCHOR: catchphrase -->
### a very cool, featureful fork of [Conduit](https://conduit.rs/)
### a very cool [Matrix](https://matrix.org/) chat homeserver written in Rust
<!-- ANCHOR_END: catchphrase -->
@@ -15,16 +15,15 @@ information and how to deploy/setup conduwuit.
#### What is Matrix?
[Matrix](https://matrix.org) is an open network for secure and decentralized
communication. Users from every Matrix homeserver can chat with users from all
other Matrix servers. You can even use bridges (also called Matrix Appservices)
to communicate with users outside of Matrix, like a community on Discord.
[Matrix](https://matrix.org) is an open, federated, and extensible network for
decentralised communication. Users from any Matrix homeserver can chat with users from all
other homeservers over federation. Matrix is designed to be extensible and built on top of.
You can even use bridges such as Matrix Appservices to communicate with users outside of Matrix, like a community on Discord.
#### What is the goal?
A high-performance and efficient Matrix homeserver that's easy to set up and
just works. You can install it on a mini-computer like the Raspberry Pi to
host Matrix for your family, friends or company.
A high-performance, efficient, low-cost, and featureful Matrix homeserver that's
easy to set up and just works with minimal configuration needed.
#### Can I try it out?
@@ -37,17 +36,22 @@ homeserver". This means there are rules, so please read the rules:
[https://transfem.dev/homeserver_rules.txt](https://transfem.dev/homeserver_rules.txt)
transfem.dev is also listed at
[servers.joinmatrix.org](https://servers.joinmatrix.org/)
[servers.joinmatrix.org](https://servers.joinmatrix.org/), which is a list of
popular public Matrix homeservers, including some others that run conduwuit.
#### What is the current status?
conduwuit is technically a hard fork of Conduit, which is in Beta. The Beta status
initially was inherited from Conduit, however overtime this Beta status is rapidly
becoming less and less relevant as our codebase significantly diverges more and more.
conduwuit is technically a hard fork of [Conduit](https://conduit.rs/), which is in beta.
The beta status initially was inherited from Conduit, however the huge amount of
codebase divergance, changes, fixes, and improvements have effectively made this
beta status not entirely applicable to us anymore.
conduwuit is quite stable and very usable as a daily driver and for a low-medium
sized homeserver. There is still a lot of more work to be done, but it is in a far
better place than the project was in early 2024.
conduwuit is very stable based on our rapidly growing userbase, has lots of features that users
expect, and very usable as a daily driver for small, medium, and upper-end medium sized homeservers.
A lot of critical stability and performance issues have been fixed, and a lot of
necessary groundwork has finished; making this project way better than it was
back in the start at ~early 2024.
#### How is conduwuit funded? Is conduwuit sustainable?
@@ -72,16 +76,37 @@ Conduit like before. If you are truly finding yourself wanting to migrate back
to Conduit, we would appreciate all your feedback and if we can assist with
any issues or concerns.
#### Can I migrate from Synapse or Dendrite?
Currently there is no known way to seamlessly migrate all user data from the old
homeserver to conduwuit. However it is perfectly acceptable to replace the old
homeserver software with conduwuit using the same server name and there will not
be any issues with federation.
There is an interest in developing a built-in seamless user data migration
method into conduwuit, however there is no concrete ETA or timeline for this.
<!-- ANCHOR_END: body -->
<!-- ANCHOR: footer -->
#### Contact
If you run into any question, feel free to
[`#conduwuit:puppygock.gay`](https://matrix.to/#/#conduwuit:puppygock.gay)
is the official project Matrix room. You can get support here, ask questions or
concerns, get assistance setting up conduwuit, etc.
- Ask us in `#conduwuit:puppygock.gay` on Matrix
- [Open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new)
This room should stay relevant and focused on conduwuit. An offtopic general
chatter room can be found there as well.
Please keep the issue trackers focused on bug reports and enhancement requests.
General support is extremely difficult to be offered over an issue tracker, and
simple questions should be asked directly in an interactive platform like our
Matrix room above as they can turn into a relevant discussion and/or may not be
simple to answer. If you're not sure, just ask in the Matrix room.
If you have a bug or feature to request: [Open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new)
#### Donate
@@ -89,9 +114,11 @@ conduwuit development is purely made possible by myself and contributors. I do
not get paid to work on this, and I work on it in my free time. Donations are
heavily appreciated! 💜🥺
- Liberapay: <https://liberapay.com/girlbossceo>
- Ko-fi (note they take a fee): <https://ko-fi.com/puppygock>
- GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
- Liberapay (preferred): <https://liberapay.com/girlbossceo>
- GitHub Sponsors (preferred): <https://github.com/sponsors/girlbossceo>
- Ko-fi: <https://ko-fi.com/puppygock>
I do not and will not accept cryptocurrency donations, including things related.
#### Logo
@@ -105,11 +132,15 @@ Both, but I prefer conduwuit.
#### Mirrors of conduwuit
If GitHub is unavailable in your country, or has poor connectivity, conduwuit's
source code is mirrored onto the following additional platforms I maintain:
- GitHub: <https://github.com/girlbossceo/conduwuit>
- GitLab: <https://gitlab.com/conduwuit/conduwuit>
- git.girlcock.ceo: <https://git.girlcock.ceo/strawberry/conduwuit>
- git.gay: <https://git.gay/june/conduwuit>
- Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
- mau.dev: <https://mau.dev/june/conduwuit>
- Codeberg: <https://codeberg.org/arf/conduwuit>
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
<!-- ANCHOR_END: footer -->
+14 -1
View File
@@ -7,7 +7,20 @@ RequiresMountsFor=/var/lib/private/conduwuit
[Service]
DynamicUser=yes
Type=notify
Type=notify-reload
ReloadSignal=SIGUSR1
TTYPath=/dev/tty25
DeviceAllow=char-tty
StandardInput=tty-force
StandardOutput=tty
StandardError=journal+console
TTYReset=yes
# uncomment to allow buffer to be cleared every restart
TTYVTDisallocate=no
TTYColumns=120
TTYRows=40
AmbientCapabilities=
CapabilityBoundingSet=
+2 -1
View File
@@ -34,7 +34,8 @@ toplevel="$(git rev-parse --show-toplevel)"
pushd "$toplevel" > /dev/null
bin/nix-build-and-cache just .#linux-complement
#bin/nix-build-and-cache just .#linux-complement
bin/nix-build-and-cache just .#complement
docker load < result
popd > /dev/null
+122 -24
View File
@@ -100,20 +100,6 @@
#
#database_backups_to_keep = 1
# Set this to any float value in megabytes for conduwuit to tell the
# database engine that this much memory is available for database-related
# caches.
#
# May be useful if you have significant memory to spare to increase
# performance.
#
# Similar to the individual LRU caches, this is scaled up with your CPU
# core count.
#
# This defaults to 128.0 + (64.0 * CPU core count).
#
#db_cache_capacity_mb = varies by system
# Text which will be added to the end of the user's displayname upon
# registration with a space before the text. In Conduit, this was the
# lightning bolt emoji.
@@ -149,6 +135,34 @@
#
#cache_capacity_modifier = 1.0
# Set this to any float value in megabytes for conduwuit to tell the
# database engine that this much memory is available for database read
# caches.
#
# May be useful if you have significant memory to spare to increase
# performance.
#
# Similar to the individual LRU caches, this is scaled up with your CPU
# core count.
#
# This defaults to 128.0 + (64.0 * CPU core count).
#
#db_cache_capacity_mb = varies by system
# Set this to any float value in megabytes for conduwuit to tell the
# database engine that this much memory is available for database write
# caches.
#
# May be useful if you have significant memory to spare to increase
# performance.
#
# Similar to the individual LRU caches, this is scaled up with your CPU
# core count.
#
# This defaults to 48.0 + (4.0 * CPU core count).
#
#db_write_buffer_capacity_mb = varies by system
# This item is undocumented. Please contribute documentation for it.
#
#pdu_cache_capacity = varies by system
@@ -363,6 +377,26 @@
#
#pusher_idle_timeout = 15
# Maximum time to receive a request from a client (seconds).
#
#client_receive_timeout = 75
# Maximum time to process a request received from a client (seconds).
#
#client_request_timeout = 180
# Maximum time to transmit a response to a client (seconds)
#
#client_response_timeout = 120
# Grace period for clean shutdown of client requests (seconds).
#
#client_shutdown_timeout = 10
# Grace period for clean shutdown of federation requests (seconds).
#
#sender_shutdown_timeout = 5
# Enables registration. If set to false, no users can register on this
# server.
#
@@ -375,13 +409,16 @@
#
#allow_registration = false
# This item is undocumented. Please contribute documentation for it.
# Enabling this setting opens registration to anyone without restrictions.
# This makes your server vulnerable to abuse
#
#yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false
# A static registration token that new users will have to provide when
# creating an account. If unset and `allow_registration` is true,
# registration is open without any condition.
# you must set
# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
# to true to allow open registration without any conditions.
#
# YOU NEED TO EDIT THIS OR USE registration_token_file.
#
@@ -389,8 +426,9 @@
#
#registration_token =
# Path to a file on the system that gets read for the registration token.
# this config option takes precedence/priority over "registration_token".
# Path to a file on the system that gets read for additional registration
# tokens. Multiple tokens can be added if you separate them with
# whitespace
#
# conduwuit must be able to access the file, and it must not be empty
#
@@ -549,10 +587,6 @@
#
#proxy = "none"
# This item is undocumented. Please contribute documentation for it.
#
#jwt_secret =
# Servers listed here will be used to gather public keys of other servers
# (notary trusted key servers).
#
@@ -635,6 +669,22 @@
#
#openid_token_ttl = 3600
# Allow an existing session to mint a login token for another client.
# This requires interactive authentication, but has security ramifications
# as a malicious client could use the mechanism to spawn more than one
# session.
# Enabled by default.
#
#login_via_existing_session = true
# Login token expiration/TTL in milliseconds.
#
# These are short-lived tokens for the m.login.token endpoint.
# This is used to allow existing sessions to create new sessions.
# see login_via_existing_session.
#
#login_token_ttl = 120000
# Static TURN username to provide the client if not using a shared secret
# ("turn_secret"), It is recommended to use a shared secret over static
# credentials.
@@ -792,6 +842,9 @@
# magic number and translated to the library's default compression level
# as they all differ. See their `kDefaultCompressionLevel`.
#
# Note when using the default value we may override it with a setting
# tailored specifically conduwuit.
#
#rocksdb_compression_level = 32767
# Level of compression the specified compression algorithm for the
@@ -805,6 +858,9 @@
# less likely for this data to be used. Research your chosen compression
# algorithm.
#
# Note when using the default value we may override it with a setting
# tailored specifically conduwuit.
#
#rocksdb_bottommost_compression_level = 32767
# Whether to enable RocksDB's "bottommost_compression".
@@ -816,7 +872,7 @@
#
# See https://github.com/facebook/rocksdb/wiki/Compression for more details.
#
#rocksdb_bottommost_compression = false
#rocksdb_bottommost_compression = true
# Database recovery mode (for RocksDB WAL corruption).
#
@@ -862,6 +918,13 @@
#
#rocksdb_paranoid_file_checks = false
# Enables or disables checksum verification in rocksdb at runtime.
# Checksums are usually hardware accelerated with low overhead; they are
# enabled in rocksdb by default. Older or slower platforms may see gains
# from disabling.
#
#rocksdb_checksums = true
# Database repair mode (for RocksDB SST corruption).
#
# Use this option when the server reports corruption while running or
@@ -1320,6 +1383,13 @@
#
#admin_execute_errors_ignore = false
# List of admin commands to execute on SIGUSR2.
#
# Similar to admin_execute, but these commands are executed when the
# server receives SIGUSR2 on supporting platforms.
#
#admin_signal_execute = []
# Controls the max log level for admin command log captures (logs
# generated from running admin commands). Defaults to "info" on release
# builds, else "debug" on debug builds.
@@ -1452,7 +1522,7 @@
# responsiveness for many users at the cost of throughput for each.
#
# Setting this value to 0.0 causes the stream width to be fixed at the
# value of stream_width_default. The default is 1.0 to match the
# value of stream_width_default. The default scale is 1.0 to match the
# capabilities detected for the system.
#
#stream_width_scale = 1.0
@@ -1477,6 +1547,16 @@
#
#sender_workers = 0
# Enables listener sockets; can be set to false to disable listening. This
# option is intended for developer/diagnostic purposes only.
#
#listening = true
# Enables configuration reload when the server receives SIGUSR1 on
# supporting platforms.
#
#config_reload_signal = true
[global.tls]
# Path to a valid TLS certificate file.
@@ -1527,3 +1607,21 @@
# This item is undocumented. Please contribute documentation for it.
#
#support_mxid =
[global.blurhashing]
# blurhashing x component, 4 is recommended by https://blurha.sh/
#
#components_x = 4
# blurhashing y component, 3 is recommended by https://blurha.sh/
#
#components_y = 3
# Max raw size that the server will blurhash, this is the size of the
# image after converting it to raw data, it should be higher than the
# upload limit but not too high. The higher it is the higher the
# potential load will be for clients requesting blurhashes. The default
# is 33.55MB. Setting it to 0 disables blurhashing.
#
#blurhash_max_raw_size = 33554432
+14 -1
View File
@@ -8,7 +8,20 @@ Documentation=https://conduwuit.puppyirl.gay/
DynamicUser=yes
User=conduwuit
Group=conduwuit
Type=notify
Type=notify-reload
ReloadSignal=SIGUSR1
TTYPath=/dev/tty25
DeviceAllow=char-tty
StandardInput=tty-force
StandardOutput=tty
StandardError=journal+console
TTYReset=yes
# uncomment to allow buffer to be cleared every restart
TTYVTDisallocate=no
TTYColumns=120
TTYRows=40
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
+16 -4
View File
@@ -10,21 +10,33 @@ CONDUWUIT_DATABASE_PATH_SYMLINK=/var/lib/matrix-conduit
case $1 in
purge)
# Remove debconf changes from the db
db_purge
#db_purge
# Per https://www.debian.org/doc/debian-policy/ch-files.html#behavior
# "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 -v -r "$CONDUWUIT_CONFIG_PATH"
if test -L "$CONDUWUIT_CONFIG_PATH"; then
echo "Deleting conduwuit configuration files"
rm -v -r "$CONDUWUIT_CONFIG_PATH"
fi
fi
if [ -d "$CONDUWUIT_DATABASE_PATH" ]; then
rm -v -r "$CONDUWUIT_DATABASE_PATH"
if test -L "$CONDUWUIT_DATABASE_PATH"; then
echo "Deleting conduwuit database directory"
rm -r "$CONDUWUIT_DATABASE_PATH"
fi
fi
if [ -d "$CONDUWUIT_DATABASE_PATH_SYMLINK" ]; then
rm -v -r "$CONDUWUIT_DATABASE_PATH_SYMLINK"
if test -L "$CONDUWUIT_DATABASE_SYMLINK"; then
echo "Removing matrix-conduit symlink"
rm -r "$CONDUWUIT_DATABASE_PATH_SYMLINK"
fi
fi
;;
esac
+1 -1
View File
@@ -27,7 +27,7 @@ malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
[dependencies.rust-rocksdb]
git = "https://github.com/girlbossceo/rust-rocksdb-zaidoon1"
rev = "123d6302fed23fc706344becb2f19623265a83f8"
rev = "7b0e1bbe395a41ba8a11347a4921da590e3ad0d9"
#branch = "master"
default-features = false
@@ -17,6 +17,8 @@ services:
CONDUWUIT_PORT: 6167 # should match the loadbalancer traefik label
CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
CONDUWUIT_ALLOW_REGISTRATION: 'true'
CONDUWUIT_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#CONDUWUIT_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
CONDUWUIT_ALLOW_FEDERATION: 'true'
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
@@ -33,6 +33,8 @@ services:
CONDUWUIT_PORT: 6167
CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
CONDUWUIT_ALLOW_REGISTRATION: 'true'
CONDUWUIT_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#CONDUWUIT_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
CONDUWUIT_ALLOW_FEDERATION: 'true'
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
+2
View File
@@ -17,6 +17,8 @@ services:
CONDUWUIT_PORT: 6167
CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
CONDUWUIT_ALLOW_REGISTRATION: 'true'
CONDUWUIT_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#CONDUWUIT_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
CONDUWUIT_ALLOW_FEDERATION: 'true'
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
+56 -25
View File
@@ -86,6 +86,7 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \
cargo doc \
--workspace \
--locked \
--profile test \
--all-features \
--no-deps \
@@ -97,10 +98,11 @@ env DIRENV_DEVSHELL=all-features \
name = "clippy/default"
group = "lints"
script = """
direnv exec . \
cargo clippy \
--workspace \
--locked \
--profile test \
--all-targets \
--color=always \
-- \
-D warnings
@@ -114,8 +116,8 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \
cargo clippy \
--workspace \
--locked \
--profile test \
--all-targets \
--all-features \
--color=always \
-- \
@@ -123,32 +125,37 @@ env DIRENV_DEVSHELL=all-features \
"""
[[task]]
name = "clippy/jemalloc"
name = "clippy/no-features"
group = "lints"
script = """
env DIRENV_DEVSHELL=no-features \
direnv exec . \
cargo clippy \
--workspace \
--locked \
--profile test \
--no-default-features \
--color=always \
-- \
-D warnings
"""
[[task]]
name = "clippy/other-features"
group = "lints"
script = """
direnv exec . \
cargo clippy \
--workspace \
--locked \
--profile test \
--features jemalloc \
--all-targets \
--no-default-features \
--features=console,systemd,element_hacks,direct_tls,perf_measurements,brotli_compression,blurhashing \
--color=always \
-- \
-D warnings
"""
#[[task]]
#name = "clippy/hardened_malloc"
#group = "lints"
#script = """
#cargo clippy \
# --workspace \
# --features hardened_malloc \
# --all-targets \
# --color=always \
# -- \
# -D warnings
#"""
[[task]]
name = "lychee"
group = "lints"
@@ -167,8 +174,10 @@ env DIRENV_DEVSHELL=all-features \
direnv exec . \
cargo test \
--workspace \
--locked \
--profile test \
--all-targets \
--no-fail-fast \
--all-features \
--color=always \
-- \
@@ -179,13 +188,35 @@ env DIRENV_DEVSHELL=all-features \
name = "cargo/default"
group = "tests"
script = """
cargo test \
--workspace \
--profile test \
--all-targets \
--color=always \
-- \
--color=always
env DIRENV_DEVSHELL=default \
direnv exec . \
cargo test \
--workspace \
--locked \
--profile test \
--all-targets \
--no-fail-fast \
--color=always \
-- \
--color=always
"""
[[task]]
name = "cargo/no-features"
group = "tests"
script = """
env DIRENV_DEVSHELL=no-features \
direnv exec . \
cargo test \
--workspace \
--locked \
--profile test \
--all-targets \
--no-fail-fast \
--no-default-features \
--color=always \
-- \
--color=always
"""
# Checks if the generated example config differs from the checked in repo's
Generated
+21 -21
View File
@@ -32,11 +32,11 @@
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1733424942,
"narHash": "sha256-5t7Sl6EkOaoP4FvzLmH7HFDbdl9SizmLh53RjDQCbWQ=",
"lastModified": 1737621947,
"narHash": "sha256-8HFvG7fvIFbgtaYAY2628Tb89fA55nPm2jSiNs0/Cws=",
"owner": "cachix",
"repo": "cachix",
"rev": "8b6b0e4694b9aa78b2ea4c93bff6e1a222dc7e4a",
"rev": "f65a3cd5e339c223471e64c051434616e18cc4f5",
"type": "github"
},
"original": {
@@ -117,11 +117,11 @@
},
"crane_2": {
"locked": {
"lastModified": 1734808813,
"narHash": "sha256-3aH/0Y6ajIlfy7j52FGZ+s4icVX0oHhqBzRdlOeztqg=",
"lastModified": 1737689766,
"narHash": "sha256-ivVXYaYlShxYoKfSo5+y5930qMKKJ8CLcAoIBPQfJ6s=",
"owner": "ipetkov",
"repo": "crane",
"rev": "72e2d02dbac80c8c86bf6bf3e785536acf8ee926",
"rev": "6fe74265bbb6d016d663b1091f015e2976c4a527",
"type": "github"
},
"original": {
@@ -170,11 +170,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1735799625,
"narHash": "sha256-lFadwWDvVIub11bwfZhsh2WUByf9LOi6yjsSUMmE0xk=",
"lastModified": 1737786656,
"narHash": "sha256-ubCW9Jy7ZUOF354bWxTgLDpVnTvIpNr6qR4H/j7I0oo=",
"owner": "nix-community",
"repo": "fenix",
"rev": "a9d84a1545814910cb4ab0515ed6921e8b07ee95",
"rev": "2f721f527886f801403f389a9cabafda8f1e3b7f",
"type": "github"
},
"original": {
@@ -364,11 +364,11 @@
"liburing": {
"flake": false,
"locked": {
"lastModified": 1733603756,
"narHash": "sha256-eTKnZDZ1Ex++v+BI0DBcUBmCXAO/tE8hxK9MiyztZkU=",
"lastModified": 1737600516,
"narHash": "sha256-EKyLQ3pbcjoU5jH5atge59F4fzuhTsb6yalUj6Ve2t8=",
"owner": "axboe",
"repo": "liburing",
"rev": "c3d5d6270cd5ed48d817fc1e8e95f7c8b222f2ff",
"rev": "6c509e2b0c881a13b83b259a221bf15fc9b3f681",
"type": "github"
},
"original": {
@@ -550,11 +550,11 @@
},
"nixpkgs_5": {
"locked": {
"lastModified": 1735685343,
"narHash": "sha256-h1CpBzdJDNtSUb5QMyfFHKHocTTky+4McgQEBQBM+xA=",
"lastModified": 1737717945,
"narHash": "sha256-ET91TMkab3PmOZnqiJQYOtSGvSTvGeHoegAv4zcTefM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "81934660d6e9ea54d2f0cdee821e8533b10c221a",
"rev": "ecd26a469ac56357fd333946a99086e992452b6a",
"type": "github"
},
"original": {
@@ -567,11 +567,11 @@
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1734469478,
"narHash": "sha256-IcQ4N8xADYal79K+ONmNq4RLlIwdgUqgrVzgNgiIaG8=",
"lastModified": 1737828695,
"narHash": "sha256-8Ev6zzhNPU798JNvU27a7gj5X+6SDG3jBweUkQ59DbA=",
"owner": "girlbossceo",
"repo": "rocksdb",
"rev": "8b4808e7de2fbb5d119d8d72cdca76d8ab84bc47",
"rev": "a4d9230dcc9d03be428b9a728133f8f646c0065c",
"type": "github"
},
"original": {
@@ -599,11 +599,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1735742096,
"narHash": "sha256-q3a80h8Jf8wfmPURUgRR46nQCB3I5fhZ+/swulTF5HY=",
"lastModified": 1737728869,
"narHash": "sha256-U4pl3Hi0lT6GP4ecN3q9wdD2sdaKMbmD/5NJ1NdJ9AM=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "7e639ee3dda6ed9cecc79d41f6d38235121e483d",
"rev": "6e4c29f7ce18cea7d3d31237a4661ab932eab636",
"type": "github"
},
"original": {
+110 -62
View File
@@ -26,7 +26,7 @@
file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml`
sha256 = "sha256-s1RPtyvDGJaX/BisLT+ifVfuhDT1nZkZ1NcK8sbwELM=";
sha256 = "sha256-lMLAupxng4Fd9F1oDw8gx+qA0RuF7ou7xhNU8wgs0PU=";
};
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
@@ -169,21 +169,9 @@
# used for rust caching in CI to speed it up
sccache
# needed so we can get rid of gcc and other unused deps that bloat OCI images
removeReferencesTo
]
# liburing is Linux-exclusive
++ lib.optional stdenv.hostPlatform.isLinux liburing
# needed to build Rust applications on macOS
++ lib.optionals stdenv.hostPlatform.isDarwin [
# https://github.com/NixOS/nixpkgs/issues/206242
# ld: library not found for -liconv
libiconv
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
])
++ lib.optional stdenv.hostPlatform.isLinux liburing)
++ scope.main.buildInputs
++ scope.main.propagatedBuildInputs
++ scope.main.nativeBuildInputs;
@@ -191,27 +179,59 @@
in
{
packages = {
default = scopeHost.main;
default-debug = scopeHost.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
};
default-test = scopeHost.main.override {
profile = "test";
disable_release_max_log_level = true;
};
all-features = scopeHost.main.override {
all_features = true;
default = scopeHost.main.override {
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
default-debug = scopeHost.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
# just a test profile used for things like CI and complement
default-test = scopeHost.main.override {
profile = "test";
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
all-features = scopeHost.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
all-features-debug = scopeHost.main.override {
@@ -220,10 +240,12 @@
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
@@ -233,14 +255,16 @@
main = scopeHost.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
};
@@ -251,10 +275,12 @@
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
};
@@ -313,6 +339,14 @@
value = scopeCrossStatic.main.override {
profile = "test";
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
@@ -322,14 +356,16 @@
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
@@ -341,14 +377,16 @@
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
@@ -363,10 +401,12 @@
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
@@ -415,14 +455,16 @@
main = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
};
@@ -436,14 +478,16 @@
main = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
@@ -460,10 +504,12 @@
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
};
@@ -502,14 +548,16 @@
main = prev.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}));
+17 -4
View File
@@ -17,19 +17,32 @@ ip_range_denylist = []
url_preview_domain_contains_allowlist = ["*"]
url_preview_domain_explicit_denylist = ["*"]
media_compat_file_link = false
media_startup_check = false
prune_missing_media = false
media_startup_check = true
prune_missing_media = true
log_colors = false
admin_room_notices = false
allow_check_for_updates = false
allow_unstable_room_versions = true
intentionally_unknown_config_option_for_testing = true
rocksdb_log_level = "debug"
rocksdb_max_log_files = 1
rocksdb_recovery_mode = 0
rocksdb_paranoid_file_checks = true
log_guest_registrations = false
allow_legacy_media = true
startup_netburst = false
startup_netburst = true
startup_netburst_keep = -1
# valgrind makes things so slow
dns_timeout = 60
dns_attempts = 20
request_conn_timeout = 60
request_timeout = 120
well_known_conn_timeout = 60
well_known_timeout = 60
federation_idle_timeout = 300
sender_timeout = 300
sender_idle_timeout = 300
sender_retry_backoff_limit = 300
[global.tls]
certs = "/certificate.crt"
+8 -4
View File
@@ -18,20 +18,24 @@ let
all_features = true;
disable_release_max_log_level = true;
disable_features = [
# no reason to use jemalloc for complement, just has compatibility/build issues
"jemalloc"
# console/CLI stuff isn't used or relevant for complement
"console"
"tokio_console"
# sentry telemetry isn't useful for complement, disabled by default anyways
"sentry_telemetry"
"perf_measurements"
# the containers don't use or need systemd signal support
"systemd"
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# compression isn't needed for complement
"brotli_compression"
"gzip_compression"
"zstd_compression"
# complement doesn't need hot reloading
"conduwuit_mods"
# complement doesn't have URL preview media tests
"url_preview"
];
};
+43 -29
View File
@@ -15,7 +15,19 @@
# Options (keep sorted)
, all_features ? false
, default_features ? true
, disable_features ? []
# default list of disabled features
, disable_features ? [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
]
, disable_release_max_log_level ? false
, features ? []
, profile ? "release"
@@ -70,7 +82,7 @@ rust-jemalloc-sys' = (rust-jemalloc-sys.override {
buildDepsOnlyEnv =
let
rocksdb' = (rocksdb.override {
jemalloc = rust-jemalloc-sys';
jemalloc = lib.optional (featureEnabled "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
@@ -91,6 +103,12 @@ buildDepsOnlyEnv =
++ [ "-DPORTABLE=haswell" ]) else ([ "-DPORTABLE=1" ])
)
++ old.cmakeFlags;
# outputs has "tools" which we dont need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
preInstall = "";
});
in
{
@@ -144,6 +162,19 @@ commonAttrs = {
];
};
# This is redundant with CI
doCheck = false;
cargoTestCommand = "cargo test --locked ";
cargoExtraArgs = "--no-default-features --locked "
+ lib.optionalString
(features'' != [])
"--features " + (builtins.concatStringsSep "," features'');
cargoTestExtraArgs = "--no-default-features --locked "
+ lib.optionalString
(features'' != [])
"--features " + (builtins.concatStringsSep "," features'');
dontStrip = profile == "dev" || profile == "test";
dontPatchELF = profile == "dev" || profile == "test";
@@ -169,27 +200,7 @@ commonAttrs = {
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
# rebuilds of bindgen and its depedents.
jq
# needed so we can get rid of gcc and other unused deps that bloat OCI images
removeReferencesTo
]
# needed to build Rust applications on macOS
++ lib.optionals stdenv.hostPlatform.isDarwin [
# https://github.com/NixOS/nixpkgs/issues/206242
# ld: library not found for -liconv
libiconv
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
];
# for some reason gcc and other weird deps are added to OCI images and bloats it up
#
# <https://github.com/input-output-hk/haskell.nix/issues/829>
postInstall = with pkgsBuildHost; ''
find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${llvm} -t ${rustc.unwrapped} -t ${rustc} '{}' +
'';
];
};
in
@@ -198,15 +209,18 @@ craneLib.buildPackage ( commonAttrs // {
env = buildDepsOnlyEnv;
});
cargoExtraArgs = "--no-default-features "
# This is redundant with CI
doCheck = false;
cargoTestCommand = "cargo test --locked ";
cargoExtraArgs = "--no-default-features --locked "
+ lib.optionalString
(features'' != [])
"--features " + (builtins.concatStringsSep "," features'');
cargoTestExtraArgs = "--no-default-features --locked "
+ lib.optionalString
(features'' != [])
"--features " + (builtins.concatStringsSep "," features'');
# This is redundant with CI
cargoTestCommand = "";
cargoCheckCommand = "";
doCheck = false;
env = buildPackageEnv;
+14
View File
@@ -28,5 +28,19 @@ dockerTools.buildLayeredImage {
Env = [
"RUST_BACKTRACE=full"
];
Labels = {
"org.opencontainers.image.authors" = "June Clementine Strawberry <june@girlboss.ceo> and Jason Volk
<jason@zemos.net>";
"org.opencontainers.image.created" ="@${toString inputs.self.lastModified}";
"org.opencontainers.image.description" = "a very cool Matrix chat homeserver written in Rust";
"org.opencontainers.image.documentation" = "https://conduwuit.puppyirl.gay/";
"org.opencontainers.image.licenses" = "Apache-2.0";
"org.opencontainers.image.revision" = inputs.self.rev or inputs.self.dirtyRev or "";
"org.opencontainers.image.source" = "https://github.com/girlbossceo/conduwuit";
"org.opencontainers.image.title" = main.pname;
"org.opencontainers.image.url" = "https://conduwuit.puppyirl.gay/";
"org.opencontainers.image.vendor" = "girlbossceo";
"org.opencontainers.image.version" = main.version;
};
};
}
+1 -1
View File
@@ -9,7 +9,7 @@
# If you're having trouble making the relevant changes, bug a maintainer.
[toolchain]
channel = "1.83.0"
channel = "1.84.0"
profile = "minimal"
components = [
# For rust-analyzer
+5 -7
View File
@@ -1,6 +1,5 @@
use clap::Parser;
use conduwuit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use crate::{
appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command,
@@ -50,13 +49,10 @@ pub(super) enum AdminCommand {
}
#[tracing::instrument(skip_all, name = "command")]
pub(super) async fn process(
command: AdminCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result {
use AdminCommand::*;
Ok(match command {
match command {
| Appservices(command) => appservice::process(command, context).await?,
| Media(command) => media::process(command, context).await?,
| Users(command) => user::process(command, context).await?,
@@ -66,5 +62,7 @@ pub(super) async fn process(
| Debug(command) => debug::process(command, context).await?,
| Query(command) => query::process(command, context).await?,
| Check(command) => check::process(command, context).await?,
})
};
Ok(())
}
+3 -12
View File
@@ -2,20 +2,11 @@ mod commands;
use clap::Subcommand;
use conduwuit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use crate::Command;
use crate::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub(super) enum CheckCommand {
AllUsers,
}
pub(super) async fn process(
command: CheckCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
Ok(match command {
| CheckCommand::AllUsers => context.check_all_users().await?,
})
CheckAllUsers,
}
+29 -1
View File
@@ -1,6 +1,12 @@
use std::time::SystemTime;
use std::{fmt, time::SystemTime};
use conduwuit::Result;
use conduwuit_service::Services;
use futures::{
io::{AsyncWriteExt, BufWriter},
lock::Mutex,
Future, FutureExt,
};
use ruma::EventId;
pub(crate) struct Command<'a> {
@@ -8,4 +14,26 @@ pub(crate) struct Command<'a> {
pub(crate) body: &'a [&'a str],
pub(crate) timer: SystemTime,
pub(crate) reply_id: Option<&'a EventId>,
pub(crate) output: Mutex<BufWriter<Vec<u8>>>,
}
impl Command<'_> {
pub(crate) fn write_fmt(
&self,
arguments: fmt::Arguments<'_>,
) -> impl Future<Output = Result> + Send + '_ {
let buf = format!("{arguments}");
self.output.lock().then(|mut output| async move {
output.write_all(buf.as_bytes()).await.map_err(Into::into)
})
}
pub(crate) fn write_str<'a>(
&'a self,
s: &'a str,
) -> impl Future<Output = Result> + Send + 'a {
self.output.lock().then(move |mut output| async move {
output.write_all(s.as_bytes()).await.map_err(Into::into)
})
}
}
+138 -48
View File
@@ -6,16 +6,24 @@ use std::{
};
use conduwuit::{
debug_error, err, info, trace, utils, utils::string::EMPTY, warn, Error, PduEvent, Result,
debug_error, err, info, trace, utils,
utils::{
stream::{IterStream, ReadyExt},
string::EMPTY,
},
warn, Error, PduEvent, PduId, RawPduId, Result,
};
use futures::{FutureExt, StreamExt};
use futures::{FutureExt, StreamExt, TryStreamExt};
use ruma::{
api::{client::error::ErrorKind, federation::event::get_room_state},
events::room::message::RoomMessageEventContent,
CanonicalJsonObject, EventId, OwnedEventId, OwnedRoomOrAliasId, RoomId, RoomVersionId,
ServerName,
};
use service::rooms::state_compressor::HashSetCompressStateEvent;
use service::rooms::{
short::{ShortEventId, ShortRoomId},
state_compressor::HashSetCompressStateEvent,
};
use tracing_subscriber::EnvFilter;
use crate::admin_command;
@@ -50,7 +58,7 @@ pub(super) async fn get_auth_chain(
.rooms
.auth_chain
.event_ids_iter(room_id, once(event_id.as_ref()))
.await?
.ready_filter_map(Result::ok)
.count()
.await;
@@ -131,13 +139,42 @@ pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessage
}
}
#[admin_command]
pub(super) async fn get_short_pdu(
&self,
shortroomid: ShortRoomId,
shorteventid: ShortEventId,
) -> Result<RoomMessageEventContent> {
let pdu_id: RawPduId = PduId {
shortroomid,
shorteventid: shorteventid.into(),
}
.into();
let pdu_json = self
.services
.rooms
.timeline
.get_pdu_json_from_id(&pdu_id)
.await;
match pdu_json {
| Ok(json) => {
let json_text =
serde_json::to_string_pretty(&json).expect("canonical json is valid json");
Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json_text}\n```",)))
},
| Err(_) => Ok(RoomMessageEventContent::text_plain("PDU not found locally.")),
}
}
#[admin_command]
pub(super) async fn get_remote_pdu_list(
&self,
server: Box<ServerName>,
force: bool,
) -> Result<RoomMessageEventContent> {
if !self.services.globals.config.allow_federation {
if !self.services.server.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.",
));
@@ -202,7 +239,7 @@ pub(super) async fn get_remote_pdu(
event_id: Box<EventId>,
server: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
if !self.services.globals.config.allow_federation {
if !self.services.server.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.",
));
@@ -294,11 +331,10 @@ pub(super) async fn get_room_state(
.services
.rooms
.state_accessor
.room_state_full(&room_id)
.await?
.values()
.map(PduEvent::to_state_event)
.collect();
.room_state_full_pdus(&room_id)
.map_ok(PduEvent::into_state_event)
.try_collect()
.await?;
if room_state.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
@@ -386,7 +422,7 @@ pub(super) async fn change_log_level(
let handles = &["console"];
if reset {
let old_filter_layer = match EnvFilter::try_new(&self.services.globals.config.log) {
let old_filter_layer = match EnvFilter::try_new(&self.services.server.config.log) {
| Ok(s) => s,
| Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
@@ -405,7 +441,7 @@ pub(super) async fn change_log_level(
| Ok(()) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Successfully changed log level back to config value {}",
self.services.globals.config.log
self.services.server.config.log
)));
},
| Err(e) => {
@@ -521,7 +557,7 @@ pub(super) async fn first_pdu_in_room(
.services
.rooms
.state_cache
.server_in_room(&self.services.globals.config.server_name, &room_id)
.server_in_room(&self.services.server.name, &room_id)
.await
{
return Ok(RoomMessageEventContent::text_plain(
@@ -550,7 +586,7 @@ pub(super) async fn latest_pdu_in_room(
.services
.rooms
.state_cache
.server_in_room(&self.services.globals.config.server_name, &room_id)
.server_in_room(&self.services.server.name, &room_id)
.await
{
return Ok(RoomMessageEventContent::text_plain(
@@ -580,7 +616,7 @@ pub(super) async fn force_set_room_state_from_server(
.services
.rooms
.state_cache
.server_in_room(&self.services.globals.config.server_name, &room_id)
.server_in_room(&self.services.server.name, &room_id)
.await
{
return Ok(RoomMessageEventContent::text_plain(
@@ -607,6 +643,7 @@ pub(super) async fn force_set_room_state_from_server(
room_id: room_id.clone().into(),
event_id: first_pdu.event_id.clone(),
})
.boxed()
.await?;
for pdu in remote_state_response.pdus.clone() {
@@ -615,6 +652,7 @@ pub(super) async fn force_set_room_state_from_server(
.rooms
.event_handler
.parse_incoming_pdu(&pdu)
.boxed()
.await
{
| Ok(t) => t,
@@ -678,6 +716,7 @@ pub(super) async fn force_set_room_state_from_server(
.rooms
.event_handler
.resolve_state(&room_id, &room_version, state)
.boxed()
.await?;
info!("Forcing new room state");
@@ -692,7 +731,7 @@ pub(super) async fn force_set_room_state_from_server(
.save_state(room_id.clone().as_ref(), new_room_state)
.await?;
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
let state_lock = self.services.rooms.state.mutex.lock(&*room_id).await;
self.services
.rooms
.state
@@ -723,8 +762,7 @@ pub(super) async fn get_signing_keys(
notary: Option<Box<ServerName>>,
query: bool,
) -> Result<RoomMessageEventContent> {
let server_name =
server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
let server_name = server_name.unwrap_or_else(|| self.services.server.name.clone().into());
if let Some(notary) = notary {
let signing_keys = self
@@ -760,8 +798,7 @@ pub(super) async fn get_verify_keys(
&self,
server_name: Option<Box<ServerName>>,
) -> Result<RoomMessageEventContent> {
let server_name =
server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
let server_name = server_name.unwrap_or_else(|| self.services.server.name.clone().into());
let keys = self
.services
@@ -785,13 +822,13 @@ pub(super) async fn resolve_true_destination(
server_name: Box<ServerName>,
no_cache: bool,
) -> Result<RoomMessageEventContent> {
if !self.services.globals.config.allow_federation {
if !self.services.server.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.",
));
}
if server_name == self.services.globals.config.server_name {
if server_name == self.services.server.name {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for \
fetching local PDUs.",
@@ -810,19 +847,27 @@ pub(super) async fn resolve_true_destination(
}
#[admin_command]
pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
let html_body = conduwuit::alloc::memory_stats();
pub(super) async fn memory_stats(&self, opts: Option<String>) -> Result<RoomMessageEventContent> {
const OPTS: &str = "abcdefghijklmnopqrstuvwxyz";
if html_body.is_none() {
return Ok(RoomMessageEventContent::text_plain(
"malloc stats are not supported on your compiled malloc.",
));
}
let opts: String = OPTS
.chars()
.filter(|&c| {
let allow_any = opts.as_ref().is_some_and(|opts| opts == "*");
Ok(RoomMessageEventContent::text_html(
"This command's output can only be viewed by clients that render HTML.".to_owned(),
html_body.expect("string result"),
))
let allow = allow_any || opts.as_ref().is_some_and(|opts| opts.contains(c));
!allow
})
.collect();
let stats = conduwuit::alloc::memory_stats(&opts).unwrap_or_default();
self.write_str("```\n").await?;
self.write_str(&stats).await?;
self.write_str("\n```").await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[cfg(tokio_unstable)]
@@ -895,7 +940,7 @@ pub(super) async fn list_dependencies(&self, names: bool) -> Result<RoomMessageE
} else {
String::new()
};
writeln!(out, "{name} | {version} | {feats}")?;
writeln!(out, "| {name} | {version} | {feats} |")?;
}
Ok(RoomMessageEventContent::notice_markdown(out))
@@ -907,19 +952,64 @@ pub(super) async fn database_stats(
property: Option<String>,
map: Option<String>,
) -> Result<RoomMessageEventContent> {
let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned());
let map_name = map.as_ref().map_or(EMPTY, String::as_str);
let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned());
self.services
.db
.iter()
.filter(|(&name, _)| map_name.is_empty() || map_name == name)
.try_stream()
.try_for_each(|(&name, map)| {
let res = map.property(&property).expect("invalid property");
writeln!(self, "##### {name}:\n```\n{}\n```", res.trim())
})
.await?;
let mut out = String::new();
for (name, map) in self.services.db.iter() {
if !map_name.is_empty() && *map_name != *name {
continue;
}
let res = map.property(&property)?;
let res = res.trim();
writeln!(out, "##### {name}:\n```\n{res}\n```")?;
}
Ok(RoomMessageEventContent::notice_markdown(out))
Ok(RoomMessageEventContent::notice_plain(""))
}
#[admin_command]
pub(super) async fn database_files(
&self,
map: Option<String>,
level: Option<i32>,
) -> Result<RoomMessageEventContent> {
let mut files: Vec<_> = self.services.db.db.file_list().collect::<Result<_>>()?;
files.sort_by_key(|f| f.name.clone());
writeln!(self, "| lev | sst | keys | dels | size | column |").await?;
writeln!(self, "| ---: | :--- | ---: | ---: | ---: | :--- |").await?;
files
.into_iter()
.filter(|file| {
map.as_deref()
.is_none_or(|map| map == file.column_family_name)
})
.filter(|file| level.as_ref().is_none_or(|&level| level == file.level))
.try_stream()
.try_for_each(|file| {
writeln!(
self,
"| {} | {:<13} | {:7}+ | {:4}- | {:9} | {} |",
file.level,
file.name,
file.num_entries,
file.num_deletions,
file.size,
file.column_family_name,
)
})
.await?;
Ok(RoomMessageEventContent::notice_plain(""))
}
#[admin_command]
pub(super) async fn trim_memory(&self) -> Result<RoomMessageEventContent> {
conduwuit::alloc::trim(None)?;
writeln!(self, "done").await?;
Ok(RoomMessageEventContent::notice_plain(""))
}
+29 -2
View File
@@ -4,6 +4,7 @@ pub(crate) mod tester;
use clap::Subcommand;
use conduwuit::Result;
use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName};
use service::rooms::short::{ShortEventId, ShortRoomId};
use self::tester::TesterCommand;
use crate::admin_command_dispatch;
@@ -31,12 +32,21 @@ pub(super) enum DebugCommand {
/// the command.
ParsePdu,
/// - Retrieve and print a PDU by ID from the conduwuit database
/// - Retrieve and print a PDU by EventID from the conduwuit database
GetPdu {
/// An event ID (a $ followed by the base64 reference hash)
event_id: Box<EventId>,
},
/// - Retrieve and print a PDU by PduId from the conduwuit database
GetShortPdu {
/// Shortroomid integer
shortroomid: ShortRoomId,
/// Shorteventid integer
shorteventid: ShortEventId,
},
/// - Attempts to retrieve a PDU from a remote server. Inserts it into our
/// database/timeline if found and we do not have this PDU already
/// (following normal event auth rules, handles it as an incoming PDU).
@@ -181,7 +191,13 @@ pub(super) enum DebugCommand {
},
/// - Print extended memory usage
MemoryStats,
///
/// Optional argument is a character mask (a sequence of characters in any
/// order) which enable additional extended statistics. Known characters are
/// "abdeglmx". For convenience, a '*' will enable everything.
MemoryStats {
opts: Option<String>,
},
/// - Print general tokio runtime metric totals.
RuntimeMetrics,
@@ -207,6 +223,17 @@ pub(super) enum DebugCommand {
map: Option<String>,
},
/// - Trim memory usage
TrimMemory,
/// - List database files
DatabaseFiles {
map: Option<String>,
#[arg(long)]
level: Option<i32>,
},
/// - Developer test stubs
#[command(subcommand)]
#[allow(non_snake_case)]
+1 -1
View File
@@ -31,7 +31,7 @@ async fn failure(&self) -> Result<RoomMessageEventContent> {
#[admin_command]
async fn tester(&self) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::notice_plain("completed"))
Ok(RoomMessageEventContent::notice_plain("legacy"))
}
#[inline(never)]
+1 -1
View File
@@ -92,7 +92,7 @@ pub(super) async fn remote_user_in_rooms(
&self,
user_id: Box<UserId>,
) -> Result<RoomMessageEventContent> {
if user_id.server_name() == self.services.globals.config.server_name {
if user_id.server_name() == self.services.server.name {
return Ok(RoomMessageEventContent::text_plain(
"User belongs to our server, please use `list-joined-rooms` user admin command \
instead.",
+1
View File
@@ -1,6 +1,7 @@
#![recursion_limit = "192"]
#![allow(clippy::wildcard_imports)]
#![allow(clippy::enum_glob_use)]
#![allow(clippy::too_many_arguments)]
pub(crate) mod admin;
pub(crate) mod command;
+28 -15
View File
@@ -1,5 +1,6 @@
use std::{
fmt::Write,
mem::take,
panic::AssertUnwindSafe,
sync::{Arc, Mutex},
time::SystemTime,
@@ -17,7 +18,7 @@ use conduwuit::{
utils::string::{collect_stream, common_prefix},
warn, Error, Result,
};
use futures::future::FutureExt;
use futures::{future::FutureExt, io::BufWriter, AsyncWriteExt};
use ruma::{
events::{
relation::InReplyTo,
@@ -62,9 +63,32 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
body: &body,
timer: SystemTime::now(),
reply_id: input.reply_id.as_deref(),
output: BufWriter::new(Vec::new()).into(),
};
process(&context, command, &args).await
let (result, mut logs) = process(&context, command, &args).await;
let output = &mut context.output.lock().await;
output.flush().await.expect("final flush of output stream");
let output =
String::from_utf8(take(output.get_mut())).expect("invalid utf8 in command output stream");
match result {
| Ok(()) if logs.is_empty() =>
Ok(Some(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id))),
| Ok(()) => {
logs.write_str(output.as_str()).expect("output buffer");
Ok(Some(reply(RoomMessageEventContent::notice_markdown(logs), context.reply_id)))
},
| Err(error) => {
write!(&mut logs, "Command failed with error:\n```\n{error:#?}\n```")
.expect("output buffer");
Err(reply(RoomMessageEventContent::notice_markdown(logs), context.reply_id))
},
}
}
fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult {
@@ -81,7 +105,7 @@ async fn process(
context: &Command<'_>,
command: AdminCommand,
args: &[String],
) -> ProcessorResult {
) -> (Result, String) {
let (capture, logs) = capture_create(context);
let capture_scope = capture.start();
@@ -104,18 +128,7 @@ async fn process(
}
drop(logs);
match result {
| Ok(content) => {
write!(&mut output, "{0}", content.body())
.expect("failed to format command result to output buffer");
Ok(Some(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id)))
},
| Err(error) => {
write!(&mut output, "Command failed with error:\n```\n{error:#?}\n```")
.expect("failed to format command result to output");
Err(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id))
},
}
(result, output)
}
fn capture_create(context: &Command<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
+40 -34
View File
@@ -3,8 +3,9 @@ use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, UserId};
use crate::Command;
use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(crate) enum AccountDataCommand {
@@ -19,7 +20,7 @@ pub(crate) enum AccountDataCommand {
},
/// - Searches the account data for a specific kind.
Get {
AccountDataGet {
/// Full user ID
user_id: Box<UserId>,
/// Account data event type
@@ -29,38 +30,43 @@ pub(crate) enum AccountDataCommand {
},
}
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(super) async fn process(
subcommand: AccountDataCommand,
context: &Command<'_>,
#[admin_command]
async fn changes_since(
&self,
user_id: Box<UserId>,
since: u64,
room_id: Option<Box<RoomId>>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
let timer = tokio::time::Instant::now();
let results: Vec<_> = self
.services
.account_data
.changes_since(room_id.as_deref(), &user_id, since, None)
.collect()
.await;
let query_time = timer.elapsed();
match subcommand {
| AccountDataCommand::ChangesSince { user_id, since, room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.account_data
.changes_since(room_id.as_deref(), &user_id, since)
.collect()
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
| AccountDataCommand::Get { user_id, kind, room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.account_data
.get_raw(room_id.as_deref(), &user_id, &kind)
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
}
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
}
#[admin_command]
async fn account_data_get(
&self,
user_id: Box<UserId>,
kind: String,
room_id: Option<Box<RoomId>>,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let results = self
.services
.account_data
.get_raw(room_id.as_deref(), &user_id, &kind)
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
}
+4 -11
View File
@@ -1,6 +1,5 @@
use clap::Subcommand;
use conduwuit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use crate::Command;
@@ -18,10 +17,7 @@ pub(crate) enum AppserviceCommand {
}
/// All the getters and iterators from src/database/key_value/appservice.rs
pub(super) async fn process(
subcommand: AppserviceCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result {
let services = context.services;
match subcommand {
@@ -31,18 +27,15 @@ pub(super) async fn process(
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| AppserviceCommand::All => {
let timer = tokio::time::Instant::now();
let results = services.appservice.all().await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
}
.await
}
+7 -17
View File
@@ -1,6 +1,6 @@
use clap::Subcommand;
use conduwuit::Result;
use ruma::{events::room::message::RoomMessageEventContent, ServerName};
use ruma::ServerName;
use crate::Command;
@@ -21,10 +21,7 @@ pub(crate) enum GlobalsCommand {
}
/// All the getters and iterators from src/database/key_value/globals.rs
pub(super) async fn process(
subcommand: GlobalsCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result {
let services = context.services;
match subcommand {
@@ -33,36 +30,29 @@ pub(super) async fn process(
let results = services.globals.db.database_version().await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| GlobalsCommand::CurrentCount => {
let timer = tokio::time::Instant::now();
let results = services.globals.db.current_count();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| GlobalsCommand::LastCheckForUpdatesId => {
let timer = tokio::time::Instant::now();
let results = services.updates.last_check_for_updates_id().await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| GlobalsCommand::SigningKeysFor { origin } => {
let timer = tokio::time::Instant::now();
let results = services.server_keys.verify_keys_for(&origin).await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
}
.await
}
+18 -2
View File
@@ -3,10 +3,13 @@ mod appservice;
mod globals;
mod presence;
mod pusher;
mod raw;
mod resolver;
mod room_alias;
mod room_state_cache;
mod room_timeline;
mod sending;
mod short;
mod users;
use clap::Subcommand;
@@ -14,9 +17,10 @@ use conduwuit::Result;
use self::{
account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
presence::PresenceCommand, pusher::PusherCommand, resolver::ResolverCommand,
presence::PresenceCommand, pusher::PusherCommand, raw::RawCommand, resolver::ResolverCommand,
room_alias::RoomAliasCommand, room_state_cache::RoomStateCacheCommand,
sending::SendingCommand, users::UsersCommand,
room_timeline::RoomTimelineCommand, sending::SendingCommand, short::ShortCommand,
users::UsersCommand,
};
use crate::admin_command_dispatch;
@@ -44,6 +48,10 @@ pub(super) enum QueryCommand {
#[command(subcommand)]
RoomStateCache(RoomStateCacheCommand),
/// - rooms/timeline iterators and getters
#[command(subcommand)]
RoomTimeline(RoomTimelineCommand),
/// - globals.rs iterators and getters
#[command(subcommand)]
Globals(GlobalsCommand),
@@ -63,4 +71,12 @@ pub(super) enum QueryCommand {
/// - pusher service
#[command(subcommand)]
Pusher(PusherCommand),
/// - short service
#[command(subcommand)]
Short(ShortCommand),
/// - raw service
#[command(subcommand)]
Raw(RawCommand),
}
+6 -12
View File
@@ -1,7 +1,7 @@
use clap::Subcommand;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, UserId};
use ruma::UserId;
use crate::Command;
@@ -23,21 +23,16 @@ pub(crate) enum PresenceCommand {
}
/// All the getters and iterators in key_value/presence.rs
pub(super) async fn process(
subcommand: PresenceCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>) -> Result {
let services = context.services;
match subcommand {
| PresenceCommand::GetPresence { user_id } => {
let timer = tokio::time::Instant::now();
let results = services.presence.db.get_presence(&user_id).await;
let results = services.presence.get_presence(&user_id).await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| PresenceCommand::PresenceSince { since } => {
let timer = tokio::time::Instant::now();
@@ -49,9 +44,8 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
}
.await
}
+4 -8
View File
@@ -1,6 +1,6 @@
use clap::Subcommand;
use conduwuit::Result;
use ruma::{events::room::message::RoomMessageEventContent, UserId};
use ruma::UserId;
use crate::Command;
@@ -13,10 +13,7 @@ pub(crate) enum PusherCommand {
},
}
pub(super) async fn process(
subcommand: PusherCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
pub(super) async fn process(subcommand: PusherCommand, context: &Command<'_>) -> Result {
let services = context.services;
match subcommand {
@@ -25,9 +22,8 @@ pub(super) async fn process(
let results = services.pusher.get_pushers(&user_id).await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
}
.await
}
+576
View File
@@ -0,0 +1,576 @@
use std::{borrow::Cow, collections::BTreeMap, ops::Deref};
use clap::Subcommand;
use conduwuit::{
apply, at, is_zero,
utils::{
stream::{ReadyExt, TryIgnore, TryParallelExt},
string::EMPTY,
IterStream,
},
Err, Result,
};
use futures::{FutureExt, StreamExt, TryStreamExt};
use ruma::events::room::message::RoomMessageEventContent;
use tokio::time::Instant;
use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
#[allow(clippy::enum_variant_names)]
/// Query tables from database
pub(crate) enum RawCommand {
/// - List database maps
RawMaps,
/// - Raw database query
RawGet {
/// Map name
map: String,
/// Key
key: String,
},
/// - Raw database delete (for string keys)
RawDel {
/// Map name
map: String,
/// Key
key: String,
},
/// - Raw database keys iteration
RawKeys {
/// Map name
map: String,
/// Key prefix
prefix: Option<String>,
},
/// - Raw database key size breakdown
RawKeysSizes {
/// Map name
map: Option<String>,
/// Key prefix
prefix: Option<String>,
},
/// - Raw database keys total bytes
RawKeysTotal {
/// Map name
map: Option<String>,
/// Key prefix
prefix: Option<String>,
},
/// - Raw database values size breakdown
RawValsSizes {
/// Map name
map: Option<String>,
/// Key prefix
prefix: Option<String>,
},
/// - Raw database values total bytes
RawValsTotal {
/// Map name
map: Option<String>,
/// Key prefix
prefix: Option<String>,
},
/// - Raw database items iteration
RawIter {
/// Map name
map: String,
/// Key prefix
prefix: Option<String>,
},
/// - Raw database keys iteration
RawKeysFrom {
/// Map name
map: String,
/// Lower-bound
start: String,
/// Limit
#[arg(short, long)]
limit: Option<usize>,
},
/// - Raw database items iteration
RawIterFrom {
/// Map name
map: String,
/// Lower-bound
start: String,
/// Limit
#[arg(short, long)]
limit: Option<usize>,
},
/// - Raw database record count
RawCount {
/// Map name
map: Option<String>,
/// Key prefix
prefix: Option<String>,
},
/// - Compact database
Compact {
#[arg(short, long, alias("column"))]
map: Option<Vec<String>>,
#[arg(long)]
start: Option<String>,
#[arg(long)]
stop: Option<String>,
#[arg(long)]
from: Option<usize>,
#[arg(long)]
into: Option<usize>,
/// There is one compaction job per column; then this controls how many
/// columns are compacted in parallel. If zero, one compaction job is
/// still run at a time here, but in exclusive-mode blocking any other
/// automatic compaction jobs until complete.
#[arg(long)]
parallelism: Option<usize>,
#[arg(long, default_value("false"))]
exhaustive: bool,
},
}
#[admin_command]
pub(super) async fn compact(
&self,
map: Option<Vec<String>>,
start: Option<String>,
stop: Option<String>,
from: Option<usize>,
into: Option<usize>,
parallelism: Option<usize>,
exhaustive: bool,
) -> Result<RoomMessageEventContent> {
use conduwuit_database::compact::Options;
let default_all_maps = map
.is_none()
.then(|| {
self.services
.db
.keys()
.map(Deref::deref)
.map(ToOwned::to_owned)
})
.into_iter()
.flatten();
let maps: Vec<_> = map
.unwrap_or_default()
.into_iter()
.chain(default_all_maps)
.map(|map| self.services.db.get(&map))
.filter_map(Result::ok)
.cloned()
.collect();
if maps.is_empty() {
return Err!("--map argument invalid. not found in database");
}
let range = (
start.as_ref().map(String::as_bytes).map(Into::into),
stop.as_ref().map(String::as_bytes).map(Into::into),
);
let options = Options {
range,
level: (from, into),
exclusive: parallelism.is_some_and(is_zero!()),
exhaustive,
};
let runtime = self.services.server.runtime().clone();
let parallelism = parallelism.unwrap_or(1);
let results = maps
.into_iter()
.try_stream()
.paralleln_and_then(runtime, parallelism, move |map| {
map.compact_blocking(options.clone())?;
Ok(map.name().to_owned())
})
.collect::<Vec<_>>();
let timer = Instant::now();
let results = results.await;
let query_time = timer.elapsed();
self.write_str(&format!("Jobs completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"))
.await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_count(
&self,
map: Option<String>,
prefix: Option<String>,
) -> Result<RoomMessageEventContent> {
let prefix = prefix.as_deref().unwrap_or(EMPTY);
let default_all_maps = map
.is_none()
.then(|| self.services.db.keys().map(Deref::deref))
.into_iter()
.flatten();
let maps: Vec<_> = map
.iter()
.map(String::as_str)
.chain(default_all_maps)
.map(|map| self.services.db.get(map))
.filter_map(Result::ok)
.cloned()
.collect();
let timer = Instant::now();
let count = maps
.iter()
.stream()
.then(|map| map.raw_count_prefix(&prefix))
.ready_fold(0_usize, usize::saturating_add)
.await;
let query_time = timer.elapsed();
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{count:#?}\n```"))
.await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_keys(
&self,
map: String,
prefix: Option<String>,
) -> Result<RoomMessageEventContent> {
writeln!(self, "```").boxed().await?;
let map = self.services.db.get(map.as_str())?;
let timer = Instant::now();
prefix
.as_deref()
.map_or_else(|| map.raw_keys().boxed(), |prefix| map.raw_keys_prefix(prefix).boxed())
.map_ok(String::from_utf8_lossy)
.try_for_each(|str| writeln!(self, "{str:?}"))
.boxed()
.await?;
let query_time = timer.elapsed();
let out = format!("\n```\n\nQuery completed in {query_time:?}");
self.write_str(out.as_str()).await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_keys_sizes(
&self,
map: Option<String>,
prefix: Option<String>,
) -> Result<RoomMessageEventContent> {
let prefix = prefix.as_deref().unwrap_or(EMPTY);
let default_all_maps = map
.is_none()
.then(|| self.services.db.keys().map(Deref::deref))
.into_iter()
.flatten();
let maps: Vec<_> = map
.iter()
.map(String::as_str)
.chain(default_all_maps)
.map(|map| self.services.db.get(map))
.filter_map(Result::ok)
.cloned()
.collect();
let timer = Instant::now();
let result = maps
.iter()
.stream()
.map(|map| map.raw_keys_prefix(&prefix))
.flatten()
.ignore_err()
.map(<[u8]>::len)
.ready_fold_default(|mut map: BTreeMap<_, usize>, len| {
let entry = map.entry(len).or_default();
*entry = entry.saturating_add(1);
map
})
.await;
let query_time = timer.elapsed();
let result = format!("```\n{result:#?}\n```\n\nQuery completed in {query_time:?}");
self.write_str(result.as_str()).await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_keys_total(
&self,
map: Option<String>,
prefix: Option<String>,
) -> Result<RoomMessageEventContent> {
let prefix = prefix.as_deref().unwrap_or(EMPTY);
let default_all_maps = map
.is_none()
.then(|| self.services.db.keys().map(Deref::deref))
.into_iter()
.flatten();
let maps: Vec<_> = map
.iter()
.map(String::as_str)
.chain(default_all_maps)
.map(|map| self.services.db.get(map))
.filter_map(Result::ok)
.cloned()
.collect();
let timer = Instant::now();
let result = maps
.iter()
.stream()
.map(|map| map.raw_keys_prefix(&prefix))
.flatten()
.ignore_err()
.map(<[u8]>::len)
.ready_fold_default(|acc: usize, len| acc.saturating_add(len))
.await;
let query_time = timer.elapsed();
self.write_str(&format!("```\n{result:#?}\n\n```\n\nQuery completed in {query_time:?}"))
.await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_vals_sizes(
&self,
map: Option<String>,
prefix: Option<String>,
) -> Result<RoomMessageEventContent> {
let prefix = prefix.as_deref().unwrap_or(EMPTY);
let default_all_maps = map
.is_none()
.then(|| self.services.db.keys().map(Deref::deref))
.into_iter()
.flatten();
let maps: Vec<_> = map
.iter()
.map(String::as_str)
.chain(default_all_maps)
.map(|map| self.services.db.get(map))
.filter_map(Result::ok)
.cloned()
.collect();
let timer = Instant::now();
let result = maps
.iter()
.stream()
.map(|map| map.raw_stream_prefix(&prefix))
.flatten()
.ignore_err()
.map(at!(1))
.map(<[u8]>::len)
.ready_fold_default(|mut map: BTreeMap<_, usize>, len| {
let entry = map.entry(len).or_default();
*entry = entry.saturating_add(1);
map
})
.await;
let query_time = timer.elapsed();
let result = format!("```\n{result:#?}\n```\n\nQuery completed in {query_time:?}");
self.write_str(result.as_str()).await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_vals_total(
&self,
map: Option<String>,
prefix: Option<String>,
) -> Result<RoomMessageEventContent> {
let prefix = prefix.as_deref().unwrap_or(EMPTY);
let default_all_maps = map
.is_none()
.then(|| self.services.db.keys().map(Deref::deref))
.into_iter()
.flatten();
let maps: Vec<_> = map
.iter()
.map(String::as_str)
.chain(default_all_maps)
.map(|map| self.services.db.get(map))
.filter_map(Result::ok)
.cloned()
.collect();
let timer = Instant::now();
let result = maps
.iter()
.stream()
.map(|map| map.raw_stream_prefix(&prefix))
.flatten()
.ignore_err()
.map(at!(1))
.map(<[u8]>::len)
.ready_fold_default(|acc: usize, len| acc.saturating_add(len))
.await;
let query_time = timer.elapsed();
self.write_str(&format!("```\n{result:#?}\n\n```\n\nQuery completed in {query_time:?}"))
.await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_iter(
&self,
map: String,
prefix: Option<String>,
) -> Result<RoomMessageEventContent> {
writeln!(self, "```").await?;
let map = self.services.db.get(&map)?;
let timer = Instant::now();
prefix
.as_deref()
.map_or_else(|| map.raw_stream().boxed(), |prefix| map.raw_stream_prefix(prefix).boxed())
.map_ok(apply!(2, String::from_utf8_lossy))
.map_ok(apply!(2, Cow::into_owned))
.try_for_each(|keyval| writeln!(self, "{keyval:?}"))
.boxed()
.await?;
let query_time = timer.elapsed();
self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}"))
.await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_keys_from(
&self,
map: String,
start: String,
limit: Option<usize>,
) -> Result<RoomMessageEventContent> {
writeln!(self, "```").await?;
let map = self.services.db.get(&map)?;
let timer = Instant::now();
map.raw_keys_from(&start)
.map_ok(String::from_utf8_lossy)
.take(limit.unwrap_or(usize::MAX))
.try_for_each(|str| writeln!(self, "{str:?}"))
.boxed()
.await?;
let query_time = timer.elapsed();
self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}"))
.await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
pub(super) async fn raw_iter_from(
&self,
map: String,
start: String,
limit: Option<usize>,
) -> Result<RoomMessageEventContent> {
let map = self.services.db.get(&map)?;
let timer = Instant::now();
let result = map
.raw_stream_from(&start)
.map_ok(apply!(2, String::from_utf8_lossy))
.map_ok(apply!(2, Cow::into_owned))
.take(limit.unwrap_or(usize::MAX))
.try_collect::<Vec<(String, String)>>()
.await?;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
)))
}
#[admin_command]
pub(super) async fn raw_del(&self, map: String, key: String) -> Result<RoomMessageEventContent> {
let map = self.services.db.get(&map)?;
let timer = Instant::now();
map.remove(&key);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Operation completed in {query_time:?}"
)))
}
#[admin_command]
pub(super) async fn raw_get(&self, map: String, key: String) -> Result<RoomMessageEventContent> {
let map = self.services.db.get(&map)?;
let timer = Instant::now();
let handle = map.get(&key).await?;
let query_time = timer.elapsed();
let result = String::from_utf8_lossy(&handle);
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:?}\n```"
)))
}
#[admin_command]
pub(super) async fn raw_maps(&self) -> Result<RoomMessageEventContent> {
let list: Vec<_> = self.services.db.iter().map(at!(0)).copied().collect();
Ok(RoomMessageEventContent::notice_markdown(format!("{list:#?}")))
}
+33 -42
View File
@@ -1,7 +1,6 @@
use std::fmt::Write;
use clap::Subcommand;
use conduwuit::{utils::time, Result};
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
use crate::{admin_command, admin_command_dispatch};
@@ -28,56 +27,48 @@ async fn destinations_cache(
) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedDest;
let mut out = String::new();
writeln!(out, "| Server Name | Destination | Hostname | Expires |")?;
writeln!(out, "| ----------- | ----------- | -------- | ------- |")?;
let row = |(name, &CachedDest { ref dest, ref host, expire })| {
writeln!(self, "| Server Name | Destination | Hostname | Expires |").await?;
writeln!(self, "| ----------- | ----------- | -------- | ------- |").await?;
let mut destinations = self.services.resolver.cache.destinations().boxed();
while let Some((name, CachedDest { dest, host, expire })) = destinations.next().await {
if let Some(server_name) = server_name.as_ref() {
if name != server_name {
continue;
}
}
let expire = time::format(expire, "%+");
writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line");
};
let map = self
.services
.resolver
.cache
.destinations
.read()
.expect("locked");
if let Some(server_name) = server_name.as_ref() {
map.get_key_value(server_name).map(row);
} else {
map.iter().for_each(row);
self.write_str(&format!("| {name} | {dest} | {host} | {expire} |\n"))
.await?;
}
Ok(RoomMessageEventContent::notice_markdown(out))
Ok(RoomMessageEventContent::notice_plain(""))
}
#[admin_command]
async fn overrides_cache(&self, server_name: Option<String>) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedOverride;
let mut out = String::new();
writeln!(out, "| Server Name | IP | Port | Expires |")?;
writeln!(out, "| ----------- | --- | ----:| ------- |")?;
let row = |(name, &CachedOverride { ref ips, port, expire })| {
writeln!(self, "| Server Name | IP | Port | Expires | Overriding |").await?;
writeln!(self, "| ----------- | --- | ----:| ------- | ---------- |").await?;
let mut overrides = self.services.resolver.cache.overrides().boxed();
while let Some((name, CachedOverride { ips, port, expire, overriding })) =
overrides.next().await
{
if let Some(server_name) = server_name.as_ref() {
if name != server_name {
continue;
}
}
let expire = time::format(expire, "%+");
writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line");
};
let map = self
.services
.resolver
.cache
.overrides
.read()
.expect("locked");
if let Some(server_name) = server_name.as_ref() {
map.get_key_value(server_name).map(row);
} else {
map.iter().for_each(row);
self.write_str(&format!("| {name} | {ips:?} | {port} | {expire} | {overriding:?} |\n"))
.await?;
}
Ok(RoomMessageEventContent::notice_markdown(out))
Ok(RoomMessageEventContent::notice_plain(""))
}
+6 -14
View File
@@ -1,7 +1,7 @@
use clap::Subcommand;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
use ruma::{RoomAliasId, RoomId};
use crate::Command;
@@ -24,10 +24,7 @@ pub(crate) enum RoomAliasCommand {
}
/// All the getters and iterators in src/database/key_value/rooms/alias.rs
pub(super) async fn process(
subcommand: RoomAliasCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>) -> Result {
let services = context.services;
match subcommand {
@@ -36,9 +33,7 @@ pub(super) async fn process(
let results = services.rooms.alias.resolve_local_alias(&alias).await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| RoomAliasCommand::LocalAliasesForRoom { room_id } => {
let timer = tokio::time::Instant::now();
@@ -51,9 +46,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```")
},
| RoomAliasCommand::AllLocalAliases => {
let timer = tokio::time::Instant::now();
@@ -66,9 +59,8 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
)))
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```")
},
}
.await
}
+24 -23
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduwuit::Result;
use conduwuit::{Error, Result};
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
@@ -76,13 +76,10 @@ pub(crate) enum RoomStateCacheCommand {
},
}
pub(super) async fn process(
subcommand: RoomStateCacheCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Command<'_>) -> Result {
let services = context.services;
match subcommand {
let c = match subcommand {
| RoomStateCacheCommand::ServerInRoom { server, room_id } => {
let timer = tokio::time::Instant::now();
let result = services
@@ -92,7 +89,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
)))
},
@@ -107,7 +104,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -122,7 +119,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -137,7 +134,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -152,7 +149,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -167,7 +164,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -176,7 +173,7 @@ pub(super) async fn process(
let results = services.rooms.state_cache.room_joined_count(&room_id).await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -189,7 +186,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -204,7 +201,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -219,7 +216,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -232,7 +229,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -245,7 +242,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -260,7 +257,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -274,7 +271,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -288,7 +285,7 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
@@ -301,9 +298,13 @@ pub(super) async fn process(
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
}
}?;
context.write_str(c.body()).await?;
Ok(())
}
+61
View File
@@ -0,0 +1,61 @@
use clap::Subcommand;
use conduwuit::{utils::stream::TryTools, PduCount, Result};
use futures::TryStreamExt;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId};
use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
/// Query tables from database
pub(crate) enum RoomTimelineCommand {
Pdus {
room_id: OwnedRoomOrAliasId,
from: Option<String>,
#[arg(short, long)]
limit: Option<usize>,
},
Last {
room_id: OwnedRoomOrAliasId,
},
}
#[admin_command]
pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
let result = self
.services
.rooms
.timeline
.last_timeline_count(None, &room_id)
.await?;
Ok(RoomMessageEventContent::notice_markdown(format!("{result:#?}")))
}
#[admin_command]
pub(super) async fn pdus(
&self,
room_id: OwnedRoomOrAliasId,
from: Option<String>,
limit: Option<usize>,
) -> Result<RoomMessageEventContent> {
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?;
let result: Vec<_> = self
.services
.rooms
.timeline
.pdus_rev(None, &room_id, from)
.try_take(limit.unwrap_or(3))
.try_collect()
.await?;
Ok(RoomMessageEventContent::notice_markdown(format!("{result:#?}")))
}
+8 -1
View File
@@ -62,7 +62,14 @@ pub(crate) enum SendingCommand {
}
/// All the getters and iterators in key_value/sending.rs
pub(super) async fn process(
pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -> Result {
let c = reprocess(subcommand, context).await?;
context.write_str(c.body()).await?;
Ok(())
}
/// All the getters and iterators in key_value/sending.rs
pub(super) async fn reprocess(
subcommand: SendingCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
+45
View File
@@ -0,0 +1,45 @@
use clap::Subcommand;
use conduwuit::Result;
use ruma::{events::room::message::RoomMessageEventContent, OwnedEventId, OwnedRoomOrAliasId};
use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
/// Query tables from database
pub(crate) enum ShortCommand {
ShortEventId {
event_id: OwnedEventId,
},
ShortRoomId {
room_id: OwnedRoomOrAliasId,
},
}
#[admin_command]
pub(super) async fn short_event_id(
&self,
event_id: OwnedEventId,
) -> Result<RoomMessageEventContent> {
let shortid = self
.services
.rooms
.short
.get_shorteventid(&event_id)
.await?;
Ok(RoomMessageEventContent::notice_markdown(format!("{shortid:#?}")))
}
#[admin_command]
pub(super) async fn short_room_id(
&self,
room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> {
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
let shortid = self.services.rooms.short.get_shortroomid(&room_id).await?;
Ok(RoomMessageEventContent::notice_markdown(format!("{shortid:#?}")))
}
+47 -1
View File
@@ -15,6 +15,8 @@ pub(crate) enum UsersCommand {
IterUsers,
IterUsers2,
PasswordHash {
user_id: OwnedUserId,
},
@@ -89,6 +91,33 @@ pub(crate) enum UsersCommand {
room_id: OwnedRoomId,
session_id: String,
},
GetSharedRooms {
user_a: OwnedUserId,
user_b: OwnedUserId,
},
}
#[admin_command]
async fn get_shared_rooms(
&self,
user_a: OwnedUserId,
user_b: OwnedUserId,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result: Vec<_> = self
.services
.rooms
.state_cache
.get_shared_rooms(&user_a, &user_b)
.map(ToOwned::to_owned)
.collect()
.await;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
)))
}
#[admin_command]
@@ -207,6 +236,23 @@ async fn iter_users(&self) -> Result<RoomMessageEventContent> {
)))
}
#[admin_command]
async fn iter_users2(&self) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result: Vec<_> = self.services.users.stream().collect().await;
let result: Vec<_> = result
.into_iter()
.map(ruma::UserId::as_bytes)
.map(String::from_utf8_lossy)
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:?}\n```"
)))
}
#[admin_command]
async fn count_users(&self) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
@@ -367,7 +413,7 @@ async fn get_to_device_events(
let result = self
.services
.users
.get_to_device_events(&user_id, &device_id)
.get_to_device_events(&user_id, &device_id, None, None)
.collect::<Vec<_>>()
.await;
let query_time = timer.elapsed();
+17 -7
View File
@@ -44,7 +44,14 @@ pub(crate) enum RoomAliasCommand {
},
}
pub(super) async fn process(
pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) -> Result {
let c = reprocess(command, context).await?;
context.write_str(c.body()).await?;
Ok(())
}
pub(super) async fn reprocess(
command: RoomAliasCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
@@ -65,7 +72,7 @@ pub(super) async fn process(
))),
};
match command {
| RoomAliasCommand::Set { force, room_id, .. } =>
| RoomAliasCommand::Set { force, room_id, .. } => {
match (force, services.rooms.alias.resolve_local_alias(&room_alias).await) {
| (true, Ok(id)) => {
match services.rooms.alias.set_alias(
@@ -99,8 +106,9 @@ pub(super) async fn process(
))),
}
},
},
| RoomAliasCommand::Remove { .. } =>
}
},
| RoomAliasCommand::Remove { .. } => {
match services.rooms.alias.resolve_local_alias(&room_alias).await {
| Ok(id) => match services
.rooms
@@ -117,15 +125,17 @@ pub(super) async fn process(
},
| Err(_) =>
Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
},
| RoomAliasCommand::Which { .. } =>
}
},
| RoomAliasCommand::Which { .. } => {
match services.rooms.alias.resolve_local_alias(&room_alias).await {
| Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
"Alias resolves to {id}"
))),
| Err(_) =>
Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
},
}
},
| RoomAliasCommand::List { .. } => unreachable!(),
}
},
+7 -1
View File
@@ -25,7 +25,13 @@ pub(crate) enum RoomDirectoryCommand {
},
}
pub(super) async fn process(
pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>) -> Result {
let c = reprocess(command, context).await?;
context.write_str(c.body()).await?;
Ok(())
}
pub(super) async fn reprocess(
command: RoomDirectoryCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
+18 -14
View File
@@ -1,4 +1,4 @@
use std::{fmt::Write, sync::Arc};
use std::{fmt::Write, path::PathBuf, sync::Arc};
use conduwuit::{info, utils::time, warn, Err, Result};
use ruma::events::room::message::RoomMessageEventContent;
@@ -22,11 +22,22 @@ pub(super) async fn uptime(&self) -> Result<RoomMessageEventContent> {
pub(super) async fn show_config(&self) -> Result<RoomMessageEventContent> {
// Construct and send the response
Ok(RoomMessageEventContent::text_markdown(format!(
"```\n{}\n```",
self.services.globals.config
"{}",
*self.services.server.config
)))
}
#[admin_command]
pub(super) async fn reload_config(
&self,
path: Option<PathBuf>,
) -> Result<RoomMessageEventContent> {
let path = path.as_deref().into_iter();
self.services.config.reload(path)?;
Ok(RoomMessageEventContent::text_plain("Successfully reconfigured."))
}
#[admin_command]
pub(super) async fn list_features(
&self,
@@ -81,7 +92,7 @@ pub(super) async fn clear_caches(&self) -> Result<RoomMessageEventContent> {
#[admin_command]
pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
let result = self.services.globals.db.backup_list()?;
let result = self.services.db.db.backup_list()?;
if result.is_empty() {
Ok(RoomMessageEventContent::text_plain("No backups found."))
@@ -92,31 +103,24 @@ pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
#[admin_command]
pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> {
let globals = Arc::clone(&self.services.globals);
let db = Arc::clone(&self.services.db);
let mut result = self
.services
.server
.runtime()
.spawn_blocking(move || match globals.db.backup() {
.spawn_blocking(move || match db.db.backup() {
| Ok(()) => String::new(),
| Err(e) => e.to_string(),
})
.await?;
if result.is_empty() {
result = self.services.globals.db.backup_list()?;
result = self.services.db.db.backup_list()?;
}
Ok(RoomMessageEventContent::notice_markdown(result))
}
#[admin_command]
pub(super) async fn list_database_files(&self) -> Result<RoomMessageEventContent> {
let result = self.services.globals.db.file_list()?;
Ok(RoomMessageEventContent::notice_markdown(result))
}
#[admin_command]
pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
let message = message.join(" ");
+7 -3
View File
@@ -1,5 +1,7 @@
mod commands;
use std::path::PathBuf;
use clap::Subcommand;
use conduwuit::Result;
@@ -14,6 +16,11 @@ pub(super) enum ServerCommand {
/// - Show configuration values
ShowConfig,
/// - Reload configuration values
ReloadConfig {
path: Option<PathBuf>,
},
/// - List the features built into the server
ListFeatures {
#[arg(short, long)]
@@ -39,9 +46,6 @@ pub(super) enum ServerCommand {
/// - List database backups
ListBackups,
/// - List database files
ListDatabaseFiles,
/// - Send a message to the admin room.
AdminNotice {
message: Vec<String>,
+31 -28
View File
@@ -31,19 +31,21 @@ const BULK_JOIN_REASON: &str = "Bulk force joining this room as initiated by the
#[admin_command]
pub(super) async fn list_users(&self) -> Result<RoomMessageEventContent> {
let users = self
let users: Vec<_> = self
.services
.users
.list_local_users()
.map(ToString::to_string)
.collect::<Vec<_>>()
.collect()
.await;
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
plain_msg += users.join("\n").as_str();
plain_msg += "\n```";
Ok(RoomMessageEventContent::notice_markdown(plain_msg))
self.write_str(plain_msg.as_str()).await?;
Ok(RoomMessageEventContent::text_plain(""))
}
#[admin_command]
@@ -81,12 +83,12 @@ pub(super) async fn create_user(
// content is set to the user's display name with a space before it
if !self
.services
.globals
.server
.config
.new_user_displayname_suffix
.is_empty()
{
write!(displayname, " {}", self.services.globals.config.new_user_displayname_suffix)
write!(displayname, " {}", self.services.server.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer");
}
@@ -112,8 +114,8 @@ pub(super) async fn create_user(
)
.await?;
if !self.services.globals.config.auto_join_rooms.is_empty() {
for room in &self.services.globals.config.auto_join_rooms {
if !self.services.server.config.auto_join_rooms.is_empty() {
for room in &self.services.server.config.auto_join_rooms {
let Ok(room_id) = self.services.rooms.alias.resolve(room).await else {
error!(%user_id, "Failed to resolve room alias to room ID when attempting to auto join {room}, skipping");
continue;
@@ -912,29 +914,30 @@ pub(super) async fn redact_event(
self.services.globals.server_name()
);
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
let redaction_event_id = {
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
let redaction_event_id = self
.services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
redacts: Some(event.event_id.clone()),
..PduBuilder::timeline(&RoomRedactionEventContent {
self.services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
redacts: Some(event.event_id.clone()),
reason: Some(reason),
})
},
&sender_user,
&room_id,
&state_lock,
)
.await?;
..PduBuilder::timeline(&RoomRedactionEventContent {
redacts: Some(event.event_id.clone()),
reason: Some(reason),
})
},
&sender_user,
&room_id,
&state_lock,
)
.await?
};
drop(state_lock);
let out = format!("Successfully redacted event. Redaction event ID: {redaction_event_id}");
Ok(RoomMessageEventContent::text_plain(format!(
"Successfully redacted event. Redaction event ID: {redaction_event_id}"
)))
self.write_str(out.as_str()).await?;
Ok(RoomMessageEventContent::text_plain(""))
}
+1 -1
View File
@@ -50,7 +50,7 @@ http.workspace = true
http-body-util.workspace = true
hyper.workspace = true
ipaddress.workspace = true
jsonwebtoken.workspace = true
itertools.workspace = true
log.workspace = true
rand.workspace = true
reqwest.workspace = true
+9 -9
View File
@@ -299,7 +299,7 @@ pub(crate) async fn register_route(
if !services.globals.new_user_displayname_suffix().is_empty()
&& body.appservice_info.is_none()
{
write!(displayname, " {}", services.globals.config.new_user_displayname_suffix)
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer");
}
@@ -365,7 +365,7 @@ pub(crate) async fn register_route(
\"{device_display_name}\""
);
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
@@ -378,7 +378,7 @@ pub(crate) async fn register_route(
} else {
info!("New user \"{user_id}\" registered on this server.");
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
@@ -395,7 +395,7 @@ pub(crate) async fn register_route(
info!("New guest user \"{user_id}\" registered on this server.");
if !device_display_name.is_empty() {
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
@@ -407,7 +407,7 @@ pub(crate) async fn register_route(
}
} else {
#[allow(clippy::collapsible_else_if)]
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
@@ -438,10 +438,10 @@ pub(crate) async fn register_route(
}
if body.appservice_info.is_none()
&& !services.globals.config.auto_join_rooms.is_empty()
&& !services.server.config.auto_join_rooms.is_empty()
&& (services.globals.allow_guests_auto_join_rooms() || !is_guest)
{
for room in &services.globals.config.auto_join_rooms {
for room in &services.server.config.auto_join_rooms {
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
error!(
"Failed to resolve room alias to room ID when attempting to auto join \
@@ -570,7 +570,7 @@ pub(crate) async fn change_password_route(
info!("User {sender_user} changed their password.");
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
@@ -673,7 +673,7 @@ pub(crate) async fn deactivate_route(
info!("User {sender_user} deactivated their account.");
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
+6 -4
View File
@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduwuit::{Result, Server};
use ruma::{
api::client::discovery::get_capabilities::{
self, Capabilities, GetLoginTokenCapability, RoomVersionStability,
@@ -10,7 +11,7 @@ use ruma::{
};
use serde_json::json;
use crate::{Result, Ruma};
use crate::Ruma;
/// # `GET /_matrix/client/v3/capabilities`
///
@@ -21,7 +22,7 @@ pub(crate) async fn get_capabilities_route(
_body: Ruma<get_capabilities::v3::Request>,
) -> Result<get_capabilities::v3::Response> {
let available: BTreeMap<RoomVersionId, RoomVersionStability> =
services.server.available_room_versions().collect();
Server::available_room_versions().collect();
let mut capabilities = Capabilities::default();
capabilities.room_versions = RoomVersionsCapability {
@@ -32,8 +33,9 @@ pub(crate) async fn get_capabilities_route(
// we do not implement 3PID stuff
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability { enabled: false };
// we dont support generating tokens yet
capabilities.get_login_token = GetLoginTokenCapability { enabled: false };
capabilities.get_login_token = GetLoginTokenCapability {
enabled: services.server.config.login_via_existing_session,
};
// MSC4133 capability
capabilities
+90 -88
View File
@@ -1,24 +1,22 @@
use std::iter::once;
use axum::extract::State;
use conduwuit::{
at, err, ref_at,
utils::{
future::TryExtExt,
stream::{BroadbandExt, ReadyExt, WidebandExt},
stream::{BroadbandExt, ReadyExt, TryIgnore, WidebandExt},
IterStream,
},
Err, Result,
Err, PduEvent, Result,
};
use futures::{join, try_join, FutureExt, StreamExt, TryFutureExt};
use ruma::{
api::client::{context::get_context, filter::LazyLoadOptions},
events::StateEventType,
OwnedEventId, UserId,
use futures::{
future::{join, join3, try_join3, OptionFuture},
FutureExt, StreamExt, TryFutureExt, TryStreamExt,
};
use ruma::{api::client::context::get_context, events::StateEventType, OwnedEventId, UserId};
use service::rooms::{lazy_loading, lazy_loading::Options, short::ShortStateKey};
use crate::{
client::message::{event_filter, ignored_filter, update_lazy, visibility_filter, LazySet},
client::message::{event_filter, ignored_filter, lazy_loading_witness, visibility_filter},
Ruma,
};
@@ -35,10 +33,10 @@ pub(crate) async fn get_context_route(
State(services): State<crate::State>,
body: Ruma<get_context::v3::Request>,
) -> Result<get_context::v3::Response> {
let filter = &body.filter;
let sender = body.sender();
let (sender_user, _) = sender;
let (sender_user, sender_device) = sender;
let room_id = &body.room_id;
let filter = &body.filter;
// Use limit or else 10, with maximum 100
let limit: usize = body
@@ -47,25 +45,13 @@ pub(crate) async fn get_context_route(
.unwrap_or(LIMIT_DEFAULT)
.min(LIMIT_MAX);
// some clients, at least element, seem to require knowledge of redundant
// members for "inline" profiles on the timeline to work properly
let lazy_load_enabled = matches!(filter.lazy_load_options, LazyLoadOptions::Enabled { .. });
let lazy_load_redundant = if let LazyLoadOptions::Enabled { include_redundant_members } =
filter.lazy_load_options
{
include_redundant_members
} else {
false
};
let base_token = services
let base_id = services
.rooms
.timeline
.get_pdu_count(&body.event_id)
.get_pdu_id(&body.event_id)
.map_err(|_| err!(Request(NotFound("Event not found."))));
let base_event = services
let base_pdu = services
.rooms
.timeline
.get_pdu(&body.event_id)
@@ -77,117 +63,133 @@ pub(crate) async fn get_context_route(
.user_can_see_event(sender_user, &body.room_id, &body.event_id)
.map(Ok);
let (base_token, base_event, visible) = try_join!(base_token, base_event, visible)?;
let (base_id, base_pdu, visible) = try_join3(base_id, base_pdu, visible).await?;
if base_event.room_id != body.room_id || base_event.event_id != body.event_id {
if base_pdu.room_id != body.room_id || base_pdu.event_id != body.event_id {
return Err!(Request(NotFound("Base event not found.")));
}
if !visible
|| ignored_filter(&services, (base_token, base_event.clone()), sender_user)
.await
.is_none()
{
if !visible {
return Err!(Request(Forbidden("You don't have permission to view this event.")));
}
let events_before =
services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(base_token));
let base_count = base_id.pdu_count();
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);
let events_before = services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(base_count))
.ignore_err()
.ready_filter_map(|item| event_filter(item, filter))
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
.take(limit / 2)
.collect();
let events_after = services
.rooms
.timeline
.pdus(Some(sender_user), room_id, Some(base_token));
let (events_before, events_after) = try_join!(events_before, events_after)?;
let events_before = events_before
.pdus(Some(sender_user), room_id, Some(base_count))
.ignore_err()
.ready_filter_map(|item| event_filter(item, filter))
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
.take(limit / 2)
.collect();
let events_after = events_after
.ready_filter_map(|item| event_filter(item, filter))
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
.take(limit / 2)
.collect();
let (base_event, events_before, events_after): (_, Vec<_>, Vec<_>) =
join3(base_event, events_before, events_after).await;
let (events_before, events_after): (Vec<_>, Vec<_>) = join!(events_before, events_after);
let lazy_loading_context = lazy_loading::Context {
user_id: sender_user,
device_id: sender_device,
room_id,
token: Some(base_count.into_unsigned()),
options: Some(&filter.lazy_load_options),
};
let lazy_loading_witnessed: OptionFuture<_> = filter
.lazy_load_options
.is_enabled()
.then_some(
base_event
.iter()
.chain(events_before.iter())
.chain(events_after.iter()),
)
.map(|witnessed| lazy_loading_witness(&services, &lazy_loading_context, witnessed))
.into();
let state_at = events_after
.last()
.map(ref_at!(1))
.map_or(body.event_id.as_ref(), |e| e.event_id.as_ref());
.map_or(body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
let state_ids = services
.rooms
.state_accessor
.pdu_shortstatehash(state_at)
.or_else(|_| services.rooms.state.get_room_shortstatehash(room_id))
.and_then(|shortstatehash| services.rooms.state_accessor.state_full_ids(shortstatehash))
.map_err(|e| err!(Database("State not found: {e}")))
.await?;
let lazy = once(&(base_token, base_event.clone()))
.chain(events_before.iter())
.chain(events_after.iter())
.stream()
.fold(LazySet::new(), |lazy, item| {
update_lazy(&services, room_id, sender, lazy, item, lazy_load_redundant)
})
.await;
let lazy = &lazy;
let state: Vec<_> = state_ids
.iter()
.stream()
.broad_filter_map(|(shortstatekey, event_id)| {
.map_ok(|shortstatehash| {
services
.rooms
.short
.get_statekey_from_short(*shortstatekey)
.map_ok(move |(event_type, state_key)| (event_type, state_key, event_id))
.ok()
.state_accessor
.state_full_ids(shortstatehash)
.map(Ok)
})
.ready_filter_map(|(event_type, state_key, event_id)| {
if !lazy_load_enabled || event_type != StateEventType::RoomMember {
return Some(event_id);
.map_err(|e| err!(Database("State not found: {e}")))
.try_flatten_stream()
.try_collect()
.boxed();
let (lazy_loading_witnessed, state_ids) = join(lazy_loading_witnessed, state_ids).await;
let state_ids: Vec<(ShortStateKey, OwnedEventId)> = state_ids?;
let shortstatekeys = state_ids.iter().map(at!(0)).stream();
let shorteventids = state_ids.iter().map(ref_at!(1)).stream();
let lazy_loading_witnessed = lazy_loading_witnessed.unwrap_or_default();
let state: Vec<_> = services
.rooms
.short
.multi_get_statekey_from_short(shortstatekeys)
.zip(shorteventids)
.ready_filter_map(|item| Some((item.0.ok()?, item.1)))
.ready_filter_map(|((event_type, state_key), event_id)| {
if filter.lazy_load_options.is_enabled()
&& event_type == StateEventType::RoomMember
&& state_key
.as_str()
.try_into()
.is_ok_and(|user_id: &UserId| !lazy_loading_witnessed.contains(user_id))
{
return None;
}
state_key
.as_str()
.try_into()
.ok()
.filter(|&user_id: &&UserId| lazy.contains(user_id))
.map(|_| event_id)
Some(event_id)
})
.broad_filter_map(|event_id: &OwnedEventId| {
services.rooms.timeline.get_pdu(event_id).ok()
services.rooms.timeline.get_pdu(event_id.as_ref()).ok()
})
.map(|pdu| pdu.to_state_event())
.map(PduEvent::into_state_event)
.collect()
.await;
Ok(get_context::v3::Response {
event: Some(base_event.to_room_event()),
event: base_event.map(at!(1)).as_ref().map(PduEvent::to_room_event),
start: events_before
.last()
.map(at!(0))
.or(Some(base_token))
.or(Some(base_count))
.as_ref()
.map(ToString::to_string),
end: events_after
.last()
.map(at!(0))
.or(Some(base_token))
.or(Some(base_count))
.as_ref()
.map(ToString::to_string),
+3 -3
View File
@@ -152,7 +152,7 @@ pub(crate) async fn set_room_visibility_route(
match &body.visibility {
| room::Visibility::Public => {
if services.globals.config.lockdown_public_room_directory
if services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
@@ -162,7 +162,7 @@ pub(crate) async fn set_room_visibility_route(
body.room_id
);
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
@@ -181,7 +181,7 @@ pub(crate) async fn set_room_visibility_route(
services.rooms.directory.set_public(&body.room_id);
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
+17 -8
View File
@@ -31,7 +31,7 @@ pub(crate) async fn get_media_config_route(
_body: Ruma<get_media_config::v1::Request>,
) -> Result<get_media_config::v1::Response> {
Ok(get_media_config::v1::Response {
upload_size: ruma_from_usize(services.globals.config.max_request_size),
upload_size: ruma_from_usize(services.server.config.max_request_size),
})
}
@@ -57,19 +57,28 @@ pub(crate) async fn create_content_route(
let filename = body.filename.as_deref();
let content_type = body.content_type.as_deref();
let content_disposition = make_content_disposition(None, content_type, filename);
let mxc = Mxc {
let ref mxc = Mxc {
server_name: services.globals.server_name(),
media_id: &utils::random_string(MXC_LENGTH),
};
services
.media
.create(&mxc, Some(user), Some(&content_disposition), content_type, &body.file)
.await
.map(|()| create_content::v3::Response {
content_uri: mxc.to_string().into(),
blurhash: None,
})
.create(mxc, Some(user), Some(&content_disposition), content_type, &body.file)
.await?;
let blurhash = body.generate_blurhash.then(|| {
services
.media
.create_blurhash(&body.file, content_type, filename)
.ok()
.flatten()
});
Ok(create_content::v3::Response {
content_uri: mxc.to_string().into(),
blurhash: blurhash.flatten(),
})
}
/// # `GET /_matrix/client/v1/media/thumbnail/{serverName}/{mediaId}`
+6 -6
View File
@@ -27,7 +27,7 @@ pub(crate) async fn get_media_config_legacy_route(
_body: Ruma<get_media_config::v3::Request>,
) -> Result<get_media_config::v3::Response> {
Ok(get_media_config::v3::Response {
upload_size: ruma_from_usize(services.globals.config.max_request_size),
upload_size: ruma_from_usize(services.server.config.max_request_size),
})
}
@@ -50,7 +50,7 @@ pub(crate) async fn get_media_config_legacy_legacy_route(
/// # `GET /_matrix/media/v3/preview_url`
///
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview_legacy")]
#[tracing::instrument(skip_all, fields(%client), name = "url_preview_legacy", level = "debug")]
pub(crate) async fn get_media_preview_legacy_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -131,7 +131,7 @@ pub(crate) async fn create_content_legacy_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy", level = "debug")]
pub(crate) async fn get_content_legacy_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -197,7 +197,7 @@ pub(crate) async fn get_content_legacy_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy", level = "debug")]
pub(crate) async fn get_content_legacy_legacy_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -216,7 +216,7 @@ pub(crate) async fn get_content_legacy_legacy_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy", level = "debug")]
pub(crate) async fn get_content_as_filename_legacy_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -303,7 +303,7 @@ pub(crate) async fn get_content_as_filename_legacy_legacy_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get_legacy")]
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get_legacy", level = "debug")]
pub(crate) async fn get_content_thumbnail_legacy_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+721 -47
View File
@@ -1,5 +1,7 @@
use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap, HashSet},
iter::once,
net::IpAddr,
sync::Arc,
};
@@ -7,18 +9,19 @@ use std::{
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
debug, debug_info, debug_warn, err, info,
pdu::{self, gen_event_id_canonical_json, PduBuilder},
at, debug, debug_info, debug_warn, err, info,
pdu::{gen_event_id_canonical_json, PduBuilder},
result::FlatOk,
trace,
utils::{self, shuffle, IterStream, ReadyExt},
warn, Err, PduEvent, Result,
};
use futures::{join, FutureExt, StreamExt};
use futures::{join, FutureExt, StreamExt, TryFutureExt};
use ruma::{
api::{
client::{
error::ErrorKind,
knock::knock_room,
membership::{
ban_user, forget_room, get_member_events, invite_user, join_room_by_id,
join_room_by_id_or_alias,
@@ -37,12 +40,16 @@ use ruma::{
},
StateEventType,
},
state_res, CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName,
OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
state_res, CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId,
OwnedServerName, OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
};
use service::{
appservice::RegistrationInfo,
rooms::{state::RoomMutexGuard, state_compressor::HashSetCompressStateEvent},
pdu::gen_event_id,
rooms::{
state::RoomMutexGuard,
state_compressor::{CompressedState, HashSetCompressStateEvent},
},
Services,
};
@@ -68,7 +75,7 @@ async fn banned_room_check(
if let Some(room_id) = room_id {
if services.rooms.metadata.is_banned(room_id).await
|| services
.globals
.server
.config
.forbidden_remote_server_names
.contains(&room_id.server_name().unwrap().to_owned())
@@ -78,12 +85,12 @@ async fn banned_room_check(
attempted to join a banned room or banned room server name: {room_id}"
);
if services.globals.config.auto_deactivate_banned_room_attempts {
if services.server.config.auto_deactivate_banned_room_attempts {
warn!(
"Automatically deactivating user {user_id} due to attempted banned room join"
);
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
@@ -109,7 +116,7 @@ async fn banned_room_check(
}
} else if let Some(server_name) = server_name {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(&server_name.to_owned())
@@ -119,12 +126,12 @@ async fn banned_room_check(
name {server_name} that is globally forbidden. Rejecting.",
);
if services.globals.config.auto_deactivate_banned_room_attempts {
if services.server.config.auto_deactivate_banned_room_attempts {
warn!(
"Automatically deactivating user {user_id} due to attempted banned room join"
);
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
@@ -348,6 +355,116 @@ pub(crate) async fn join_room_by_id_or_alias_route(
Ok(join_room_by_id_or_alias::v3::Response { room_id: join_room_response.room_id })
}
/// # `POST /_matrix/client/*/knock/{roomIdOrAlias}`
///
/// Tries to knock the room to ask permission to join for the sender user.
#[tracing::instrument(skip_all, fields(%client), name = "knock")]
pub(crate) async fn knock_room_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<knock_room::v3::Request>,
) -> Result<knock_room::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let body = body.body;
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) {
| Ok(room_id) => {
banned_room_check(
&services,
sender_user,
Some(&room_id),
room_id.server_name(),
client,
)
.await?;
let mut servers = body.via.clone();
servers.extend(
services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Some(server) = room_id.server_name() {
servers.push(server.to_owned());
}
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
(servers, room_id)
},
| Err(room_alias) => {
let (room_id, mut servers) = services
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
banned_room_check(
&services,
sender_user,
Some(&room_id),
Some(room_alias.server_name()),
client,
)
.await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
let addl_state_servers = services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default();
let mut addl_servers: Vec<_> = addl_state_servers
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
.await;
addl_servers.sort_unstable();
addl_servers.dedup();
shuffle(&mut addl_servers);
servers.append(&mut addl_servers);
(servers, room_id)
},
};
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers)
.boxed()
.await
}
/// # `POST /_matrix/client/v3/rooms/{roomId}/leave`
///
/// Tries to leave the sender user from a room.
@@ -403,6 +520,17 @@ pub(crate) async fn invite_user_route(
)));
}
if let Ok(target_user_membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, user_id)
.await
{
if target_user_membership.membership == MembershipState::Ban {
return Err!(Request(Forbidden("User is banned from this room.")));
}
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
@@ -439,6 +567,16 @@ pub(crate) async fn kick_user_route(
return Ok(kick_user::v3::Response::new());
};
if !matches!(
event.membership,
MembershipState::Invite | MembershipState::Knock | MembershipState::Join,
) {
return Err!(Request(Forbidden(
"Cannot kick a user who is not apart of the room (current membership: {})",
event.membership
)));
}
services
.rooms
.timeline
@@ -527,7 +665,7 @@ pub(crate) async fn unban_user_route(
if current_member_content.membership != MembershipState::Ban {
return Err!(Request(Forbidden(
"Cannot ban a user who is not banned (current membership: {})",
"Cannot unban a user who is not banned (current membership: {})",
current_member_content.membership
)));
}
@@ -631,11 +769,12 @@ pub(crate) async fn get_member_events_route(
.rooms
.state_accessor
.room_state_full(&body.room_id)
.await?
.iter()
.filter(|(key, _)| key.0 == StateEventType::RoomMember)
.map(|(_, pdu)| pdu.to_member_event())
.collect(),
.ready_filter_map(Result::ok)
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
.map(at!(1))
.map(PduEvent::into_member_event)
.collect()
.await,
})
}
@@ -852,7 +991,7 @@ async fn join_room_by_id_helper_remote(
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = pdu::gen_event_id(&join_event_stub, &room_version_id)?;
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub
@@ -1020,7 +1159,7 @@ async fn join_room_by_id_helper_remote(
};
let auth_check = state_res::event_auth::auth_check(
&state_res::RoomVersion::new(&room_version_id).expect("room version is supported"),
&state_res::RoomVersion::new(&room_version_id)?,
&parsed_join_pdu,
None, // TODO: third party invite
|k, s| state_fetch(k, s.to_owned()),
@@ -1033,10 +1172,10 @@ async fn join_room_by_id_helper_remote(
}
info!("Compressing state from send_join");
let compressed = state
.iter()
.stream()
.then(|(&k, id)| services.rooms.state_compressor.compress_state_event(k, id))
let compressed: CompressedState = services
.rooms
.state_compressor
.compress_state_events(state.iter().map(|(ssk, eid)| (ssk, eid.borrow())))
.collect()
.await;
@@ -1081,7 +1220,7 @@ async fn join_room_by_id_helper_remote(
.append_pdu(
&parsed_join_pdu,
join_event,
vec![(*parsed_join_pdu.event_id).to_owned()],
once(parsed_join_pdu.event_id.borrow()),
&state_lock,
)
.await?;
@@ -1272,7 +1411,7 @@ async fn join_room_by_id_helper_local(
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = pdu::gen_event_id(&join_event_stub, &room_version_id)?;
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub
@@ -1314,6 +1453,7 @@ async fn join_room_by_id_helper_local(
.rooms
.event_handler
.handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true)
.boxed()
.await?;
} else {
return Err(error);
@@ -1381,6 +1521,7 @@ async fn make_join_request(
);
make_join_response_and_server =
Err!(BadServerResponse("No server available to assist in joining."));
return make_join_response_and_server;
}
}
@@ -1491,6 +1632,7 @@ pub(crate) async fn invite_helper(
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
.boxed()
.await?
.ok_or_else(|| {
err!(Request(InvalidParam("Could not accept incoming PDU as timeline event.")))
@@ -1557,7 +1699,7 @@ pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
for room_id in all_rooms {
// ignore errors
if let Err(e) = leave_room(services, user_id, &room_id, None).await {
warn!(%room_id, %user_id, %e, "Failed to leave room");
warn!(%user_id, "Failed to leave {room_id} remotely: {e}");
}
services.rooms.state_cache.forget(&room_id, user_id);
@@ -1570,14 +1712,15 @@ pub async fn leave_room(
room_id: &RoomId,
reason: Option<String>,
) -> Result<()> {
//use conduwuit::utils::stream::OptionStream;
use futures::TryFutureExt;
// Ask a remote server if we don't have this room
// Ask a remote server if we don't have this room and are not knocking on it
if !services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await && !services
.rooms
.state_cache
.is_knocked(user_id, room_id)
.await
{
if let Err(e) = remote_leave_room(services, user_id, room_id).await {
@@ -1589,7 +1732,8 @@ pub async fn leave_room(
.rooms
.state_cache
.invite_state(user_id, room_id)
.map_err(|_| services.rooms.state_cache.left_state(user_id, room_id))
.or_else(|_| services.rooms.state_cache.knock_state(user_id, room_id))
.or_else(|_| services.rooms.state_cache.left_state(user_id, room_id))
.await
.ok();
@@ -1671,13 +1815,6 @@ async fn remote_leave_room(
let mut make_leave_response_and_server =
Err!(BadServerResponse("No server available to assist in leaving."));
let invite_state = services
.rooms
.state_cache
.invite_state(user_id, room_id)
.await
.map_err(|_| err!(Request(BadState("User is not invited."))))?;
let mut servers: HashSet<OwnedServerName> = services
.rooms
.state_cache
@@ -1686,13 +1823,39 @@ async fn remote_leave_room(
.collect()
.await;
servers.extend(
invite_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Ok(invite_state) = services
.rooms
.state_cache
.invite_state(user_id, room_id)
.await
{
servers.extend(
invite_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
} else if let Ok(knock_state) = services
.rooms
.state_cache
.knock_state(user_id, room_id)
.await
{
servers.extend(
knock_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.filter_map(|sender| {
if !services.globals.user_is_local(sender) {
Some(sender.server_name().to_owned())
} else {
None
}
}),
);
}
if let Some(room_id_server_name) = room_id.server_name() {
servers.insert(room_id_server_name.to_owned());
@@ -1767,7 +1930,7 @@ async fn remote_leave_room(
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
// Generate event id
let event_id = pdu::gen_event_id(&leave_event_stub, &room_version_id)?;
let event_id = gen_event_id(&leave_event_stub, &room_version_id)?;
// Add event_id back
leave_event_stub
@@ -1793,3 +1956,514 @@ async fn remote_leave_room(
Ok(())
}
async fn knock_room_by_id_helper(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
) -> Result<knock_room::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
if services
.rooms
.state_cache
.is_invited(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already invited in {room_id} but attempted to knock");
return Err!(Request(Forbidden(
"You cannot knock on a room you are already invited/accepted to."
)));
}
if services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already joined in {room_id} but attempted to knock");
return Err!(Request(Forbidden("You cannot knock on a room you are already joined in.")));
}
if services
.rooms
.state_cache
.is_knocked(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already knocked in {room_id}");
return Ok(knock_room::v3::Response { room_id: room_id.into() });
}
if let Ok(membership) = services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.await
{
if membership.membership == MembershipState::Ban {
debug_warn!("{sender_user} is banned from {room_id} but attempted to knock");
return Err!(Request(Forbidden("You cannot knock on a room you are banned from.")));
}
}
let server_in_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await;
let local_knock = server_in_room
|| servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
if local_knock {
knock_room_helper_local(services, sender_user, room_id, reason, servers, state_lock)
.boxed()
.await?;
} else {
knock_room_helper_remote(services, sender_user, room_id, reason, servers, state_lock)
.boxed()
.await?;
}
Ok(knock_room::v3::Response::new(room_id.to_owned()))
}
async fn knock_room_helper_local(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
state_lock: RoomMutexGuard,
) -> Result {
debug_info!("We can knock locally");
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
if matches!(
room_version_id,
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
) {
return Err!(Request(Forbidden("This room does not support knocking.")));
}
let content = RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
..RoomMemberEventContent::new(MembershipState::Knock)
};
// Try normal knock first
let Err(error) = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await
else {
return Ok(());
};
if servers.is_empty() || (servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
{
return Err(error);
}
warn!("We couldn't do the knock locally, maybe federation can help to satisfy the knock");
let (make_knock_response, remote_server) =
make_knock_request(services, sender_user, room_id, servers).await?;
info!("make_knock finished");
let room_version_id = make_knock_response.room_version;
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut knock_event_stub = serde_json::from_str::<CanonicalJsonObject>(
make_knock_response.event.get(),
)
.map_err(|e| {
err!(BadServerResponse("Invalid make_knock event json received from server: {e:?}"))
})?;
knock_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
knock_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
knock_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
..RoomMemberEventContent::new(MembershipState::Knock)
})
.expect("event is valid, we just created it"),
);
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut knock_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&knock_event_stub, &room_version_id)?;
// Add event_id
knock_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.await,
};
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.await?;
info!("send_knock finished");
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
info!("Parsing knock event");
let parsed_knock_pdu = PduEvent::from_id_val(&event_id, knock_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
info!("Updating membership locally to knock state with provided stripped state events");
services
.rooms
.state_cache
.update_membership(
room_id,
sender_user,
parsed_knock_pdu
.get_content::<RoomMemberEventContent>()
.expect("we just created this"),
sender_user,
Some(send_knock_response.knock_room_state),
None,
false,
)
.await?;
info!("Appending room knock event locally");
services
.rooms
.timeline
.append_pdu(
&parsed_knock_pdu,
knock_event,
once(parsed_knock_pdu.event_id.borrow()),
&state_lock,
)
.await?;
Ok(())
}
async fn knock_room_helper_remote(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
state_lock: RoomMutexGuard,
) -> Result {
info!("Knocking {room_id} over federation.");
let (make_knock_response, remote_server) =
make_knock_request(services, sender_user, room_id, servers).await?;
info!("make_knock finished");
let room_version_id = make_knock_response.room_version;
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut knock_event_stub: CanonicalJsonObject =
serde_json::from_str(make_knock_response.event.get()).map_err(|e| {
err!(BadServerResponse("Invalid make_knock event json received from server: {e:?}"))
})?;
knock_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
knock_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
knock_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
..RoomMemberEventContent::new(MembershipState::Knock)
})
.expect("event is valid, we just created it"),
);
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut knock_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&knock_event_stub, &room_version_id)?;
// Add event_id
knock_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.await,
};
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.await?;
info!("send_knock finished");
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
info!("Parsing knock event");
let parsed_knock_pdu = PduEvent::from_id_val(&event_id, knock_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
info!("Going through send_knock response knock state events");
let state = send_knock_response
.knock_room_state
.iter()
.map(|event| serde_json::from_str::<CanonicalJsonObject>(event.clone().into_json().get()))
.filter_map(Result::ok);
let mut state_map: HashMap<u64, OwnedEventId> = HashMap::new();
for event in state {
let Some(state_key) = event.get("state_key") else {
debug_warn!("send_knock stripped state event missing state_key: {event:?}");
continue;
};
let Some(event_type) = event.get("type") else {
debug_warn!("send_knock stripped state event missing event type: {event:?}");
continue;
};
let Ok(state_key) = serde_json::from_value::<String>(state_key.clone().into()) else {
debug_warn!("send_knock stripped state event has invalid state_key: {event:?}");
continue;
};
let Ok(event_type) = serde_json::from_value::<StateEventType>(event_type.clone().into())
else {
debug_warn!("send_knock stripped state event has invalid event type: {event:?}");
continue;
};
let event_id = gen_event_id(&event, &room_version_id)?;
let shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&event_type, &state_key)
.await;
services.rooms.outlier.add_pdu_outlier(&event_id, &event);
state_map.insert(shortstatekey, event_id.clone());
}
info!("Compressing state from send_knock");
let compressed: CompressedState = services
.rooms
.state_compressor
.compress_state_events(state_map.iter().map(|(ssk, eid)| (ssk, eid.borrow())))
.collect()
.await;
debug!("Saving compressed state");
let HashSetCompressStateEvent {
shortstatehash: statehash_before_knock,
added,
removed,
} = services
.rooms
.state_compressor
.save_state(room_id, Arc::new(compressed))
.await?;
debug!("Forcing state for new room");
services
.rooms
.state
.force_state(room_id, statehash_before_knock, added, removed, &state_lock)
.await?;
let statehash_after_knock = services
.rooms
.state
.append_to_state(&parsed_knock_pdu)
.await?;
info!("Updating membership locally to knock state with provided stripped state events");
services
.rooms
.state_cache
.update_membership(
room_id,
sender_user,
parsed_knock_pdu
.get_content::<RoomMemberEventContent>()
.expect("we just created this"),
sender_user,
Some(send_knock_response.knock_room_state),
None,
false,
)
.await?;
info!("Appending room knock event locally");
services
.rooms
.timeline
.append_pdu(
&parsed_knock_pdu,
knock_event,
once(parsed_knock_pdu.event_id.borrow()),
&state_lock,
)
.await?;
info!("Setting final room state for new room");
// We set the room state after inserting the pdu, so that we never have a moment
// in time where events in the current room state do not exist
services
.rooms
.state
.set_room_state(room_id, statehash_after_knock, &state_lock);
Ok(())
}
async fn make_knock_request(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
servers: &[OwnedServerName],
) -> Result<(federation::knock::create_knock_event_template::v1::Response, OwnedServerName)> {
let mut make_knock_response_and_server =
Err!(BadServerResponse("No server available to assist in knocking."));
let mut make_knock_counter: usize = 0;
for remote_server in servers {
if services.globals.server_is_ours(remote_server) {
continue;
}
info!("Asking {remote_server} for make_knock ({make_knock_counter})");
let make_knock_response = services
.sending
.send_federation_request(
remote_server,
federation::knock::create_knock_event_template::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services.server.supported_room_versions().collect(),
},
)
.await;
trace!("make_knock response: {make_knock_response:?}");
make_knock_counter = make_knock_counter.saturating_add(1);
make_knock_response_and_server = make_knock_response.map(|r| (r, remote_server.clone()));
if make_knock_response_and_server.is_ok() {
break;
}
if make_knock_counter > 40 {
warn!(
"50 servers failed to provide valid make_knock response, assuming no server can \
assist in knocking."
);
make_knock_response_and_server =
Err!(BadServerResponse("No server available to assist in knocking."));
return make_knock_response_and_server;
}
}
make_knock_response_and_server
}
+83 -78
View File
@@ -1,16 +1,14 @@
use std::collections::HashSet;
use axum::extract::State;
use conduwuit::{
at, is_equal_to,
utils::{
result::{FlatOk, LogErr},
stream::{BroadbandExt, WidebandExt},
stream::{BroadbandExt, TryIgnore, WidebandExt},
IterStream, ReadyExt,
},
Event, PduCount, Result,
Event, PduCount, PduEvent, Result,
};
use futures::{FutureExt, StreamExt};
use futures::{future::OptionFuture, pin_mut, FutureExt, StreamExt, TryFutureExt};
use ruma::{
api::{
client::{filter::RoomEventFilter, message::get_message_events},
@@ -18,14 +16,19 @@ use ruma::{
},
events::{AnyStateEvent, StateEventType, TimelineEventType, TimelineEventType::*},
serde::Raw,
DeviceId, OwnedUserId, RoomId, UserId,
RoomId, UserId,
};
use service::{
rooms::{
lazy_loading,
lazy_loading::{Options, Witness},
timeline::PdusIterItem,
},
Services,
};
use service::{rooms::timeline::PdusIterItem, Services};
use crate::Ruma;
pub(crate) type LazySet = HashSet<OwnedUserId>;
/// list of safe and common non-state events to ignore if the user is ignored
const IGNORED_MESSAGE_TYPES: &[TimelineEventType; 17] = &[
Audio,
@@ -84,13 +87,6 @@ pub(crate) async fn get_message_events_route(
.unwrap_or(LIMIT_DEFAULT)
.min(LIMIT_MAX);
services.rooms.lazy_loading.lazy_load_confirm_delivery(
sender_user,
sender_device,
room_id,
from,
);
if matches!(body.dir, Direction::Backward) {
services
.rooms
@@ -107,14 +103,14 @@ pub(crate) async fn get_message_events_route(
.rooms
.timeline
.pdus(Some(sender_user), room_id, Some(from))
.await?
.ignore_err()
.boxed(),
| Direction::Backward => services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(from))
.await?
.ignore_err()
.boxed(),
};
@@ -127,35 +123,34 @@ pub(crate) async fn get_message_events_route(
.collect()
.await;
let lazy = events
.iter()
.stream()
.fold(LazySet::new(), |lazy, item| {
update_lazy(&services, room_id, sender, lazy, item, false)
})
.await;
let lazy_loading_context = lazy_loading::Context {
user_id: sender_user,
device_id: sender_device,
room_id,
token: Some(from.into_unsigned()),
options: Some(&filter.lazy_load_options),
};
let state = lazy
.iter()
.stream()
.broad_filter_map(|user_id| get_member_event(&services, room_id, user_id))
let witness: OptionFuture<_> = filter
.lazy_load_options
.is_enabled()
.then(|| lazy_loading_witness(&services, &lazy_loading_context, events.iter()))
.into();
let state = witness
.map(Option::into_iter)
.map(|option| option.flat_map(Witness::into_iter))
.map(IterStream::stream)
.into_stream()
.flatten()
.broad_filter_map(|user_id| async move {
get_member_event(&services, room_id, &user_id).await
})
.collect()
.await;
let next_token = events.last().map(at!(0));
if !cfg!(feature = "element_hacks") {
if let Some(next_token) = next_token {
services.rooms.lazy_loading.lazy_load_mark_sent(
sender_user,
sender_device,
room_id,
lazy,
next_token,
);
}
}
let chunk = events
.into_iter()
.map(at!(1))
@@ -170,6 +165,52 @@ pub(crate) async fn get_message_events_route(
})
}
pub(crate) async fn lazy_loading_witness<'a, I>(
services: &Services,
lazy_loading_context: &lazy_loading::Context<'_>,
events: I,
) -> Witness
where
I: Iterator<Item = &'a PdusIterItem> + Clone + Send,
{
let oldest = events
.clone()
.map(|(count, _)| count)
.copied()
.min()
.unwrap_or_else(PduCount::max);
let newest = events
.clone()
.map(|(count, _)| count)
.copied()
.max()
.unwrap_or_else(PduCount::max);
let receipts = services
.rooms
.read_receipt
.readreceipts_since(lazy_loading_context.room_id, oldest.into_unsigned());
pin_mut!(receipts);
let witness: Witness = events
.stream()
.map(|(_, pdu)| pdu.sender.clone())
.chain(
receipts
.ready_take_while(|(_, c, _)| *c <= newest.into_unsigned())
.map(|(user_id, ..)| user_id.to_owned()),
)
.collect()
.await;
services
.rooms
.lazy_loading
.witness_retain(witness, lazy_loading_context)
.await
}
async fn get_member_event(
services: &Services,
room_id: &RoomId,
@@ -179,47 +220,11 @@ async fn get_member_event(
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
.map_ok(PduEvent::into_state_event)
.await
.map(|member_event| member_event.to_state_event())
.ok()
}
pub(crate) async fn update_lazy(
services: &Services,
room_id: &RoomId,
sender: (&UserId, &DeviceId),
mut lazy: LazySet,
item: &PdusIterItem,
force: bool,
) -> LazySet {
let (_, event) = &item;
let (sender_user, sender_device) = sender;
/* TODO: Remove the "element_hacks" check when these are resolved:
* https://github.com/vector-im/element-android/issues/3417
* https://github.com/vector-im/element-web/issues/21034
*/
if force || cfg!(features = "element_hacks") {
lazy.insert(event.sender().into());
return lazy;
}
if lazy.contains(event.sender()) {
return lazy;
}
if !services
.rooms
.lazy_loading
.lazy_load_was_sent_before(sender_user, sender_device, room_id, event.sender())
.await
{
lazy.insert(event.sender().into());
}
lazy
}
pub(crate) async fn ignored_filter(
services: &Services,
item: PdusIterItem,
+1 -1
View File
@@ -37,7 +37,7 @@ pub(crate) async fn create_openid_token_route(
Ok(account::request_openid_token::v3::Response {
access_token,
token_type: TokenType::Bearer,
matrix_server_name: services.globals.config.server_name.clone(),
matrix_server_name: services.server.name.clone(),
expires_in: Duration::from_secs(expires_in),
})
}
+9 -4
View File
@@ -82,14 +82,19 @@ pub(crate) async fn get_presence_route(
presence.content.status_msg
};
let last_active_ago = match presence.content.currently_active {
| Some(true) => None,
| _ => presence
.content
.last_active_ago
.map(|millis| Duration::from_millis(millis.into())),
};
Ok(get_presence::v3::Response {
// TODO: Should ruma just use the presenceeventcontent type here?
status_msg,
currently_active: presence.content.currently_active,
last_active_ago: presence
.content
.last_active_ago
.map(|millis| Duration::from_millis(millis.into())),
last_active_ago,
presence: presence.content.presence,
})
} else {
+1 -1
View File
@@ -50,7 +50,7 @@ pub(crate) async fn report_room_route(
if !services
.rooms
.state_cache
.server_in_room(&services.globals.config.server_name, &body.room_id)
.server_in_room(&services.server.name, &body.room_id)
.await
{
return Err!(Request(NotFound(
+4 -4
View File
@@ -71,7 +71,7 @@ pub(crate) async fn create_room_route(
let room_id: OwnedRoomId = if let Some(custom_room_id) = &body.room_id {
custom_room_id_check(&services, custom_room_id)?
} else {
RoomId::new(&services.globals.config.server_name)
RoomId::new(&services.server.name)
};
// check if room ID doesn't already exist instead of erroring on auth check
@@ -83,7 +83,7 @@ pub(crate) async fn create_room_route(
}
if body.visibility == room::Visibility::Public
&& services.globals.config.lockdown_public_room_directory
&& services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
@@ -93,7 +93,7 @@ pub(crate) async fn create_room_route(
&room_id
);
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
@@ -450,7 +450,7 @@ pub(crate) async fn create_room_route(
if body.visibility == room::Visibility::Public {
services.rooms.directory.set_public(&room_id);
if services.globals.config.admin_room_notices {
if services.server.config.admin_room_notices {
services
.admin
.send_text(&format!(
+12 -10
View File
@@ -1,6 +1,10 @@
use axum::extract::State;
use conduwuit::{at, utils::BoolExt, Err, Result};
use futures::StreamExt;
use conduwuit::{
at,
utils::{stream::TryTools, BoolExt},
Err, PduEvent, Result,
};
use futures::TryStreamExt;
use ruma::api::client::room::initial_sync::v3::{PaginationChunk, Request, Response};
use crate::Ruma;
@@ -27,19 +31,17 @@ pub(crate) async fn room_initial_sync_route(
.rooms
.timeline
.pdus_rev(None, room_id, None)
.await?
.take(limit)
.collect()
.await;
.try_take(limit)
.try_collect()
.await?;
let state: Vec<_> = services
.rooms
.state_accessor
.room_state_full_pdus(room_id)
.await?
.into_iter()
.map(|pdu| pdu.to_state_event())
.collect();
.map_ok(PduEvent::into_state_event)
.try_collect()
.await?;
let messages = PaginationChunk {
start: events.last().map(at!(0)).as_ref().map(ToString::to_string),
+6 -6
View File
@@ -7,7 +7,7 @@ use conduwuit::{
utils::{stream::ReadyExt, IterStream},
Err, PduEvent, Result,
};
use futures::{future::OptionFuture, FutureExt, StreamExt, TryFutureExt};
use futures::{future::OptionFuture, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
use ruma::{
api::client::search::search_events::{
self,
@@ -181,15 +181,15 @@ async fn category_room_events(
}
async fn procure_room_state(services: &Services, room_id: &RoomId) -> Result<RoomState> {
let state_map = services
let state = services
.rooms
.state_accessor
.room_state_full(room_id)
.room_state_full_pdus(room_id)
.map_ok(PduEvent::into_state_event)
.try_collect()
.await?;
let state_events = state_map.values().map(PduEvent::to_state_event).collect();
Ok(state_events)
Ok(state)
}
async fn check_room_visible(
+93 -50
View File
@@ -1,3 +1,5 @@
use std::time::Duration;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{debug, err, info, utils::ReadyExt, warn, Err};
@@ -6,9 +8,10 @@ use ruma::{
api::client::{
error::ErrorKind,
session::{
get_login_token,
get_login_types::{
self,
v3::{ApplicationServiceLoginType, PasswordLoginType},
v3::{ApplicationServiceLoginType, PasswordLoginType, TokenLoginType},
},
login::{
self,
@@ -16,33 +19,31 @@ use ruma::{
},
logout, logout_all,
},
uiaa::UserIdentifier,
uiaa,
},
OwnedUserId, UserId,
};
use serde::Deserialize;
use service::uiaa::SESSION_ID_LENGTH;
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::{utils, utils::hash, Error, Result, Ruma};
#[derive(Debug, Deserialize)]
struct Claims {
sub: String,
//exp: usize,
}
/// # `GET /_matrix/client/v3/login`
///
/// Get the supported login types of this server. One of these should be used as
/// the `type` field when logging in.
#[tracing::instrument(skip_all, fields(%client), name = "login")]
pub(crate) async fn get_login_types_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
_body: Ruma<get_login_types::v3::Request>,
) -> Result<get_login_types::v3::Response> {
Ok(get_login_types::v3::Response::new(vec![
get_login_types::v3::LoginType::Password(PasswordLoginType::default()),
get_login_types::v3::LoginType::ApplicationService(ApplicationServiceLoginType::default()),
get_login_types::v3::LoginType::Token(TokenLoginType {
get_login_token: services.server.config.login_via_existing_session,
}),
]))
}
@@ -77,7 +78,9 @@ pub(crate) async fn login_route(
..
}) => {
debug!("Got password login type");
let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
let user_id = if let Some(uiaa::UserIdentifier::UserIdOrLocalpart(user_id)) =
identifier
{
UserId::parse_with_server_name(
user_id.to_lowercase(),
services.globals.server_name(),
@@ -108,32 +111,10 @@ pub(crate) async fn login_route(
},
| login::v3::LoginInfo::Token(login::v3::Token { token }) => {
debug!("Got token login type");
if let Some(jwt_decoding_key) = services.globals.jwt_decoding_key() {
let token = jsonwebtoken::decode::<Claims>(
token,
jwt_decoding_key,
&jsonwebtoken::Validation::default(),
)
.map_err(|e| {
warn!("Failed to parse JWT token from user logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Token is invalid.")
})?;
let username = token.claims.sub.to_lowercase();
UserId::parse_with_server_name(username, services.globals.server_name()).map_err(
|e| {
err!(Request(InvalidUsername(debug_error!(
?e,
"Failed to parse login username"
))))
},
)?
} else {
return Err!(Request(Unknown(
"Token login is not supported (server has no jwt decoding key)."
)));
if !services.server.config.login_via_existing_session {
return Err!(Request(Unknown("Token login is not enabled.")));
}
services.users.find_from_login_token(token).await?
},
#[allow(deprecated)]
| login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService {
@@ -141,21 +122,22 @@ pub(crate) async fn login_route(
user,
}) => {
debug!("Got appservice login type");
let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
UserId::parse_with_server_name(
user_id.to_lowercase(),
services.globals.server_name(),
)
} else if let Some(user) = user {
OwnedUserId::parse(user)
} else {
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 appservice logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
})?;
let user_id =
if let Some(uiaa::UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
UserId::parse_with_server_name(
user_id.to_lowercase(),
services.globals.server_name(),
)
} else if let Some(user) = user {
OwnedUserId::parse(user)
} else {
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 appservice logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
})?;
if let Some(ref info) = body.appservice_info {
if !info.is_user_match(&user_id) {
@@ -247,6 +229,67 @@ pub(crate) async fn login_route(
})
}
/// # `POST /_matrix/client/v1/login/get_token`
///
/// Allows a logged-in user to get a short-lived token which can be used
/// to log in with the m.login.token flow.
///
/// <https://spec.matrix.org/v1.13/client-server-api/#post_matrixclientv1loginget_token>
#[tracing::instrument(skip_all, fields(%client), name = "login_token")]
pub(crate) async fn login_token_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_login_token::v1::Request>,
) -> Result<get_login_token::v1::Response> {
if !services.server.config.login_via_existing_session {
return Err!(Request(Forbidden("Login via an existing session is not enabled")));
}
let sender_user = body.sender_user();
let sender_device = body.sender_device();
// This route SHOULD have UIA
// TODO: How do we make only UIA sessions that have not been used before valid?
let mut uiaainfo = uiaa::UiaaInfo {
flows: vec![uiaa::AuthFlow { stages: vec![uiaa::AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
} else if let Some(json) = body.json_body.as_ref() {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo));
} else {
return Err!(Request(NotJson("No JSON body was sent when required.")));
}
let login_token = utils::random_string(TOKEN_LENGTH);
let expires_in = services.users.create_login_token(sender_user, &login_token);
Ok(get_login_token::v1::Response {
expires_in: Duration::from_millis(expires_in),
login_token,
})
}
/// # `POST /_matrix/client/v3/logout`
///
/// Log out the current device.
+6 -6
View File
@@ -1,5 +1,6 @@
use axum::extract::State;
use conduwuit::{err, pdu::PduBuilder, utils::BoolExt, Err, PduEvent, Result};
use futures::TryStreamExt;
use ruma::{
api::client::state::{get_state_events, get_state_events_for_key, send_state_event},
events::{
@@ -82,11 +83,10 @@ pub(crate) async fn get_state_events_route(
room_state: services
.rooms
.state_accessor
.room_state_full(&body.room_id)
.await?
.values()
.map(PduEvent::to_state_event)
.collect(),
.room_state_full_pdus(&body.room_id)
.map_ok(PduEvent::into_state_event)
.try_collect()
.await?,
})
}
@@ -133,7 +133,7 @@ pub(crate) async fn get_state_events_for_key_route(
Ok(get_state_events_for_key::v3::Response {
content: event_format.or(|| event.get_content_as_value()),
event: event_format.then(|| event.to_state_event_value()),
event: event_format.then(|| event.into_state_event_value()),
})
}
+55 -14
View File
@@ -1,16 +1,31 @@
mod v3;
mod v4;
mod v5;
use conduwuit::{
utils::stream::{BroadbandExt, ReadyExt},
utils::{
stream::{BroadbandExt, ReadyExt, TryIgnore},
IterStream,
},
PduCount,
};
use futures::StreamExt;
use ruma::{RoomId, UserId};
use futures::{pin_mut, StreamExt};
use ruma::{
directory::RoomTypeFilter,
events::TimelineEventType::{
self, Beacon, CallInvite, PollStart, RoomEncrypted, RoomMessage, Sticker,
},
RoomId, UserId,
};
pub(crate) use self::{v3::sync_events_route, v4::sync_events_v4_route};
pub(crate) use self::{
v3::sync_events_route, v4::sync_events_v4_route, v5::sync_events_v5_route,
};
use crate::{service::Services, Error, PduEvent, Result};
pub(crate) const DEFAULT_BUMP_TYPES: &[TimelineEventType; 6] =
&[CallInvite, PollStart, Beacon, RoomEncrypted, RoomMessage, Sticker];
async fn load_timeline(
services: &Services,
sender_user: &UserId,
@@ -29,23 +44,19 @@ async fn load_timeline(
return Ok((Vec::new(), false));
}
let mut non_timeline_pdus = services
let non_timeline_pdus = services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, None)
.await?
.ignore_err()
.ready_skip_while(|&(pducount, _)| pducount > next_batch.unwrap_or_else(PduCount::max))
.ready_take_while(|&(pducount, _)| pducount > roomsincecount);
// Take the last events for the timeline
let timeline_pdus: Vec<_> = non_timeline_pdus
.by_ref()
.take(limit)
.collect::<Vec<_>>()
.await
.into_iter()
.rev()
.collect();
pin_mut!(non_timeline_pdus);
let timeline_pdus: Vec<_> = non_timeline_pdus.by_ref().take(limit).collect().await;
let timeline_pdus: Vec<_> = timeline_pdus.into_iter().rev().collect();
// They /sync response doesn't always return all messages, so we say the output
// is limited unless there are events in non_timeline_pdus
@@ -73,3 +84,33 @@ async fn share_encrypted_room(
})
.await
}
pub(crate) async fn filter_rooms<'a>(
services: &Services,
rooms: &[&'a RoomId],
filter: &[RoomTypeFilter],
negate: bool,
) -> Vec<&'a RoomId> {
rooms
.iter()
.stream()
.filter_map(|r| async move {
let room_type = services.rooms.state_accessor.get_room_type(r).await;
if room_type.as_ref().is_err_and(|e| !e.is_not_found()) {
return None;
}
let room_type_filter = RoomTypeFilter::from(room_type.ok());
let include = if negate {
!filter.contains(&room_type_filter)
} else {
filter.is_empty() || filter.contains(&room_type_filter)
};
include.then_some(r)
})
.collect()
.await
}
+503 -536
View File
File diff suppressed because it is too large Load Diff
+57 -65
View File
@@ -23,24 +23,23 @@ use ruma::{
DeviceLists, UnreadNotificationsCount,
},
},
directory::RoomTypeFilter,
events::{
room::member::{MembershipState, RoomMemberEventContent},
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType,
TimelineEventType::{self, *},
TimelineEventType::*,
},
serde::Raw,
uint, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, UInt,
uint, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UInt,
};
use service::{rooms::read_receipt::pack_receipts, Services};
use service::rooms::read_receipt::pack_receipts;
use super::{load_timeline, share_encrypted_room};
use crate::{client::ignored_filter, Ruma};
use crate::{
client::{filter_rooms, ignored_filter, sync::v5::TodoRooms, DEFAULT_BUMP_TYPES},
Ruma,
};
const SINGLE_CONNECTION_SYNC: &str = "single_connection_sync";
const DEFAULT_BUMP_TYPES: &[TimelineEventType; 6] =
&[CallInvite, PollStart, Beacon, RoomEncrypted, RoomMessage, Sticker];
pub(crate) const SINGLE_CONNECTION_SYNC: &str = "single_connection_sync";
/// POST `/_matrix/client/unstable/org.matrix.msc3575/sync`
///
@@ -113,12 +112,27 @@ pub(crate) async fn sync_events_v4_route(
.collect()
.await;
let all_rooms = all_joined_rooms
let all_knocked_rooms: Vec<_> = services
.rooms
.state_cache
.rooms_knocked(sender_user)
.map(|r| r.0)
.collect()
.await;
let all_invited_rooms: Vec<&RoomId> = all_invited_rooms.iter().map(AsRef::as_ref).collect();
let all_knocked_rooms: Vec<&RoomId> = all_knocked_rooms.iter().map(AsRef::as_ref).collect();
let all_rooms: Vec<&RoomId> = all_joined_rooms
.iter()
.chain(all_invited_rooms.iter())
.map(Clone::clone)
.map(AsRef::as_ref)
.chain(all_invited_rooms.iter().map(AsRef::as_ref))
.chain(all_knocked_rooms.iter().map(AsRef::as_ref))
.collect();
let all_joined_rooms = all_joined_rooms.iter().map(AsRef::as_ref).collect();
let all_invited_rooms = all_invited_rooms.iter().map(AsRef::as_ref).collect();
if body.extensions.to_device.enabled.unwrap_or(false) {
services
.users
@@ -139,7 +153,7 @@ pub(crate) async fn sync_events_v4_route(
if body.extensions.account_data.enabled.unwrap_or(false) {
account_data.global = services
.account_data
.changes_since(None, sender_user, globalsince)
.changes_since(None, sender_user, globalsince, Some(next_batch))
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Global))
.collect()
.await;
@@ -150,7 +164,7 @@ pub(crate) async fn sync_events_v4_route(
room.clone(),
services
.account_data
.changes_since(Some(&room), sender_user, globalsince)
.changes_since(Some(&room), sender_user, globalsince, Some(next_batch))
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
.collect()
.await,
@@ -171,6 +185,7 @@ pub(crate) async fn sync_events_v4_route(
);
for room_id in &all_joined_rooms {
let room_id: &&RoomId = room_id;
let Ok(current_shortstatehash) =
services.rooms.state.get_room_shortstatehash(room_id).await
else {
@@ -226,13 +241,15 @@ pub(crate) async fn sync_events_v4_route(
.rooms
.state_accessor
.state_full_ids(current_shortstatehash)
.await?;
.collect()
.await;
let since_state_ids = services
let since_state_ids: HashMap<_, _> = services
.rooms
.state_accessor
.state_full_ids(since_shortstatehash)
.await?;
.collect()
.await;
for (key, id) in current_state_ids {
if since_state_ids.get(&key) != Some(&id) {
@@ -323,7 +340,7 @@ pub(crate) async fn sync_events_v4_route(
}
let mut lists = BTreeMap::new();
let mut todo_rooms = BTreeMap::new(); // and required state
let mut todo_rooms: TodoRooms = BTreeMap::new(); // and required state
for (list_id, list) in &body.lists {
let active_rooms = match list.filters.clone().and_then(|f| f.is_invite) {
@@ -344,7 +361,7 @@ pub(crate) async fn sync_events_v4_route(
| None => active_rooms,
};
let mut new_known_rooms = BTreeSet::new();
let mut new_known_rooms: BTreeSet<OwnedRoomId> = BTreeSet::new();
let ranges = list.ranges.clone();
lists.insert(list_id.clone(), sync_events::v4::SyncList {
@@ -366,9 +383,9 @@ pub(crate) async fn sync_events_v4_route(
Vec::new()
};
new_known_rooms.extend(room_ids.iter().cloned());
new_known_rooms.extend(room_ids.clone().into_iter().map(ToOwned::to_owned));
for room_id in &room_ids {
let todo_room = todo_rooms.entry(room_id.clone()).or_insert((
let todo_room = todo_rooms.entry((*room_id).to_owned()).or_insert((
BTreeSet::new(),
0_usize,
u64::MAX,
@@ -390,7 +407,7 @@ pub(crate) async fn sync_events_v4_route(
todo_room.2 = todo_room.2.min(
known_rooms
.get(list_id.as_str())
.and_then(|k| k.get(room_id))
.and_then(|k| k.get(*room_id))
.copied()
.unwrap_or(0),
);
@@ -399,7 +416,7 @@ pub(crate) async fn sync_events_v4_route(
op: SlidingOp::Sync,
range: Some(r),
index: None,
room_ids,
room_ids: room_ids.into_iter().map(ToOwned::to_owned).collect(),
room_id: None,
}
})
@@ -409,8 +426,8 @@ pub(crate) async fn sync_events_v4_route(
if let Some(conn_id) = &body.conn_id {
services.sync.update_sync_known_rooms(
sender_user.clone(),
sender_device.clone(),
sender_user,
&sender_device,
conn_id.clone(),
list_id.clone(),
new_known_rooms,
@@ -455,8 +472,8 @@ pub(crate) async fn sync_events_v4_route(
if let Some(conn_id) = &body.conn_id {
services.sync.update_sync_known_rooms(
sender_user.clone(),
sender_device.clone(),
sender_user,
&sender_device,
conn_id.clone(),
"subscriptions".to_owned(),
known_subscription_rooms,
@@ -480,7 +497,8 @@ pub(crate) async fn sync_events_v4_route(
let mut timestamp: Option<_> = None;
let mut invite_state = None;
let (timeline_pdus, limited);
if all_invited_rooms.contains(room_id) {
let new_room_id: &RoomId = (*room_id).as_ref();
if all_invited_rooms.contains(&new_room_id) {
// TODO: figure out a timestamp we can use for remote invites
invite_state = services
.rooms
@@ -510,10 +528,10 @@ pub(crate) async fn sync_events_v4_route(
}
account_data.rooms.insert(
room_id.clone(),
room_id.to_owned(),
services
.account_data
.changes_since(Some(room_id), sender_user, *roomsince)
.changes_since(Some(room_id), sender_user, *roomsince, Some(next_batch))
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
.collect()
.await,
@@ -740,10 +758,9 @@ pub(crate) async fn sync_events_v4_route(
});
}
if rooms
.iter()
.all(|(_, r)| r.timeline.is_empty() && r.required_state.is_empty())
{
if rooms.iter().all(|(id, r)| {
r.timeline.is_empty() && r.required_state.is_empty() && !receipts.rooms.contains_key(id)
}) {
// Hang a few seconds so requests are not spammed
// Stop hanging if new info arrives
let default = Duration::from_secs(30);
@@ -762,7 +779,12 @@ pub(crate) async fn sync_events_v4_route(
Some(sync_events::v4::ToDevice {
events: services
.users
.get_to_device_events(sender_user, &sender_device)
.get_to_device_events(
sender_user,
&sender_device,
Some(globalsince),
Some(next_batch),
)
.collect()
.await,
next_batch: next_batch.to_string(),
@@ -789,33 +811,3 @@ pub(crate) async fn sync_events_v4_route(
delta_token: None,
})
}
async fn filter_rooms(
services: &Services,
rooms: &[OwnedRoomId],
filter: &[RoomTypeFilter],
negate: bool,
) -> Vec<OwnedRoomId> {
rooms
.iter()
.stream()
.filter_map(|r| async move {
let room_type = services.rooms.state_accessor.get_room_type(r).await;
if room_type.as_ref().is_err_and(|e| !e.is_not_found()) {
return None;
}
let room_type_filter = RoomTypeFilter::from(room_type.ok());
let include = if negate {
!filter.contains(&room_type_filter)
} else {
filter.is_empty() || filter.contains(&room_type_filter)
};
include.then_some(r.to_owned())
})
.collect()
.await
}
+888
View File
@@ -0,0 +1,888 @@
use std::{
cmp::{self, Ordering},
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
time::Duration,
};
use axum::extract::State;
use conduwuit::{
debug, error, extract_variant, trace,
utils::{
math::{ruma_from_usize, usize_from_ruma},
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
},
warn, Error, Result,
};
use futures::{FutureExt, StreamExt, TryFutureExt};
use ruma::{
api::client::{
error::ErrorKind,
sync::sync_events::{self, DeviceLists, UnreadNotificationsCount},
},
events::{
room::member::{MembershipState, RoomMemberEventContent},
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, StateEventType, TimelineEventType,
},
serde::Raw,
state_res::TypeStateKey,
uint, DeviceId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId,
};
use service::{rooms::read_receipt::pack_receipts, PduCount};
use super::{filter_rooms, share_encrypted_room};
use crate::{
client::{ignored_filter, sync::load_timeline, DEFAULT_BUMP_TYPES},
Ruma,
};
type SyncInfo<'a> = (&'a UserId, &'a DeviceId, u64, &'a sync_events::v5::Request);
/// `POST /_matrix/client/unstable/org.matrix.simplified_msc3575/sync`
/// ([MSC4186])
///
/// A simplified version of sliding sync ([MSC3575]).
///
/// Get all new events in a sliding window of rooms since the last sync or a
/// given point in time.
///
/// [MSC3575]: https://github.com/matrix-org/matrix-spec-proposals/pull/3575
/// [MSC4186]: https://github.com/matrix-org/matrix-spec-proposals/pull/4186
pub(crate) async fn sync_events_v5_route(
State(services): State<crate::State>,
body: Ruma<sync_events::v5::Request>,
) -> Result<sync_events::v5::Response> {
debug_assert!(DEFAULT_BUMP_TYPES.is_sorted(), "DEFAULT_BUMP_TYPES is not sorted");
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let mut body = body.body;
// Setup watchers, so if there's no response, we can wait for them
let watcher = services.sync.watch(sender_user, sender_device);
let next_batch = services.globals.next_count()?;
let conn_id = body.conn_id.clone();
let globalsince = body
.pos
.as_ref()
.and_then(|string| string.parse().ok())
.unwrap_or(0);
if globalsince != 0
&& !services.sync.snake_connection_cached(
sender_user.clone(),
sender_device.clone(),
conn_id.clone(),
) {
debug!("Restarting sync stream because it was gone from the database");
return Err(Error::Request(
ErrorKind::UnknownPos,
"Connection data lost since last time".into(),
http::StatusCode::BAD_REQUEST,
));
}
// Client / User requested an initial sync
if globalsince == 0 {
services.sync.forget_snake_sync_connection(
sender_user.clone(),
sender_device.clone(),
conn_id.clone(),
);
}
// Get sticky parameters from cache
let known_rooms = services.sync.update_snake_sync_request_with_cache(
sender_user.clone(),
sender_device.clone(),
&mut body,
);
let all_joined_rooms: Vec<_> = services
.rooms
.state_cache
.rooms_joined(sender_user)
.map(ToOwned::to_owned)
.collect()
.await;
let all_invited_rooms: Vec<_> = services
.rooms
.state_cache
.rooms_invited(sender_user)
.map(|r| r.0)
.collect()
.await;
let all_knocked_rooms: Vec<_> = services
.rooms
.state_cache
.rooms_knocked(sender_user)
.map(|r| r.0)
.collect()
.await;
let all_rooms: Vec<&RoomId> = all_joined_rooms
.iter()
.map(AsRef::as_ref)
.chain(all_invited_rooms.iter().map(AsRef::as_ref))
.chain(all_knocked_rooms.iter().map(AsRef::as_ref))
.collect();
let all_joined_rooms = all_joined_rooms.iter().map(AsRef::as_ref).collect();
let all_invited_rooms = all_invited_rooms.iter().map(AsRef::as_ref).collect();
let pos = next_batch.clone().to_string();
let mut todo_rooms: TodoRooms = BTreeMap::new();
let sync_info: SyncInfo<'_> = (sender_user, sender_device, globalsince, &body);
let mut response = sync_events::v5::Response {
txn_id: body.txn_id.clone(),
pos,
lists: BTreeMap::new(),
rooms: BTreeMap::new(),
extensions: sync_events::v5::response::Extensions {
account_data: collect_account_data(services, sync_info).await,
e2ee: collect_e2ee(services, sync_info, &all_joined_rooms).await?,
to_device: collect_to_device(services, sync_info, next_batch).await,
receipts: collect_receipts(services).await,
typing: sync_events::v5::response::Typing::default(),
},
};
handle_lists(
services,
sync_info,
&all_invited_rooms,
&all_joined_rooms,
&all_rooms,
&mut todo_rooms,
&known_rooms,
&mut response,
)
.await;
fetch_subscriptions(services, sync_info, &known_rooms, &mut todo_rooms).await;
response.rooms = process_rooms(
services,
sender_user,
next_batch,
&all_invited_rooms,
&todo_rooms,
&mut response,
&body,
)
.await?;
if response.rooms.iter().all(|(id, r)| {
r.timeline.is_empty()
&& r.required_state.is_empty()
&& !response.extensions.receipts.rooms.contains_key(id)
}) && response
.extensions
.to_device
.clone()
.is_none_or(|to| to.events.is_empty())
{
// Hang a few seconds so requests are not spammed
// Stop hanging if new info arrives
let default = Duration::from_secs(30);
let duration = cmp::min(body.timeout.unwrap_or(default), default);
_ = tokio::time::timeout(duration, watcher).await;
}
trace!(
rooms=?response.rooms.len(),
account_data=?response.extensions.account_data.rooms.len(),
receipts=?response.extensions.receipts.rooms.len(),
"responding to request with"
);
Ok(response)
}
type KnownRooms = BTreeMap<String, BTreeMap<OwnedRoomId, u64>>;
pub(crate) type TodoRooms = BTreeMap<OwnedRoomId, (BTreeSet<TypeStateKey>, usize, u64)>;
async fn fetch_subscriptions(
services: crate::State,
(sender_user, sender_device, globalsince, body): SyncInfo<'_>,
known_rooms: &KnownRooms,
todo_rooms: &mut TodoRooms,
) {
let mut known_subscription_rooms = BTreeSet::new();
for (room_id, room) in &body.room_subscriptions {
if !services.rooms.metadata.exists(room_id).await {
continue;
}
let todo_room =
todo_rooms
.entry(room_id.clone())
.or_insert((BTreeSet::new(), 0_usize, u64::MAX));
let limit: UInt = room.timeline_limit;
todo_room.0.extend(room.required_state.iter().cloned());
todo_room.1 = todo_room.1.max(usize_from_ruma(limit));
// 0 means unknown because it got out of date
todo_room.2 = todo_room.2.min(
known_rooms
.get("subscriptions")
.and_then(|k| k.get(room_id))
.copied()
.unwrap_or(0),
);
known_subscription_rooms.insert(room_id.clone());
}
// where this went (protomsc says it was removed)
//for r in body.unsubscribe_rooms {
// known_subscription_rooms.remove(&r);
// body.room_subscriptions.remove(&r);
//}
if let Some(conn_id) = &body.conn_id {
services.sync.update_snake_sync_known_rooms(
sender_user,
sender_device,
conn_id.clone(),
"subscriptions".to_owned(),
known_subscription_rooms,
globalsince,
);
}
}
#[allow(clippy::too_many_arguments)]
async fn handle_lists<'a>(
services: crate::State,
(sender_user, sender_device, globalsince, body): SyncInfo<'_>,
all_invited_rooms: &Vec<&'a RoomId>,
all_joined_rooms: &Vec<&'a RoomId>,
all_rooms: &Vec<&'a RoomId>,
todo_rooms: &'a mut TodoRooms,
known_rooms: &'a KnownRooms,
response: &'_ mut sync_events::v5::Response,
) -> KnownRooms {
for (list_id, list) in &body.lists {
let active_rooms = match list.filters.clone().and_then(|f| f.is_invite) {
| Some(true) => all_invited_rooms,
| Some(false) => all_joined_rooms,
| None => all_rooms,
};
let active_rooms = match list.filters.clone().map(|f| f.not_room_types) {
| Some(filter) if filter.is_empty() => active_rooms,
| Some(value) => &filter_rooms(&services, active_rooms, &value, true).await,
| None => active_rooms,
};
let mut new_known_rooms: BTreeSet<OwnedRoomId> = BTreeSet::new();
let ranges = list.ranges.clone();
for mut range in ranges {
range.0 = uint!(0);
range.1 = range
.1
.clamp(range.0, UInt::try_from(active_rooms.len()).unwrap_or(UInt::MAX));
let room_ids =
active_rooms[usize_from_ruma(range.0)..usize_from_ruma(range.1)].to_vec();
let new_rooms: BTreeSet<OwnedRoomId> =
room_ids.clone().into_iter().map(From::from).collect();
new_known_rooms.extend(new_rooms);
//new_known_rooms.extend(room_ids..cloned());
for room_id in room_ids {
let todo_room = todo_rooms.entry(room_id.to_owned()).or_insert((
BTreeSet::new(),
0_usize,
u64::MAX,
));
let limit: usize = usize_from_ruma(list.room_details.timeline_limit).min(100);
todo_room
.0
.extend(list.room_details.required_state.iter().cloned());
todo_room.1 = todo_room.1.max(limit);
// 0 means unknown because it got out of date
todo_room.2 = todo_room.2.min(
known_rooms
.get(list_id.as_str())
.and_then(|k| k.get(room_id))
.copied()
.unwrap_or(0),
);
}
}
response
.lists
.insert(list_id.clone(), sync_events::v5::response::List {
count: ruma_from_usize(active_rooms.len()),
});
if let Some(conn_id) = &body.conn_id {
services.sync.update_snake_sync_known_rooms(
sender_user,
sender_device,
conn_id.clone(),
list_id.clone(),
new_known_rooms,
globalsince,
);
}
}
BTreeMap::default()
}
async fn process_rooms(
services: crate::State,
sender_user: &UserId,
next_batch: u64,
all_invited_rooms: &[&RoomId],
todo_rooms: &TodoRooms,
response: &mut sync_events::v5::Response,
body: &sync_events::v5::Request,
) -> Result<BTreeMap<OwnedRoomId, sync_events::v5::response::Room>> {
let mut rooms = BTreeMap::new();
for (room_id, (required_state_request, timeline_limit, roomsince)) in todo_rooms {
let roomsincecount = PduCount::Normal(*roomsince);
let mut timestamp: Option<_> = None;
let mut invite_state = None;
let (timeline_pdus, limited);
let new_room_id: &RoomId = (*room_id).as_ref();
if all_invited_rooms.contains(&new_room_id) {
// TODO: figure out a timestamp we can use for remote invites
invite_state = services
.rooms
.state_cache
.invite_state(sender_user, room_id)
.await
.ok();
(timeline_pdus, limited) = (Vec::new(), true);
} else {
(timeline_pdus, limited) = match load_timeline(
&services,
sender_user,
room_id,
roomsincecount,
Some(PduCount::from(next_batch)),
*timeline_limit,
)
.await
{
| Ok(value) => value,
| Err(err) => {
warn!("Encountered missing timeline in {}, error {}", room_id, err);
continue;
},
};
}
if body.extensions.account_data.enabled == Some(true) {
response.extensions.account_data.rooms.insert(
room_id.to_owned(),
services
.account_data
.changes_since(Some(room_id), sender_user, *roomsince, Some(next_batch))
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
.collect()
.await,
);
}
let last_privateread_update = services
.rooms
.read_receipt
.last_privateread_update(sender_user, room_id)
.await > *roomsince;
let private_read_event = if last_privateread_update {
services
.rooms
.read_receipt
.private_read_get(room_id, sender_user)
.await
.ok()
} else {
None
};
let mut receipts: Vec<Raw<AnySyncEphemeralRoomEvent>> = services
.rooms
.read_receipt
.readreceipts_since(room_id, *roomsince)
.filter_map(|(read_user, _ts, v)| async move {
services
.users
.user_is_ignored(read_user, sender_user)
.await
.or_some(v)
})
.collect()
.await;
if let Some(private_read_event) = private_read_event {
receipts.push(private_read_event);
}
let receipt_size = receipts.len();
if receipt_size > 0 {
response
.extensions
.receipts
.rooms
.insert(room_id.clone(), pack_receipts(Box::new(receipts.into_iter())));
}
if roomsince != &0
&& timeline_pdus.is_empty()
&& response
.extensions
.account_data
.rooms
.get(room_id)
.is_none_or(Vec::is_empty)
&& receipt_size == 0
{
continue;
}
let prev_batch = timeline_pdus
.first()
.map_or(Ok::<_, Error>(None), |(pdu_count, _)| {
Ok(Some(match pdu_count {
| PduCount::Backfilled(_) => {
error!("timeline in backfill state?!");
"0".to_owned()
},
| PduCount::Normal(c) => c.to_string(),
}))
})?
.or_else(|| {
if roomsince != &0 {
Some(roomsince.to_string())
} else {
None
}
});
let room_events: Vec<_> = timeline_pdus
.iter()
.stream()
.filter_map(|item| ignored_filter(&services, item.clone(), sender_user))
.map(|(_, pdu)| pdu.to_sync_room_event())
.collect()
.await;
for (_, pdu) in timeline_pdus {
let ts = pdu.origin_server_ts;
if DEFAULT_BUMP_TYPES.binary_search(&pdu.kind).is_ok()
&& timestamp.is_none_or(|time| time <= ts)
{
timestamp = Some(ts);
}
}
let required_state = required_state_request
.iter()
.stream()
.filter_map(|state| async move {
services
.rooms
.state_accessor
.room_state_get(room_id, &state.0, &state.1)
.await
.map(|s| s.to_sync_state_event())
.ok()
})
.collect()
.await;
// Heroes
let heroes: Vec<_> = services
.rooms
.state_cache
.room_members(room_id)
.ready_filter(|member| *member != sender_user)
.filter_map(|user_id| {
services
.rooms
.state_accessor
.get_member(room_id, user_id)
.map_ok(|memberevent| sync_events::v5::response::Hero {
user_id: user_id.into(),
name: memberevent.displayname,
avatar: memberevent.avatar_url,
})
.ok()
})
.take(5)
.collect()
.await;
let name = match heroes.len().cmp(&(1_usize)) {
| Ordering::Greater => {
let firsts = heroes[1..]
.iter()
.map(|h| h.name.clone().unwrap_or_else(|| h.user_id.to_string()))
.collect::<Vec<_>>()
.join(", ");
let last = heroes[0]
.name
.clone()
.unwrap_or_else(|| heroes[0].user_id.to_string());
Some(format!("{firsts} and {last}"))
},
| Ordering::Equal => Some(
heroes[0]
.name
.clone()
.unwrap_or_else(|| heroes[0].user_id.to_string()),
),
| Ordering::Less => None,
};
let heroes_avatar = if heroes.len() == 1 {
heroes[0].avatar.clone()
} else {
None
};
rooms.insert(room_id.clone(), sync_events::v5::response::Room {
name: services
.rooms
.state_accessor
.get_name(room_id)
.await
.ok()
.or(name),
avatar: if let Some(heroes_avatar) = heroes_avatar {
ruma::JsOption::Some(heroes_avatar)
} else {
match services.rooms.state_accessor.get_avatar(room_id).await {
| ruma::JsOption::Some(avatar) => ruma::JsOption::from_option(avatar.url),
| ruma::JsOption::Null => ruma::JsOption::Null,
| ruma::JsOption::Undefined => ruma::JsOption::Undefined,
}
},
initial: Some(roomsince == &0),
is_dm: None,
invite_state,
unread_notifications: UnreadNotificationsCount {
highlight_count: Some(
services
.rooms
.user
.highlight_count(sender_user, room_id)
.await
.try_into()
.expect("notification count can't go that high"),
),
notification_count: Some(
services
.rooms
.user
.notification_count(sender_user, room_id)
.await
.try_into()
.expect("notification count can't go that high"),
),
},
timeline: room_events,
required_state,
prev_batch,
limited,
joined_count: Some(
services
.rooms
.state_cache
.room_joined_count(room_id)
.await
.unwrap_or(0)
.try_into()
.unwrap_or_else(|_| uint!(0)),
),
invited_count: Some(
services
.rooms
.state_cache
.room_invited_count(room_id)
.await
.unwrap_or(0)
.try_into()
.unwrap_or_else(|_| uint!(0)),
),
num_live: None, // Count events in timeline greater than global sync counter
bump_stamp: timestamp,
heroes: Some(heroes),
});
}
Ok(rooms)
}
async fn collect_account_data(
services: crate::State,
(sender_user, _, globalsince, body): (&UserId, &DeviceId, u64, &sync_events::v5::Request),
) -> sync_events::v5::response::AccountData {
let mut account_data = sync_events::v5::response::AccountData {
global: Vec::new(),
rooms: BTreeMap::new(),
};
if !body.extensions.account_data.enabled.unwrap_or(false) {
return sync_events::v5::response::AccountData::default();
}
account_data.global = services
.account_data
.changes_since(None, sender_user, globalsince, None)
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Global))
.collect()
.await;
if let Some(rooms) = &body.extensions.account_data.rooms {
for room in rooms {
account_data.rooms.insert(
room.clone(),
services
.account_data
.changes_since(Some(room), sender_user, globalsince, None)
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
.collect()
.await,
);
}
}
account_data
}
async fn collect_e2ee<'a>(
services: crate::State,
(sender_user, sender_device, globalsince, body): (
&UserId,
&DeviceId,
u64,
&sync_events::v5::Request,
),
all_joined_rooms: &'a Vec<&'a RoomId>,
) -> Result<sync_events::v5::response::E2EE> {
if !body.extensions.e2ee.enabled.unwrap_or(false) {
return Ok(sync_events::v5::response::E2EE::default());
}
let mut left_encrypted_users = HashSet::new(); // Users that have left any encrypted rooms the sender was in
let mut device_list_changes = HashSet::new();
let mut device_list_left = HashSet::new();
// Look for device list updates of this account
device_list_changes.extend(
services
.users
.keys_changed(sender_user, globalsince, None)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
for room_id in all_joined_rooms {
let Ok(current_shortstatehash) =
services.rooms.state.get_room_shortstatehash(room_id).await
else {
error!("Room {room_id} has no state");
continue;
};
let since_shortstatehash = services
.rooms
.user
.get_token_shortstatehash(room_id, globalsince)
.await
.ok();
let encrypted_room = services
.rooms
.state_accessor
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")
.await
.is_ok();
if let Some(since_shortstatehash) = since_shortstatehash {
// Skip if there are only timeline changes
if since_shortstatehash == current_shortstatehash {
continue;
}
let since_encryption = services
.rooms
.state_accessor
.state_get(since_shortstatehash, &StateEventType::RoomEncryption, "")
.await;
let since_sender_member: Option<RoomMemberEventContent> = services
.rooms
.state_accessor
.state_get_content(
since_shortstatehash,
&StateEventType::RoomMember,
sender_user.as_str(),
)
.ok()
.await;
let joined_since_last_sync = since_sender_member
.as_ref()
.is_none_or(|member| member.membership != MembershipState::Join);
let new_encrypted_room = encrypted_room && since_encryption.is_err();
if encrypted_room {
let current_state_ids: HashMap<_, OwnedEventId> = services
.rooms
.state_accessor
.state_full_ids(current_shortstatehash)
.collect()
.await;
let since_state_ids: HashMap<_, _> = services
.rooms
.state_accessor
.state_full_ids(since_shortstatehash)
.collect()
.await;
for (key, id) in current_state_ids {
if since_state_ids.get(&key) != Some(&id) {
let Ok(pdu) = services.rooms.timeline.get_pdu(&id).await else {
error!("Pdu in state not found: {id}");
continue;
};
if pdu.kind == TimelineEventType::RoomMember {
if let Some(state_key) = &pdu.state_key {
let user_id =
OwnedUserId::parse(state_key.clone()).map_err(|_| {
Error::bad_database("Invalid UserId in member PDU.")
})?;
if user_id == *sender_user {
continue;
}
let content: RoomMemberEventContent = pdu.get_content()?;
match content.membership {
| MembershipState::Join => {
// A new user joined an encrypted room
if !share_encrypted_room(
&services,
sender_user,
&user_id,
Some(room_id),
)
.await
{
device_list_changes.insert(user_id);
}
},
| MembershipState::Leave => {
// Write down users that have left encrypted rooms we
// are in
left_encrypted_users.insert(user_id);
},
| _ => {},
}
}
}
}
}
if joined_since_last_sync || new_encrypted_room {
// If the user is in a new encrypted room, give them all joined users
device_list_changes.extend(
services
.rooms
.state_cache
.room_members(room_id)
// Don't send key updates from the sender to the sender
.ready_filter(|user_id| sender_user != *user_id)
// Only send keys if the sender doesn't share an encrypted room with the target
// already
.filter_map(|user_id| {
share_encrypted_room(&services, sender_user, user_id, Some(room_id))
.map(|res| res.or_some(user_id.to_owned()))
})
.collect::<Vec<_>>()
.await,
);
}
}
}
// Look for device list updates in this room
device_list_changes.extend(
services
.users
.room_keys_changed(room_id, globalsince, None)
.map(|(user_id, _)| user_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
}
for user_id in left_encrypted_users {
let dont_share_encrypted_room =
!share_encrypted_room(&services, sender_user, &user_id, None).await;
// If the user doesn't share an encrypted room with the target anymore, we need
// to tell them
if dont_share_encrypted_room {
device_list_left.insert(user_id);
}
}
Ok(sync_events::v5::response::E2EE {
device_lists: DeviceLists {
changed: device_list_changes.into_iter().collect(),
left: device_list_left.into_iter().collect(),
},
device_one_time_keys_count: services
.users
.count_one_time_keys(sender_user, sender_device)
.await,
device_unused_fallback_key_types: None,
})
}
async fn collect_to_device(
services: crate::State,
(sender_user, sender_device, globalsince, body): SyncInfo<'_>,
next_batch: u64,
) -> Option<sync_events::v5::response::ToDevice> {
if !body.extensions.to_device.enabled.unwrap_or(false) {
return None;
}
services
.users
.remove_to_device_events(sender_user, sender_device, globalsince)
.await;
Some(sync_events::v5::response::ToDevice {
next_batch: next_batch.to_string(),
events: services
.users
.get_to_device_events(sender_user, sender_device, None, Some(next_batch))
.collect()
.await,
})
}
async fn collect_receipts(_services: crate::State) -> sync_events::v5::response::Receipts {
sync_events::v5::response::Receipts { rooms: BTreeMap::new() }
// TODO: get explicitly requested read receipts
}
+16 -12
View File
@@ -10,6 +10,7 @@ use ruma::{
},
to_device::DeviceIdOrAllDevices,
};
use service::sending::EduBuf;
use crate::Ruma;
@@ -42,18 +43,21 @@ pub(crate) async fn send_event_to_device_route(
messages.insert(target_user_id.clone(), map);
let count = services.globals.next_count()?;
services.sending.send_edu_server(
target_user_id.server_name(),
serde_json::to_vec(&federation::transactions::edu::Edu::DirectToDevice(
DirectDeviceContent {
sender: sender_user.clone(),
ev_type: body.event_type.clone(),
message_id: count.to_string().into(),
messages,
},
))
.expect("DirectToDevice EDU can be serialized"),
)?;
let mut buf = EduBuf::new();
serde_json::to_writer(
&mut buf,
&federation::transactions::edu::Edu::DirectToDevice(DirectDeviceContent {
sender: sender_user.clone(),
ev_type: body.event_type.clone(),
message_id: count.to_string().into(),
messages,
}),
)
.expect("DirectToDevice EDU can be serialized");
services
.sending
.send_edu_server(target_user_id.server_name(), buf)?;
continue;
}
+5 -7
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduwuit::Err;
use conduwuit::{utils::math::Tried, Err};
use ruma::api::client::typing::create_typing_event;
use crate::{utils, Result, Ruma};
@@ -31,17 +31,15 @@ pub(crate) async fn create_typing_event_route(
let duration = utils::clamp(
duration.as_millis().try_into().unwrap_or(u64::MAX),
services
.globals
.server
.config
.typing_client_timeout_min_s
.checked_mul(1000)
.unwrap(),
.try_mul(1000)?,
services
.globals
.server
.config
.typing_client_timeout_max_s
.checked_mul(1000)
.unwrap(),
.try_mul(1000)?,
);
services
.rooms
+1
View File
@@ -52,6 +52,7 @@ pub(crate) async fn get_supported_versions_route(
("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */
("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
]),
};
+1 -1
View File
@@ -38,7 +38,7 @@ pub(crate) async fn turn_server_route(
let user = body.sender_user.unwrap_or_else(|| {
UserId::parse_with_server_name(
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
&services.globals.config.server_name,
&services.server.name,
)
.unwrap()
});
+5
View File
@@ -34,6 +34,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::register_route)
.ruma_route(&client::get_login_types_route)
.ruma_route(&client::login_route)
.ruma_route(&client::login_token_route)
.ruma_route(&client::whoami_route)
.ruma_route(&client::logout_route)
.ruma_route(&client::logout_all_route)
@@ -99,6 +100,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::join_room_by_id_route)
.ruma_route(&client::join_room_by_id_or_alias_route)
.ruma_route(&client::joined_members_route)
.ruma_route(&client::knock_room_route)
.ruma_route(&client::leave_room_route)
.ruma_route(&client::forget_room_route)
.ruma_route(&client::joined_rooms_route)
@@ -144,6 +146,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
)
.ruma_route(&client::sync_events_route)
.ruma_route(&client::sync_events_v4_route)
.ruma_route(&client::sync_events_v5_route)
.ruma_route(&client::get_context_route)
.ruma_route(&client::get_message_events_route)
.ruma_route(&client::search_events_route)
@@ -204,8 +207,10 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&server::get_room_state_route)
.ruma_route(&server::get_room_state_ids_route)
.ruma_route(&server::create_leave_event_template_route)
.ruma_route(&server::create_knock_event_template_route)
.ruma_route(&server::create_leave_event_v1_route)
.ruma_route(&server::create_leave_event_v2_route)
.ruma_route(&server::create_knock_event_v1_route)
.ruma_route(&server::create_join_event_template_route)
.ruma_route(&server::create_join_event_v1_route)
.ruma_route(&server::create_join_event_v2_route)
+3 -3
View File
@@ -71,7 +71,7 @@ pub(super) async fn auth(
match metadata {
| &get_public_rooms::v3::Request::METADATA => {
if !services
.globals
.server
.config
.allow_public_room_directory_without_auth
{
@@ -94,7 +94,7 @@ pub(super) async fn auth(
| &get_display_name::v3::Request::METADATA
| &get_avatar_url::v3::Request::METADATA
| &get_timezone_key::unstable::Request::METADATA => {
if services.globals.config.require_auth_for_profile_requests {
if services.server.config.require_auth_for_profile_requests {
match token {
| Token::Appservice(_) | Token::User(_) => {
// we should have validated the token above
@@ -127,7 +127,7 @@ pub(super) async fn auth(
}),
| (AuthScheme::AccessToken, Token::None) => match metadata {
| &get_turn_server_info::v3::Request::METADATA => {
if services.globals.config.turn_allow_guests {
if services.server.config.turn_allow_guests {
Ok(Auth {
origin: None,
sender_user: None,
+1 -1
View File
@@ -32,7 +32,7 @@ pub(super) async fn from(
let query = serde_html_form::from_str(query)
.map_err(|e| err!(Request(Unknown("Failed to read query parameters: {e}"))))?;
let max_body_size = services.globals.config.max_request_size;
let max_body_size = services.server.config.max_request_size;
let body = axum::body::to_bytes(body, max_body_size)
.await
+17 -13
View File
@@ -2,10 +2,10 @@ use std::cmp;
use axum::extract::State;
use conduwuit::{
utils::{IterStream, ReadyExt},
utils::{stream::TryTools, IterStream, ReadyExt},
PduCount, Result,
};
use futures::{FutureExt, StreamExt};
use futures::{FutureExt, StreamExt, TryStreamExt};
use ruma::{api::federation::backfill::get_backfill, uint, MilliSecondsSinceUnixEpoch};
use super::AccessCheck;
@@ -57,26 +57,30 @@ pub(crate) async fn get_backfill_route(
.rooms
.timeline
.pdus_rev(None, &body.room_id, Some(from.saturating_add(1)))
.await?
.take(limit)
.filter_map(|(_, pdu)| async move {
services
.try_take(limit)
.try_filter_map(|(_, pdu)| async move {
Ok(services
.rooms
.state_accessor
.server_can_see_event(body.origin(), &pdu.room_id, &pdu.event_id)
.await
.then_some(pdu)
.then_some(pdu))
})
.filter_map(|pdu| async move {
services
.try_filter_map(|pdu| async move {
Ok(services
.rooms
.timeline
.get_pdu_json(&pdu.event_id)
.await
.ok()
.ok())
})
.then(|pdu| services.sending.convert_to_outgoing_federation_event(pdu))
.collect()
.await,
.and_then(|pdu| {
services
.sending
.convert_to_outgoing_federation_event(pdu)
.map(Ok)
})
.try_collect()
.await?,
})
}
+2 -2
View File
@@ -1,7 +1,7 @@
use std::{borrow::Borrow, iter::once};
use axum::extract::State;
use conduwuit::{Error, Result};
use conduwuit::{utils::stream::ReadyExt, Error, Result};
use futures::StreamExt;
use ruma::{
api::{client::error::ErrorKind, federation::authorization::get_event_authorization},
@@ -48,7 +48,7 @@ pub(crate) async fn get_event_authorization_route(
.rooms
.auth_chain
.event_ids_iter(room_id, once(body.event_id.borrow()))
.await?
.ready_filter_map(Result::ok)
.filter_map(|id| async move { services.rooms.timeline.get_pdu_json(&id).await.ok() })
.then(|pdu| services.sending.convert_to_outgoing_federation_event(pdu))
.collect()
+7 -11
View File
@@ -6,8 +6,9 @@ use ruma::{
api::{client::error::ErrorKind, federation::membership::create_invite},
events::room::member::{MembershipState, RoomMemberEventContent},
serde::JsonObject,
CanonicalJsonValue, OwnedEventId, OwnedUserId, UserId,
CanonicalJsonValue, OwnedUserId, UserId,
};
use service::pdu::gen_event_id;
use crate::Ruma;
@@ -36,7 +37,7 @@ pub(crate) async fn create_invite_route(
if let Some(server) = body.room_id.server_name() {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
@@ -46,7 +47,7 @@ pub(crate) async fn create_invite_route(
}
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(body.origin())
@@ -86,12 +87,7 @@ pub(crate) async fn create_invite_route(
.map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
// Generate event id
let event_id = OwnedEventId::parse(format!(
"${}",
ruma::signatures::reference_hash(&signed_event, &body.room_version)
.expect("ruma can calculate reference hashes")
))
.expect("ruma's reference hashes are valid event ids");
let event_id = gen_event_id(&signed_event, &body.room_version)?;
// Add event_id back
signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string()));
@@ -115,12 +111,12 @@ pub(crate) async fn create_invite_route(
let mut invite_state = body.invite_room_state.clone();
let mut event: JsonObject = serde_json::from_str(body.event.get())
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid invite event bytes."))?;
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
event.insert("event_id".to_owned(), "$placeholder".into());
let pdu: PduEvent = serde_json::from_value(event.into())
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid invite event."))?;
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
invite_state.push(pdu.to_stripped_state_event());
+2 -2
View File
@@ -42,7 +42,7 @@ pub(crate) async fn create_join_event_template_route(
.await?;
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(body.origin())
@@ -59,7 +59,7 @@ pub(crate) async fn create_join_event_template_route(
if let Some(server) = body.room_id.server_name() {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
+29 -13
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduwuit::Err;
use conduwuit::{debug_warn, Err};
use ruma::{
api::{client::error::ErrorKind, federation::knock::create_knock_event_template},
events::room::member::{MembershipState, RoomMemberEventContent},
@@ -15,7 +15,8 @@ use crate::{service::pdu::PduBuilder, Error, Result, Ruma};
///
/// Creates a knock template.
pub(crate) async fn create_knock_event_template_route(
State(services): State<crate::State>, body: Ruma<create_knock_event_template::v1::Request>,
State(services): State<crate::State>,
body: Ruma<create_knock_event_template::v1::Request>,
) -> Result<create_knock_event_template::v1::Response> {
if !services.rooms.metadata.exists(&body.room_id).await {
return Err!(Request(NotFound("Room is unknown to this server.")));
@@ -33,14 +34,14 @@ pub(crate) async fn create_knock_event_template_route(
.await?;
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(body.origin())
{
warn!(
"Server {} for remote user {} tried knocking room ID {} which has a server name that is globally \
forbidden. Rejecting.",
"Server {} for remote user {} tried knocking room ID {} which has a server name \
that is globally forbidden. Rejecting.",
body.origin(),
&body.user_id,
&body.room_id,
@@ -50,7 +51,7 @@ pub(crate) async fn create_knock_event_template_route(
if let Some(server) = body.room_id.server_name() {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
@@ -63,29 +64,44 @@ pub(crate) async fn create_knock_event_template_route(
if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) {
return Err(Error::BadRequest(
ErrorKind::IncompatibleRoomVersion {
room_version: room_version_id,
},
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Room version does not support knocking.",
));
}
if !body.ver.contains(&room_version_id) {
return Err(Error::BadRequest(
ErrorKind::IncompatibleRoomVersion {
room_version: room_version_id,
},
ErrorKind::IncompatibleRoomVersion { room_version: room_version_id },
"Your homeserver does not support the features required to knock on this room.",
));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
if let Ok(membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
{
if membership.membership == MembershipState::Ban {
debug_warn!(
"Remote user {} is banned from {} but attempted to knock",
&body.user_id,
&body.room_id
);
return Err!(Request(Forbidden("You cannot knock on a room you are banned from.")));
}
}
let (_pdu, mut pdu_json) = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent::new(MembershipState::Knock)),
PduBuilder::state(
body.user_id.to_string(),
&RoomMemberEventContent::new(MembershipState::Knock),
),
&body.user_id,
&body.room_id,
&state_lock,
+4 -2
View File
@@ -9,7 +9,7 @@ use serde_json::value::to_raw_value;
use super::make_join::maybe_strip_event_id;
use crate::{service::pdu::PduBuilder, Ruma};
/// # `PUT /_matrix/federation/v1/make_leave/{roomId}/{eventId}`
/// # `GET /_matrix/federation/v1/make_leave/{roomId}/{eventId}`
///
/// Creates a leave template.
pub(crate) async fn create_leave_event_template_route(
@@ -21,7 +21,9 @@ pub(crate) async fn create_leave_event_template_route(
}
if body.user_id.server_name() != body.origin() {
return Err!(Request(BadJson("Not allowed to leave on behalf of another server/user.")));
return Err!(Request(Forbidden(
"Not allowed to leave on behalf of another server/user."
)));
}
// ACL check origin
+12 -2
View File
@@ -14,7 +14,12 @@ use crate::Ruma;
/// # `GET /_matrix/federation/v1/media/download/{mediaId}`
///
/// Load media from our server.
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
#[tracing::instrument(
name = "media_get",
level = "debug",
skip_all,
fields(%client)
)]
pub(crate) async fn get_content_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -51,7 +56,12 @@ pub(crate) async fn get_content_route(
/// # `GET /_matrix/federation/v1/media/thumbnail/{mediaId}`
///
/// Load media thumbnail from our server.
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
#[tracing::instrument(
name = "media_thumbnail_get",
level = "debug",
skip_all,
fields(%client)
)]
pub(crate) async fn get_content_thumbnail_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+4
View File
@@ -6,6 +6,7 @@ pub(super) mod hierarchy;
pub(super) mod invite;
pub(super) mod key;
pub(super) mod make_join;
pub(super) mod make_knock;
pub(super) mod make_leave;
pub(super) mod media;
pub(super) mod openid;
@@ -13,6 +14,7 @@ pub(super) mod publicrooms;
pub(super) mod query;
pub(super) mod send;
pub(super) mod send_join;
pub(super) mod send_knock;
pub(super) mod send_leave;
pub(super) mod state;
pub(super) mod state_ids;
@@ -28,6 +30,7 @@ pub(super) use hierarchy::*;
pub(super) use invite::*;
pub(super) use key::*;
pub(super) use make_join::*;
pub(super) use make_knock::*;
pub(super) use make_leave::*;
pub(super) use media::*;
pub(super) use openid::*;
@@ -35,6 +38,7 @@ pub(super) use publicrooms::*;
pub(super) use query::*;
pub(super) use send::*;
pub(super) use send_join::*;
pub(super) use send_knock::*;
pub(super) use send_leave::*;
pub(super) use state::*;
pub(super) use state_ids::*;
+3 -3
View File
@@ -13,14 +13,14 @@ use crate::{Error, Result, Ruma};
/// # `POST /_matrix/federation/v1/publicRooms`
///
/// Lists the public rooms on this server.
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
#[tracing::instrument(name = "publicrooms", level = "debug", skip_all, fields(%client))]
pub(crate) async fn get_public_rooms_filtered_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms_filtered::v1::Request>,
) -> Result<get_public_rooms_filtered::v1::Response> {
if !services
.globals
.server
.config
.allow_public_room_directory_over_federation
{
@@ -51,7 +51,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
/// # `GET /_matrix/federation/v1/publicRooms`
///
/// Lists the public rooms on this server.
#[tracing::instrument(skip_all, fields(%client), "publicrooms")]
#[tracing::instrument(name = "publicrooms", level = "debug", skip_all, fields(%client))]
pub(crate) async fn get_public_rooms_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
+1 -1
View File
@@ -63,7 +63,7 @@ pub(crate) async fn get_profile_information_route(
body: Ruma<get_profile_information::v1::Request>,
) -> Result<get_profile_information::v1::Response> {
if !services
.globals
.server
.config
.allow_inbound_profile_lookup_federation_requests
{
+370 -259
View File
@@ -3,17 +3,27 @@ use std::{collections::BTreeMap, net::IpAddr, time::Instant};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
debug, debug_warn, err, error, result::LogErr, trace, utils::ReadyExt, warn, Err, Error,
Result,
debug,
debug::INFO_SPAN_LEVEL,
debug_warn, err, error,
result::LogErr,
trace,
utils::{
stream::{automatic_width, BroadbandExt, TryBroadbandExt},
IterStream, ReadyExt,
},
warn, Err, Error, Result,
};
use futures::{FutureExt, StreamExt};
use futures::{FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt};
use itertools::Itertools;
use ruma::{
api::{
client::error::ErrorKind,
federation::transactions::{
edu::{
DeviceListUpdateContent, DirectDeviceContent, Edu, PresenceContent,
ReceiptContent, SigningKeyUpdateContent, TypingContent,
PresenceUpdate, ReceiptContent, ReceiptData, ReceiptMap, SigningKeyUpdateContent,
TypingContent,
},
send_transaction_message,
},
@@ -21,25 +31,34 @@ use ruma::{
events::receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType},
serde::Raw,
to_device::DeviceIdOrAllDevices,
OwnedEventId, ServerName,
CanonicalJsonObject, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
};
use serde_json::value::RawValue as RawJsonValue;
use service::{
sending::{EDU_LIMIT, PDU_LIMIT},
Services,
};
use utils::millis_since_unix_epoch;
use crate::{
utils::{self},
Ruma,
};
type ResolvedMap = BTreeMap<OwnedEventId, Result<()>>;
type ResolvedMap = BTreeMap<OwnedEventId, Result>;
type Pdu = (OwnedRoomId, OwnedEventId, CanonicalJsonObject);
/// # `PUT /_matrix/federation/v1/send/{txnId}`
///
/// Push EDUs and PDUs to this server.
#[tracing::instrument(skip_all, fields(%client, origin = body.origin().as_str()), name = "send")]
#[tracing::instrument(
name = "txn",
level = INFO_SPAN_LEVEL,
skip_all,
fields(
%client,
origin = body.origin().as_str()
),
)]
pub(crate) async fn send_transaction_message_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
@@ -65,90 +84,41 @@ pub(crate) async fn send_transaction_message_route(
let txn_start_time = Instant::now();
trace!(
pdus = ?body.pdus.len(),
edus = ?body.edus.len(),
pdus = body.pdus.len(),
edus = body.edus.len(),
elapsed = ?txn_start_time.elapsed(),
id = ?body.transaction_id,
origin =?body.origin(),
"Starting txn",
);
let resolved_map =
handle_pdus(&services, &client, &body.pdus, body.origin(), &txn_start_time)
.boxed()
.await?;
let pdus = body
.pdus
.iter()
.stream()
.broad_then(|pdu| services.rooms.event_handler.parse_incoming_pdu(pdu))
.inspect_err(|e| debug_warn!("Could not parse PDU: {e}"))
.ready_filter_map(Result::ok);
handle_edus(&services, &client, &body.edus, body.origin())
.boxed()
.await;
let edus = body
.edus
.iter()
.map(|edu| edu.json().get())
.map(serde_json::from_str)
.filter_map(Result::ok)
.stream();
let results = handle(&services, &client, body.origin(), txn_start_time, pdus, edus).await?;
debug!(
pdus = ?body.pdus.len(),
edus = ?body.edus.len(),
pdus = body.pdus.len(),
edus = body.edus.len(),
elapsed = ?txn_start_time.elapsed(),
id = ?body.transaction_id,
origin =?body.origin(),
"Finished txn",
);
Ok(send_transaction_message::v1::Response {
pdus: resolved_map
.into_iter()
.map(|(e, r)| (e, r.map_err(error::sanitized_message)))
.collect(),
})
}
async fn handle_pdus(
services: &Services,
_client: &IpAddr,
pdus: &[Box<RawJsonValue>],
origin: &ServerName,
txn_start_time: &Instant,
) -> Result<ResolvedMap> {
let mut parsed_pdus = Vec::with_capacity(pdus.len());
for pdu in pdus {
parsed_pdus.push(match services.rooms.event_handler.parse_incoming_pdu(pdu).await {
| Ok(t) => t,
| Err(e) => {
debug_warn!("Could not parse PDU: {e}");
continue;
},
});
// We do not add the event_id field to the pdu here because of signature
// and hashes checks
}
let mut resolved_map = BTreeMap::new();
for (event_id, value, room_id) in parsed_pdus {
services.server.check_running()?;
let pdu_start_time = Instant::now();
let mutex_lock = services
.rooms
.event_handler
.mutex_federation
.lock(&room_id)
.await;
let result = services
.rooms
.event_handler
.handle_incoming_pdu(origin, &room_id, &event_id, value, true)
.await
.map(|_| ());
drop(mutex_lock);
debug!(
pdu_elapsed = ?pdu_start_time.elapsed(),
txn_elapsed = ?txn_start_time.elapsed(),
"Finished PDU {event_id}",
);
resolved_map.insert(event_id, result);
}
for (id, result) in &resolved_map {
for (id, result) in &results {
if let Err(e) = result {
if matches!(e, Error::BadRequest(ErrorKind::NotFound, _)) {
warn!("Incoming PDU failed {id}: {e:?}");
@@ -156,39 +126,117 @@ async fn handle_pdus(
}
}
Ok(resolved_map)
Ok(send_transaction_message::v1::Response {
pdus: results
.into_iter()
.map(|(e, r)| (e, r.map_err(error::sanitized_message)))
.collect(),
})
}
async fn handle_edus(
async fn handle(
services: &Services,
client: &IpAddr,
edus: &[Raw<Edu>],
origin: &ServerName,
) {
for edu in edus
.iter()
.filter_map(|edu| serde_json::from_str::<Edu>(edu.json().get()).ok())
{
match edu {
| Edu::Presence(presence) => {
handle_edu_presence(services, client, origin, presence).await;
},
| Edu::Receipt(receipt) =>
handle_edu_receipt(services, client, origin, receipt).await,
| Edu::Typing(typing) => handle_edu_typing(services, client, origin, typing).await,
| Edu::DeviceListUpdate(content) => {
handle_edu_device_list_update(services, client, origin, content).await;
},
| Edu::DirectToDevice(content) => {
handle_edu_direct_to_device(services, client, origin, content).await;
},
| Edu::SigningKeyUpdate(content) => {
handle_edu_signing_key_update(services, client, origin, content).await;
},
| Edu::_Custom(ref _custom) => {
debug_warn!(?edus, "received custom/unknown EDU");
},
}
started: Instant,
pdus: impl Stream<Item = Pdu> + Send,
edus: impl Stream<Item = Edu> + Send,
) -> Result<ResolvedMap> {
// group pdus by room
let pdus = pdus
.collect()
.map(|mut pdus: Vec<_>| {
pdus.sort_by(|(room_a, ..), (room_b, ..)| room_a.cmp(room_b));
pdus.into_iter()
.into_grouping_map_by(|(room_id, ..)| room_id.clone())
.collect()
})
.await;
// we can evaluate rooms concurrently
let results: ResolvedMap = pdus
.into_iter()
.try_stream()
.broad_and_then(|(room_id, pdus): (_, Vec<_>)| {
handle_room(services, client, origin, started, room_id, pdus.into_iter())
.map_ok(Vec::into_iter)
.map_ok(IterStream::try_stream)
})
.try_flatten()
.try_collect()
.boxed()
.await?;
// evaluate edus after pdus, at least for now.
edus.for_each_concurrent(automatic_width(), |edu| handle_edu(services, client, origin, edu))
.boxed()
.await;
Ok(results)
}
async fn handle_room(
services: &Services,
_client: &IpAddr,
origin: &ServerName,
txn_start_time: Instant,
room_id: OwnedRoomId,
pdus: impl Iterator<Item = Pdu> + Send,
) -> Result<Vec<(OwnedEventId, Result)>> {
let _room_lock = services
.rooms
.event_handler
.mutex_federation
.lock(&room_id)
.await;
let room_id = &room_id;
pdus.try_stream()
.and_then(|(_, event_id, value)| async move {
services.server.check_running()?;
let pdu_start_time = Instant::now();
let result = services
.rooms
.event_handler
.handle_incoming_pdu(origin, room_id, &event_id, value, true)
.await
.map(|_| ());
debug!(
pdu_elapsed = ?pdu_start_time.elapsed(),
txn_elapsed = ?txn_start_time.elapsed(),
"Finished PDU {event_id}",
);
Ok((event_id, result))
})
.try_collect()
.await
}
async fn handle_edu(services: &Services, client: &IpAddr, origin: &ServerName, edu: Edu) {
match edu {
| Edu::Presence(presence) if services.server.config.allow_incoming_presence =>
handle_edu_presence(services, client, origin, presence).await,
| Edu::Receipt(receipt) if services.server.config.allow_incoming_read_receipts =>
handle_edu_receipt(services, client, origin, receipt).await,
| Edu::Typing(typing) if services.server.config.allow_incoming_typing =>
handle_edu_typing(services, client, origin, typing).await,
| Edu::DeviceListUpdate(content) =>
handle_edu_device_list_update(services, client, origin, content).await,
| Edu::DirectToDevice(content) =>
handle_edu_direct_to_device(services, client, origin, content).await,
| Edu::SigningKeyUpdate(content) =>
handle_edu_signing_key_update(services, client, origin, content).await,
| Edu::_Custom(ref _custom) => debug_warn!(?edu, "received custom/unknown EDU"),
| _ => trace!(?edu, "skipped"),
}
}
@@ -198,32 +246,41 @@ async fn handle_edu_presence(
origin: &ServerName,
presence: PresenceContent,
) {
if !services.globals.allow_incoming_presence() {
presence
.push
.into_iter()
.stream()
.for_each_concurrent(automatic_width(), |update| {
handle_edu_presence_update(services, origin, update)
})
.await;
}
async fn handle_edu_presence_update(
services: &Services,
origin: &ServerName,
update: PresenceUpdate,
) {
if update.user_id.server_name() != origin {
debug_warn!(
%update.user_id, %origin,
"received presence EDU for user not belonging to origin"
);
return;
}
for update in presence.push {
if update.user_id.server_name() != origin {
debug_warn!(
%update.user_id, %origin,
"received presence EDU for user not belonging to origin"
);
continue;
}
services
.presence
.set_presence(
&update.user_id,
&update.presence,
Some(update.currently_active),
Some(update.last_active_ago),
update.status_msg.clone(),
)
.await
.log_err()
.ok();
}
services
.presence
.set_presence(
&update.user_id,
&update.presence,
Some(update.currently_active),
Some(update.last_active_ago),
update.status_msg.clone(),
)
.await
.log_err()
.ok();
}
async fn handle_edu_receipt(
@@ -232,66 +289,94 @@ async fn handle_edu_receipt(
origin: &ServerName,
receipt: ReceiptContent,
) {
if !services.globals.allow_incoming_read_receipts() {
receipt
.receipts
.into_iter()
.stream()
.for_each_concurrent(automatic_width(), |(room_id, room_updates)| {
handle_edu_receipt_room(services, origin, room_id, room_updates)
})
.await;
}
async fn handle_edu_receipt_room(
services: &Services,
origin: &ServerName,
room_id: OwnedRoomId,
room_updates: ReceiptMap,
) {
if services
.rooms
.event_handler
.acl_check(origin, &room_id)
.await
.is_err()
{
debug_warn!(
%origin, %room_id,
"received read receipt EDU from ACL'd server"
);
return;
}
for (room_id, room_updates) in receipt.receipts {
if services
.rooms
.event_handler
.acl_check(origin, &room_id)
.await
.is_err()
{
debug_warn!(
%origin, %room_id,
"received read receipt EDU from ACL'd server"
);
continue;
}
let room_id = &room_id;
room_updates
.read
.into_iter()
.stream()
.for_each_concurrent(automatic_width(), |(user_id, user_updates)| async move {
handle_edu_receipt_room_user(services, origin, room_id, &user_id, user_updates).await;
})
.await;
}
for (user_id, user_updates) in room_updates.read {
if user_id.server_name() != origin {
debug_warn!(
%user_id, %origin,
"received read receipt EDU for user not belonging to origin"
);
continue;
}
if services
.rooms
.state_cache
.room_members(&room_id)
.ready_any(|member| member.server_name() == user_id.server_name())
.await
{
for event_id in &user_updates.event_ids {
let user_receipts =
BTreeMap::from([(user_id.clone(), user_updates.data.clone())]);
let receipts = BTreeMap::from([(ReceiptType::Read, user_receipts)]);
let receipt_content = BTreeMap::from([(event_id.to_owned(), receipts)]);
let event = ReceiptEvent {
content: ReceiptEventContent(receipt_content),
room_id: room_id.clone(),
};
services
.rooms
.read_receipt
.readreceipt_update(&user_id, &room_id, &event)
.await;
}
} else {
debug_warn!(
%user_id, %room_id, %origin,
"received read receipt EDU from server who does not have a member in the room",
);
continue;
}
}
async fn handle_edu_receipt_room_user(
services: &Services,
origin: &ServerName,
room_id: &RoomId,
user_id: &UserId,
user_updates: ReceiptData,
) {
if user_id.server_name() != origin {
debug_warn!(
%user_id, %origin,
"received read receipt EDU for user not belonging to origin"
);
return;
}
if !services
.rooms
.state_cache
.server_in_room(origin, room_id)
.await
{
debug_warn!(
%user_id, %room_id, %origin,
"received read receipt EDU from server who does not have a member in the room",
);
return;
}
let data = &user_updates.data;
user_updates
.event_ids
.into_iter()
.stream()
.for_each_concurrent(automatic_width(), |event_id| async move {
let user_data = [(user_id.to_owned(), data.clone())];
let receipts = [(ReceiptType::Read, BTreeMap::from(user_data))];
let content = [(event_id.clone(), BTreeMap::from(receipts))];
services
.rooms
.read_receipt
.readreceipt_update(user_id, room_id, &ReceiptEvent {
content: ReceiptEventContent(content.into()),
room_id: room_id.to_owned(),
})
.await;
})
.await;
}
async fn handle_edu_typing(
@@ -300,10 +385,6 @@ async fn handle_edu_typing(
origin: &ServerName,
typing: TypingContent,
) {
if !services.globals.config.allow_incoming_typing {
return;
}
if typing.user_id.server_name() != origin {
debug_warn!(
%typing.user_id, %origin,
@@ -326,41 +407,38 @@ async fn handle_edu_typing(
return;
}
if services
if !services
.rooms
.state_cache
.is_joined(&typing.user_id, &typing.room_id)
.await
{
if typing.typing {
let timeout = utils::millis_since_unix_epoch().saturating_add(
services
.globals
.config
.typing_federation_timeout_s
.saturating_mul(1000),
);
services
.rooms
.typing
.typing_add(&typing.user_id, &typing.room_id, timeout)
.await
.log_err()
.ok();
} else {
services
.rooms
.typing
.typing_remove(&typing.user_id, &typing.room_id)
.await
.log_err()
.ok();
}
} else {
debug_warn!(
%typing.user_id, %typing.room_id, %origin,
"received typing EDU for user not in room"
);
return;
}
if typing.typing {
let secs = services.server.config.typing_federation_timeout_s;
let timeout = millis_since_unix_epoch().saturating_add(secs.saturating_mul(1000));
services
.rooms
.typing
.typing_add(&typing.user_id, &typing.room_id, timeout)
.await
.log_err()
.ok();
} else {
services
.rooms
.typing
.typing_remove(&typing.user_id, &typing.room_id)
.await
.log_err()
.ok();
}
}
@@ -389,7 +467,12 @@ async fn handle_edu_direct_to_device(
origin: &ServerName,
content: DirectDeviceContent,
) {
let DirectDeviceContent { sender, ev_type, message_id, messages } = content;
let DirectDeviceContent {
ref sender,
ref ev_type,
ref message_id,
messages,
} = content;
if sender.server_name() != origin {
debug_warn!(
@@ -402,60 +485,88 @@ async fn handle_edu_direct_to_device(
// Check if this is a new transaction id
if services
.transaction_ids
.existing_txnid(&sender, None, &message_id)
.existing_txnid(sender, None, message_id)
.await
.is_ok()
{
return;
}
for (target_user_id, map) in &messages {
for (target_device_id_maybe, event) in map {
let Ok(event) = event.deserialize_as().map_err(|e| {
err!(Request(InvalidParam(error!("To-Device event is invalid: {e}"))))
}) else {
continue;
};
let ev_type = ev_type.to_string();
match target_device_id_maybe {
| DeviceIdOrAllDevices::DeviceId(target_device_id) => {
services
.users
.add_to_device_event(
&sender,
target_user_id,
target_device_id,
&ev_type,
event,
)
.await;
},
| DeviceIdOrAllDevices::AllDevices => {
let (sender, ev_type, event) = (&sender, &ev_type, &event);
services
.users
.all_device_ids(target_user_id)
.for_each(|target_device_id| {
services.users.add_to_device_event(
sender,
target_user_id,
target_device_id,
ev_type,
event.clone(),
)
})
.await;
},
}
}
}
// process messages concurrently for different users
let ev_type = ev_type.to_string();
messages
.into_iter()
.stream()
.for_each_concurrent(automatic_width(), |(target_user_id, map)| {
handle_edu_direct_to_device_user(services, target_user_id, sender, &ev_type, map)
})
.await;
// Save transaction id with empty data
services
.transaction_ids
.add_txnid(&sender, None, &message_id, &[]);
.add_txnid(sender, None, message_id, &[]);
}
async fn handle_edu_direct_to_device_user<Event: Send + Sync>(
services: &Services,
target_user_id: OwnedUserId,
sender: &UserId,
ev_type: &str,
map: BTreeMap<DeviceIdOrAllDevices, Raw<Event>>,
) {
for (target_device_id_maybe, event) in map {
let Ok(event) = event
.deserialize_as()
.map_err(|e| err!(Request(InvalidParam(error!("To-Device event is invalid: {e}")))))
else {
continue;
};
handle_edu_direct_to_device_event(
services,
&target_user_id,
sender,
target_device_id_maybe,
ev_type,
event,
)
.await;
}
}
async fn handle_edu_direct_to_device_event(
services: &Services,
target_user_id: &UserId,
sender: &UserId,
target_device_id_maybe: DeviceIdOrAllDevices,
ev_type: &str,
event: serde_json::Value,
) {
match target_device_id_maybe {
| DeviceIdOrAllDevices::DeviceId(ref target_device_id) => {
services
.users
.add_to_device_event(sender, target_user_id, target_device_id, ev_type, event)
.await;
},
| DeviceIdOrAllDevices::AllDevices => {
services
.users
.all_device_ids(target_user_id)
.for_each(|target_device_id| {
services.users.add_to_device_event(
sender,
target_user_id,
target_device_id,
ev_type,
event.clone(),
)
})
.await;
},
}
}
async fn handle_edu_signing_key_update(
+18 -19
View File
@@ -1,10 +1,10 @@
#![allow(deprecated)]
use std::{borrow::Borrow, collections::HashMap};
use std::borrow::Borrow;
use axum::extract::State;
use conduwuit::{
err,
at, err,
pdu::gen_event_id_canonical_json,
utils::stream::{IterStream, TryBroadbandExt},
warn, Err, Result,
@@ -186,14 +186,13 @@ async fn create_join_event(
.map_err(|e| err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}")))))?;
let origin: OwnedServerName = serde_json::from_value(
serde_json::to_value(
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event missing origin property."))))?,
)
.expect("CanonicalJson is valid json value"),
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
.clone()
.into(),
)
.map_err(|e| err!(Request(BadJson(warn!("origin field is not a valid server name: {e}")))))?;
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
let mutex_lock = services
.rooms
@@ -212,14 +211,16 @@ async fn create_join_event(
drop(mutex_lock);
let state_ids: HashMap<_, OwnedEventId> = services
let state_ids: Vec<OwnedEventId> = services
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.await?;
.map(at!(1))
.collect()
.await;
let state = state_ids
.values()
.iter()
.try_stream()
.broad_and_then(|event_id| services.rooms.timeline.get_pdu_json(event_id))
.broad_and_then(|pdu| {
@@ -232,13 +233,11 @@ async fn create_join_event(
.boxed()
.await?;
let starting_events = state_ids.values().map(Borrow::borrow);
let starting_events = state_ids.iter().map(Borrow::borrow);
let auth_chain = services
.rooms
.auth_chain
.event_ids_iter(room_id, starting_events)
.await?
.map(Ok)
.broad_and_then(|event_id| async move {
services.rooms.timeline.get_pdu_json(&event_id).await
})
@@ -269,7 +268,7 @@ pub(crate) async fn create_join_event_v1_route(
body: Ruma<create_join_event::v1::Request>,
) -> Result<create_join_event::v1::Response> {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(body.origin())
@@ -285,7 +284,7 @@ pub(crate) async fn create_join_event_v1_route(
if let Some(server) = body.room_id.server_name() {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
@@ -317,7 +316,7 @@ pub(crate) async fn create_join_event_v2_route(
body: Ruma<create_join_event::v2::Request>,
) -> Result<create_join_event::v2::Response> {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(body.origin())
@@ -327,7 +326,7 @@ pub(crate) async fn create_join_event_v2_route(
if let Some(server) = body.room_id.server_name() {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
+38 -41
View File
@@ -1,7 +1,8 @@
use axum::extract::State;
use conduwuit::{err, pdu::gen_event_id_canonical_json, warn, Err, Error, PduEvent, Result};
use conduwuit::{err, pdu::gen_event_id_canonical_json, warn, Err, PduEvent, Result};
use futures::FutureExt;
use ruma::{
api::{client::error::ErrorKind, federation::knock::send_knock},
api::federation::knock::send_knock,
events::{
room::member::{MembershipState, RoomMemberEventContent},
StateEventType,
@@ -17,16 +18,18 @@ use crate::Ruma;
///
/// Submits a signed knock event.
pub(crate) async fn create_knock_event_v1_route(
State(services): State<crate::State>, body: Ruma<send_knock::v1::Request>,
State(services): State<crate::State>,
body: Ruma<send_knock::v1::Request>,
) -> Result<send_knock::v1::Response> {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(body.origin())
{
warn!(
"Server {} tried knocking room ID {} who has a server name that is globally forbidden. Rejecting.",
"Server {} tried knocking room ID {} who has a server name that is globally \
forbidden. Rejecting.",
body.origin(),
&body.room_id,
);
@@ -35,13 +38,14 @@ pub(crate) async fn create_knock_event_v1_route(
if let Some(server) = body.room_id.server_name() {
if services
.globals
.server
.config
.forbidden_remote_server_names
.contains(&server.to_owned())
{
warn!(
"Server {} tried knocking room ID {} which has a server name that is globally forbidden. Rejecting.",
"Server {} tried knocking room ID {} which has a server name that is globally \
forbidden. Rejecting.",
body.origin(),
&body.room_id,
);
@@ -50,7 +54,7 @@ pub(crate) async fn create_knock_event_v1_route(
}
if !services.rooms.metadata.exists(&body.room_id).await {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server."));
return Err!(Request(NotFound("Room is unknown to this server.")));
}
// ACL check origin server
@@ -74,44 +78,42 @@ pub(crate) async fn create_knock_event_v1_route(
let event_type: StateEventType = serde_json::from_value(
value
.get("type")
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "Event missing type property."))?
.ok_or_else(|| err!(Request(InvalidParam("Event has no event type."))))?
.clone()
.into(),
)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event has invalid event type."))?;
.map_err(|e| err!(Request(InvalidParam("Event has invalid event type: {e}"))))?;
if event_type != StateEventType::RoomMember {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
return Err!(Request(InvalidParam(
"Not allowed to send non-membership state event to knock endpoint.",
));
)));
}
let content: RoomMemberEventContent = serde_json::from_value(
value
.get("content")
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "Event missing content property"))?
.ok_or_else(|| err!(Request(InvalidParam("Membership event has no content"))))?
.clone()
.into(),
)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event content is empty or invalid"))?;
.map_err(|e| err!(Request(InvalidParam("Event has invalid membership content: {e}"))))?;
if content.membership != MembershipState::Knock {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Not allowed to send a non-knock membership event to knock endpoint.",
));
return Err!(Request(InvalidParam(
"Not allowed to send a non-knock membership event to knock endpoint."
)));
}
// ACL check sender server name
let sender: OwnedUserId = serde_json::from_value(
value
.get("sender")
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "Event missing sender property."))?
.ok_or_else(|| err!(Request(InvalidParam("Event has no sender user ID."))))?
.clone()
.into(),
)
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "sender is not a valid user ID."))?;
.map_err(|e| err!(Request(BadJson("Event sender is not a valid user ID: {e}"))))?;
services
.rooms
@@ -127,36 +129,32 @@ pub(crate) async fn create_knock_event_v1_route(
let state_key: OwnedUserId = serde_json::from_value(
value
.get("state_key")
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "Event missing state_key property."))?
.ok_or_else(|| err!(Request(InvalidParam("Event does not have a state_key"))))?
.clone()
.into(),
)
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "state_key is invalid or not a user ID."))?;
.map_err(|e| err!(Request(BadJson("Event does not have a valid state_key: {e}"))))?;
if state_key != sender {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"State key does not match sender user",
));
return Err!(Request(InvalidParam("state_key does not match sender user of event.")));
};
let origin: OwnedServerName = serde_json::from_value(
serde_json::to_value(
value
.get("origin")
.ok_or_else(|| Error::BadRequest(ErrorKind::InvalidParam, "Event missing origin property."))?,
)
.expect("CanonicalJson is valid json value"),
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
.clone()
.into(),
)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "origin is not a server name."))?;
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
let mut event: JsonObject = serde_json::from_str(body.pdu.get())
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid knock event PDU."))?;
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
event.insert("event_id".to_owned(), "$placeholder".into());
let pdu: PduEvent = serde_json::from_value(event.into())
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid knock event PDU."))?;
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
let mutex_lock = services
.rooms
@@ -169,19 +167,18 @@ pub(crate) async fn create_knock_event_v1_route(
.rooms
.event_handler
.handle_incoming_pdu(&origin, &body.room_id, &event_id, value.clone(), true)
.boxed()
.await?
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
drop(mutex_lock);
let knock_room_state = services.rooms.state.summary_stripped(&pdu).await;
services
.sending
.send_pdu_room(&body.room_id, &pdu_id)
.await?;
Ok(send_knock::v1::Response {
knock_room_state,
})
let knock_room_state = services.rooms.state.summary_stripped(&pdu).await;
Ok(send_knock::v1::Response { knock_room_state })
}
+4 -8
View File
@@ -1,7 +1,7 @@
use std::{borrow::Borrow, iter::once};
use axum::extract::State;
use conduwuit::{err, result::LogErr, utils::IterStream, Result};
use conduwuit::{at, err, utils::IterStream, Result};
use futures::{FutureExt, StreamExt, TryStreamExt};
use ruma::{api::federation::event::get_room_state, OwnedEventId};
@@ -35,11 +35,9 @@ pub(crate) async fn get_room_state_route(
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.await
.log_err()
.map_err(|_| err!(Request(NotFound("PDU state IDs not found."))))?
.into_values()
.collect();
.map(at!(1))
.collect()
.await;
let pdus = state_ids
.iter()
@@ -58,8 +56,6 @@ pub(crate) async fn get_room_state_route(
.rooms
.auth_chain
.event_ids_iter(&body.room_id, once(body.event_id.borrow()))
.await?
.map(Ok)
.and_then(|id| async move { services.rooms.timeline.get_pdu_json(&id).await })
.and_then(|pdu| {
services
+7 -10
View File
@@ -1,8 +1,8 @@
use std::{borrow::Borrow, iter::once};
use axum::extract::State;
use conduwuit::{err, Result};
use futures::StreamExt;
use conduwuit::{at, err, Result};
use futures::{StreamExt, TryStreamExt};
use ruma::{api::federation::event::get_room_state_ids, OwnedEventId};
use super::AccessCheck;
@@ -36,19 +36,16 @@ pub(crate) async fn get_room_state_ids_route(
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.await
.map_err(|_| err!(Request(NotFound("State ids not found"))))?
.into_values()
.collect();
.map(at!(1))
.collect()
.await;
let auth_chain_ids = services
.rooms
.auth_chain
.event_ids_iter(&body.room_id, once(body.event_id.borrow()))
.await?
.map(|id| (*id).to_owned())
.collect()
.await;
.try_collect()
.await?;
Ok(get_room_state_ids::v1::Response { auth_chain_ids, pdu_ids })
}
+13 -4
View File
@@ -1,6 +1,6 @@
use conduwuit::{implement, is_false, Err, Result};
use conduwuit_service::Services;
use futures::{future::OptionFuture, join, FutureExt};
use futures::{future::OptionFuture, join, FutureExt, StreamExt};
use ruma::{EventId, RoomId, ServerName};
pub(super) struct AccessCheck<'a> {
@@ -31,6 +31,15 @@ pub(super) async fn check(&self) -> Result {
.state_cache
.server_in_room(self.origin, self.room_id);
// if any user on our homeserver is trying to knock this room, we'll need to
// acknowledge bans or leaves
let user_is_knocking = self
.services
.rooms
.state_cache
.room_members_knocked(self.room_id)
.count();
let server_can_see: OptionFuture<_> = self
.event_id
.map(|event_id| {
@@ -42,14 +51,14 @@ pub(super) async fn check(&self) -> Result {
})
.into();
let (world_readable, server_in_room, server_can_see, acl_check) =
join!(world_readable, server_in_room, server_can_see, acl_check);
let (world_readable, server_in_room, server_can_see, acl_check, user_is_knocking) =
join!(world_readable, server_in_room, server_can_see, acl_check, user_is_knocking);
if !acl_check {
return Err!(Request(Forbidden("Server access denied.")));
}
if !world_readable && !server_in_room {
if !world_readable && !server_in_room && user_is_knocking == 0 {
return Err!(Request(Forbidden("Server is not in room.")));
}
+5 -1
View File
@@ -36,6 +36,7 @@ jemalloc_stats = [
"tikv-jemalloc-ctl/stats",
"tikv-jemallocator/stats",
]
jemalloc_conf = []
hardened_malloc = [
"dep:hardened_malloc-rs"
]
@@ -50,6 +51,9 @@ zstd_compression = [
]
perf_measurements = []
sentry_telemetry = []
conduwuit_mods = [
"dep:libloading"
]
[dependencies]
argon2.workspace = true
@@ -71,11 +75,11 @@ figment.workspace = true
futures.workspace = true
http-body-util.workspace = true
http.workspace = true
image.workspace = true
ipaddress.workspace = true
itertools.workspace = true
libc.workspace = true
libloading.workspace = true
libloading.optional = true
log.workspace = true
num-traits.workspace = true
rand.workspace = true

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