Compare commits

...

346 Commits

Author SHA1 Message Date
strawberry c29197b3f4 ci: dont publish -rc tags as latest docker branches
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-16 00:44:47 -04:00
Jason Volk 739eab46d5 refactor thumbnail math
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:40:18 +00:00
Jason Volk 923a98eb66 partially revert dc18f89c0b for now
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:40:18 +00:00
Jason Volk 4430e4dee0 de-global some services()
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk d67f19a55d cleanup some error callsites
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk b903b46d16 split thumbnailing related into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 167559bb27 rename FileMeta::file to Option<content>
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 838e4b9d8d log more details for panic in tower handler
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 038b71fc9d add state to router
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 720fbd09c2 move routes into api router top level
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk c42cb90dd3 simplify result handler / 405 error interposition
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
Jason Volk 5950355348 break from wait loop on empty taskset
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-16 02:39:49 +00:00
strawberry f79bd2ac72 bump ruma-identifiers-validation to c51ccb2c68d2e3557eb12b1a49036531711ec0e5
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry 80ec0e31b1 bump tracing fork
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry bda44b16b1 ci: run nix dynamic build test with DIRENV_DEVSHELL=dynamic
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry e2280aa1a5 ci: try removing --impure
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry bdf2de076a ci: set NIX_CONFIG with our binary caches for the earlier CI steps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry 1797fec3c9 nix: disable USE_RTTI for rocksdb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-15 22:13:05 -04:00
strawberry 188fa5a073 patch rustyline-async and use no default features
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry f0c63c539b nix: dont build more unnecessary tests/tools for rocksdb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry 649e9da1f8 nix: try to make armv8.2-a / cortex-a55 optimised builds if target is aarch64
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry df28359a19 nix: dont build rocksdb core tools (ldb / sst_dump) (-DWITH_CORE_TOOLS=1)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry 9370e93a8d nix: try to make x86-64-v2 optimised builds if target is x86_64
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry bdd5845490 docs: suggest recursive chmod
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:18 -04:00
strawberry bacffd6174 rename conduit_cache_capacity_modifier to cache_capacity_modifier
this prefix causes you to require setting the environment variable
to `CONDUWUIT_CONDUIT_CACHE_CAPACITY_MODIFIER`

alias this so we dont break any configs

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:08:15 -04:00
strawberry a1bfd7a018 nix: dont include experimental feature on all-features builds
this is just future infra for it

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
strawberry 7009f56a7a fix user directory publishing access controls
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
strawberry 2c0bfac43e nix: bump flake.lock and rocksdb to v9.4.0
• Updated input 'attic':
    'github:zhaofengli/attic/717cc95983cdc357bc347d70be20ced21f935843' (2024-06-01)
  → 'github:zhaofengli/attic/6139576a3ce6bb992e0f6c3022528ec233e45f00' (2024-07-09)
• Updated input 'complement':
    'github:matrix-org/complement/bc97f1ddc1cd7485faf80c8935ee2641f3e1b57c' (2024-07-02)
  → 'github:matrix-org/complement/0d14432e010482ea9e13a6f7c47c1533c0c9d62f' (2024-07-10)
• Updated input 'crane':
    'github:ipetkov/crane/0aed560c5c0a61c9385bddff471a13036203e11c' (2024-07-06)
  → 'github:ipetkov/crane/2d83156f23c43598cf44e152c33a59d3892f8b29' (2024-07-09)
• Updated input 'fenix':
    'github:nix-community/fenix/abc0549e3560189462a7d394cc9d50af4608d103' (2024-07-08)
  → 'github:nix-community/fenix/5087b12a595ee73131a944d922f24d81dae05725' (2024-07-13)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/a5b21ea0aa644dffd7cf958b43f11f221d53404e' (2024-07-07)
  → 'github:rust-lang/rust-analyzer/ffbc5ad993d5cd2f3b8bcf9a511165470944ab91' (2024-07-11)
• Updated input 'liburing':
    'github:axboe/liburing/7b3245583069bd481190c9da18f22e9fc8c3a805' (2024-06-22)
  → 'github:axboe/liburing/1d674f83b7d0f07553ac44d99a401b05853d9dbe' (2024-07-12)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/655a58a72a6601292512670343087c2d75d859c1' (2024-07-08)
  → 'github:NixOS/nixpkgs/7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9' (2024-07-12)
• Updated input 'rocksdb':
    'github:girlbossceo/rocksdb/a935c0273e1ba44eacf88ce3685a9b9831486155' (2024-07-02)
  → 'github:girlbossceo/rocksdb/911f4243e69c2e320a7a209bf1f5f3ff5f825495' (2024-07-13)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
strawberry fcb6c8a113 bump rust-rocksdb
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
Charles Hall 1ab77aeb91 don't strip binary for dev builds
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:06:52 -04:00
strawberry 3d73b53136 update complement results
we dont implement authenticated media yet

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-14 12:35:00 +00:00
strawberry 101fdbc9b1 bump cargo.lock
Updating bytes v1.6.0 -> v1.6.1
Updating cc v1.1.0 -> v1.1.3
Updating http-body v1.0.0 -> v1.0.1
Updating syn v2.0.70 -> v2.0.71
Updating thiserror v1.0.61 -> v1.0.62
Updating thiserror-impl v1.0.61 -> v1.0.62

Signed-off-by: strawberry <strawberry@puppygock.gay>
Co-authored-by: Jason Volk <jason@zemos.net>
2024-07-14 12:35:00 +00:00
Jason Volk e53c2fbc5a fix tracing flame envfilter defaults; remove preprocessed cfgs
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 95006f7e46 fix unnecessary preprocessing cfgs
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk cce270d938 tokio metrics
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 5ec49b3f62 split csp into array; integrate error; cleanup type
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk e4dc4a1ba5 fix graceful shutdown on unix socket
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk d2fb6d04c9 cleanup pending transactions before sender worker completes
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 05efd9b044 elaborate error macro and apply at various callsites
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk b3f2288d07 add constant-expression string utils
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 084751ae38 refactor globals::updates into a Service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk aa7a310200 move media startup into service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 38c989a07e split service manager into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 5be679e17b refactor main task stack through service mgr
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk b8baa1223d reduce RoomVersionId patterns
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk b87f1649d4 split error into directory
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 4a6f089b23 move some config checks into check unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 4600c7f32d move infallible handling into error
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 4cc92dd175 refactor Error::bad_config
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 93ec4e579b error macro suite
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk c111d2e395 abstract service worker pattern; restart on panic.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 03d890cd49 move admin tests into unit; fix
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk a35b6cbfdd use separate configurable jaeger envfilter
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-14 11:31:39 +00:00
Jason Volk 5570220c89 use separate but configurable envfilter for sentry
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 899b79873e propagate errors from tracing init
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 57969f9480 fix large stack array exceeding limit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk d88ab37120 add configuration for sentry to send panics and errors
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk bc58e5002d add interface for current log filter; fix console suppression
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 160f48043e add selective log handle reload
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 0023b09f5b make tracing reload handles into a named map
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk db3c718ddc add config for sentry stacktraces
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk e73aa2aa21 log propagated errors in admin service; minor cleanup
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 61f2a3c68b catch panic from admin commands
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 0627b46f40 add panic suite to Error
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk f10f5319db elaborate error log functor stack
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 16e76d45cb improve alloc stats interface; fix admin command formatting
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 2a304c2b6c add some usage stats output on the mutex maps
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 271f720286 move mutex maps out of globals into respective service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 2d251eb19c cleanup on drop for utils::mutex_map.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 01b2928d55 add make user admin command (#136)
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 50c2d2b801 add command to force join user to room (#136)
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 5dcdafe207 take local join branch when remote join would fail
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk c62d653989 tweak some log levels
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
Jason Volk 454dd43d4c fix membership route ABA's
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-13 21:20:05 +00:00
AlexPewMaster 8077e910f6 Fix CONDUWUIT_CONFIG variable in docker-compose.yml files (+ remove deprecated compose version) 2024-07-13 17:16:39 -04:00
strawberry b5d4a1c1b0 drop harmless state event not found message to debug_info
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry aa9540af21 slightly simplify send_state_event route
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 080975ab0e adjust/update complement conduwuit config.toml
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry f129d90900 nix: delete -DWITH_SNAPPY=1 from rocksdb cmakeFlags
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 53223a4d5f remove snappy as a default feature
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 05befa4ba2 nix: make enableLiburing in rocksdb conditional instead of default true
for some reason enableLiburing in nixpkgs rocksdb is default
true which breaks Darwin entirely

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry a8e690f22b nix: unset patches in rocksdb
we have the patch already and i dont want to make it nix
exclusive by reverting it in my rocksdb fork

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 192c1e08da add exclude_disabled and exclude_banned room list admin cmd arguments
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 0fa6976d86 add client IP and user logging on join, remove unnecessary Option
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 93e7cf461d add client IP logging to media requests
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry d036d8adcb bump rust-rocksdb to 0.27.1
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 7a4bbe2ff6 nix: drop hardened_malloc feature from all-features builds
this is non-functional and i've spent far too long trying to figure
out what the issue is

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 438911c18d nix: remove unnecessary dependencies gathering from allFeatures
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry bd71435a22 ci: build static binaries and OCI imgs with --all-features
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 53fa7c3729 nix: add all-features outputs/packages
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry d2facaee0b bump various dependencies
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 0dae9280d9 nix: bump flake.lock
• Updated input 'crane':
    'github:ipetkov/crane/087e08a41009bf083d51ab35d8e30b1b7eafa7b0' (2024-07-03)
  → 'github:ipetkov/crane/0aed560c5c0a61c9385bddff471a13036203e11c' (2024-07-06)
• Updated input 'fenix':
    'github:nix-community/fenix/f6994934e25396d3a70ddb908cefccd8d3c37ac4' (2024-07-05)
  → 'github:nix-community/fenix/27cbad7cc093c5298231b87daa04db9610053651' (2024-07-06)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/cae997e3380363a906588f14c7b4587f39cf09f5' (2024-07-03)
  → 'github:rust-lang/rust-analyzer/f2afcb874e7410121c366ae601660abe327e320b' (2024-07-05)

• Updated input 'fenix':
    'github:nix-community/fenix/27cbad7cc093c5298231b87daa04db9610053651' (2024-07-06)
  → 'github:nix-community/fenix/abc0549e3560189462a7d394cc9d50af4608d103' (2024-07-08)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/f2afcb874e7410121c366ae601660abe327e320b' (2024-07-05)
  → 'github:rust-lang/rust-analyzer/a5b21ea0aa644dffd7cf958b43f11f221d53404e' (2024-07-07)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/9f4128e00b0ae8ec65918efeba59db998750ead6' (2024-07-03)
  → 'github:NixOS/nixpkgs/655a58a72a6601292512670343087c2d75d859c1' (2024-07-08)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 35336eb686 ci: use $COMPLEMENT_SRC from nix devshell for a pinned complement rev
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry efea13a675 add access control checks for room directory publishing/removing
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry b5ee15a216 dont skip_all tracing instrument on fetch_state
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 0873e18e14 remove random duplicate function
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 6abc4ad798 make local_aliases_for_room db iterator Send
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 373991a8d6 cleanup and fix backfill from server getting
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 68ad351f84 nix: partially remove some liburing overrides
the argument one is weird and causing build script invalid arguments

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 83e853e7a3 add "unstable" endpoint legacy Element Android/iOS seems to call
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 1c453b1b55 require authentication on change password and deactivation routes
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 60141950f7 fix wrong deactivate-all argument comment
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 391bfd986e use ruma's X-Matrix to construct the sending X-Matrix header value
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry da03de1d32 bump flake.lock fully
• Updated input 'crane':
    'github:ipetkov/crane/109987da061a1bf452f435f1653c47511587d919' (2024-05-24)
  → 'github:ipetkov/crane/087e08a41009bf083d51ab35d8e30b1b7eafa7b0' (2024-07-03)
• Updated input 'fenix':
    'github:nix-community/fenix/b6fc5035b28e36a98370d0eac44f4ef3fd323df6' (2024-05-22)
  → 'github:nix-community/fenix/f6994934e25396d3a70ddb908cefccd8d3c37ac4' (2024-07-05)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/21ec8f523812b88418b2bfc64240c62b3dd967bd' (2024-05-19)
  → 'github:rust-lang/rust-analyzer/cae997e3380363a906588f14c7b4587f39cf09f5' (2024-07-03)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5710852ba686cc1fd0d3b8e22b3117d43ba374c2' (2024-05-21)
  → 'github:NixOS/nixpkgs/9f4128e00b0ae8ec65918efeba59db998750ead6' (2024-07-03)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry e54f4d4397 ci: move complement diff results test output
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry daa5c34ea3 fix empty version string for NixOS users
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
strawberry 3259ea08b5 use cond compilation for config check, fix docker check w/unix sockets, use our logging instead of tracing
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-10 22:00:46 -04:00
Jason Volk 158de10fe6 log erroring errors; improve inspection functors.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-09 04:09:34 +00:00
Jason Volk 4718387dbe fix wrapper macro semicolons
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-09 03:38:07 +00:00
Jason Volk a43c78e55f add RumaError to Error; encapsulate RumaResponse in api
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-09 03:38:07 +00:00
Jason Volk 7ba0777bd3 move RumaResponse out of core Error; cleanup Error conversions.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 59d86d3641 restrict clippy::string_slice
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 51df946911 de-branch duration limit statement
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 23a9055199 relax and de-clutter let_underscore_must_use
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 100c6f572b trim unused dependencies in member crates
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk eb5556e74e additional tracing spans / log cleanup.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 8296e0ed67 slightly optimize command completer
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:10 +00:00
Jason Volk 8e3be6feb0 slightly optimize Destination::get_prefix()
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 59c4062305 set trivial/leaf spans to debug level
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 113a27c1d5 use usize for max_request_size config item
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 5af880e4f4 fix reference count on punned ColumnFamily Arc
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 56a1b0e761 restrict untripped clippies
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 5722c4ae39 fix needless collect
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk dfd13780df mitigate additional cast lints
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk dcd7422c45 fix as conversions
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 7397064edd fix arithmetic side-effects
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 52a561ff9e abstract expoential backoff to math utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 5e72d36800 add math utils; integrate checked expression macros
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 611f09829e use shorthand constraint syntax and formatting
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk a388c2e06e allow clippy::enum_glob_use
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk 24b37e03a0 add pretty time util
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
Jason Volk a309ef55c9 restore signal state after channel failures
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-08 22:02:09 +00:00
AlexPewMaster c1c084dda1 Introduce a new way of deploying conduwuit using caddy in Docker 2024-07-08 17:59:59 -04:00
strawberry 72d9e8ed2b bump conduwuit version to 0.4.5
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-07 14:32:41 -04:00
Jason Volk a3638dbb15 use rwlock for command handler.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-07 06:24:08 +00:00
Jason Volk 5254eb4f72 add basic tab completion to console
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-07 06:24:08 +00:00
Jason Volk b31e81a469 add common_prefix string util
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Jason Volk 0e580292a6 encap admin handler init/fini in crate
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Jason Volk 38a24e0170 remove unused deps in member crates
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Jason Volk 8350aced39 add ctrl-\ quit support to admin console
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Jason Volk 04e3de08eb add log to error functors for Result::map_or_else
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-06 15:26:07 +00:00
Christoph Dittmann 2bc53139fa Don't send empty presence EDUs
I run a homeserver whose logs show a high number of incoming empty
presence EDUs originating from the user agent "Conduwuit/0.4.4". They
arrive at a rate of about 2 queries per second per Conduwuit server.

The empty EDUs all look the same, only with `origin_server_ts`
increasing:

```
{"origin":"example.com","origin_server_ts":1720266475601,"edus":[{"edu_type":"m.presence","content":{"push":[]}}]}
```

These updates are unnecessary because they don't do anything. They
only increase network traffic and CPU usage on both sides.

After this commit, the empty presence updates are no longer inserted
into the outgoing event queue.
2024-07-06 14:13:33 +02:00
strawberry 8691141237 ci: try running nix-build-and-cache prior dynamic build test
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-05 02:48:36 -04:00
Jason Volk 17a3ed4c56 add admin query resolver commands
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 22:15:54 +00:00
Jason Volk 2dd68d3fa5 add time format string util
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk dc18f89c0b don't cache server name lookups indefinitely (#436)
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk eaf1cf38a5 refactor resolver tuples into structs
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk eeda96d94a inline analysis and symbol reduction; emits smaller than 64 bytes marked inline
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk 1e8b8cce0f even more byte utils optimizations
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk db46d6dd6b optimize millis_since_unix_epoch()
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk 08274150e5 cork database during global counter increment
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk e125af620e impl crate::Service for Service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk 177c9e8bfa add split_once_infallible string util
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk 4f5c6de853 start rand utils suite
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk 25c004f08c remove dead cache
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk f104ced55d remove unnecessary wrapper
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
Jason Volk 0cea64309a move PduEvent from services to core
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-04 21:06:35 +00:00
strawberry 229f2fde7a skip 2 more flakey complement tests still
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-04 11:17:13 -04:00
strawberry 9a0ffd55da skip complement test TestClientSpacesSummary still
too flakey still

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-04 01:55:22 -04:00
renovate[bot] d1b3faf4bd Update Rust crate sd-notify to v0.4.2 2024-07-03 23:02:44 -04:00
strawberry 4014cb4413 document emergency_password
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 23:02:44 -04:00
strawberry b2dbc3bc10 update complement results
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 23:02:38 -04:00
strawberry e2033fb6c1 notify users to update their build scripts if using rocksdb or sha256_media
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 22:12:47 -04:00
strawberry 6b433392a4 slight docs updates
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 22:12:47 -04:00
strawberry 0832c28a40 dedupe some state accessors in various places
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 22:12:47 -04:00
strawberry b0eec60724 add MSC3266 room summary API support
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 22:12:47 -04:00
strawberry 82acc7c0b0 ci: add back ubuntu free disk space
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 22:12:47 -04:00
strawberry c9384e9c78 allow all complement tests again
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 22:12:45 -04:00
strawberry 3502e6ef87 bump conduwuit to 0.4.4
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 11:30:16 -04:00
strawberry deca2c52c6 switch to ruma's X-Matrix parser
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 11:30:16 -04:00
strawberry daa46a0dc4 auto-fix some markdown lints
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 11:30:16 -04:00
strawberry 74791642c1 add markdownlint and run lychee in documentation CI workflow
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 11:30:16 -04:00
strawberry 1a64e42cfe add /openid/request_token and /openid/userinfo routes
heavily changed and improved by me

Co-authored-by: mikoto <avdb@keemail.me>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-03 11:30:11 -04:00
Jason Volk 5edd391e83 support admin server restart --force
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 7658387a74 remove resolver wrapper; use std mutex
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk be2d1c722b encap dep:chrono in time utils
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 8cf55c702f split time utils into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 3b9fba233c split bytes utils into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk af81baae44 split string utils into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 52d470058a split hash utils into directory
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk df0328f43f split send txn handler
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 473b29d524 move remaining runtime caches into their respective service
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk e1d1dac95e add missing media compat example config
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk a2d25215a3 consolidate key/value types; consistent interface arguments
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 46423cab4f fix counter increment race
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 2e2cf08bb2 optimize increment / increment batch
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk ee64fb149c optimize get w/ zero-copy ref handle
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 0522fe7d92 reimplement iterator from lowlevel
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 6dd6e4bfaf simplify cork interface related
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk a1ced0a56f fix admin query timers not covering full iteration
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jacob Taylor 0171262581 make pduid_pdu/eventid_outlierpdu scale with conduit_cache_capacity_modifier via cache_size(), and fix lint 2024-07-03 06:34:16 +00:00
Jason Volk 972037dcd9 optimize column family handles
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
Jason Volk 0613140130 optimize bytes conversion utils
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-03 06:34:16 +00:00
morguldir f43c09b05d ci: pin cache-nix-action
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-07-03 06:13:39 +02:00
morguldir b035cd7cbe ci: remove detsys from documentation/build workflow too
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-07-03 04:51:59 +02:00
morguldir b7aa79884d ci: try out cargo caching 2024-07-03 03:39:49 +02:00
morguldir 9c6cc44abb ci: use correct config file
also had to try putting it at the end again
2024-07-03 01:56:14 +02:00
morguldir 9c0d7b1da0 ci: change to cache-nix-action 2024-07-03 01:08:30 +02:00
strawberry ed3a464722 remove uselsss partial_cmp with cmp
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-02 21:21:37 -04:00
strawberry 28ac3790c2 sync upstream spaces/hierarchy federation MR
also had to fix a million clippy lints

fix(spaces): deal with hierarchy recursion
fix(spaces): properly handle max_depth
refactor(spaces): token scheme to prevent clients from modifying max_depth and suggested_only
perf(spaces): use tokens to skip to room to start populating results at
feat(spaces): request hierarchy from servers in via field of child event

Co-authored-by: Matthias Ahouansou <matthias@ahouansou.cz>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-02 21:20:28 -04:00
strawberry 9115901c66 update complement results
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-02 21:20:24 -04:00
strawberry 652faafe6e bump ruma
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-02 16:46:19 -04:00
strawberry 8deba94a12 update various deps, update rocksdb to v9.3.1
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-02 16:46:13 -04:00
strawberry bb27f21ac1 simplify getting join_authorized_via_users_server for make/send_join, remove unnecessary async
Co-authored-by: Matthias Ahouansou <matthias@ahouansou.cz>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-02 15:32:40 -04:00
strawberry 339a1fc4e8 only sign /send_join events if restricted join via room version and join_authorised_via_users_server
Co-authored-by: Matthias Ahouansou <matthias@ahouansou.cz>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-02 15:01:11 -04:00
strawberry 75731671d1 use RoomMemberEventContent for send_join/send_leave
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-02 14:46:53 -04:00
Jason Volk 20aab1792b fix version format regression 3a51e18ce6
Signed-off-by: Jason Volk <jason@zemos.net>
2024-07-01 23:35:04 +00:00
Kimiblock Moe e9f4cd58a9 Bind Mount the old matrix-conduit directory 2024-07-01 17:19:50 -04:00
renovate[bot] 6bae136720 Update Rust crate clap to v4.5.8 2024-07-01 00:18:18 -04:00
renovate[bot] 39a1e994eb chore(deps): update rust crate log to v0.4.22 2024-07-01 00:18:09 -04:00
renovate[bot] c2c36197e9 chore(deps): update nixos/nix docker tag to v2.23.1 2024-07-01 00:17:53 -04:00
Paul Robertson c952db72e7 remove unused CONDUWUIT_CONFIG_FILE variable 2024-07-01 00:16:32 -04:00
Paul Robertson 1ca484f90d explain how to configure conduwuit 2024-07-01 00:16:23 -04:00
Paul Robertson b1150cded9 rename configuruation.md to configuration/examples.md 2024-07-01 00:16:23 -04:00
strawberry 2b35f83089 ci: exclude renovate[bot]
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-01 00:14:16 -04:00
strawberry 792d4aa6ce fix nightly clippy lint
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-01 00:14:16 -04:00
strawberry 6c461025e5 use proper redacts field
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-07-01 00:14:16 -04:00
Jason Volk 9d1db7d171 rename fsck to check
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 2387f7f955 move tester command under debug
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 5805394ca5 add tester timer stub
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 3301cde8e7 add timer around admin command processing
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk b28d216e00 improve debug get-room-state; use RoomOrAliasId
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk f127987c7a add convenience alias resolver to interface
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk fa02d7b7e3 diffuse get_alias_helper into services::rooms::alias
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 96a16190c5 mute dev_release_log_level from --all-features for now
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk d9054f5364 add backreference to layer in capture data struct
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk db288b169e add config option for tokio_console runtime enablement
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk bc50034173 idiomize various expressions
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 20fe84379c rename log::Server to log::Log
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 06f01d384f simplify argon init
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk bd19fac4c5 split tracing init into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 703c275266 split sentry init; add user-agent, trace hooks.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk c05f00661b use extern namespace in log wrapper macros
convert use tracing statements

Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 0e46b750a0 default to jemalloc when both hardened_malloc and jemalloc enabled.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 3a51e18ce6 pre-format version strings
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 7638bbc49c keep column list lexically sorted
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 35049c94de simplify db ThreadMode typedef
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 7db8478dc0 hoist iterator modes; group fns
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk df7a1dee15 remove --features=rocksdb from smoketest; use separate target dir
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk dc633100da hoist read_options/write_options instances
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 0f1432f448 improve appservice::Data interface encap
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 30b5ad3870 de-feature rocksdb 2024-06-30 18:02:16 +00:00
Jason Volk cb48e25783 refactor dyn KvTree out of services
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 57acc4f655 fix needless pass by value
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk 875d9e8b07 fix complex types
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:16 +00:00
Jason Volk de21f7442a devirtualize database
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:02:14 +00:00
Jason Volk 7ad7badd60 devirtualize service Data traits
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:01:20 +00:00
Jason Volk a6edaad6fc error enum cleanup
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:01:20 +00:00
Jason Volk fbcdb3860a eliminate dep:rust-rocksdb et al from admin crate
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:01:20 +00:00
Jason Volk 01f4455ceb convert rocksdb errors locally; remove from Error.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:01:20 +00:00
Jason Volk c1712d4d8b optimize increment
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:01:20 +00:00
Jason Volk 3480074f61 sha256_media integration
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:01:20 +00:00
Jason Volk efbdced535 cleanup migration function a bit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-30 18:01:20 +00:00
Paul Robertson 19d8f0b27e dont use any for a single feature check 2024-06-30 10:40:54 -04:00
Paul Robertson 8d5305c769 lock cleanup 2024-06-30 10:40:35 -04:00
Paul Robertson 73b85372d6 drop parking_lot feature 2024-06-29 11:20:48 -04:00
Paul Robertson 5e6b9ec1cf drop thread_local feature 2024-06-29 11:20:48 -04:00
Paul Robertson e24b7d2f3e drop sqlite from docs 2024-06-29 11:20:48 -04:00
Paul Robertson c61258fa0d drop rusqlite dependency 2024-06-29 11:20:48 -04:00
Paul Robertson 65177d237a drop the sqlite feature from core 2024-06-29 11:20:48 -04:00
Paul Robertson 325f1c0bd2 drop the sqlite feature from database 2024-06-29 11:20:45 -04:00
Paul Robertson 68f1b4789a drop the sqlite feature from main 2024-06-29 11:05:33 -04:00
Paul Robertson 624cd2acfa remove sqlite code 2024-06-29 11:05:33 -04:00
morguldir fb758f5f4b chore(deps): bump Cargo.lock (subtle crate was yanked) 2024-06-27 17:41:13 -04:00
morguldir 5d138a7e5e ci: skip development.md file when running lychee 2024-06-27 17:41:13 -04:00
morguldir 563618cc9e ci: free disk space before starting tests 2024-06-27 17:41:13 -04:00
morguldir 0a28d80cf2 ci: try another upstream cache
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-06-27 17:41:13 -04:00
Integral 6f9d7b5667 docs: fix broken link to development.md 2024-06-23 12:20:53 -04:00
Jason Volk d875e0c1c0 fix handling of empty admin command lines
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-23 09:52:16 -04:00
renovate[bot] 7a71012589 chore(deps): update aquasecurity/trivy-action action to v0.23.0 2024-06-22 18:37:54 -04:00
strawberry 189688994b ci: properly exclude renovate from docker publishing
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-22 18:35:30 -04:00
strawberry bc093e9544 bump conduwuit version to 0.4.3
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-22 17:56:34 -04:00
Jason Volk 2dae3052e2 remove unnecessary html render
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-22 21:22:56 +00:00
Jason Volk 0f07b86159 bump Cargo.lock 2024-06-22 21:22:56 +00:00
Jason Volk 2f84bc895d Improve additional command outputs containing codeblocks.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-22 21:22:56 +00:00
Jason Volk 3b34e72456 improve output scheme for admin query commands
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-22 21:22:56 +00:00
Jason Volk 7eee88160a truncate other span names and fields
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-22 21:22:56 +00:00
strawberry 5f46623371 redaction fixes 2024-06-22 21:22:56 +00:00
Jason Volk cbb97b4fdf bump termimad
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-18 10:56:19 +00:00
Jason Volk 14dcc8db15 truncate MatchedPath for span field
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-18 10:56:19 +00:00
Jason Volk 213a6d441f shorten several request span names.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-18 10:56:19 +00:00
Jason Volk 98d96b89a5 minor reductions
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-18 10:56:19 +00:00
Jason Volk 1c0ed91f6f rename ruma_wrapper to router
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 05:04:00 +00:00
Jason Volk 64705fa27d rename router to routes
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 05:04:00 +00:00
Jason Volk 8affdc43a6 additional termimad config
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 02:10:27 +00:00
Jason Volk 1ccdba8921 use markdown for list-database-files command
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 02:10:27 +00:00
Jason Volk 50ce87161b refactor admin command visibilities and use statements
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 02:10:27 +00:00
Jason Volk c6f4b20e17 add server restart support w/ admin command
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 02:10:27 +00:00
Jason Volk 2cb31275f0 fix missing command response; use non-empty command responses
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 02:10:27 +00:00
Jason Volk 5aee03d14a switch to crate rustyline_async
improve console signal and interrupt stack

Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 02:10:27 +00:00
Jason Volk 959fd2e6c4 split main signal handler to unit
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-17 02:10:27 +00:00
strawberry f40a3ea4a6 docs: more improvements, fixes, cleanup, etc
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 21:30:51 -04:00
strawberry aa963c61da docs: add slight theme changes, better title
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 20:58:17 -04:00
strawberry c98cf13010 ci: typo, store path is /conduwuit
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 19:17:29 -04:00
strawberry 73dd4b1f8f ci: enable accept-flake-config, use the same args everywhere
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 18:54:03 -04:00
strawberry c921deaee2 docs: update differences.md
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 15:35:08 -04:00
strawberry a5de4d30bb ci: add forgejo/gitea actions file
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 15:35:08 -04:00
strawberry 9e9b256b83 temp: comment out hardened_malloc code
sorry but this is just non-functional in our build system,
i haven't had time to figure out why nix doesn't like what
i'm doing with it, and i haven't heard anyone using this

(the user who i anticipated would use this has not updated
their conduwuit in many months)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 15:20:57 -04:00
strawberry ddf327e8b6 bump termimad
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry 3341a8e56e ci: allow drafts to run artifact builds again
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry 4942d684ea bump gitlab nix ci to 2.23.0
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry f8f6dba674 docs: add conduwuit-bin to arch-linux.md
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry e5c9bf1992 misc build changes to mdbook, add favicon/logo
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry 502856a4a2 use pinned revs of git cargo deps where possible
should reduce all but rocksdb from here: https://github.com/chaotic-cx/nyx/blob/0a48586e8c81ce5647915a1664d104616d5704b6/pkgs/conduwuit-git/pins.json

Co-authored-by: PedroHLC <root@pedrohlc.com>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
PedroHLC 2be2a0dc91 fix: add tracing-log patch, use pinned tracing revs
original patch from https://github.com/chaotic-cx/nyx/commit/637ff3ce0c8c19673d5f06d31c18a61db9d84459#diff-73188cdc15fe4e672c637dfc8a06ca08c30f789aa31dc8e1a5297f76779bc369

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry cd3e7394bf nix: support pushing to conduwuit.cachix.org
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry a543bec1a5 ci: add conduwuit cachix nix binary cache
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry a589a34d15 ci: unify/simplify documentation and ci nix steps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry 4312ac91c1 ci: set NIX_CONFIG for documentation workflow too
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry e83b426caf ci: set SOURCE_DATE_EPOCH for at least debian pkgs
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry cad1d34611 ci: disable detsys telemetry, use our binary cache URL as upstream
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry ce4517269b ci: output complement diff results to summary again
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry 169617ab87 ci: remove erroneous DIRENV_DEVSHELL
engage already sets `DIRENV_DEVSHELL` as necessary

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
strawberry 3596fe0e18 ci: dont let renovate publish docker images
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-16 13:16:51 -04:00
Jason Volk a22524496d console command interruption
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 08:02:14 +00:00
Jason Volk 1d1b1644e9 add admin debug echo command
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 08:02:14 +00:00
Jason Volk 483f0a9c86 add admin command for admin room notices
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 08:02:14 +00:00
Jason Volk 3d3d63fdf4 admin commands for shutdown/reload
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 07:24:51 +00:00
Jason Volk 08f2b8579c abstract shutdown/reload functionality.
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 07:24:51 +00:00
Jason Volk 30e7298dd7 fix doc-lazy-continuation
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 07:24:51 +00:00
Jason Volk 83565007bb fix lint group priority
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 07:24:51 +00:00
Jason Volk 3872ae80f7 fix PanicInfo deprecation
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 07:24:51 +00:00
Jason Volk 0923b6f428 fix use std::mem related lint
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 07:24:51 +00:00
Jason Volk 48d9677959 rustfmt nightly/stable compats
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 03:07:59 +00:00
Jason Volk 02bd67dc4b admin command path simplifications; fix message to room
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 03:07:59 +00:00
Jason Volk 3813628acd log full custom edu rather than just content
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-16 03:07:59 +00:00
Kimiblock Moe 7b7593a6f5 Change the env name 2024-06-15 23:06:38 -04:00
Kimiblock Moe 1323506c88 Fix naming issues, directories will be moved in the post_upgrade function 2024-06-15 23:06:38 -04:00
Kimiblock Moe 332fc74fb8 Add systemd service for Arch 2024-06-15 23:06:38 -04:00
strawberry 03c04ce0a1 dont allow "remote admins" to run public escaped cmds on behalf of others
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-06-14 23:46:07 -04:00
Jason Volk 08bf074cbb reduce roomid_mutex_state
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-14 22:09:55 +00:00
Jason Volk 539aa27815 reduce roomid_mutex_federation
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-14 22:09:55 +00:00
Jason Volk 22272bdc16 reduce roomid_mutex_insert
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-14 22:09:55 +00:00
Jason Volk 8b68d6306c add MutexMap to utils
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-14 22:09:55 +00:00
Jason Volk d4775f0763 add server-side command escape w/ public echo for admins
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-14 06:40:47 +00:00
Jason Volk 571ab6ac2b admin terminal console
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-13 03:33:24 +00:00
Jason Volk 5df7443437 add log suppression tool
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-13 02:16:46 +00:00
Jason Volk e76e604771 add MxidError to our Error
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 21:06:09 +00:00
Jason Volk 1f9225e4d1 add markdown log format for capture
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 21:06:09 +00:00
Jason Volk c914a4fd91 capture logs for resolve-true-destination admin cmd
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 18:52:39 +00:00
Jason Volk aa34021b27 tracing capture interface
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 18:25:27 +00:00
Jason Volk 1bb4021b90 add format util to log suite
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 18:25:27 +00:00
Jason Volk 877c04de52 add color utils to log suite
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 18:25:27 +00:00
Jason Volk 029e1c630a split core log into directory
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 18:25:27 +00:00
Jason Volk 39110ebc3c add fmt::Error as core Error source
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 18:25:27 +00:00
Jason Volk 2ab3231ea6 add tracing_subscriber::filter::ParseError to amalgam
Signed-off-by: Jason Volk <jason@zemos.net>
2024-06-12 18:25:27 +00:00
309 changed files with 14927 additions and 10600 deletions
+6 -1
View File
@@ -11,5 +11,10 @@ indent_style = space
insert_final_newline = true
max_line_length = 120
[*.{md,txt}]
indent_style = space
indent_size = 4
max_line_length = 80
[*.nix]
indent_size = 2
indent_size = 2
+264
View File
@@ -0,0 +1,264 @@
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/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
+84 -48
View File
@@ -16,6 +16,7 @@ on:
- 'docker/**'
branches:
- main
- change-ci-cache
tags:
- '*'
# Allows you to run this workflow manually from the Actions tab
@@ -30,16 +31,18 @@ env:
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 }}
# Use the all-features devshell instead of default, to ensure that features
# match between nix and cargo
DIRENV_DEVSHELL: all-features
# Get error output from nix that we can actually use
NIX_CONFIG: show-trace = true
# 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/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=
permissions:
packages: write
@@ -50,11 +53,14 @@ jobs:
name: Test
runs-on: ubuntu-latest
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
- name: Sync repository
uses: actions/checkout@v4
- name: Tag comparison check
if: startsWith(github.ref, 'refs/tags/v')
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
run: |
# Tag mismatch with latest repo tag check to prevent potential downgrades
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
@@ -65,8 +71,7 @@ jobs:
exit 1
fi
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- uses: nixbuild/nix-quick-install-action@v28
- name: Enable Cachix binary cache
run: |
@@ -74,20 +79,38 @@ jobs:
cachix use crane
cachix use nix-community
- name: Configure Magic Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v5.1.0
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/.lock') }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}-
# collect garbage until Nix store size (in bytes) is at most this number
# before trying to save a new cache
gc-max-store-size-linux: 2073741824
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}-
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
purge-last-accessed: 86400
# except the version with the `primary-key`, if it exists
purge-primary-key: never
# always save the cache
save-always: true
- 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
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/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
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
EOF
@@ -95,7 +118,7 @@ jobs:
- 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
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
direnv allow
nix develop .#all-features --command true
@@ -103,19 +126,16 @@ jobs:
run: |
bin/nix-build-and-cache ci
- uses: Swatinem/rust-cache@v2
- name: Run CI tests
run: |
direnv exec . engage > >(tee -a test_output.log)
- name: Sync Complement repository
uses: 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'
# the nix devshell sets $COMPLEMENT_SRC, so "/dev/null" is no-op
direnv exec . bin/complement "/dev/null" complement_test_logs.jsonl complement_test_results.jsonl > >(tee -a test_output.log)
cp -v -f result complement_oci_image.tar.gz
- name: Upload Complement OCI image
@@ -141,14 +161,7 @@ jobs:
- 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)
- name: Add Complement diff result to Job Summary
run: |
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
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_diff_output.log)
- name: Update Job Summary
if: success() || failure()
@@ -156,8 +169,14 @@ jobs:
if [ ${{ job.status }} == 'success' ]; then
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
else
echo '# CI failure' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
tail -n 20 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
tail -n 40 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
echo '```diff' >> $GITHUB_STEP_SUMMARY
tail -n 100 complement_diff_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
@@ -165,7 +184,6 @@ jobs:
name: Build
runs-on: ubuntu-latest
needs: tests
if: github.event.pull_request.draft != true
strategy:
matrix:
include:
@@ -175,29 +193,46 @@ jobs:
- name: Sync repository
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- uses: nixbuild/nix-quick-install-action@v28
- name: Install and enable Cachix binary cache
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Configure Magic Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v5.1.0
with:
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/*.nix', '**/.lock') }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}-
# collect garbage until Nix store size (in bytes) is at most this number
# before trying to save a new cache
gc-max-store-size-linux: 2073741824
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}-
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
purge-last-accessed: 86400
# except the version with the `primary-key`, if it exists
purge-primary-key: never
# always save the cache
save-always: true
- 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
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/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
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
EOF
@@ -205,15 +240,16 @@ jobs:
- 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
nix profile install --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 }}
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features
mkdir -v -p target/release/
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
cp -v -f result/bin/conduit target/release/conduwuit
@@ -240,7 +276,7 @@ jobs:
- name: Build OCI image ${{ matrix.target }}
run: |
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
- name: Upload OCI image ${{ matrix.target }}
@@ -255,20 +291,20 @@ jobs:
name: Docker publish
runs-on: ubuntu-latest
needs: build
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '')
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && github.event.pull_request.user.login != 'renovate[bot]'
env:
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
DOCKER_TAG: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
DOCKER_BRANCH: docker.io/${{ 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/${{ 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/${{ 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/${{ 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/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GHCR_BRANCH: ghcr.io/${{ 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') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
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 }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
+56 -34
View File
@@ -16,9 +16,16 @@ env:
TERM: ansi
# Publishing to my nix binary cache
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
# conduwuit.cachix.org
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
# 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, and use our binary caches for the earlier CI steps
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=
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
@@ -29,7 +36,6 @@ concurrency:
jobs:
docs:
name: Documentation and GitHub Pages
runs-on: ubuntu-latest
permissions:
@@ -48,49 +54,65 @@ jobs:
if: github.event_name != 'pull_request'
uses: actions/configure-pages@v5
- name: Install Nix (with flakes and nix-command enabled)
uses: cachix/install-nix-action@v27
- uses: nixbuild/nix-quick-install-action@v28
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v5.1.0
with:
nix_path: nixpkgs=channel:nixos-unstable
# restore and save a cache using this key
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/.lock') }}
# if there's no cache hit, restore a cache by this prefix
restore-prefixes-first-match: nix-${{ runner.os }}-
# collect garbage until Nix store size (in bytes) is at most this number
# before trying to save a new cache
gc-max-store-size-linux: 2073741824
# do purge caches
purge: true
# purge all versions of the cache
purge-prefixes: nix-${{ runner.os }}-
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
purge-last-accessed: 86400
# except the version with the `primary-key`, if it exists
purge-primary-key: never
# always save the cache
save-always: true
# Add `nix-community`, Crane, upstream Conduit, and conduwuit binary caches
extra_nix_config: |
experimental-features = nix-command flakes
extra-substituters = https://nix-community.cachix.org
extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=
extra-substituters = https://crane.cachix.org
extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=
extra-substituters = https://attic.kennel.juneis.dog/conduit
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
extra-substituters = https://attic.kennel.juneis.dog/conduwuit
extra-trusted-public-keys = conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=
- 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/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: Add alternative Nix binary caches if specified
- name: Use alternative Nix binary caches if specified
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
run: |
echo "extra-substituters = ${{ env.ATTIC_ENDPOINT }}" >> /etc/nix/nix.conf
echo "extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}" >> /etc/nix/nix.conf
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
EOF
- name: Pop/push Magic Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Configure `nix-direnv`
- name: Prepare build environment
run: |
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
- name: Install `direnv` and `nix-direnv`
run: nix-env -f "<nixpkgs>" -iA direnv -iA nix-direnv
# Do this to shorten the logs for the real CI step
- name: Populate `/nix/store`
run: nix develop --command true
- name: Allow direnv
run: direnv allow
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
direnv allow
nix develop --command true
- name: Cache CI dependencies
run: |
./bin/nix-build-and-cache ci
bin/nix-build-and-cache ci
- name: Run lychee and markdownlint
run: |
direnv exec . engage just lints lychee
direnv exec . engage just lints markdownlint
- name: Build documentation (book)
run: |
+2 -2
View File
@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v4
- name: Run Trivy code and vulnerability scanner on repo
uses: aquasecurity/trivy-action@0.22.0
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: repo
format: sarif
@@ -34,7 +34,7 @@ jobs:
severity: CRITICAL,HIGH,MEDIUM,LOW
- name: Run Trivy code and vulnerability scanner on filesystem
uses: aquasecurity/trivy-action@0.22.0
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: fs
format: sarif
+2 -2
View File
@@ -58,7 +58,7 @@ before_script:
ci:
stage: ci
image: nixos/nix:2.22.1
image: nixos/nix:2.23.3
script:
# Cache CI dependencies
- ./bin/nix-build-and-cache ci
@@ -83,7 +83,7 @@ ci:
artifacts:
stage: artifacts
image: nixos/nix:2.22.1
image: nixos/nix:2.23.3
script:
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl
- cp result/bin/conduit x86_64-unknown-linux-musl
+1
View File
@@ -0,0 +1 @@
.gitignore
+1 -2
View File
@@ -61,7 +61,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement over email at
strawberry@puppygock.gay or over Matrix at @strawberry:puppygock.gay.
<strawberry@puppygock.gay> or over Matrix at @strawberry:puppygock.gay.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
@@ -131,4 +131,3 @@ For answers to common questions about this code of conduct, see the FAQ at
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+4 -1
View File
@@ -1,6 +1,6 @@
# Contributing guide
This page is for about contributing to conduwuit. The [development](docs/development.md) page may be of interest for you as well.
This page is for about contributing to conduwuit. The [development](development.md) page may be of interest for you as well.
If you would like to work on an [issue][issues] that is not assigned, preferably ask in the Matrix room first at [#conduwuit:puppygock.gay][conduwuit-matrix], and comment on it.
@@ -19,6 +19,7 @@ To test, format, lint, etc that CI would do, install engage, allow the `.envrc`
All of the tasks are defined at the [engage.toml][engage.toml] file. You can view all of them neatly by running `engage list`
If you would like to run only a specific engage task group, use `just`:
- `engage just <group>`
- Example: `engage just lints`
@@ -37,6 +38,7 @@ The following binaries are used in [`engage.toml`][engage.toml]:
- [`cargo-audit`][cargo-audit]
- [`cargo-deb`][cargo-deb]
- [`lychee`][lychee]
- [`markdownlint-cli`][markdownlint-cli]
### Matrix tests
@@ -86,6 +88,7 @@ By sending a pull request or patch, you are agreeing that your changes are allow
[sytest]: https://github.com/matrix-org/sytest/
[cargo-deb]: https://github.com/kornelski/cargo-deb
[lychee]: https://github.com/lycheeverse/lychee
[markdownlint-cli]: https://github.com/igorshubovych/markdownlint-cli
[cargo-audit]: https://github.com/RustSec/rustsec/tree/main/cargo-audit
[direnv]: https://direnv.net/
[mdbook]: https://rust-lang.github.io/mdBook/
Generated
+586 -586
View File
File diff suppressed because it is too large Load Diff
+69 -65
View File
@@ -20,11 +20,14 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/girlbossceo/conduwuit"
rust-version = "1.77.0"
version = "0.4.2"
version = "0.4.5"
[workspace.metadata.crane]
name = "conduit"
[workspace.dependencies.const-str]
version = "0.5.7"
[workspace.dependencies.sanitize-filename]
version = "0.5.0"
@@ -50,7 +53,7 @@ version = "0.8.5"
# Used for the http request / response body type for Ruma endpoints used with reqwest
[workspace.dependencies.bytes]
version = "1.6.0"
version = "1.6.1"
[workspace.dependencies.http-body-util]
version = "0.1.1"
@@ -113,11 +116,11 @@ features = [
]
[workspace.dependencies.serde]
version = "1.0.203"
version = "1.0.204"
features = ["rc"]
[workspace.dependencies.serde_json]
version = "1.0.117"
version = "1.0.120"
features = ["raw_value"]
# Used for appservice registration files
@@ -159,6 +162,8 @@ default-features = false
[workspace.dependencies.tracing-subscriber]
version = "0.3.18"
features = ["env-filter"]
[workspace.dependencies.tracing-core]
version = "0.1.32"
# for URL previews
[workspace.dependencies.webpage]
@@ -167,7 +172,7 @@ default-features = false
# used for conduit's CLI and admin room command parsing
[workspace.dependencies.clap]
version = "4.5.4"
version = "4.5.9"
default-features = false
features = [
"std",
@@ -195,6 +200,9 @@ features = [
"io-util",
]
[workspace.dependencies.tokio-metrics]
version = "0.3.1"
[workspace.dependencies.libloading]
version = "0.8.3"
@@ -206,11 +214,11 @@ features = ["serde"]
# standard date and time tools
[workspace.dependencies.chrono]
version = "0.4.38"
features = ["alloc"]
features = ["alloc", "std"]
default-features = false
[workspace.dependencies.hyper]
version = "1.3.1"
version = "1.4.0"
features = [
"server",
"http1",
@@ -218,7 +226,7 @@ features = [
]
[workspace.dependencies.hyper-util]
version = "0.1.5"
version = "0.1.6"
features = [
"client",
"server-auto",
@@ -243,7 +251,7 @@ default-features = false
# Used for conduit::Error type
[workspace.dependencies.thiserror]
version = "1.0.61"
version = "1.0.62"
# Used when hashing the state
[workspace.dependencies.ring]
@@ -263,7 +271,7 @@ version = "2.1.1"
version = "0.3.1"
[workspace.dependencies.async-trait]
version = "0.1.80"
version = "0.1.81"
[workspace.dependencies.lru-cache]
version = "0.1.2"
@@ -271,15 +279,18 @@ version = "0.1.2"
# Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma]
git = "https://github.com/girlbossceo/ruwuma"
branch = "conduwuit-changes"
#branch = "conduwuit-changes"
rev = "c51ccb2c68d2e3557eb12b1a49036531711ec0e5"
features = [
"compat",
"rand",
"appservice-api-c",
"client-api",
"federation-api",
"markdown",
"push-gateway-api-c",
"state-res",
"server-util",
"unstable-exhaustive-types",
"ring-compat",
"unstable-unspecified",
@@ -289,6 +300,7 @@ features = [
"unstable-msc2870",
"unstable-msc3026",
"unstable-msc3061",
"unstable-msc3266",
"unstable-msc3575",
"unstable-msc4121",
"unstable-msc4125",
@@ -297,7 +309,7 @@ features = [
[workspace.dependencies.ruma-identifiers-validation]
git = "https://github.com/girlbossceo/ruwuma"
branch = "conduwuit-changes"
rev = "c51ccb2c68d2e3557eb12b1a49036531711ec0e5"
[workspace.dependencies.rust-rocksdb]
path = "deps/rust-rocksdb"
@@ -305,7 +317,6 @@ package = "rust-rocksdb-uwu"
features = [
"multi-threaded-cf",
"mt_static",
"snappy",
"lz4",
"zstd",
"zlib",
@@ -374,24 +385,6 @@ version = "0.5.4"
default-features = false
features = ["use_std"]
[workspace.dependencies.rusqlite]
git = "https://github.com/rusqlite/rusqlite"
#branch = "master"
rev = "e00b626e2b1c67347d789fb7f600281705c89381"
features = ["bundled"]
# used only by rusqlite
[workspace.dependencies.parking_lot]
version = "0.12.3"
# used only by rusqlite
[workspace.dependencies.thread_local]
version = "1.1.8"
[workspace.dependencies.tokio-metrics]
version = "0.3.1"
default-features = false
[workspace.dependencies.console-subscriber]
version = "0.3"
@@ -411,26 +404,49 @@ features = [
"light",
]
[workspace.dependencies.rustyline-async]
version = "0.4.2"
default-features = false
[workspace.dependencies.termimad]
version = "0.29.4"
default-features = false
[workspace.dependencies.checked_ops]
version = "0.1"
#
# Patches
#
# backport of [https://github.com/tokio-rs/tracing/pull/2956] to the 0.1.x branch of tracing.
# we can switch back to upstream if #2956 is merged and backported in the upstream repo.
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
[patch.crates-io.tracing-subscriber]
git = "https://github.com/girlbossceo/tracing"
branch = "tracing-subscriber/env-filter-clone-0.1.x-backport"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
[patch.crates-io.tracing]
git = "https://github.com/girlbossceo/tracing"
branch = "tracing-subscriber/env-filter-clone-0.1.x-backport"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
[patch.crates-io.tracing-core]
git = "https://github.com/girlbossceo/tracing"
branch = "tracing-subscriber/env-filter-clone-0.1.x-backport"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
[patch.crates-io.tracing-log]
git = "https://github.com/girlbossceo/tracing"
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
# fixes hyper graceful shutdowns [https://github.com/programatik29/axum-server/issues/114]
# https://github.com/girlbossceo/axum-server/commit/8e3368d899079818934e61cc9c839abcbbcada8a
[patch.crates-io.axum-server]
git = "https://github.com/girlbossceo/axum-server"
branch = "phlip9/fix-graceful-shutdown"
rev = "8e3368d899079818934e61cc9c839abcbbcada8a"
# 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
[patch.crates-io.rustyline-async]
git = "https://github.com/girlbossceo/rustyline-async"
rev = "de26100b0db03e419a3d8e1dd26895d170d1fe50"
#
# Our crates
@@ -721,7 +737,6 @@ nursery = "warn"
## some sadness
missing_const_for_fn = { level = "allow", priority = 1 } # TODO
needless_collect = { level = "allow", priority = 1 } # TODO
option_if_let_else = { level = "allow", priority = 1 } # TODO
redundant_pub_crate = { level = "allow", priority = 1 } # TODO
significant_drop_in_scrutinee = { level = "allow", priority = 1 } # TODO
@@ -731,34 +746,20 @@ significant_drop_tightening = { level = "allow", priority = 1 } # TODO
pedantic = "warn"
## some sadness
cast_possible_truncation = "allow"
cast_precision_loss = "allow"
cast_sign_loss = "allow"
doc_markdown = "allow"
error_impl_error = "allow"
expect_used = "allow"
if_not_else = "allow"
if_then_some_else_none = "allow"
implicit_return = "allow"
inline_always = "allow"
map_err_ignore = "allow"
missing_docs_in_private_items = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
mod_module_files = "allow"
module_name_repetitions = "allow"
multiple_inherent_impl = "allow"
no_effect_underscore_binding = "allow"
ref_patterns = "allow"
same_name_method = "allow"
doc_markdown = { level = "allow", priority = 1 }
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 }
missing_docs_in_private_items = { level = "allow", priority = 1 }
missing_errors_doc = { level = "allow", priority = 1 }
missing_panics_doc = { level = "allow", priority = 1 }
module_name_repetitions = { level = "allow", priority = 1 }
no_effect_underscore_binding = { level = "allow", priority = 1 }
similar_names = { level = "allow", priority = 1 }
single_call_fn = "allow"
string_add = "allow"
string_slice = "allow"
struct_field_names = { level = "allow", priority = 1 }
unnecessary_wraps = { level = "allow", priority = 1 }
unused_async = { level = "allow", priority = 1 }
unwrap_used = "allow"
###################
perf = "warn"
@@ -766,8 +767,10 @@ perf = "warn"
###################
#restriction = "warn"
#arithmetic_side_effects = "warn" # TODO
#as_conversions = "warn" # TODO
allow_attributes = "warn"
arithmetic_side_effects = "warn"
as_conversions = "warn"
as_underscore = "warn"
assertions_on_result_states = "warn"
dbg_macro = "warn"
default_union_representation = "warn"
@@ -781,7 +784,6 @@ fn_to_numeric_cast_any = "warn"
format_push_string = "warn"
get_unwrap = "warn"
impl_trait_in_params = "warn"
let_underscore_must_use = "warn"
let_underscore_untyped = "warn"
lossy_float_literal = "warn"
mem_forget = "warn"
@@ -795,6 +797,7 @@ rest_pat_in_fully_bound_structs = "warn"
semicolon_outside_block = "warn"
str_to_string = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
tests_outside_test_module = "warn"
@@ -805,6 +808,7 @@ unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unseparated_literal_suffix = "warn"
#unwrap_used = "warn" # TODO
verbose_file_reads = "warn"
###################
@@ -812,10 +816,10 @@ style = "warn"
## some sadness
# trivial assertions are quite alright
assertions_on_constants = "allow"
assertions_on_constants = { level = "allow", priority = 1 }
###################
suspicious = "warn"
## some sadness
let_underscore_future = "allow"
let_underscore_future = { level = "allow", priority = 1 }
+1
View File
@@ -43,6 +43,7 @@ from time to time.
#### Contact
If you run into any question, feel free to
- Ask us in `#conduwuit:puppygock.gay` on Matrix
- [Open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new)
+62
View File
@@ -0,0 +1,62 @@
[Unit]
Description=conduwuit Matrix homeserver
After=network.target
Documentation=https://conduwuit.puppyirl.gay/
RequiresMountsFor=/var/lib/private/conduwuit
[Service]
DynamicUser=yes
Type=notify
AmbientCapabilities=
CapabilityBoundingSet=
DevicePolicy=closed
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
ProcSubset=pid
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectProc=invisible
ProtectSystem=strict
PrivateDevices=yes
PrivateMounts=yes
PrivateTmp=yes
PrivateUsers=yes
PrivateIPC=yes
RemoveIPC=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service @resources
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
SystemCallErrorNumber=EPERM
StateDirectory=conduwuit
RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit
BindPaths=/var/lib/private/conduwuit:/var/lib/private/matrix-conduit
ExecStart=/usr/bin/conduwuit
Restart=on-failure
RestartSec=5
TimeoutStopSec=4m
TimeoutStartSec=4m
StartLimitInterval=1m
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
+13 -6
View File
@@ -7,7 +7,7 @@ set -euo pipefail
# The `COMPLEMENT_SRC` environment variable is set in the Nix dev shell, which
# points to a store path containing the Complement source code. It's likely you
# want to just pass that as the first argument to use it here.
COMPLEMENT_SRC="$1"
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
# A `.jsonl` file to write test logs to
LOG_FILE="$2"
@@ -17,11 +17,18 @@ RESULTS_FILE="$3"
OCI_IMAGE="complement-conduit:main"
# Complement tests that are skipped due to flakiness/reliability issues (likely
# Complement itself induced based on various open issues)
#
# According to Go docs, these are separated by forward slashes and not pipes (why)
SKIPPED_COMPLEMENT_TESTS='-skip=TestJumpToDateEndpoint.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestFederationRoomsInvite.*|TestClientSpacesSummary.*'
# Complement tests that are skipped due to flakiness/reliability issues
SKIPPED_COMPLEMENT_TESTS='-skip=TestClientSpacesSummary.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestJumpToDateEndpoint.*'
# $COMPLEMENT_SRC needs to be a directory to Complement source code
if [ -f "$COMPLEMENT_SRC" ]; then
echo "\$COMPLEMENT_SRC must be a directory/path to Complement source code"
exit 1
fi
# quick test to make sure we can actually write to $LOG_FILE and $RESULTS_FILE
touch $LOG_FILE && rm -v $LOG_FILE
touch $RESULTS_FILE && rm -v $RESULTS_FILE
toplevel="$(git rev-parse --show-toplevel)"
+8 -1
View File
@@ -52,7 +52,7 @@ just() {
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduwuit}" \
"$ATTIC_TOKEN"
# Upload them to Attic (conduwuit store)
# Upload them to Attic (conduwuit store) and Cachix
#
# Use `xargs` and a here-string because something would probably explode if
# several thousand arguments got passed to a command at once. Hopefully no
@@ -61,6 +61,12 @@ just() {
IFS=$'\n'
nix shell --inputs-from "$toplevel" attic -c xargs \
attic push conduwuit <<< "${cache[*]}"
# push to cachix if available
if [ "$CACHIX_AUTH_TOKEN" ]; then
nix shell --inputs-from "$toplevel" cachix -c xargs \
cachix push conduwuit <<< "${cache[*]}"
fi
)
}
@@ -73,6 +79,7 @@ ci() {
"$toplevel#devShells.x86_64-linux.default"
"$toplevel#devShells.x86_64-linux.all-features"
attic#default
cachix#default
nixpkgs#direnv
nixpkgs#jq
nixpkgs#nix-direnv
+7 -1
View File
@@ -1,13 +1,19 @@
[book]
title = "conduwuit"
title = "conduwuit 🏳️‍⚧️ 💜 🦴"
description = "conduwuit, which is a well-maintained fork of Conduit, is a simple, fast and reliable chat server for the Matrix protocol"
language = "en"
authors = ["strawberry (June)"]
text-direction = "ltr"
multilingual = false
src = "docs"
[build]
build-dir = "public"
create-missing = true
extra-watch-dirs = ["debian", "docs"]
[rust]
edition = "2021"
[output.html]
git-repository-url = "https://github.com/girlbossceo/conduwuit"
+56 -4
View File
@@ -57,6 +57,16 @@
# Defaults to 0.15
#sentry_traces_sample_rate = 0.15
# Whether to attach a stacktrace to Sentry reports.
#sentry_attach_stacktrace = false
# Send panics to sentry. This is true by default, but sentry has to be enabled.
#sentry_send_panic = true
# Send errors to sentry. This is true by default, but sentry has to be enabled. This option is
# only effective in release-mode; forced to false in debug-mode.
#sentry_send_error = true
### Database configuration
@@ -64,9 +74,7 @@
# Note: this was previously "/var/lib/matrix-conduit"
database_path = "/var/lib/conduwuit"
# Database backend: Only rocksdb and sqlite are supported. Please note that sqlite
# will perform significantly worse than rocksdb as it is not intended to be used the
# way it is by conduwuit. sqlite only exists for historical reasons.
# Database backend: Only rocksdb is supported.
database_backend = "rocksdb"
@@ -198,6 +206,11 @@ registration_token = "change this token for something specific to your server"
# defaults to false
# block_non_admin_invites = false
# Allows admins to enter commands in rooms other than #admins by prefixing with \!admin. The reply
# will be publicly visible to the room, originating from the sender.
# defaults to true
#admin_escape_commands = true
# List of forbidden username patterns/strings. Values in this list are matched as *contains*.
# This is checked upon username availability check, registration, and startup as warnings if any local users in your database
# have a forbidden username.
@@ -368,12 +381,51 @@ allow_profile_lookup_federation_requests = true
# If 'tracing_flame' is enabled, set the path to write the generated profile.
# tracing_flame_output_path = "./tracing.folded"
# Enable the tokio-console. This option is only relevant to developers.
# See: docs/development.md#debugging-with-tokio-console for more information.
#tokio_console = false
# Enable backward-compatibility with Conduit's media directory by creating symlinks of media. This
# option is only necessary if you plan on using Conduit again. Otherwise setting this to false
# reduces filesystem clutter and overhead for managing these symlinks in the directory.
#
# Enabled by default.
#media_compat_file_link = true
# Checks consistency of the media directory at startup:
# 1. When `media_compat_file_link` is enbled, this check will upgrade media when switching back
# and forth between Conduit and Conduwuit. Both options must be enabled to handle this.
# 2. When media is deleted from the directory, this check will also delete its database entry.
#
# If none of these checks apply to your use cases, and your media directory is significantly large
# setting this to false may reduce startup time.
#
# Enabled by default.
#media_statup_check = true
# OpenID token expiration/TTL in seconds
#
# These are the OpenID tokens that are primarily used for Matrix account integrations, *not* OIDC/OpenID Connect/etc
#
# Defaults to 3600 (1 hour)
#openid_token_ttl = 3600
# Emergency password feature. This password set here will let you login to the server service account (e.g. `@conduit`)
# and let you run admin commands, invite yourself to the admin room, etc.
#
# no default.
#emergency_password = ""
### Generic database options
# Set this to any float value to multiply conduwuit's in-memory LRU caches with.
# May be useful if you have significant memory to spare to increase performance.
#
# This was previously called `conduit_cache_capacity_modifier`
#
# Defaults to 1.0.
#conduit_cache_capacity_modifier = 1.0
#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-related caches.
# May be useful if you have significant memory to spare to increase performance.
+1 -1
View File
@@ -15,7 +15,7 @@ in `/etc/conduwuit/conduwuit.toml`.
### Running
The package uses the [`conduwuit.service`](../configuration.md#example-systemd-unit-file) systemd unit file to start and stop conduwuit. The binary is installed at `/usr/sbin/conduwuit`.
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop conduwuit. The binary is installed at `/usr/sbin/conduwuit`.
This package assumes by default that conduwuit will be placed behind a reverse proxy. The default config options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS, so you will need to set up TLS certificates and renewal for it to work properly if you intend to federate.
-1
View File
@@ -6,7 +6,6 @@ set -e
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
CONDUWUIT_CONFIG_FILE="${CONDUWUIT_CONFIG_PATH}/conduwuit.toml"
case "$1" in
configure)
+2 -2
View File
@@ -10,7 +10,7 @@ repository.workspace = true
version = "0.0.1"
[features]
default = ["snappy", "lz4", "zstd", "zlib", "bzip2"]
default = ["lz4", "zstd", "zlib", "bzip2"]
jemalloc = ["rust-rocksdb/jemalloc"]
io-uring = ["rust-rocksdb/io-uring"]
valgrind = ["rust-rocksdb/valgrind"]
@@ -27,7 +27,7 @@ malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
[dependencies.rust-rocksdb]
git = "https://github.com/zaidoon1/rust-rocksdb"
rev = "e9e1cb5ba92a44ea225fe8d13b31aa23621b9035"
rev = "4056a3b0f823013fec49f6d0b3e5698856e6476a"
#branch = "master"
default-features = false
+10 -9
View File
@@ -2,19 +2,20 @@
- [Introduction](introduction.md)
- [Differences from upstream Conduit](differences.md)
- [Example configuration](configuration.md)
- [Configuration](configuration.md)
- [Examples](configuration/examples.md)
- [Deploying](deploying.md)
- [Generic](deploying/generic.md)
- [NixOS](deploying/nixos.md)
- [Docker](deploying/docker.md)
- [Arch Linux](deploying/arch-linux.md)
- [Debian](deploying/debian.md)
- [Generic](deploying/generic.md)
- [NixOS](deploying/nixos.md)
- [Docker](deploying/docker.md)
- [Arch Linux](deploying/arch-linux.md)
- [Debian](deploying/debian.md)
- [TURN](turn.md)
- [Appservices](appservices.md)
- [Maintenance](maintenance.md)
- [Troubleshooting](troubleshooting.md)
- [Development](development.md)
- [Contributing](contributing.md)
- [Testing](development/testing.md)
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
- [Contributing](contributing.md)
- [Testing](development/testing.md)
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
- [conduwuit Community Code of Conduct](conduwuit_coc.md)
+2 -2
View File
@@ -18,7 +18,7 @@ First, go into the `#admins` room of your homeserver. The first person that
registered on the homeserver automatically joins it. Then send a message into
the room like this:
@conduit:your.server.name: register-appservice
!admin appservices register
```
paste
the
@@ -33,7 +33,7 @@ the room like this:
You can confirm it worked by sending a message like this:
`!admin appservices list`
The `@conduit` bot should answer with `Appservices (1): your-bridge`
The server bot should answer with `Appservices (1): your-bridge`
Then you are done. conduwuit will send messages to the appservices and the
appservice can send requests to the homeserver. You don't need to restart
+4 -4
View File
@@ -1,6 +1,6 @@
# Conduwuit Community Code of Conduct
# conduwuit Community Code of Conduct
Welcome to the conduwuit community! Were excited to have you here. Conduwuit is a hard-fork of the Conduit homeserver,
Welcome to the conduwuit community! Were excited to have you here. conduwuit is a hard-fork of the Conduit homeserver,
aimed at making Matrix more accessible and inclusive for everyone.
This space is dedicated to fostering a positive, supportive, and inclusive environment for everyone. This Code of
@@ -39,7 +39,7 @@ additional guidelines specific to the conduwuit community.
## Matrix Community
This Code of Conduct applies to the entire [Conduwuit Matrix Space](https://matrix.to/#/#conduwuit-space:puppygock.gay)
This Code of Conduct applies to the entire [conduwuit Matrix Space](https://matrix.to/#/#conduwuit-space:puppygock.gay)
and its rooms, including:
### [#conduwuit:puppygock.gay](https://matrix.to/#/#conduwuit:puppygock.gay)
@@ -74,4 +74,4 @@ a DM to one of the moderators directly.
Together, lets build a community where everyone feels valued and respected.
- The Conduwuit Moderation Team
- The conduwuit Moderation Team
+15 -8
View File
@@ -1,11 +1,18 @@
# Example configuration
# Configuration
``` toml
{{#include ../conduwuit-example.toml}}
```
This chapter describes various ways to configure conduwuit.
# Example systemd unit file
## Basics
```
{{#include ../debian/conduwuit.service}}
```
Conduwuit uses a config file for the majority of the settings. Please refer to the
[example config file](./configuration/examples.md#example-configuration) for all of those settings.
The config file to use can either be specified on the command line when running conduwuit by specifying the
`-c`, `--config` flag. Alternatively, you can use the environment variable `CONDUWUIT_CONFIG` to specify the config
file to used.
## Environment variables
All of the settings that are found in the config file can be specified by using environment variables.
The environment variable names should be all caps and prefixed with `CONDUWUIT_`.
For example, if the setting you are changing is `max_request_size`, then the environment variable to set is
`CONDUWUIT_MAX_REQUEST_SIZE`.
+32
View File
@@ -0,0 +1,32 @@
## Example configuration
<details>
<summary>Example configuration</summary>
```toml
{{#include ../../conduwuit-example.toml}}
```
</details>
## Debian systemd unit file
<details>
<summary>Debian systemd unit file</summary>
```
{{#include ../../debian/conduwuit.service}}
```
</details>
## Arch Linux systemd unit file
<details>
<summary>Arch Linux systemd unit file</summary>
```
{{#include ../../arch/conduwuit.service}}
```
</details>
+1 -1
View File
@@ -1,3 +1,3 @@
# Deploying
This chapter describes various ways to deploy Conduwuit.
This chapter describes various ways to deploy conduwuit.
+1
View File
@@ -6,3 +6,4 @@ The conduwuit AUR packages are community maintained and are not maintained by co
- [conduwuit](https://aur.archlinux.org/packages/conduwuit) - latest tagged conduwuit
- [conduwuit-git](https://aur.archlinux.org/packages/conduwuit-git) - latest git conduwuit from `main` branch
- [conduwuit-bin](https://aur.archlinux.org/packages/conduwuit-bin) - latest tagged conduwuit static binary
@@ -1,5 +1,4 @@
# conduwuit - Behind Traefik Reverse Proxy
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
@@ -24,7 +23,7 @@ services:
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUWUIT_LOG: warn,state_res=warn
CONDUWUIT_ADDRESS: 0.0.0.0
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
@@ -1,5 +1,4 @@
# conduwuit - Traefik Reverse Proxy Labels
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
@@ -0,0 +1,54 @@
services:
caddy:
# This compose file uses caddy-docker-proxy as the reverse proxy for conduwuit!
# For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
image: lucaslorentz/caddy-docker-proxy:ci-alpine
ports:
- 80:80
- 443:443
environment:
- CADDY_INGRESS_NETWORKS=caddy
networks:
- caddy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
restart: unless-stopped
labels:
caddy: example.com
caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}
homeserver:
### If you already built the conduwuit image with 'docker build' or want to use a registry image,
### then you are ready to go.
image: girlbossceo/conduwuit:latest
restart: unless-stopped
volumes:
- db:/var/lib/conduwuit
#- ./conduwuit.toml:/etc/conduwuit.toml
environment:
CONDUWUIT_SERVER_NAME: example.com # EDIT THIS
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
CONDUWUIT_DATABASE_BACKEND: rocksdb
CONDUWUIT_PORT: 6167
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
CONDUWUIT_ALLOW_REGISTRATION: 'true'
CONDUWUIT_ALLOW_FEDERATION: 'true'
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUWUIT_LOG: warn,state_res=warn
CONDUWUIT_ADDRESS: 0.0.0.0
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
networks:
- caddy
labels:
caddy: matrix.example.com
caddy.reverse_proxy: "{{upstreams 6167}}"
volumes:
db:
networks:
caddy:
external: true
@@ -1,5 +1,4 @@
# conduwuit - Behind Traefik Reverse Proxy
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
@@ -16,7 +15,7 @@ services:
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
CONDUWUIT_ALLOW_REGISTRATION : 'true'
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
### Uncomment and change values as desired
# CONDUWUIT_ADDRESS: 0.0.0.0
# CONDUWUIT_PORT: 6167
@@ -28,7 +27,6 @@ services:
# CONDUWUIT_DATABASE_PATH: /srv/conduwuit/.local/share/conduwuit
# CONDUWUIT_WORKERS: 10
# CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
+1 -3
View File
@@ -1,5 +1,4 @@
# conduwuit
version: '2.4' # uses '2.4' for cpuset
services:
homeserver:
@@ -24,8 +23,7 @@ services:
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUWUIT_LOG: warn,state_res=warn
CONDUWUIT_ADDRESS: 0.0.0.0
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
#
### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second
+17 -13
View File
@@ -4,7 +4,6 @@
To run conduwuit with Docker you can either build the image yourself or pull it from a registry.
### Use a registry
OCI images for conduwuit are available in the registries listed below.
@@ -24,13 +23,13 @@ OCI images for conduwuit are available in the registries listed below.
[shield-latest]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/latest
[shield-main]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/main
Use
```bash
docker image pull <link>
```
to pull it to your machine.
to pull it to your machine.
### Run
@@ -39,19 +38,15 @@ When you have the image you can simply run it with
```bash
docker run -d -p 8448:6167 \
-v db:/var/lib/conduwuit/ \
-e CONDUIT_SERVER_NAME="your.server.name" \
-e CONDUIT_DATABASE_BACKEND="rocksdb" \
-e CONDUIT_ALLOW_REGISTRATION=false \
-e CONDUIT_ALLOW_FEDERATION=true \
-e CONDUIT_MAX_REQUEST_SIZE="40000000" \
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
-e CONDUIT_LOG="warn,ruma_state_res=warn" \
-e CONDUWUIT_SERVER_NAME="your.server.name" \
-e CONDUWUIT_DATABASE_BACKEND="rocksdb" \
-e CONDUWUIT_ALLOW_REGISTRATION=false \
--name conduit <link>
```
or you can use [docker compose](#docker-compose).
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, the example config can be found [here](../configuration.md).
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, the example config can be found [here](../configuration/examples.md).
You can pass in different env vars to change config values on the fly. You can even configure conduwuit completely by using env vars. For an overview of possible
values, please take a look at the [`docker-compose.yml`](docker-compose.yml) file.
@@ -62,14 +57,24 @@ If you just want to test conduwuit for a short time, you can use the `--rm` flag
If the `docker run` command is not for you or your setup, you can also use one of the provided `docker-compose` files.
Depending on your proxy setup, you can use one of the following files;
- If you already have a `traefik` instance set up, use [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml)
- If you don't have a `traefik` instance set up (or any other reverse proxy), use [`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
- If you don't have a `traefik` instance set up and would like to use it, use [`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
- If you want a setup that works out of the box with `caddy-docker-proxy`, use [`docker-compose.with-caddy.yml`](docker-compose.with-caddy.yml) and replace all `example.com` placeholders with your own domain
- For any other reverse proxy, use [`docker-compose.yml`](docker-compose.yml)
When picking the traefik-related compose file, rename it so it matches `docker-compose.yml`, and
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
for your server.
When picking the `caddy-docker-proxy` compose file, it's important to first create the `caddy` network before spinning up the containers:
```bash
docker network create caddy
```
After that, you can rename it so it matches `docker-compose.yml` and spin up the containers!
Additional info about deploying conduwuit can be found [here](generic.md).
### Build
@@ -107,7 +112,6 @@ either expose ports `443` and `8448` or serve two endpoints `.well-known/matrix/
With the service `well-known` we use a single `nginx` container that will serve those two files.
## Voice communication
See the [TURN](../turn.md) page.
+11 -13
View File
@@ -23,7 +23,7 @@ Otherwise, follow standard Rust project build guides (installing git and cloning
While conduwuit can run as any user it is better to use dedicated users for different services. This also allows
you to make sure that the file permissions are correctly set up.
In Debian or RHEL, you can use this command to create a conduwuit user:
In Debian or Fedora/RHEL, you can use this command to create a conduwuit user:
```bash
sudo adduser --system conduwuit --group --disabled-login --no-create-home
@@ -43,23 +43,21 @@ If conduwuit runs behind a router or in a container and has a different public I
## Setting up a systemd service
The systemd unit for conduwuit can be found [here](../configuration.md#example-systemd-unit-file). You may need to change the `ExecStart=` path to where you placed the conduwuit binary.
The systemd unit for conduwuit can be found [here](../configuration/examples.md#example-systemd-unit-file). You may need to change the `ExecStart=` path to where you placed the conduwuit binary.
## Creating the conduwuit configuration file
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. The example config can be found at [conduwuit-example.toml](../configuration.md).**Please take a moment to read it. You need to change at least the server name.**
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. The example config can be found at [conduwuit-example.toml](../configuration/examples.md).**Please take a moment to read it. You need to change at least the server name.**
RocksDB is the only supported database backend. SQLite only exists for historical reasons, is not recommended, and will be removed soon (likely in v0.5.0). Any performance issues, storage issues, database issues, etc will not be assisted if using SQLite and you will be asked to migrate to RocksDB first.
RocksDB is the only supported database backend.
## Setting the correct file permissions
If you are using a dedicated user for conduwuit, you will need to allow it to read the config. To do that you can run this command on
Debian or RHEL:
If you are using a dedicated user for conduwuit, you will need to allow it to read the config. To do that you can run this:
```bash
sudo chown -R root:root /etc/conduwuit
sudo chmod 755 /etc/conduwuit
sudo chmod -R 755 /etc/conduwuit
```
If you use the default database path you also need to run this:
@@ -93,28 +91,28 @@ your.server.name, your.server.name:8448 {
That's it! Just start and enable the service and you're set.
```bash
$ sudo systemctl enable --now caddy
sudo systemctl enable --now caddy
```
## You're done!
## You're done
Now you can start conduwuit with:
```bash
$ sudo systemctl start conduwuit
sudo systemctl start conduwuit
```
Set it to start automatically when your system boots with:
```bash
$ sudo systemctl enable conduwuit
sudo systemctl enable conduwuit
```
## How do I know it works?
You can open [a Matrix client](https://matrix.org/ecosystem/clients), enter your homeserver and try to register.
You can also use these commands as a quick health check.
You can also use these commands as a quick health check (replace `your.server.name`).
```bash
$ curl https://your.server.name/_conduwuit/server_version
+2 -1
View File
@@ -8,6 +8,7 @@ conduwuit can be acquired by [Lix][lix] from various places:
A binary cache for conduwuit that the CI/CD publishes to is available at the
following places (both are the same just different names):
```
https://attic.kennel.juneis.dog/conduit
conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
@@ -30,7 +31,7 @@ The `flake.nix` and `default.nix` do not (currently) provide a NixOS module, so
(for now) [`services.matrix-conduit`][module] from Nixpkgs should be used to
configure conduwuit.
If you want to run the latest code, you should get Conduwuit from the `flake.nix`
If you want to run the latest code, you should get conduwuit from the `flake.nix`
or `default.nix` and set [`services.matrix-conduit.package`][package]
appropriately.
+2 -2
View File
@@ -7,7 +7,7 @@ it, you can safely ignore this section. If you plan on contributing, see the
## Debugging with `tokio-console`
[`tokio-console`][1] can be a useful tool for debugging and profiling. To make
a `tokio-console`-enabled build of Conduwuit, enable the `tokio_console` feature,
a `tokio-console`-enabled build of conduwuit, enable the `tokio_console` feature,
disable the default `release_max_log_level` feature, and set the
`--cfg tokio_unstable` flag to enable experimental tokio APIs. A build might
look like this:
@@ -16,7 +16,7 @@ look like this:
RUSTFLAGS="--cfg tokio_unstable" cargo build \
--release \
--no-default-features \
--features=rocksdb,systemd,element_hacks,sentry_telemetry,gzip_compression,brotli_compression,zstd_compression,tokio_console
--features=systemd,element_hacks,gzip_compression,brotli_compression,zstd_compression,tokio_console
```
[1]: https://docs.rs/tokio-console/latest/tokio_console/
+3
View File
@@ -65,14 +65,17 @@ When a symbol is referenced between crates they become bound: **crates cannot be
Proper resource management is essential for reliable reloading to occur. This is a very basic ask in RAII-idiomatic Rust and the exposure to reloading hazards is remarkably low, generally stemming from poor patterns and practices. Unfortunately static analysis doesn't enforce reload-safety programmatically (though it could one day), for now hazards can be avoided by knowing a few basic do's and dont's:
1. Understand that code is memory. Just like one is forbidden from referencing free'd memory, one must not transfer control to free'd code. Exposure to this is primarily from two things:
- Callbacks, which this project makes very little use of.
- Async tasks, which are addressed below.
2. Tie all resources to a scope or object lifetime with greatest possible symmetry (locality). For our purposes this applies to code resources, which means async blocks and tokio tasks.
- **Never spawn a task without receiving and storing its JoinHandle**.
- **Always wait on join handles** before leaving a scope or in another cleanup function called by an owning scope.
3. Know any minor specific quirks documented in code or here:
- Don't use `tokio::spawn`, instead use our `Handle` in `core/server.rs`, which is reachable in most of the codebase via `services()` or other state. This is due to some bugs or assumptions made in tokio, as it happens in `unsafe {}` blocks, which are mitigated by circumventing some thread-local variables. Using runtime handles is good practice in any case.
The initial implementation PR is available [here][1].
+81 -44
View File
@@ -1,65 +1,82 @@
#### **Note: This list may not up to date. There are rapidly more and more improvements, fixes, changes, etc being made that it is becoming more difficult to maintain this list. I recommend that you give conduwuit a try and see the differences for yourself. If you have any concerns, feel free to join the conduwuit Matrix room and ask any pre-usage questions.**
### list of features, bug fixes, etc that conduwuit does that Conduit does not:
### list of features, bug fixes, etc that conduwuit does that Conduit does not
Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
## Performance:
- Concurrency support for key fetching for faster remote room joins and room joins that will error less frequently
## Performance
- Concurrency support for individual homeserver key fetching for faster remote room joins and room joins that will error less frequently
- Send `Cache-Control` response header with `immutable` and 1 year cache length for all media requests (download and thumbnail) to instruct clients to cache media, and reduce server load from media requests that could be otherwise cached
- Add feature flags and config options to enable/build with zstd, brotli, and/or gzip HTTP body compression (response and request)
- Eliminate all usage of the thread-blocking `getaddrinfo(3)` call upon DNS queries, significantly improving federation latency/ping and cache DNS results (NXDOMAINs, successful queries, etc) using hickory-dns / hickory-resolver
- Enable HTTP/2 support on all requests
- Vastly improve RocksDB default settings to use new features that help with performance significantly, uses settings tailored to SSDs, various ways to tweak RocksDB, and a conduwuit setting to tell RocksDB to use settings that are tailored to HDDs or slow spinning rust storage or buggy filesystems.
- Add a Cargo build profile for aggressive build-time performance optimisations for release builds (1 codegen unit, no debug, fat LTO, etc, and optimise all crates with same)
- Implement database flush and cleanup conduwuit operations when using RocksDB
- Implement RocksDB write buffer corking and coalescing in database write-heavy areas
- Perform connection pooling and keepalives where necessary to significantly improve federation performance and latency
- Various config options to tweak connection pooling, request timeouts, connection timeouts, DNS timeouts and settings, etc with good defaults which also help huge with performance via reusing connections and retrying where needed
- Implement building conduwuit with jemalloc (which extends to the RocksDB jemalloc feature for maximum gains) or hardened_malloc light variant, and produce CI builds with jemalloc for performance (Nix doesn't seem to build [hardened_malloc-rs](https://github.com/girlbossceo/hardened_malloc-rs) properly)
- Add support for caching DNS results with hickory-dns / hickory-resolver in conduwuit (not a replacement for a proper resolver cache, but still far better than nothing)
- Properly get and use the amount of parallelism / tokio workers
- Implement building conduwuit with jemalloc (which extends to the RocksDB jemalloc feature for maximum gains) or hardened_malloc light variant, and io_uring support, and produce CI builds with jemalloc and io_uring by default for performance (Nix doesn't seem to build [hardened_malloc-rs](https://github.com/girlbossceo/hardened_malloc-rs) properly)
- Add support for caching DNS results with hickory-dns / hickory-resolver in conduwuit (not a replacement for a proper resolver cache, but still far better than nothing), also properly falls back on TCP for UDP errors or if a SRV response is too large
- Add config option for using DNS over TCP, and config option for controlling A/AAAA record lookup strategy (e.g. don't query AAAA records if you don't have IPv6 connectivity)
- Overall significant database, Client-Server, and federation performance and latency improvements (check out the ping room leaderboards if you don't believe me :>)
- Add config options for RocksDB compression and bottommost compression, including choosing the algorithm and compression level
- Use [loole](https://github.com/mahdi-shojaee/loole) MPSC channels instead of tokio MPSC channels for huge performance boosts in sending channels (mainly relevant for federation) and presence channels
- Use `tracing`/`log`'s `release_max_level_info` feature to improve performance, build speeds, binary size, and CPU usage in release builds by avoid compiling debug/trace log level macros that users will generally never use (can be disabled with a build-time feature flag)
- Enable RocksDB async read I/O via `io_uring` by default
- Remove some unnecessary checks on EDU handling for incoming transactions, effectively speeding them up
- Simplify, dedupe, etc huge chunks of the codebase, including some that were unnecessary overhead, binary bloats, or preventing compiler/linker optimisations
- Implement zero-copy RocksDB database accessors, substantially improving performance caused by unnecessary memory allocations
## General Fixes/Features
## General Fixes:
- Add legacy Element client hack fixing password changes and deactivations on legacy Element Android/iOS due to usage of an unspecced `user` field for UIAA
- Raise and improve all the various request timeouts making some things like room joins and client bugs error less or none at all than they should, and make them all user configurable
- Add missing `reason` field to user ban events (`/ban`)
- Fixed spec compliance issue with room version 8 - 11 joins (https://github.com/matrix-org/synapse/issues/16717 / https://github.com/matrix-org/matrix-spec/issues/1708)
- Safer and cleaner shutdowns on both database side as we run cleanup on shutdown and exits database loop better (no potential hanging issues in database loop), overall cleaner shutdown logic
- Safer and cleaner shutdowns across incoming/outgoing requests (graceful shutdown) and the database
- Stop sending `make_join` requests on room joins if 15 servers respond with `M_UNSUPPORTED_ROOM_VERSION` or `M_INVALID_ROOM_VERSION`
- Stop sending `make_join` requests if 50 servers cannot provide `make_join` for us
- Respect *most* client parameters for `/media/` requests (`allow_redirect` still needs work)
- Increased graceful shutdown timeout from a low 60 seconds to 180 seconds to avoid killing connections and let the remaining ones finish processing
- Return joined member count of rooms for push rules/conditions instead of a hardcoded value of 10
- Make `CONDUIT_CONFIG` optional, relevant for container users that configure only by environment variables and no longer need to set `CONDUIT_CONFIG` to an empty string.
- Allow HEAD and PATCH (MSC4138) HTTP requests in CORS for clients (despite not being explicity mentioned in Matrix spec, HTTP spec says all HEAD requests need to behave the same as GET requests, Synapse supports HEAD requests)
- Fix using conduwuit with flake-compat on NixOS
- Resolve and remove some "features" from upstream that result in concurrency hazards, exponential backoff issues, or arbitrary performance limiters
- Find more servers for outbound federation `/hierarchy` requests instead of just the room ID server name
- Support for suggesting servers to join through at `/_matrix/client/v3/directory/room/{roomAlias}`
- Support for suggesting servers to join through us at `/_matrix/federation/v1/query/directory`
- Add workaround for [Out Of Your Element](https://gitdab.com/cadence/out-of-your-element) appservice bridge to make it functional on conduwuit (bug has already been reported)
- Misc edge-case search fixes (e.g. potentially missing some events)
- Misc `/sync` fixes (e.g. returning unnecessary data or incorrect/invalid responses)
- Add `replaces_state` and `prev_sender` in `unsigned` for state event changes which primarily makes Element's "See history" button on a state event functional
- Fix Conduit not allowing incoming federation requests for various world readable rooms
- Fix Conduit not respecting the client-requested file name on media requests
- Prevent sending junk / non-membership events to `/send_join` and `/send_leave` endpoints
- Only allow the requested membership type on `/send_join` and `/send_leave` endpoints (e.g. don't allow leave memberships on join endpoints)
- Prevent state key impersonation on `/send_join` and `/send_leave` endpoints
- Validate `X-Matrix` origin and request body `"origin"` field on incoming transactions
- Add `GET /_matrix/client/v1/register/m.login.registration_token/validity` endpoint
- Explicitly define support for sliding sync at `/_matrix/client/versions` (`org.matrix.msc3575`)
- Fix seeing empty status messages on user presences
## Moderation
## Moderation:
- (Also see [Admin Room](#admin-room) for all the admin commands pertaining to moderation, there's a lot!)
- Add support for room banning/blocking by ID using admin command
- Add support for serving `support` well-known from `[well_known.support]` (MSC1929)
- Add support for serving `support` well-known from `[global.well_known]` (MSC1929) (`/.well-known/matrix/support`)
- Config option to forbid publishing rooms to the room directory (`lockdown_public_room_directory`) except for admins
- Admin commands to delete room aliases and unpublish rooms from our room directory
- For all [`/report`](https://spec.matrix.org/v1.9/client-server-api/#post_matrixclientv3roomsroomidreporteventid) requests: check if the reported event ID belongs to the reported room ID, raise report reasoning character limit to 750, fix broken formatting, make a small delayed random response per spec suggestion on privacy, and check if the sender user is in the reported room.
- For all [`/report`](https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3roomsroomidreporteventid) requests: check if the reported event ID belongs to the reported room ID, raise report reasoning character limit to 750, fix broken formatting, make a small delayed random response per spec suggestion on privacy, and check if the sender user is in the reported room.
- Support blocking servers from downloading remote media from, returning a 404
- Don't allow `m.call.invite` events to be sent in public rooms (prevents calling the entire room)
- On new public room creations, only allow moderators to send `m.call.invite`, `org.matrix.msc3401.call`, and `org.matrix.msc3401.call.member` events
- On new public room creations, only allow moderators to send `m.call.invite`, `org.matrix.msc3401.call`, and `org.matrix.msc3401.call.member` events to prevent unprivileged users from calling the entire room
- Add support for a "global ACLs" feature (`forbidden_remote_server_names`) that blocks inbound remote room invites, room joins by room ID on server name, room joins by room alias on server name, incoming federated joins, and incoming federated room directory requests. This is very helpful for blocking servers that are purely toxic/bad and serve no value in allowing our users to suffer from things like room invite spam or such. Please note that this is not a substitute for room ACLs.
- Add support for a config option to forbid our local users from sending federated room directory requests for (`forbidden_remote_room_directory_server_names`). Similar to above, useful for blocking servers that help prevent our users from wandering into bad areas of Matrix via room directories of those malicious servers.
- Add config option for auto remediating/deactivating local non-admin users who attempt to join bad/forbidden rooms (`auto_deactivate_banned_room_attempts`)
- Deactivating users will remove their profile picture, blurhash, display name, and leave all rooms by default just like Synapse and for additional privacy
- Reject some EDUs from ACL'd users such as read receipts and typing indicators
## Privacy/Security
## Privacy/Security:
- Add config option for device name federation with a privacy-friendly default (disabled)
- Add config option for requiring authentication to the `/publicRooms` endpoint (room directory) with a default enabled for privacy
- Add config option for federating `/publicRooms` endpoint (room directory) to other servers with a default disabled for privacy
@@ -69,48 +86,59 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
- Config option to block non-admin users from sending room invites or receiving remote room invites. Admin users are still allowed.
- Config option to disable incoming and/or outgoing remote read receipts
- Config option to disable incoming and/or outgoing remote typing indicators
- Config option to disable incoming, outgoing, and/or local presence
- Config option to disable incoming, outgoing, and/or local presence and for timing out remote users
- Sanitise file names for the `Content-Disposition` header for all media requests (thumbnails, downloads, uploads)
- Return `inline` or `attachment` based on the detected file MIME type for the `Content-Disposition` and only allow images/videos/text/audio to be `inline`
- Send secure default HTTP headers such as a strong restrictive CSP, deny iframes, disable `X-XSS-Protection`, disable interest cohort in `Permission-Policy`, etc to mitigate any potential attack surface such as from untrusted media
- Media repository on handling `Content-Disposition` and `Content-Type` is fully spec compliant and secured
- Send secure default HTTP headers such as a strong restrictive CSP (see MSC4149), deny iframes, disable `X-XSS-Protection`, disable interest cohort in `Permission-Policy`, etc to mitigate any potential attack surface such as from untrusted media
## Administration/Logging
## Administration/Logging:
- Commandline argument to specify the path to a config file instead of relying on `CONDUIT_CONFIG`
- Revamped admin room infrastructure and commands
- Substantially clean up, improve, and fix logging (less noisy dead server logging, registration attempts, more useful troubleshooting logging, proper error propagation, etc)
- Configurable RocksDB logging (`LOG` files) with proper defaults (rotate, max size, verbosity, etc) to stop LOG files from accumulating so much
- Explicit startup error if your configuration allows open registration without a token or such like Synapse with a way to bypass it if needed
- Replace the lightning bolt emoji option with support for setting any arbitrary text (e.g. another emoji) to suffix to all new user registrations, with a conduwuit default of 🏳️‍⚧️
- Replace the lightning bolt emoji option with support for setting any arbitrary text (e.g. another emoji) to suffix to all new user registrations, with a conduwuit default of "🏳️‍⚧️"
- Implement config option to auto join rooms upon registration
- Warn on unknown config options specified
- Add `/_conduwuit/server_version` route to return the version of conduwuit without relying on the federation API `/_matrix/federation/v1/version`
- Add `/_conduwuit/local_user_count` route to return the amount of registered active local users on your homeserver *if federation is enabled*
- Add configurable RocksDB recovery modes to aid in recovering corrupted RocksDB databases
- Support config options via `CONDUWUIT_` prefix and accessing non-global struct config options with the `__` split (e.g. `CONDUWUIT_WELL_KNOWN__SERVER`)
- Add support for listening on multiple TCP ports
- Disable update check by default as it's not useful for conduwuit
- Add support for listening on multiple TCP ports and multiple addresses
- **Opt-in** Sentry.io telemetry and metrics, mainly used for crash reporting
- Log the client IP on various requests such as registrations, banned room join attempts, logins, deactivations, federation transactions, etc
- Fix Conduit dropping some remote server federation response errors
## Maintenance/Stability
## Maintenance/Stability:
- GitLab CI ported to GitHub Actions
- Repo is mirrored to GitHub, GitLab, git.gay, git.girlcock.ceo, sourcehut, and Codeberg (see README.md for their links)
- Add support for the Matrix spec compliance test suite [Complement](https://github.com/matrix-org/complement/) via the Nix flake and various other fixes for it
- Implement running and diff'ing Complement results in CI and error if any mismatch occurs to prevent large cases of conduwuit regressions
- Repo is (officially) mirrored to GitHub, GitLab, git.gay, git.girlcock.ceo, sourcehut, and Codeberg (see README.md for their links)
- Docker container images published to GitLab Container Registry, GitHub Container Registry, and Dockerhub
- Extensively revamp the example config to be extremely helpful and useful to both new users and power users
- Fixed every single clippy (default lints) and rustc warnings, including some that were performance related or potential safety issues / unsoundness
- Add a **lot** of other clippy and rustc lints and a rustfmt.toml file
- Has [Renovate](https://docs.renovatebot.com/), [Trivy](https://github.com/aquasecurity/trivy-action), and keeps ALL dependencies as up to date as possible
- Attempts and interest in removing extreme and unnecessary panics/unwraps/expects that can lead to denial of service or such (upstream and upstream contributors want this unusual behaviour for some reason)
- Repo uses [Renovate](https://docs.renovatebot.com/), [Trivy](https://github.com/aquasecurity/trivy-action), and keeps ALL dependencies as up to date as possible
- Purge unmaintained/irrelevant/broken database backends (heed, sled, persy) and other unnecessary code or overhead
- webp support for images
- Add cargo audit support to CI
- CI tests with all features
- Add timestamp by commit date support to building OCI images for keeping image build reproducibility and still have a meaningful "last modified date" for OCI image metadata
- Update rusqlite/sqlite (not that you should be using it)
- Add documentation lints via lychee and markdownlint-cli to CI
- CI tests for all sorts of feature matrixes (jemalloc, non-defaullt, all features, etc)
- Add static and dynamic linking smoke tests in CI to prevent any potential linking regressions for Complement, static binaries, Nix devshells, etc
- Add timestamp by commit date when building OCI images for keeping image build reproducibility and still have a meaningful "last modified date" for OCI image
- Add timestamp by commit date via `SOURCE_DATE_EPOCH` for Debian packages
- Startup check if conduwuit running in a container and is listening on 127.0.0.1 (generally containers are using NAT networking and 0.0.0.0 is the intended listening address)
- Add a panic catcher layer to return panic messages in HTTP responses if a panic occurs
- Add full compatibility support for SHA256 media file names instead of base64 file names to overcome filesystem file name length limitations (OS error file name too long) while still retaining upstream database compatibility
- Remove SQLite support due to being very poor performance, difficult to maintain against RocksDB, and is a blocker to significantly improved database code
## Admin Room
## Admin Room:
- Add support for a console CLI interface that can issue admin commands and output them in your terminal
- Add support for an admin-user-only commandline admin room interface that can be issued in any room with the `\\!admin` or `\!admin` prefix and returns the response as yourself in the same room
- Add admin commands for uptime, server startup, server shutdown, and server restart
- Fix admin room handler to not panic/crash if the admin room command response fails (e.g. too large message)
- Add command to dynamically change conduwuit's tracing log level filter on the fly
- Add admin command to fetch a server's `/.well-known/matrix/support` file
@@ -124,20 +152,28 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
- Add admin command to bulk delete media via a codeblock list of MXC URLs.
- Add admin command to delete both the thumbnail and media MXC URLs from an event ID (e.g. from an abuse report)
- Add admin command to list all the rooms a local user is joined in
- Add admin command to delete all remote media in the past X minutes as a form of deleting media that you don't want on your server that a remote user posted in a room
- Add admin command to return a room's state
- Add admin command to list joined members in a room
- Add admin command to view the room topic of a room
- Add admin command to delete all remote media in the past X minutes as a form of deleting media that you don't want on your server that a remote user posted in a room, a `--force` flag to ignore errors, and support for reading `last modified time` instead of `creation time` for filesystems that don't support file created metadata
- Add admin command to return a room's full/complete state
- Admin debug command to fetch a PDU from a remote server and inserts it into our database/timeline as backfill
- Add admin command to delete media via a specific MXC. This deletes the MXC from our database, and the file locally.
- Add admin commands for banning (blocking) room IDs from our local users joining (admins are always allowed) and evicts all our local users from that room, in addition to bulk room banning support, and blocks room invites (remote and local) to the banned room, as a moderation feature
- Add admin commands to output jemalloc memory stats and memory usage
- Add admin command to get conduwuit's uptime
- Add admin command to get rooms a *remote* user shares with us
- Add debug admin commands to get the earliest and latest PDU in a room
- Add debug admin command to echo a message
- Add admin command to insert rooms tags for a user, most useful for inserting the `m.server_notice` tag on your admin room to make it "persistent" in the "System Alerts" section of Element
- Add experimental admin debug command for Dendrite's `AdminDownloadState` (`/admin/downloadState/{serverName}/{roomID}`) admin API endpoint to download and use a remote server's room state in the room
- Disable URL previews by default in the admin room due to various command outputs having "URLs" in them that clients may needlessly render/request
- Extend memory usage admin server command to support showing memory allocator stats such as jemalloc's
- Add admin debug command to see memory allocator's full extended debug statistics such as jemalloc's
## Misc
## Misc:
- Add guest support for accessing TURN servers via `turn_allow_guests` like Synapse
- Support for creating rooms with custom room IDs like Maunium Synapse (`room_id` request body field to `/createRoom`)
- Query parameter `?format=event|content` for returning either the room state event's content (default) for the full room state event on ` /_matrix/client/v3/rooms/{roomId}/state/{eventType}[/{stateKey}]` requests (see https://github.com/matrix-org/matrix-spec/issues/1047)
- Add **optional** feature flag to use SHA256 key names for media instead of base64 to overcome filesystem file name length limitations (OS error file name too long)
- Query parameter `?format=event|content` for returning either the room state event's content (default) for the full room state event on `/_matrix/client/v3/rooms/{roomId}/state/{eventType}[/{stateKey}]` requests (see <https://github.com/matrix-org/matrix-spec/issues/1047>)
- Send a User-Agent on all of our requests
- Send `avatar_url` on invite room membership events/changes
- Support sending [`well_known` response to client login responses](https://spec.matrix.org/v1.10/client-server-api/#post_matrixclientv3login) if using config option `[well_known.client]`
@@ -146,15 +182,16 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
- Implement legacy Matrix `/v1/` media endpoints that some clients and servers may still call
- Config option to change Conduit's behaviour of homeserver key fetching (`query_trusted_key_servers_first`). This option sets whether conduwuit will query trusted notary key servers first before the individual homeserver(s), or vice versa which may help in joining certain rooms.
- Implement unstable MSC2666 support for querying mutual rooms with a user
- Assume well-knowns are broken if they exceed past 10000 characters.
- Add support for the Matrix spec compliance test suite [Complement](https://github.com/matrix-org/complement/) via the Nix flake and various other fixes for it
- Implement unstable MSC3266 room summary API support
- Implement unstable MSC4125 support for specifying servers to join via on federated invites
- Make conduwuit build and be functional under Nix + macOS
- Log out all sessions after unsetting the emergency password
- Assume well-knowns are broken if they exceed past 12288 characters.
- Add support for listening on both HTTP and HTTPS if using direct TLS with conduwuit for usecases such as Complement
- Implement running and diff'ing Complement results in CI
- Interest in supporting other operating systems such as macOS, BSDs, and Windows, and getting them added into CI and doing builds for them
- Add config option for disabling RocksDB Direct IO if needed
- Add various documentation on maintaining conduwuit, using RocksDB online backups, some troubleshooting, using admin commands, etc
- Add various documentation on maintaining conduwuit, using RocksDB online backups, some troubleshooting, using admin commands, moderation documentation, etc
- (Developers): Add support for [hot reloadable/"live" modular development](development/hot_reload.md)
- (Developers): Add support for tokio-console
- (Developers): Add support for tracing flame graphs
- Add `release-debuginfo` Cargo build profile
- No cryptocurrency donations allowed, conduwuit is fully maintained by independent queer maintainers, and with a strong priority on inclusitivity and comfort for protected groups 🏳️‍⚧️
- [Add a community Code of Conduct for all conduwuit community spaces, primarily the Matrix space](https://conduwuit.puppyirl.gay/conduwuit_coc.html)
+2 -2
View File
@@ -1,4 +1,4 @@
# Conduwuit
# conduwuit
{{#include ../README.md:catchphrase}}
@@ -12,7 +12,7 @@ See the [differences](differences.md) page
- [Deployment options](deploying.md)
If you want to connect an Appservice to Conduwuit, take a look at the [appservices documentation](appservices.md).
If you want to connect an appservice to conduwuit, take a look at the [appservices documentation](appservices.md).
#### How can I contribute?
+8 -5
View File
@@ -5,6 +5,7 @@
conduwuit has moderation through admin room commands. "binary commands" (medium priority) and an admin API (low priority) is planned. Some moderation-related config options are available in the example config such as "global ACLs" and blocking media requests to certain servers. See the example config for the moderation config options under the "Moderation / Privacy / Security" section.
conduwuit has moderation admin commands for:
- managing room aliases (`!admin rooms alias`)
- managing room directory (`!admin rooms directory`)
- managing room banning/blocking and user removal (`!admin rooms moderation`)
@@ -28,7 +29,7 @@ Any commands with `-list` in them will require a codeblock in the message with e
If using RocksDB, there's very little you need to do. Compaction is ran automatically based on various defined thresholds tuned for conduwuit to be high performance with the least I/O amplifcation or overhead. Manually running compaction is not recommended, or compaction via a timer. RocksDB is built with io_uring support via liburing for async read I/O.
Some RocksDB settings can be adjusted such as the compression method chosen. See the RocksDB section in the [example config](configuration.md). btrfs users may benefit from disabling compression on RocksDB if CoW is in use.
Some RocksDB settings can be adjusted such as the compression method chosen. See the RocksDB section in the [example config](configuration/examples.md). btrfs users may benefit from disabling compression on RocksDB if CoW is in use.
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](troubleshooting.md).
@@ -37,11 +38,12 @@ RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting]
Currently only RocksDB supports online backups. If you'd like to backup your database online without any downtime, see the `!admin server` command for the backup commands and the `database_backup_path` config options in the example config. Please note that the format of the database backup is not the exact same. This is unfortunately a bad design choice by Facebook as we are using the database backup engine API from RocksDB, however the data is still there and can still be joined together.
To restore a backup from an online RocksDB backup:
- shutdown conduwuit
- create a new directory for merging together the data
- in the online backup created, copy all `.sst` files in `$DATABASE_BACKUP_PATH/shared_checksum` to your new directory
- trim all the strings so instead of `######_sxxxxxxxxx.sst`, it reads `######.sst`. A way of doing this with sed and bash is `for file in *.sst; do mv "$file" "$(echo "$file" | sed 's/_s.*/.sst/')"; done`
- copy all the files in `$DATABASE_BACKUP_PATH/1` to your new directory
- copy all the files in `$DATABASE_BACKUP_PATH/1` (or the latest backup number if you have multiple) to your new directory
- set your `database_path` config option to your new directory, or replace your old one with the new one you crafted
- start up conduwuit again and it should open as normal
@@ -52,12 +54,13 @@ Backing up media is also just copying the `media/` directory from your database
## Media
Media still needs various work, however conduwuit implements media deletion via:
- MXC URI
- MXC URI or Event ID (unencrypted and attempts to find the MXC URI in the event)
- Delete list of MXC URIs
- Delete remote media in the past `N` seconds/minutes
- Delete remote media in the past `N` seconds/minutes via filesystem metadata on the file created time (`btime`) or file modified time (`mtime`)
See the `!admin media` command for further information. All media in conduwuit is stored at `$DATABASE_DIR/media`. This will be configurable soon.
If you are finding yourself needing extensive granular control over media, we recommend looking into [Matrix Media Repo](https://github.com/t2bot/matrix-media-repo). conduwuit intends to implement various utilities for media, but MMR is dedicated to extensive media management.
Built-in S3 support is also planned, but for now using a "S3 filesystem" on `media/` works. conduwuit also sends a `Cache-Control` header of 1 year and immutable for all media requests (download and thumbnail) to reduce unnecessary media requests from browsers.
Built-in S3 support is also planned, but for now using a "S3 filesystem" on `media/` works. conduwuit also sends a `Cache-Control` header of 1 year and immutable for all media requests (download and thumbnail) to reduce unnecessary media requests from browsers, reduce bandwidth usage, and reduce load.
+6 -7
View File
@@ -12,11 +12,13 @@
#### Direct IO
Some filesystems may not like RocksDB using [Direct IO](https://github.com/facebook/rocksdb/wiki/Direct-IO). Direct IO is for non-buffered I/O which improves conduwuit performance, but at least FUSE is a filesystem potentially known to not like this. See the [example config](configuration.md) for disabling it if needed. Issues from Direct IO on unsupported filesystems are usually shown as startup errors.
Some filesystems may not like RocksDB using [Direct IO](https://github.com/facebook/rocksdb/wiki/Direct-IO). Direct IO is for non-buffered I/O which improves conduwuit performance, but at least FUSE is a filesystem potentially known to not like this. See the [example config](configuration/examples.md) for disabling it if needed. Issues from Direct IO on unsupported filesystems are usually shown as startup errors.
#### Database corruption
If your database is corrupted and is failing to start (e.g. checksum mismatch), it may be recoverable but careful steps must be taken, and there is no guarantee it may be recoverable.
If your database is corrupted *and* is failing to start (e.g. checksum mismatch), it may be recoverable but careful steps must be taken, and there is no guarantee it may be recoverable.
The first thing that can be done is launching conduwuit with the `rocksdb_repair` config option set to true. This will tell RocksDB to attempt to repair itself at launch. If this does not work, disable the option and continue reading.
RocksDB has the following recovery modes:
@@ -30,17 +32,14 @@ By default, conduwuit uses `TolerateCorruptedTailRecords` as generally these may
`AbsoluteConsistency` will fail to start the database if any sign of corruption is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless it forbids the database from opening (e.g. too severe). Usage of `SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or leave your database in a permanently inconsistent state, but it may do something if `PointInTime` does not work as a last ditch effort.
With this in mind:
- First start conduwuit with the `PointInTime` recovery method. See the [example config](configuration.md) for how to do this using `rocksdb_recovery_mode`
- First start conduwuit with the `PointInTime` recovery method. See the [example config](configuration/examples.md) for how to do this using `rocksdb_recovery_mode`
- If your database successfully opens, clients are recommended to clear their client cache to account for the rollback
- Leave your conduwuit running in `PointInTime` for at least 30-60 minutes so as much possible corruption is restored
- If all goes will, you should be able to restore back to using `TolerateCorruptedTailRecords` and you have successfully recovered your database
## Media
#### "File name too long"
If you are running into the "file name is too long" OS error for media requests, your filesystem cannot handle file name lengths >=255 characters. This is unfortuntely due to Conduit (upstream) using base64 for file name keys which is very problematic for some filesystems as the base64 input is untrusted and long file names or specific inputs can cause this. If you would like to avoid this, you may build conduwuit yourself with the `sha256_media` feature. **This will lose database compatibility with upstream**.
## Debugging
Note that users should not really be debugging things. If you find yourself debugging and find the issue, please let us know and/or how we can fix it. Various debug commands can be found in `!admin debug`.
+4 -15
View File
@@ -11,26 +11,15 @@ use-auth-secret
static-auth-secret=<a secret key>
realm=<your server domain>
```
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
These same values need to be set in conduwuit. You can either modify conduwuit.toml to include these lines:
```
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
turn_secret = "<secret key from coturn configuration>"
```
or append the following to the docker environment variables dependig on which configuration method you used earlier:
```yml
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
```
Restart conduwuit to apply these changes.
These same values need to be set in conduwuit. See the [example config](configuration/examples.md) in the TURN section for configuring these and restart conduwuit after.
### Run
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
```bash
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
```
+17 -2
View File
@@ -55,6 +55,11 @@ name = "lychee"
group = "versions"
script = "lychee --version"
[[task]]
name = "markdownlint"
group = "versions"
script = "markdownlint --version"
[[task]]
name = "cargo-audit"
group = "security"
@@ -136,7 +141,12 @@ cargo clippy \
[[task]]
name = "lychee"
group = "lints"
script = "lychee --verbose --offline docs *.md"
script = "lychee --verbose --offline docs *.md --exclude development.md"
[[task]]
name = "markdownlint"
group = "lints"
script = "markdownlint docs *.md || true" # TODO: fix the ton of markdown lints so we can drop `|| true`
[[task]]
name = "cargo/all"
@@ -174,5 +184,10 @@ cargo test \
name = "nix-default"
group = "tests"
script = """
nix run .#default -- --help
env DIRENV_DEVSHELL=dynamic \
direnv exec . \
bin/nix-build-and-cache just .#default
env DIRENV_DEVSHELL=dynamic \
direnv exec . \
nix run -L .#default -- --help
"""
Generated
+466 -32
View File
@@ -9,11 +9,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1711742460,
"narHash": "sha256-0O4v6e4a1toxXZ2gf5INhg4WPE5C5T+SVvsBt+45Mcc=",
"lastModified": 1720542474,
"narHash": "sha256-aKjJ/4l2I9+wNGTaOGRsuS3M1+IoTibqgEMPDikXm04=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "4dbdbee45728d8ce5788db6461aaaa89d98081f0",
"rev": "6139576a3ce6bb992e0f6c3022528ec233e45f00",
"type": "github"
},
"original": {
@@ -23,14 +23,69 @@
"type": "github"
}
},
"cachix": {
"inputs": {
"devenv": "devenv",
"flake-compat": "flake-compat_3",
"nixpkgs": "nixpkgs_3",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1719923519,
"narHash": "sha256-7Rhljj2fsklFRsu+eq7N683Z9qukmreMEj5C1GqCrSA=",
"owner": "cachix",
"repo": "cachix",
"rev": "4e9e71f78b9500fa6210cf1eaa4d75bdbab777c3",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "master",
"repo": "cachix",
"type": "github"
}
},
"cachix_2": {
"inputs": {
"devenv": "devenv_2",
"flake-compat": [
"cachix",
"devenv",
"flake-compat"
],
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
],
"pre-commit-hooks": [
"cachix",
"devenv",
"pre-commit-hooks"
]
},
"locked": {
"lastModified": 1712055811,
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
"owner": "cachix",
"repo": "cachix",
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "cachix",
"type": "github"
}
},
"complement": {
"flake": false,
"locked": {
"lastModified": 1715700731,
"narHash": "sha256-cie+b5N/TQAFD8vF/XbqfyFJkFU0qUPDbtJQDm/TfQc=",
"lastModified": 1720637557,
"narHash": "sha256-oZz6nCmFmdJZpC+K1iOG2KkzTI6rlAmndxANPDVU7X0=",
"owner": "matrix-org",
"repo": "complement",
"rev": "8587fb3cbe746754b2c883ff6c818ca4d987d0a5",
"rev": "0d14432e010482ea9e13a6f7c47c1533c0c9d62f",
"type": "github"
},
"original": {
@@ -48,11 +103,11 @@
]
},
"locked": {
"lastModified": 1702918879,
"narHash": "sha256-tWJqzajIvYcaRWxn+cLUB9L9Pv4dQ3Bfit/YjU5ze3g=",
"lastModified": 1717025063,
"narHash": "sha256-dIubLa56W9sNNz0e8jGxrX3CAkPXsq7snuFA/Ie6dn8=",
"owner": "ipetkov",
"repo": "crane",
"rev": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
"rev": "480dff0be03dac0e51a8dfc26e882b0d123a450e",
"type": "github"
},
"original": {
@@ -68,11 +123,11 @@
]
},
"locked": {
"lastModified": 1716569590,
"narHash": "sha256-5eDbq8TuXFGGO3mqJFzhUbt5zHVTf5zilQoyW5jnJwo=",
"lastModified": 1720546058,
"narHash": "sha256-iU2yVaPIZm5vMGdlT0+57vdB/aPq/V5oZFBRwYw+HBM=",
"owner": "ipetkov",
"repo": "crane",
"rev": "109987da061a1bf452f435f1653c47511587d919",
"rev": "2d83156f23c43598cf44e152c33a59d3892f8b29",
"type": "github"
},
"original": {
@@ -82,6 +137,70 @@
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix_2",
"flake-compat": [
"cachix",
"flake-compat"
],
"nix": "nix_2",
"nixpkgs": [
"cachix",
"nixpkgs"
],
"pre-commit-hooks": [
"cachix",
"pre-commit-hooks"
]
},
"locked": {
"lastModified": 1719759336,
"narHash": "sha256-3a34VL/QnHprl5gMy9xlx6d8J+iNp+W88Ex8smkgH9M=",
"owner": "cachix",
"repo": "devenv",
"rev": "bb32aa986f2f695385e54428d0eaf7d05b31466e",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"devenv_2": {
"inputs": {
"flake-compat": [
"cachix",
"devenv",
"cachix",
"flake-compat"
],
"nix": "nix",
"nixpkgs": "nixpkgs_2",
"poetry2nix": "poetry2nix",
"pre-commit-hooks": [
"cachix",
"devenv",
"cachix",
"pre-commit-hooks"
]
},
"locked": {
"lastModified": 1708704632,
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
"owner": "cachix",
"repo": "devenv",
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "python-rewrite",
"repo": "devenv",
"type": "github"
}
},
"fenix": {
"inputs": {
"nixpkgs": [
@@ -90,11 +209,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1716359173,
"narHash": "sha256-pYcjP6Gy7i6jPWrjiWAVV0BCQp+DdmGaI/k65lBb/kM=",
"lastModified": 1720852044,
"narHash": "sha256-3NBYz8VuXuKU+8ONd9NFafCNjPEGHIZQ2Mdoam1a4mY=",
"owner": "nix-community",
"repo": "fenix",
"rev": "b6fc5035b28e36a98370d0eac44f4ef3fd323df6",
"rev": "5087b12a595ee73131a944d922f24d81dae05725",
"type": "github"
},
"original": {
@@ -121,6 +240,54 @@
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_3": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_4": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_5": {
"flake": false,
"locked": {
"lastModified": 1696426674,
@@ -156,6 +323,24 @@
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
@@ -171,14 +356,36 @@
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"cachix",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"liburing": {
"flake": false,
"locked": {
"lastModified": 1716565485,
"narHash": "sha256-4R19aJNQYs6vb0/Hz4bWT56YN1P1DkFL/sxdE4Yj0CE=",
"lastModified": 1720798442,
"narHash": "sha256-gtPppAoksMLW4GuruQ36nf4EAqIA1Bs6V9Xcx8dBxrQ=",
"owner": "axboe",
"repo": "liburing",
"rev": "b90c0e670a93caabbebe2d9e24ff85cece4cfe0e",
"rev": "1d674f83b7d0f07553ac44d99a401b05853d9dbe",
"type": "github"
},
"original": {
@@ -188,6 +395,33 @@
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": "flake-compat_2",
"nixpkgs": [
"cachix",
"devenv",
"cachix",
"devenv",
"nixpkgs"
],
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1712911606,
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
"owner": "domenkozar",
"repo": "nix",
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.21",
"repo": "nix",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1710156097,
@@ -204,6 +438,60 @@
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"cachix",
"devenv",
"cachix",
"devenv",
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688870561,
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nix_2": {
"inputs": {
"flake-compat": [
"cachix",
"devenv",
"flake-compat"
],
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
],
"nixpkgs-regression": "nixpkgs-regression_2"
},
"locked": {
"lastModified": 1712911606,
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
"owner": "domenkozar",
"repo": "nix",
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.21",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1711401922,
@@ -220,6 +508,38 @@
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-regression_2": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1711460390,
@@ -236,13 +556,45 @@
"type": "github"
}
},
"nixpkgs_2": {
"nixpkgs-stable_2": {
"locked": {
"lastModified": 1716330097,
"narHash": "sha256-8BO3B7e3BiyIDsaKA0tY8O88rClYRTjvAp66y+VBUeU=",
"lastModified": 1718811006,
"narHash": "sha256-0Y8IrGhRmBmT7HHXlxxepg2t8j1X90++qRN3lukGaIk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5710852ba686cc1fd0d3b8e22b3117d43ba374c2",
"rev": "03d771e513ce90147b65fe922d87d3a0356fc125",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1692808169,
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1719848872,
"narHash": "sha256-H3+EC5cYuq+gQW8y0lSrrDZfH71LB4DAf+TDFyvwCNA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "00d80d13810dbfea8ab4ed1009b09100cca86ba8",
"type": "github"
},
"original": {
@@ -252,19 +604,85 @@
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1720768451,
"narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils_2",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"cachix",
"devenv",
"cachix",
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1692876271,
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat_4",
"gitignore": "gitignore",
"nixpkgs": [
"cachix",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable_2"
},
"locked": {
"lastModified": 1719259945,
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1716773462,
"narHash": "sha256-5kUH+XK+2lbFfUgbxuNy3YMLHbp6scfWPdtc8za1wDM=",
"lastModified": 1720900786,
"narHash": "sha256-Vta9Um/RRuWwZ46BjXftV06iWLm/j/9MX39emXUvSAY=",
"owner": "girlbossceo",
"repo": "rocksdb",
"rev": "c8a1450231e9c608edf535538dbe8ca1a8d2f3bc",
"rev": "911f4243e69c2e320a7a209bf1f5f3ff5f825495",
"type": "github"
},
"original": {
"owner": "girlbossceo",
"ref": "v9.2.1",
"ref": "v9.4.0",
"repo": "rocksdb",
"type": "github"
}
@@ -272,25 +690,26 @@
"root": {
"inputs": {
"attic": "attic",
"cachix": "cachix",
"complement": "complement",
"crane": "crane_2",
"fenix": "fenix",
"flake-compat": "flake-compat_2",
"flake-utils": "flake-utils_2",
"flake-compat": "flake-compat_5",
"flake-utils": "flake-utils_3",
"liburing": "liburing",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_2",
"nixpkgs": "nixpkgs_4",
"rocksdb": "rocksdb"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1716107283,
"narHash": "sha256-NJgrwLiLGHDrCia5AeIvZUHUY7xYGVryee0/9D3Ir1I=",
"lastModified": 1720717809,
"narHash": "sha256-6I+fm+nTLF/iaj7ffiFGlSY7POmubwUaPA/Wq0Bm53M=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "21ec8f523812b88418b2bfc64240c62b3dd967bd",
"rev": "ffbc5ad993d5cd2f3b8bcf9a511165470944ab91",
"type": "github"
},
"original": {
@@ -314,6 +733,21 @@
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
+95 -13
View File
@@ -1,6 +1,7 @@
{
inputs = {
attic.url = "github:zhaofengli/attic?ref=main";
cachix.url = "github:cachix/cachix?ref=master";
complement = { url = "github:matrix-org/complement?ref=main"; flake = false; };
crane = { url = "github:ipetkov/crane?ref=master"; inputs.nixpkgs.follows = "nixpkgs"; };
fenix = { url = "github:nix-community/fenix?ref=main"; inputs.nixpkgs.follows = "nixpkgs"; };
@@ -8,8 +9,7 @@
flake-utils.url = "github:numtide/flake-utils?ref=main";
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
# https://github.com/girlbossceo/rocksdb/commit/db6df0b185774778457dabfcbd822cb81760cade
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.2.1"; flake = false; };
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.4.0"; flake = false; };
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
};
@@ -41,6 +41,37 @@
"v"
(builtins.fromJSON (builtins.readFile ./flake.lock))
.nodes.rocksdb.original.ref;
# we have this already at https://github.com/girlbossceo/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
# unsetting this so i don't have to revert it and make this nix exclusive
patches = [];
cmakeFlags = pkgs.lib.subtractLists
[
# no real reason to have snappy, no one uses this
"-DWITH_SNAPPY=1"
# we dont need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=1"
# we dont need to build rocksdb tests
"-DWITH_TESTS=1"
# we use rust-rocksdb via C interface and dont need C++ RTTI
"-DUSE_RTTI=1"
]
old.cmakeFlags
++ [
# we dont need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=0"
# we dont need trace tools
"-DWITH_TRACE_TOOLS=0"
# we dont need to build rocksdb tests
"-DWITH_TESTS=0"
# we use rust-rocksdb via C interface and dont need C++ RTTI
"-DUSE_RTTI=0"
];
# 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 = "";
});
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
liburing = pkgs.liburing.overrideAttrs (old: {
@@ -49,16 +80,6 @@
configureFlags = pkgs.lib.subtractLists
[ "--enable-static" "--disable-shared" ]
old.configureFlags;
postInstall = old.postInstall + ''
# we remove the extra outputs
#
# we need to do this to prevent rocksdb from trying to link the
# static library in a dynamic stdenv
rm $out/lib/liburing*${
if pkgs.stdenv.hostPlatform.isStatic then ".so*" else ".a"
}
'';
});
});
@@ -107,6 +128,9 @@
# Needed for finding broken markdown links
lychee
# Needed for linting markdown files
markdownlint-cli
# Useful for editing the book locally
mdbook
])
@@ -120,9 +144,29 @@
{
packages = {
default = scopeHost.main;
all-features = scopeHost.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
oci-image = scopeHost.oci-image;
oci-image-all-features = scopeHost.oci-image.override {
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"
];
};
};
oci-image-hmalloc = scopeHost.oci-image.override {
main = scopeHost.main.override {
features = ["hardened_malloc"];
@@ -157,6 +201,20 @@
value = scopeCrossStatic.main;
}
# An output for a statically-linked binary with `--all-features`
{
name = "${binaryName}-all-features";
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"
];
};
}
# An output for a statically-linked binary with hardened_malloc
{
name = "${binaryName}-hmalloc";
@@ -171,6 +229,22 @@
value = scopeCrossStatic.oci-image;
}
# An output for an OCI image based on that binary with `--all-features`
{
name = "oci-image-${crossSystem}-all-features";
value = scopeCrossStatic.oci-image.override {
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"
];
};
};
}
# An output for an OCI image based on that binary with hardened_malloc
{
name = "oci-image-${crossSystem}-hmalloc";
@@ -192,7 +266,15 @@
devShells.default = mkDevShell scopeHostStatic;
devShells.all-features = mkDevShell
(scopeHostStatic.overrideScope (final: prev: {
main = prev.main.override { all_features = true; };
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"
];
};
}));
devShells.no-features = mkDevShell
(scopeHostStatic.overrideScope (final: prev: {
+3 -2
View File
@@ -18,7 +18,9 @@ stdenv.mkDerivation {
"README.md"
"debian/conduwuit.service"
"debian/README.md"
"arch/conduwuit.service"
"docs"
"theme"
];
};
@@ -27,7 +29,6 @@ stdenv.mkDerivation {
];
buildPhase = ''
mdbook build
mv public $out
mdbook build -d $out
'';
}
+7 -3
View File
@@ -5,13 +5,17 @@ allow_guest_registration = true
allow_public_room_directory_over_federation = true
allow_public_room_directory_without_auth = true
allow_registration = true
allow_unstable_room_versions = true
database_backend = "rocksdb"
database_path = "/database"
log = "trace"
log = "trace,h2=warn,hyper=warn"
port = [8008, 8448]
trusted_servers = []
query_trusted_key_servers_first = false
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
ip_range_denylist = []
url_preview_domain_contains_allowlist = ["*"]
media_compat_file_link = false
media_statup_check = false
rocksdb_direct_io = false
[global.tls]
certs = "/certificate.crt"
+6
View File
@@ -13,6 +13,12 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
lib.concatStringsSep
" "
([]
++ lib.optionals
stdenv.targetPlatform.isx86_64
[ "-C" "target-cpu=x86-64-v2" ]
++ lib.optionals
stdenv.targetPlatform.isAarch64
[ "-C" "target-cpu=cortex-a55" ] # cortex-a55 == ARMv8.2-a
# This disables PIE for static builds, which isn't great in terms
# of security. Unfortunately, my hand is forced because nixpkgs'
# `libstdc++.a` is built without `-fPIE`, which precludes us from
+34 -10
View File
@@ -25,11 +25,7 @@ let
# on the nix side depend on feature values.
crateFeatures = path:
let manifest = lib.importTOML "${path}/Cargo.toml"; in
lib.remove "default" (lib.attrNames manifest.features) ++
lib.attrNames
(lib.filterAttrs
(_: dependency: dependency.optional or false)
manifest.dependencies);
lib.remove "default" (lib.attrNames manifest.features);
crateDefaultFeatures = path:
(lib.importTOML "${path}/Cargo.toml").features.default;
allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main";
@@ -43,7 +39,7 @@ features'' = lib.subtractLists disable_features' features';
featureEnabled = feature : builtins.elem feature features'';
enableLiburing = featureEnabled "io_uring" && stdenv.isLinux;
enableLiburing = featureEnabled "io_uring" && !stdenv.isDarwin;
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
@@ -70,12 +66,34 @@ buildDepsOnlyEnv =
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
enableJemalloc = featureEnabled "jemalloc" && !stdenv.isDarwin;
# for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely
enableLiburing = enableLiburing;
}).overrideAttrs (old: {
# TODO: static rocksdb fails to build on darwin
# TODO: static rocksdb fails to build on darwin, also see <https://github.com/NixOS/nixpkgs/issues/320448>
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
# TODO: switch to enableUring option once https://github.com/NixOS/nixpkgs/pull/314945 is available
buildInputs = old.buildInputs ++ lib.optional enableLiburing liburing;
enableLiburing = enableLiburing;
sse42Support = stdenv.targetPlatform.isx86_64;
cmakeFlags = if stdenv.targetPlatform.isx86_64
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
++ lib.optionals stdenv.targetPlatform.isx86_64 [
"-DPORTABLE=x86-64-v2"
"-DUSE_SSE=1"
"-DHAVE_SSE=1"
"-DHAVE_SSE42=1"
]
else if stdenv.targetPlatform.isAarch64
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
++ lib.optionals stdenv.targetPlatform.isAarch64 [
# cortex-a55 == ARMv8.2-a
"-DPORTABLE=armv8.2-a"
]
else old.cmakeFlags;
});
in
{
@@ -102,7 +120,11 @@ buildPackageEnv = {
# Only needed in static stdenv because these are transitive dependencies of rocksdb
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
" -L${lib.getLib liburing}/lib -luring";
" -L${lib.getLib liburing}/lib -luring"
+ lib.optionalString stdenv.targetPlatform.isx86_64
" -Ctarget-cpu=x86-64-v2"
+ lib.optionalString stdenv.targetPlatform.isAarch64
" -Ctarget-cpu=cortex-a55"; # cortex-a55 == ARMv8.2-a
};
@@ -127,6 +149,8 @@ commonAttrs = {
];
};
dontStrip = profile == "dev";
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
nativeBuildInputs = [
+2 -19
View File
@@ -17,41 +17,24 @@ crate-type = [
]
[features]
dev_release_log_level = []
#dev_release_log_level = []
release_max_log_level = [
"tracing/max_level_trace",
"tracing/release_max_level_info",
"log/max_level_trace",
"log/release_max_level_info",
]
rocksdb = [
"dep:rust-rocksdb",
]
jemalloc = [
"rust-rocksdb/jemalloc",
]
io_uring = [
"rust-rocksdb/io-uring",
]
zstd_compression = [
"rust-rocksdb/zstd",
]
[dependencies]
clap.workspace = true
conduit-api.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
conduit-service.workspace = true
const-str.workspace = true
futures-util.workspace = true
log.workspace = true
loole.workspace = true
regex.workspace = true
ruma.workspace = true
rust-rocksdb.optional = true
rust-rocksdb.workspace = true
serde_json.workspace = true
serde.workspace = true
serde_yaml.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
@@ -1,8 +1,8 @@
use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent};
use crate::{escape_html, services, Result};
use crate::{services, Result};
pub(crate) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -26,7 +26,7 @@ pub(crate) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent>
}
}
pub(crate) async fn unregister(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> {
pub(super) async fn unregister(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> {
match services()
.appservice
.unregister_appservice(&appservice_identifier)
@@ -39,7 +39,7 @@ pub(crate) async fn unregister(_body: Vec<&str>, appservice_identifier: String)
}
}
pub(crate) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> {
pub(super) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> {
match services()
.appservice
.get_registration(&appservice_identifier)
@@ -48,18 +48,13 @@ pub(crate) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Res
Some(config) => {
let config_str = serde_yaml::to_string(&config).expect("config should've been validated on register");
let output = format!("Config for {appservice_identifier}:\n\n```yaml\n{config_str}\n```",);
let output_html = format!(
"Config for {}:\n\n<pre><code class=\"language-yaml\">{}</code></pre>",
escape_html(&appservice_identifier),
escape_html(&config_str),
);
Ok(RoomMessageEventContent::text_html(output, output_html))
Ok(RoomMessageEventContent::notice_markdown(output))
},
None => Ok(RoomMessageEventContent::text_plain("Appservice does not exist.")),
}
}
pub(crate) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let appservices = services().appservice.iter_ids().await;
let output = format!("Appservices ({}): {}", appservices.len(), appservices.join(", "));
Ok(RoomMessageEventContent::text_plain(output))
+5 -5
View File
@@ -1,14 +1,14 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::appservice_command::{list, register, show, unregister};
pub(crate) mod appservice_command;
use self::commands::*;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum AppserviceCommand {
pub(super) enum AppserviceCommand {
/// - Register an appservice using its registration YAML
///
/// This command needs a YAML generated by an appservice (such as a bridge),
@@ -38,7 +38,7 @@ pub(crate) enum AppserviceCommand {
List,
}
pub(crate) async fn process(command: AppserviceCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: AppserviceCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
AppserviceCommand::Register => register(body).await?,
AppserviceCommand::Unregister {
@@ -1,11 +1,12 @@
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use crate::{services, Result};
use crate::services;
/// Uses the iterator in `src/database/key_value/users.rs` to iterator over
/// every user in our database (remote and local). Reports total count, any
/// errors if there were any, etc
pub(crate) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let results = services().users.db.iter();
let query_time = timer.elapsed();
@@ -18,8 +19,8 @@ pub(crate) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEvent
let message = format!(
"Database query completed in {query_time:?}:\n\n```\nTotal entries: {total:?}\nFailure/Invalid user count: \
{err_count:?}\nSuccess/Valid user count: {ok_count:?}```"
{err_count:?}\nSuccess/Valid user count: {ok_count:?}\n```"
);
Ok(RoomMessageEventContent::notice_html(message, String::new()))
Ok(RoomMessageEventContent::notice_markdown(message))
}
+19
View File
@@ -0,0 +1,19 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(super) enum CheckCommand {
AllUsers,
}
pub(super) async fn process(command: CheckCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
CheckCommand::AllUsers => check_all_users(body).await?,
})
}
@@ -1,22 +1,31 @@
use std::{
collections::{BTreeMap, HashMap},
sync::Arc,
sync::{Arc, Mutex},
time::Instant,
};
use api::client::validate_and_add_event_id;
use conduit::{utils::HtmlEscape, Error, Result};
use conduit::{
debug, info, log,
log::{capture, Capture},
warn, Error, Result,
};
use ruma::{
api::{client::error::ErrorKind, federation::event::get_room_state},
events::room::message::RoomMessageEventContent,
CanonicalJsonObject, EventId, RoomId, RoomVersionId, ServerName,
CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName,
};
use service::{rooms::event_handler::parse_incoming_pdu, sending::resolve::resolve_actual_dest, services, PduEvent};
use service::{rooms::event_handler::parse_incoming_pdu, sending::resolve_actual_dest, services, PduEvent};
use tokio::sync::RwLock;
use tracing::{debug, info, warn};
use tracing_subscriber::EnvFilter;
pub(crate) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
pub(super) async fn echo(_body: Vec<&str>, message: Vec<String>) -> Result<RoomMessageEventContent> {
let message = message.join(" ");
Ok(RoomMessageEventContent::notice_plain(message))
}
pub(super) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
let event_id = Arc::<EventId>::from(event_id);
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? {
let room_id_str = event
@@ -42,14 +51,14 @@ pub(crate) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
}
}
pub(crate) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
));
}
let string = body[1..body.len() - 1].join("\n");
let string = body[1..body.len().saturating_sub(1)].join("\n");
match serde_json::from_str(&string) {
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
Ok(hash) => {
@@ -70,7 +79,7 @@ pub(crate) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent
}
}
pub(crate) async fn get_pdu(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
pub(super) async fn get_pdu(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
let mut outlier = false;
let mut pdu_json = services()
.rooms
@@ -83,32 +92,21 @@ pub(crate) async fn get_pdu(_body: Vec<&str>, event_id: Box<EventId>) -> Result<
match pdu_json {
Some(json) => {
let json_text = serde_json::to_string_pretty(&json).expect("canonical json is valid json");
Ok(RoomMessageEventContent::text_html(
format!(
"{}\n```json\n{}\n```",
if outlier {
"Outlier PDU found in our database"
} else {
"PDU found in our database"
},
json_text
),
format!(
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
if outlier {
"Outlier PDU found in our database"
} else {
"PDU found in our database"
},
HtmlEscape(&json_text)
),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"{}\n```json\n{}\n```",
if outlier {
"Outlier PDU found in our database"
} else {
"PDU found in our database"
},
json_text
)))
},
None => Ok(RoomMessageEventContent::text_plain("PDU not found locally.")),
}
}
pub(crate) async fn get_remote_pdu_list(
pub(super) async fn get_remote_pdu_list(
body: Vec<&str>, server: Box<ServerName>, force: bool,
) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation {
@@ -155,7 +153,7 @@ pub(crate) async fn get_remote_pdu_list(
Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."))
}
pub(crate) async fn get_remote_pdu(
pub(super) async fn get_remote_pdu(
_body: Vec<&str>, event_id: Box<EventId>, server: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation {
@@ -227,17 +225,10 @@ pub(crate) async fn get_remote_pdu(
let json_text = serde_json::to_string_pretty(&json).expect("canonical json is valid json");
Ok(RoomMessageEventContent::text_html(
format!(
"{}\n```json\n{}\n```",
"Got PDU from specified server and handled as backfilled PDU successfully. Event body:", json_text
),
format!(
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
"Got PDU from specified server and handled as backfilled PDU successfully. Event body:",
HtmlEscape(&json_text)
),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"{}\n```json\n{}\n```",
"Got PDU from specified server and handled as backfilled PDU successfully. Event body:", json_text
)))
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Remote server did not have PDU or failed sending request to remote server: {e}"
@@ -245,7 +236,8 @@ pub(crate) async fn get_remote_pdu(
}
}
pub(crate) async fn get_room_state(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
pub(super) async fn get_room_state(_body: Vec<&str>, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
let room_id = services().rooms.alias.resolve(&room).await?;
let room_state = services()
.rooms
.state_accessor
@@ -261,24 +253,17 @@ pub(crate) async fn get_room_state(_body: Vec<&str>, room_id: Box<RoomId>) -> Re
));
}
let json_text = serde_json::to_string_pretty(&room_state).map_err(|e| {
let json = serde_json::to_string_pretty(&room_state).map_err(|e| {
warn!("Failed converting room state vector in our database to pretty JSON: {e}");
Error::bad_database(
"Failed to convert room state events to pretty JSON, possible invalid room state events in our database",
)
})?;
Ok(RoomMessageEventContent::text_html(
format!("{}\n```json\n{}\n```", "Found full room state", json_text),
format!(
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
"Found full room state",
HtmlEscape(&json_text)
),
))
Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json}\n```")))
}
pub(crate) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
if server == services().globals.server_name() {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves.",
@@ -298,14 +283,9 @@ pub(crate) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<Ro
let json_text_res = serde_json::to_string_pretty(&response.server);
if let Ok(json) = json_text_res {
return Ok(RoomMessageEventContent::text_html(
format!("Got response which took {ping_time:?} time:\n```json\n{json}\n```"),
format!(
"<p>Got response which took {ping_time:?} time:</p>\n<pre><code \
class=\"language-json\">{}\n</code></pre>\n",
HtmlEscape(&json)
),
));
return Ok(RoomMessageEventContent::notice_markdown(format!(
"Got response which took {ping_time:?} time:\n```json\n{json}\n```"
)));
}
Ok(RoomMessageEventContent::text_plain(format!(
@@ -321,7 +301,7 @@ pub(crate) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<Ro
}
}
pub(crate) async fn force_device_list_updates(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn force_device_list_updates(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
// Force E2EE device list updates for all users
for user_id in services().users.iter().filter_map(Result::ok) {
services().users.mark_device_key_update(&user_id)?;
@@ -331,9 +311,11 @@ pub(crate) async fn force_device_list_updates(_body: Vec<&str>) -> Result<RoomMe
))
}
pub(crate) async fn change_log_level(
pub(super) async fn change_log_level(
_body: Vec<&str>, filter: Option<String>, reset: bool,
) -> Result<RoomMessageEventContent> {
let handles = &["console"];
if reset {
let old_filter_layer = match EnvFilter::try_new(&services().globals.config.log) {
Ok(s) => s,
@@ -346,8 +328,9 @@ pub(crate) async fn change_log_level(
match services()
.server
.tracing_reload_handle
.reload(&old_filter_layer)
.log
.reload
.reload(&old_filter_layer, Some(handles))
{
Ok(()) => {
return Ok(RoomMessageEventContent::text_plain(format!(
@@ -375,8 +358,9 @@ pub(crate) async fn change_log_level(
match services()
.server
.tracing_reload_handle
.reload(&new_filter_layer)
.log
.reload
.reload(&new_filter_layer, Some(handles))
{
Ok(()) => {
return Ok(RoomMessageEventContent::text_plain("Successfully changed log level"));
@@ -392,7 +376,7 @@ pub(crate) async fn change_log_level(
Ok(RoomMessageEventContent::text_plain("No log level was specified."))
}
pub(crate) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -415,7 +399,7 @@ pub(crate) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent
}
}
pub(crate) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -446,7 +430,7 @@ pub(crate) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventConte
}
#[tracing::instrument(skip(_body))]
pub(crate) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
pub(super) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !services()
.rooms
.state_cache
@@ -467,7 +451,7 @@ pub(crate) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) ->
}
#[tracing::instrument(skip(_body))]
pub(crate) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
pub(super) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !services()
.rooms
.state_cache
@@ -488,7 +472,7 @@ pub(crate) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -
}
#[tracing::instrument(skip(_body))]
pub(crate) async fn force_set_room_state_from_server(
pub(super) async fn force_set_room_state_from_server(
_body: Vec<&str>, server_name: Box<ServerName>, room_id: Box<RoomId>,
) -> Result<RoomMessageEventContent> {
if !services()
@@ -598,17 +582,7 @@ pub(crate) async fn force_set_room_state_from_server(
.state_compressor
.save_state(room_id.clone().as_ref(), new_room_state)?;
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(room_id.clone().into())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let state_lock = services().rooms.state.mutex.lock(&room_id).await;
services()
.rooms
.state
@@ -628,7 +602,7 @@ pub(crate) async fn force_set_room_state_from_server(
))
}
pub(crate) async fn resolve_true_destination(
pub(super) async fn resolve_true_destination(
_body: Vec<&str>, server_name: Box<ServerName>, no_cache: bool,
) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation {
@@ -643,23 +617,73 @@ pub(crate) async fn resolve_true_destination(
));
}
let (actual_dest, hostname_uri) = resolve_actual_dest(&server_name, !no_cache).await?;
let filter: &capture::Filter = &|data| {
data.level() <= log::Level::DEBUG
&& data.mod_name().starts_with("conduit")
&& matches!(data.span_name(), "actual" | "well-known" | "srv")
};
Ok(RoomMessageEventContent::text_plain(format!(
"Actual destination: {actual_dest} | Hostname URI: {hostname_uri}"
)))
let state = &services().server.log.capture;
let logs = Arc::new(Mutex::new(String::new()));
let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone()));
let capture_scope = capture.start();
let actual = resolve_actual_dest(&server_name, !no_cache).await?;
drop(capture_scope);
let msg = format!(
"{}\nDestination: {}\nHostname URI: {}",
logs.lock().expect("locked"),
actual.dest,
actual.host,
);
Ok(RoomMessageEventContent::text_markdown(msg))
}
#[must_use]
pub(crate) fn memory_stats() -> RoomMessageEventContent {
pub(super) fn memory_stats() -> RoomMessageEventContent {
let html_body = conduit::alloc::memory_stats();
if html_body.is_empty() {
if html_body.is_none() {
return RoomMessageEventContent::text_plain("malloc stats are not supported on your compiled malloc.");
}
RoomMessageEventContent::text_html(
"This command's output can only be viewed by clients that render HTML.".to_owned(),
html_body,
html_body.expect("string result"),
)
}
#[cfg(tokio_unstable)]
pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let out = services().server.metrics.runtime_metrics().map_or_else(
|| "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"),
);
Ok(RoomMessageEventContent::text_markdown(out))
}
#[cfg(not(tokio_unstable))]
pub(super) async fn runtime_metrics(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::text_markdown(
"Runtime metrics require building with `tokio_unstable`.",
))
}
#[cfg(tokio_unstable)]
pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let out = services().server.metrics.runtime_interval().map_or_else(
|| "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"),
);
Ok(RoomMessageEventContent::text_markdown(out))
}
#[cfg(not(tokio_unstable))]
pub(super) async fn runtime_interval(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::text_markdown(
"Runtime metrics require building with `tokio_unstable`.",
))
}
+35 -16
View File
@@ -1,18 +1,21 @@
mod commands;
pub(crate) mod tester;
use clap::Subcommand;
use debug_commands::{first_pdu_in_room, force_set_room_state_from_server, latest_pdu_in_room};
use ruma::{events::room::message::RoomMessageEventContent, EventId, RoomId, ServerName};
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, EventId, OwnedRoomOrAliasId, RoomId, ServerName};
use tester::TesterCommand;
use self::debug_commands::{
change_log_level, force_device_list_updates, get_auth_chain, get_pdu, get_remote_pdu, get_remote_pdu_list,
get_room_state, memory_stats, parse_pdu, ping, resolve_true_destination, sign_json, verify_json,
};
use crate::Result;
pub(crate) mod debug_commands;
use self::commands::*;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum DebugCommand {
pub(super) enum DebugCommand {
/// - Echo input of admin command
Echo {
message: Vec<String>,
},
/// - Get the auth_chain of a PDU
GetAuthChain {
/// An event ID (the $ character followed by the base64 reference hash)
@@ -35,8 +38,8 @@ pub(crate) enum DebugCommand {
},
/// - 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).
/// database/timeline if found and we do not have this PDU already
/// (following normal event auth rules, handles it as an incoming PDU).
GetRemotePdu {
/// An event ID (a $ followed by the base64 reference hash)
event_id: Box<EventId>,
@@ -47,8 +50,7 @@ pub(crate) enum DebugCommand {
},
/// - Same as `get-remote-pdu` but accepts a codeblock newline delimited
/// list
/// of PDUs and a single server to fetch from
/// list of PDUs and a single server to fetch from
GetRemotePduList {
/// Argument for us to attempt to fetch all the events from the
/// specified remote server.
@@ -71,7 +73,7 @@ pub(crate) enum DebugCommand {
/// Of course the check is still done on the actual client API.
GetRoomState {
/// Room ID
room_id: Box<RoomId>,
room_id: OwnedRoomOrAliasId,
},
/// - Sends a federation request to the remote server's
@@ -157,10 +159,24 @@ pub(crate) enum DebugCommand {
/// - Print extended memory usage
MemoryStats,
/// - Print general tokio runtime metric totals.
RuntimeMetrics,
/// - Print detailed tokio runtime metrics accumulated since last command
/// invocation.
RuntimeInterval,
/// - Developer test stubs
#[command(subcommand)]
Tester(TesterCommand),
}
pub(crate) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
DebugCommand::Echo {
message,
} => echo(body, message).await?,
DebugCommand::GetAuthChain {
event_id,
} => get_auth_chain(body, event_id).await?,
@@ -204,5 +220,8 @@ pub(crate) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<Ro
no_cache,
} => resolve_true_destination(body, server_name, no_cache).await?,
DebugCommand::MemoryStats => memory_stats(),
DebugCommand::RuntimeMetrics => runtime_metrics(body).await?,
DebugCommand::RuntimeInterval => runtime_interval(body).await?,
DebugCommand::Tester(command) => tester::process(command, body).await?,
})
}
+42
View File
@@ -0,0 +1,42 @@
use ruma::events::room::message::RoomMessageEventContent;
use crate::Result;
#[derive(clap::Subcommand)]
#[cfg_attr(test, derive(Debug))]
pub(crate) enum TesterCommand {
Tester,
Timer,
}
pub(super) async fn process(command: TesterCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
TesterCommand::Tester => tester(body).await,
TesterCommand::Timer => timer(body).await,
}
}
#[inline(never)]
#[rustfmt::skip]
#[allow(unused_variables)]
async fn tester(body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::notice_plain("completed"))
}
#[inline(never)]
#[rustfmt::skip]
async fn timer(body: Vec<&str>) -> Result<RoomMessageEventContent> {
let started = std::time::Instant::now();
timed(&body);
let elapsed = started.elapsed();
Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}")))
}
#[inline(never)]
#[rustfmt::skip]
#[allow(unused_variables)]
fn timed(body: &[&str]) {
}
@@ -2,31 +2,36 @@ use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
use crate::{escape_html, get_room_info, services, utils::HtmlEscape, Result};
use crate::{escape_html, get_room_info, services, Result};
pub(crate) async fn disable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
pub(super) async fn disable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
services().rooms.metadata.disable_room(&room_id, true)?;
Ok(RoomMessageEventContent::text_plain("Room disabled."))
}
pub(crate) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
pub(super) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
services().rooms.metadata.disable_room(&room_id, false)?;
Ok(RoomMessageEventContent::text_plain("Room enabled."))
}
pub(crate) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let map = services().globals.roomid_federationhandletime.read().await;
pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let map = services()
.rooms
.event_handler
.federation_handletime
.read()
.expect("locked");
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
for (r, (e, i)) in map.iter() {
let elapsed = i.elapsed();
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60,)
.expect("should be able to write to string buffer");
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
}
Ok(RoomMessageEventContent::text_plain(&msg))
}
pub(crate) async fn fetch_support_well_known(
pub(super) async fn fetch_support_well_known(
_body: Vec<&str>, server_name: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
let response = services()
@@ -63,16 +68,12 @@ pub(crate) async fn fetch_support_well_known(
},
};
Ok(RoomMessageEventContent::text_html(
format!("Got JSON response:\n\n```json\n{pretty_json}\n```"),
format!(
"<p>Got JSON response:</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
HtmlEscape(&pretty_json)
),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Got JSON response:\n\n```json\n{pretty_json}\n```"
)))
}
pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
pub(super) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
if user_id.server_name() == services().globals.config.server_name {
return Ok(RoomMessageEventContent::text_plain(
"User belongs to our server, please use `list-joined-rooms` user admin command instead.",
+6 -8
View File
@@ -1,16 +1,14 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
use self::federation_commands::{
disable_room, enable_room, fetch_support_well_known, incoming_federation, remote_user_in_rooms,
};
use crate::Result;
pub(crate) mod federation_commands;
use self::commands::*;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum FederationCommand {
pub(super) enum FederationCommand {
/// - List all rooms we are currently handling an incoming pdu from
IncomingFederation,
@@ -43,7 +41,7 @@ pub(crate) enum FederationCommand {
},
}
pub(crate) async fn process(command: FederationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: FederationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
FederationCommand::DisableRoom {
room_id,
-19
View File
@@ -1,19 +0,0 @@
use clap::Subcommand;
use ruma::events::room::message::RoomMessageEventContent;
use self::fsck_commands::check_all_users;
use crate::Result;
pub(crate) mod fsck_commands;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum FsckCommand {
CheckAllUsers,
}
pub(crate) async fn process(command: FsckCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
FsckCommand::CheckAllUsers => check_all_users(body).await?,
})
}
+148 -213
View File
@@ -1,36 +1,31 @@
use std::sync::Arc;
use std::{panic::AssertUnwindSafe, time::Instant};
use clap::Parser;
use regex::Regex;
use clap::{CommandFactory, Parser};
use conduit::{error, trace, Error};
use futures_util::future::FutureExt;
use ruma::{
events::{
relation::InReplyTo,
room::message::{Relation::Reply, RoomMessageEventContent},
TimelineEventType,
},
OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
OwnedEventId,
};
use serde_json::value::to_raw_value;
use tokio::sync::MutexGuard;
use tracing::error;
extern crate conduit_service as service;
use conduit::{Error, Result};
pub(crate) use service::admin::{AdminRoomEvent, Service};
use service::{admin::HandlerResult, pdu::PduBuilder};
use conduit::{utils::string::common_prefix, Result};
pub(crate) use service::admin::{Command, Service};
use service::admin::{CommandOutput, CommandResult, HandlerResult};
use self::{fsck::FsckCommand, tester::TesterCommands};
use crate::{
appservice, appservice::AppserviceCommand, debug, debug::DebugCommand, escape_html, federation,
federation::FederationCommand, fsck, media, media::MediaCommand, query, query::QueryCommand, room,
room::RoomCommand, server, server::ServerCommand, services, tester, user, user::UserCommand,
appservice, appservice::AppserviceCommand, check, check::CheckCommand, debug, debug::DebugCommand, federation,
federation::FederationCommand, media, media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand,
server, server::ServerCommand, services, user, user::UserCommand,
};
pub(crate) const PAGE_SIZE: usize = 100;
#[cfg_attr(test, derive(Debug))]
#[derive(Parser)]
#[command(name = "@conduit:server.name:", version = env!("CARGO_PKG_VERSION"))]
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
pub(crate) enum AdminCommand {
#[command(subcommand)]
/// - Commands for managing appservices
@@ -56,166 +51,85 @@ pub(crate) enum AdminCommand {
/// - Commands for managing media
Media(MediaCommand),
#[command(subcommand)]
/// - Commands for checking integrity
Check(CheckCommand),
#[command(subcommand)]
/// - Commands for debugging things
Debug(DebugCommand),
#[command(subcommand)]
/// - Query all the database getters and iterators
/// - Low-level queries for database getters and iterators
Query(QueryCommand),
#[command(subcommand)]
/// - Query all the database getters and iterators
Fsck(FsckCommand),
#[command(subcommand)]
Tester(TesterCommands),
}
#[must_use]
pub fn handle(event: AdminRoomEvent, room: OwnedRoomId, user: OwnedUserId) -> HandlerResult {
Box::pin(handle_event(event, room, user))
}
pub(crate) fn handle(command: Command) -> HandlerResult { Box::pin(handle_command(command)) }
async fn handle_event(event: AdminRoomEvent, admin_room: OwnedRoomId, server_user: OwnedUserId) -> Result<()> {
let (mut message_content, reply) = match event {
AdminRoomEvent::SendMessage(content) => (content, None),
AdminRoomEvent::ProcessMessage(room_message, reply_id) => {
// This future is ~8 KiB so it's better to start it off the stack.
(Box::pin(process_admin_message(room_message)).await, Some(reply_id))
},
};
#[must_use]
pub(crate) fn complete(line: &str) -> String { complete_admin_command(AdminCommand::command(), line) }
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(admin_room.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
if let Some(reply) = reply {
message_content.relates_to = Some(Reply {
in_reply_to: InReplyTo {
event_id: reply.into(),
},
});
}
let response_pdu = PduBuilder {
event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&message_content).expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
};
if let Err(e) = services()
.rooms
.timeline
.build_and_append_pdu(response_pdu, &server_user, &admin_room, &state_lock)
#[tracing::instrument(skip_all, name = "admin")]
async fn handle_command(command: Command) -> CommandResult {
AssertUnwindSafe(process_command(&command))
.catch_unwind()
.await
{
handle_response_error(&e, &admin_room, &server_user, &state_lock).await?;
}
Ok(())
.map_err(Error::from_panic)
.or_else(|error| handle_panic(&error, command))
}
async fn handle_response_error(
e: &Error, admin_room: &RoomId, server_user: &UserId, state_lock: &MutexGuard<'_, ()>,
) -> Result<()> {
error!("Failed to build and append admin room response PDU: \"{e}\"");
let error_room_message = RoomMessageEventContent::text_plain(format!(
"Failed to build and append admin room PDU: \"{e}\"\n\nThe original admin command may have finished \
successfully, but we could not return the output."
));
async fn process_command(command: &Command) -> CommandOutput {
process_admin_message(&command.command)
.await
.and_then(|content| reply(content, command.reply_id.clone()))
}
let response_pdu = PduBuilder {
event_type: TimelineEventType::RoomMessage,
content: to_raw_value(&error_room_message).expect("event is valid, we just created it"),
unsigned: None,
state_key: None,
redacts: None,
};
fn handle_panic(error: &Error, command: Command) -> CommandResult {
let link = "Please submit a [bug report](https://github.com/girlbossceo/conduwuit/issues/new). 🥺";
let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}");
let content = RoomMessageEventContent::notice_markdown(msg);
error!("Panic while processing command: {error:?}");
Ok(reply(content, command.reply_id))
}
services()
.rooms
.timeline
.build_and_append_pdu(response_pdu, server_user, admin_room, state_lock)
.await?;
fn reply(mut content: RoomMessageEventContent, reply_id: Option<OwnedEventId>) -> Option<RoomMessageEventContent> {
content.relates_to = reply_id.map(|event_id| Reply {
in_reply_to: InReplyTo {
event_id,
},
});
Ok(())
Some(content)
}
// Parse and process a message from the admin room
async fn process_admin_message(room_message: String) -> RoomMessageEventContent {
let mut lines = room_message.lines().filter(|l| !l.trim().is_empty());
let command_line = lines.next().expect("each string has at least one line");
async fn process_admin_message(msg: &str) -> CommandOutput {
let mut lines = msg.lines().filter(|l| !l.trim().is_empty());
let command = lines.next().expect("each string has at least one line");
let body = lines.collect::<Vec<_>>();
let admin_command = match parse_admin_command(command_line) {
Ok(command) => command,
let parsed = match parse_admin_command(command) {
Ok(parsed) => parsed,
Err(error) => {
let server_name = services().globals.server_name();
let message = error.replace("server.name", server_name.as_str());
let html_message = usage_to_html(&message, server_name);
return RoomMessageEventContent::text_html(message, html_message);
return Some(RoomMessageEventContent::notice_markdown(message));
},
};
match process_admin_command(admin_command, body).await {
Ok(reply_message) => reply_message,
Err(error) => {
let markdown_message = format!("Encountered an error while handling the command:\n```\n{error}\n```",);
let html_message = format!("Encountered an error while handling the command:\n<pre>\n{error}\n</pre>",);
RoomMessageEventContent::text_html(markdown_message, html_message)
},
let timer = Instant::now();
let result = process_admin_command(parsed, body).await;
let elapsed = timer.elapsed();
conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}");
match result {
Ok(reply) => Some(reply),
Err(error) => Some(RoomMessageEventContent::notice_markdown(format!(
"Encountered an error while handling the command:\n```\n{error:#?}\n```"
))),
}
}
// Parse chat messages from the admin room into an AdminCommand object
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> {
// Note: argv[0] is `@conduit:servername:`, which is treated as the main command
let mut argv = command_line.split_whitespace().collect::<Vec<_>>();
// Replace `help command` with `command --help`
// Clap has a help subcommand, but it omits the long help description.
if argv.len() > 1 && argv[1] == "help" {
argv.remove(1);
argv.push("--help");
}
// Backwards compatibility with `register_appservice`-style commands
let command_with_dashes_argv1;
if argv.len() > 1 && argv[1].contains('_') {
command_with_dashes_argv1 = argv[1].replace('_', "-");
argv[1] = &command_with_dashes_argv1;
}
// Backwards compatibility with `register_appservice`-style commands
let command_with_dashes_argv2;
if argv.len() > 2 && argv[2].contains('_') {
command_with_dashes_argv2 = argv[2].replace('_', "-");
argv[2] = &command_with_dashes_argv2;
}
// if the user is using the `query` command (argv[1]), replace the database
// function/table calls with underscores to match the codebase
let command_with_dashes_argv3;
if argv.len() > 3 && argv[1].eq("query") {
command_with_dashes_argv3 = argv[3].replace('_', "-");
argv[3] = &command_with_dashes_argv3;
}
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
}
#[tracing::instrument(skip_all, name = "command")]
async fn process_admin_command(command: AdminCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
let reply_message_content = match command {
AdminCommand::Appservices(command) => appservice::process(command, body).await?,
@@ -226,81 +140,102 @@ async fn process_admin_command(command: AdminCommand, body: Vec<&str>) -> Result
AdminCommand::Server(command) => server::process(command, body).await?,
AdminCommand::Debug(command) => debug::process(command, body).await?,
AdminCommand::Query(command) => query::process(command, body).await?,
AdminCommand::Fsck(command) => fsck::process(command, body).await?,
AdminCommand::Tester(command) => tester::process(command, body).await?,
AdminCommand::Check(command) => check::process(command, body).await?,
};
Ok(reply_message_content)
}
// Utility to turn clap's `--help` text to HTML.
fn usage_to_html(text: &str, server_name: &ServerName) -> String {
// Replace `@conduit:servername:-subcmdname` with `@conduit:servername:
// subcmdname`
let text = text.replace(&format!("@conduit:{server_name}:-"), &format!("@conduit:{server_name}: "));
// Parse chat messages from the admin room into an AdminCommand object
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> {
let argv = parse_command_line(command_line);
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
}
// For the conduit admin room, subcommands become main commands
let text = text.replace("SUBCOMMAND", "COMMAND");
let text = text.replace("subcommand", "command");
fn complete_admin_command(mut cmd: clap::Command, line: &str) -> String {
let argv = parse_command_line(line);
let mut ret = Vec::<String>::with_capacity(argv.len().saturating_add(1));
// Escape option names (e.g. `<element-id>`) since they look like HTML tags
let text = escape_html(&text);
'token: for token in argv.into_iter().skip(1) {
let cmd_ = cmd.clone();
let mut choice = Vec::new();
// Italicize the first line (command name and version text)
let re = Regex::new("^(.*?)\n").expect("Regex compilation should not fail");
let text = re.replace_all(&text, "<em>$1</em>\n");
// Unmerge wrapped lines
let text = text.replace("\n ", " ");
// Wrap option names in backticks. The lines look like:
// -V, --version Prints version information
// And are converted to:
// <code>-V, --version</code>: Prints version information
// (?m) enables multi-line mode for ^ and $
let re = Regex::new("(?m)^ {4}(([a-zA-Z_&;-]+(, )?)+) +(.*)$").expect("Regex compilation should not fail");
let text = re.replace_all(&text, "<code>$1</code>: $4");
// Look for a `[commandbody]` tag. If it exists, use all lines below it that
// start with a `#` in the USAGE section.
let mut text_lines = text.lines().collect::<Vec<&str>>();
let mut command_body = String::new();
if let Some(line_index) = text_lines.iter().position(|line| *line == "[commandbody]") {
text_lines.remove(line_index);
while text_lines
.get(line_index)
.is_some_and(|line| line.starts_with('#'))
{
command_body += if text_lines[line_index].starts_with("# ") {
&text_lines[line_index][2..]
} else {
&text_lines[line_index][1..]
};
command_body += "[nobr]\n";
text_lines.remove(line_index);
for sub in cmd_.get_subcommands() {
let name = sub.get_name();
if *name == token {
// token already complete; recurse to subcommand
ret.push(token);
cmd.clone_from(sub);
continue 'token;
} else if name.starts_with(&token) {
// partial match; add to choices
choice.push(name);
}
}
if choice.len() == 1 {
// One choice. Add extra space because it's complete
let choice = *choice.first().expect("only choice");
ret.push(choice.to_owned());
ret.push(String::new());
} else if choice.is_empty() {
// Nothing found, return original string
ret.push(token);
} else {
// Find the common prefix
ret.push(common_prefix(&choice).into());
}
// Return from completion
return ret.join(" ");
}
let text = text_lines.join("\n");
// Improve the usage section
let text = if command_body.is_empty() {
// Wrap the usage line in code tags
let re = Regex::new("(?m)^USAGE:\n {4}(@conduit:.*)$").expect("Regex compilation should not fail");
re.replace_all(&text, "USAGE:\n<code>$1</code>").to_string()
} else {
// Wrap the usage line in a code block, and add a yaml block example
// This makes the usage of e.g. `register-appservice` more accurate
let re = Regex::new("(?m)^USAGE:\n {4}(.*?)\n\n").expect("Regex compilation should not fail");
re.replace_all(&text, "USAGE:\n<pre>$1[nobr]\n[commandbodyblock]</pre>")
.replace("[commandbodyblock]", &command_body)
};
// Add HTML line-breaks
text.replace("\n\n\n", "\n\n")
.replace('\n', "<br>\n")
.replace("[nobr]<br>", "")
// Return from no completion. Needs a space though.
ret.push(String::new());
ret.join(" ")
}
// Parse chat messages from the admin room into an AdminCommand object
fn parse_command_line(command_line: &str) -> Vec<String> {
let mut argv = command_line
.split_whitespace()
.map(str::to_owned)
.collect::<Vec<String>>();
// Remove any escapes that came with a server-side escape command
if !argv.is_empty() && argv[0].ends_with("admin") {
argv[0] = argv[0].trim_start_matches('\\').into();
}
// First indice has to be "admin" but for console convenience we add it here
let server_user = services().globals.server_user.as_str();
if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with(server_user) {
argv.insert(0, "admin".to_owned());
}
// Replace `help command` with `command --help`
// Clap has a help subcommand, but it omits the long help description.
if argv.len() > 1 && argv[1] == "help" {
argv.remove(1);
argv.push("--help".to_owned());
}
// Backwards compatibility with `register_appservice`-style commands
if argv.len() > 1 && argv[1].contains('_') {
argv[1] = argv[1].replace('_', "-");
}
// Backwards compatibility with `register_appservice`-style commands
if argv.len() > 2 && argv[2].contains('_') {
argv[2] = argv[2].replace('_', "-");
}
// if the user is using the `query` command (argv[1]), replace the database
// function/table calls with underscores to match the codebase
if argv.len() > 3 && argv[1].eq("query") {
argv[3] = argv[3].replace('_', "-");
}
trace!(?command_line, ?argv, "parse");
argv
}
@@ -1,9 +1,10 @@
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
use tracing::{debug, info};
use crate::{services, Result};
use crate::services;
pub(crate) async fn delete(
pub(super) async fn delete(
_body: Vec<&str>, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
) -> Result<RoomMessageEventContent> {
if event_id.is_some() && mxc.is_some() {
@@ -14,7 +15,7 @@ pub(crate) async fn delete(
if let Some(mxc) = mxc {
debug!("Got MXC URL: {mxc}");
services().media.delete(mxc.to_string()).await?;
services().media.delete(mxc.as_ref()).await?;
return Ok(RoomMessageEventContent::text_plain(
"Deleted the MXC from our database and on our filesystem.",
@@ -122,7 +123,7 @@ pub(crate) async fn delete(
}
for mxc_url in mxc_urls {
services().media.delete(mxc_url).await?;
services().media.delete(&mxc_url).await?;
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
}
@@ -137,7 +138,7 @@ pub(crate) async fn delete(
))
}
pub(crate) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -153,7 +154,7 @@ pub(crate) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventConte
for mxc in mxc_list {
debug!("Deleting MXC {mxc} in bulk");
services().media.delete(mxc.to_owned()).await?;
services().media.delete(mxc).await?;
mxc_deletion_count = mxc_deletion_count
.checked_add(1)
.expect("mxc_deletion_count should not get this high");
@@ -164,7 +165,7 @@ pub(crate) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventConte
)))
}
pub(crate) async fn delete_past_remote_media(
pub(super) async fn delete_past_remote_media(
_body: Vec<&str>, duration: String, force: bool,
) -> Result<RoomMessageEventContent> {
let deleted_count = services()
+6 -6
View File
@@ -1,14 +1,14 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
use self::media_commands::{delete, delete_list, delete_past_remote_media};
use crate::Result;
pub(crate) mod media_commands;
use self::commands::*;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum MediaCommand {
pub(super) enum MediaCommand {
/// - Deletes a single media file from our database and on the filesystem
/// via a single MXC URL
Delete {
@@ -38,7 +38,7 @@ pub(crate) enum MediaCommand {
},
}
pub(crate) async fn process(command: MediaCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: MediaCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
MediaCommand::Delete {
mxc,
+29 -28
View File
@@ -1,13 +1,15 @@
#![allow(clippy::wildcard_imports)]
pub(crate) mod appservice;
pub(crate) mod check;
pub(crate) mod debug;
pub(crate) mod federation;
pub(crate) mod fsck;
pub(crate) mod handler;
pub(crate) mod media;
pub(crate) mod query;
pub(crate) mod room;
pub(crate) mod server;
pub(crate) mod tester;
mod tests;
pub(crate) mod user;
pub(crate) mod utils;
@@ -16,7 +18,6 @@ extern crate conduit_core as conduit;
extern crate conduit_service as service;
pub(crate) use conduit::{mod_ctor, mod_dtor, Result};
pub use handler::handle;
pub(crate) use service::{services, user_is_local};
pub(crate) use crate::{
@@ -27,29 +28,29 @@ pub(crate) use crate::{
mod_ctor! {}
mod_dtor! {}
#[cfg(test)]
mod test {
use clap::Parser;
use crate::handler::AdminCommand;
#[test]
fn get_help_short() { get_help_inner("-h"); }
#[test]
fn get_help_long() { get_help_inner("--help"); }
#[test]
fn get_help_subcommand() { get_help_inner("help"); }
fn get_help_inner(input: &str) {
let error = AdminCommand::try_parse_from(["argv[0] doesn't matter", input])
.unwrap_err()
.to_string();
// Search for a handful of keywords that suggest the help printed properly
assert!(error.contains("Usage:"));
assert!(error.contains("Commands:"));
assert!(error.contains("Options:"));
}
/// Install the admin command handler
pub async fn init() {
_ = services()
.admin
.complete
.write()
.expect("locked for writing")
.insert(handler::complete);
_ = services()
.admin
.handle
.write()
.await
.insert(handler::handle);
}
/// Uninstall the admin command handler
pub async fn fini() {
_ = services().admin.handle.write().await.take();
_ = services()
.admin
.complete
.write()
.expect("locked for writing")
.take();
}
+7 -11
View File
@@ -4,7 +4,7 @@ use super::AccountData;
use crate::{services, Result};
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(crate) async fn account_data(subcommand: AccountData) -> Result<RoomMessageEventContent> {
pub(super) async fn account_data(subcommand: AccountData) -> Result<RoomMessageEventContent> {
match subcommand {
AccountData::ChangesSince {
user_id,
@@ -14,14 +14,12 @@ pub(crate) async fn account_data(subcommand: AccountData) -> Result<RoomMessageE
let timer = tokio::time::Instant::now();
let results = services()
.account_data
.db
.changes_since(room_id.as_deref(), &user_id, since)?;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
AccountData::Get {
user_id,
@@ -31,14 +29,12 @@ pub(crate) async fn account_data(subcommand: AccountData) -> Result<RoomMessageE
let timer = tokio::time::Instant::now();
let results = services()
.account_data
.db
.get(room_id.as_deref(), &user_id, kind)?;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
}
}
+8 -10
View File
@@ -4,7 +4,7 @@ use super::Appservice;
use crate::{services, Result};
/// All the getters and iterators from src/database/key_value/appservice.rs
pub(crate) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEventContent> {
pub(super) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEventContent> {
match subcommand {
Appservice::GetRegistration {
appservice_id,
@@ -16,20 +16,18 @@ pub(crate) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEven
.get_registration(appservice_id.as_ref());
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Appservice::All => {
let timer = tokio::time::Instant::now();
let results = services().appservice.db.all();
let results = services().appservice.all();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
}
}
+17 -22
View File
@@ -4,47 +4,43 @@ use super::Globals;
use crate::{services, Result};
/// All the getters and iterators from src/database/key_value/globals.rs
pub(crate) async fn globals(subcommand: Globals) -> Result<RoomMessageEventContent> {
pub(super) async fn globals(subcommand: Globals) -> Result<RoomMessageEventContent> {
match subcommand {
Globals::DatabaseVersion => {
let timer = tokio::time::Instant::now();
let results = services().globals.db.database_version();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Globals::CurrentCount => {
let timer = tokio::time::Instant::now();
let results = services().globals.db.current_count();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Globals::LastCheckForUpdatesId => {
let timer = tokio::time::Instant::now();
let results = services().globals.db.last_check_for_updates_id();
let results = services().updates.last_check_for_updates_id();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Globals::LoadKeypair => {
let timer = tokio::time::Instant::now();
let results = services().globals.db.load_keypair();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Globals::SigningKeysFor {
origin,
@@ -53,10 +49,9 @@ pub(crate) async fn globals(subcommand: Globals) -> Result<RoomMessageEventConte
let results = services().globals.db.signing_keys_for(&origin);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
}
}
+43 -22
View File
@@ -1,29 +1,30 @@
pub(crate) mod account_data;
pub(crate) mod appservice;
pub(crate) mod globals;
pub(crate) mod presence;
pub(crate) mod room_alias;
pub(crate) mod room_state_cache;
pub(crate) mod sending;
pub(crate) mod users;
mod account_data;
mod appservice;
mod globals;
mod presence;
mod resolver;
mod room_alias;
mod room_state_cache;
mod sending;
mod users;
use clap::Subcommand;
use conduit::Result;
use room_state_cache::room_state_cache;
use ruma::{
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
RoomAliasId, RoomId, ServerName, UserId,
OwnedServerName, RoomAliasId, RoomId, ServerName, UserId,
};
use self::{
account_data::account_data, appservice::appservice, globals::globals, presence::presence, room_alias::room_alias,
sending::sending, users::users,
account_data::account_data, appservice::appservice, globals::globals, presence::presence, resolver::resolver,
room_alias::room_alias, sending::sending, users::users,
};
use crate::Result;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// Query tables from database
pub(crate) enum QueryCommand {
pub(super) enum QueryCommand {
/// - account_data.rs iterators and getters
#[command(subcommand)]
AccountData(AccountData),
@@ -55,12 +56,16 @@ pub(crate) enum QueryCommand {
/// - users.rs iterators and getters
#[command(subcommand)]
Users(Users),
/// - resolver service
#[command(subcommand)]
Resolver(Resolver),
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(crate) enum AccountData {
pub(super) enum AccountData {
/// - Returns all changes to the account data that happened after `since`.
ChangesSince {
/// Full user ID
@@ -85,7 +90,7 @@ pub(crate) enum AccountData {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/appservice.rs
pub(crate) enum Appservice {
pub(super) enum Appservice {
/// - Gets the appservice registration info/details from the ID as a string
GetRegistration {
/// Appservice registration ID
@@ -99,7 +104,7 @@ pub(crate) enum Appservice {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/presence.rs
pub(crate) enum Presence {
pub(super) enum Presence {
/// - Returns the latest presence event for the given user.
GetPresence {
/// Full user ID
@@ -117,7 +122,7 @@ pub(crate) enum Presence {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
pub(crate) enum RoomAlias {
pub(super) enum RoomAlias {
ResolveLocalAlias {
/// Full room alias
alias: Box<RoomAliasId>,
@@ -135,7 +140,7 @@ pub(crate) enum RoomAlias {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum RoomStateCache {
pub(super) enum RoomStateCache {
ServerInRoom {
server: Box<ServerName>,
room_id: Box<RoomId>,
@@ -208,7 +213,7 @@ pub(crate) enum RoomStateCache {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/globals.rs
pub(crate) enum Globals {
pub(super) enum Globals {
DatabaseVersion,
CurrentCount,
@@ -227,7 +232,7 @@ pub(crate) enum Globals {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/sending.rs
pub(crate) enum Sending {
pub(super) enum Sending {
/// - Queries database for all `servercurrentevent_data`
ActiveRequests,
@@ -283,12 +288,27 @@ pub(crate) enum Sending {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// All the getters and iterators from src/database/key_value/users.rs
pub(crate) enum Users {
pub(super) enum Users {
Iter,
}
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
/// Resolver service and caches
pub(super) enum Resolver {
/// Query the destinations cache
DestinationsCache {
server_name: Option<OwnedServerName>,
},
/// Query the overrides cache
OverridesCache {
name: Option<String>,
},
}
/// Processes admin query commands
pub(crate) async fn process(command: QueryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: QueryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
QueryCommand::AccountData(command) => account_data(command).await?,
QueryCommand::Appservice(command) => appservice(command).await?,
@@ -298,5 +318,6 @@ pub(crate) async fn process(command: QueryCommand, _body: Vec<&str>) -> Result<R
QueryCommand::Globals(command) => globals(command).await?,
QueryCommand::Sending(command) => sending(command).await?,
QueryCommand::Users(command) => users(command).await?,
QueryCommand::Resolver(command) => resolver(command).await?,
})
}
+8 -11
View File
@@ -4,7 +4,7 @@ use super::Presence;
use crate::{services, Result};
/// All the getters and iterators in key_value/presence.rs
pub(crate) async fn presence(subcommand: Presence) -> Result<RoomMessageEventContent> {
pub(super) async fn presence(subcommand: Presence) -> Result<RoomMessageEventContent> {
match subcommand {
Presence::GetPresence {
user_id,
@@ -13,24 +13,21 @@ pub(crate) async fn presence(subcommand: Presence) -> Result<RoomMessageEventCon
let results = services().presence.db.get_presence(&user_id)?;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
Presence::PresenceSince {
since,
} => {
let timer = tokio::time::Instant::now();
let results = services().presence.db.presence_since(since);
let presence_since: Vec<(_, _, _)> = results.collect();
let query_time = timer.elapsed();
let presence_since: Vec<(_, _, _)> = results.collect();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{presence_since:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{presence_since:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{presence_since:#?}\n```"
)))
},
}
}
+87
View File
@@ -0,0 +1,87 @@
use std::fmt::Write;
use conduit::{utils::time, Result};
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
use super::Resolver;
use crate::services;
/// All the getters and iterators in key_value/users.rs
pub(super) async fn resolver(subcommand: Resolver) -> Result<RoomMessageEventContent> {
match subcommand {
Resolver::DestinationsCache {
server_name,
} => destinations_cache(server_name).await,
Resolver::OverridesCache {
name,
} => overrides_cache(name).await,
}
}
async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> {
use service::sending::CachedDest;
let mut out = String::new();
writeln!(out, "| Server Name | Destination | Hostname | Expires |")?;
writeln!(out, "| ----------- | ----------- | -------- | ------- |")?;
let row = |(
name,
&CachedDest {
ref dest,
ref host,
expire,
},
)| {
let expire = time::format(expire, "%+");
writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line");
};
let map = services()
.globals
.resolver
.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);
}
Ok(RoomMessageEventContent::notice_markdown(out))
}
async fn overrides_cache(server_name: Option<String>) -> Result<RoomMessageEventContent> {
use service::sending::CachedOverride;
let mut out = String::new();
writeln!(out, "| Server Name | IP | Port | Expires |")?;
writeln!(out, "| ----------- | --- | ----:| ------- |")?;
let row = |(
name,
&CachedOverride {
ref ips,
port,
expire,
},
)| {
let expire = time::format(expire, "%+");
writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line");
};
let map = services()
.globals
.resolver
.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);
}
Ok(RoomMessageEventContent::notice_markdown(out))
}
+15 -20
View File
@@ -4,45 +4,40 @@ use super::RoomAlias;
use crate::{services, Result};
/// All the getters and iterators in src/database/key_value/rooms/alias.rs
pub(crate) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEventContent> {
pub(super) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEventContent> {
match subcommand {
RoomAlias::ResolveLocalAlias {
alias,
} => {
let timer = tokio::time::Instant::now();
let results = services().rooms.alias.db.resolve_local_alias(&alias);
let results = services().rooms.alias.resolve_local_alias(&alias);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomAlias::LocalAliasesForRoom {
room_id,
} => {
let timer = tokio::time::Instant::now();
let results = services().rooms.alias.db.local_aliases_for_room(&room_id);
let results = services().rooms.alias.local_aliases_for_room(&room_id);
let aliases: Vec<_> = results.collect();
let query_time = timer.elapsed();
let aliases: Vec<_> = results.collect();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{aliases:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{aliases:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
)))
},
RoomAlias::AllLocalAliases => {
let timer = tokio::time::Instant::now();
let results = services().rooms.alias.db.all_local_aliases();
let results = services().rooms.alias.all_local_aliases();
let aliases: Vec<_> = results.collect();
let query_time = timer.elapsed();
let aliases: Vec<_> = results.collect();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{aliases:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{aliases:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
)))
},
}
}
+49 -65
View File
@@ -3,7 +3,7 @@ use ruma::events::room::message::RoomMessageEventContent;
use super::RoomStateCache;
use crate::{services, Result};
pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomMessageEventContent> {
pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomMessageEventContent> {
match subcommand {
RoomStateCache::ServerInRoom {
server,
@@ -16,10 +16,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.server_in_room(&server, &room_id);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{result:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{result:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
)))
},
RoomStateCache::RoomServers {
room_id,
@@ -32,10 +31,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::ServerRooms {
server,
@@ -44,10 +42,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
let results: Result<Vec<_>> = services().rooms.state_cache.server_rooms(&server).collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomMembers {
room_id,
@@ -60,10 +57,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::LocalUsersInRoom {
room_id,
@@ -76,10 +72,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::ActiveLocalUsersInRoom {
room_id,
@@ -92,10 +87,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomJoinedCount {
room_id,
@@ -104,10 +98,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
let results = services().rooms.state_cache.room_joined_count(&room_id);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomInvitedCount {
room_id,
@@ -116,10 +109,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
let results = services().rooms.state_cache.room_invited_count(&room_id);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomUserOnceJoined {
room_id,
@@ -132,10 +124,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomMembersInvited {
room_id,
@@ -148,10 +139,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::GetInviteCount {
room_id,
@@ -164,10 +154,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.get_invite_count(&room_id, &user_id);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::GetLeftCount {
room_id,
@@ -180,10 +169,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.get_left_count(&room_id, &user_id);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomsJoined {
user_id,
@@ -196,10 +184,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomsInvited {
user_id,
@@ -212,10 +199,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::RoomsLeft {
user_id,
@@ -224,10 +210,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
let results: Result<Vec<_>> = services().rooms.state_cache.rooms_left(&user_id).collect();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCache::InviteState {
user_id,
@@ -240,10 +225,9 @@ pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
.invite_state(&user_id, &room_id);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
}
}
+36 -66
View File
@@ -4,19 +4,17 @@ use super::Sending;
use crate::{service::sending::Destination, services, Result};
/// All the getters and iterators in key_value/sending.rs
pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventContent> {
pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventContent> {
match subcommand {
Sending::ActiveRequests => {
let timer = tokio::time::Instant::now();
let results = services().sending.db.active_requests();
let active_requests: Result<Vec<(_, _, _)>> = results.collect();
let query_time = timer.elapsed();
let active_requests: Result<Vec<(_, _, _)>> = results.collect();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{active_requests:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{active_requests:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
)))
},
Sending::QueuedRequests {
appservice_id,
@@ -30,8 +28,8 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
--help for more details.",
));
}
let (results, query_time) = match (appservice_id, server_name, user_id, push_key) {
let timer = tokio::time::Instant::now();
let results = match (appservice_id, server_name, user_id, push_key) {
(Some(appservice_id), None, None, None) => {
if appservice_id.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
@@ -40,25 +38,15 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
let timer = tokio::time::Instant::now();
let results = services()
services()
.sending
.db
.queued_requests(&Destination::Appservice(appservice_id));
let query_time = timer.elapsed();
(results, query_time)
},
(None, Some(server_name), None, None) => {
let timer = tokio::time::Instant::now();
let results = services()
.sending
.db
.queued_requests(&Destination::Normal(server_name.into()));
let query_time = timer.elapsed();
(results, query_time)
.queued_requests(&Destination::Appservice(appservice_id))
},
(None, Some(server_name), None, None) => services()
.sending
.db
.queued_requests(&Destination::Normal(server_name.into())),
(None, None, Some(user_id), Some(push_key)) => {
if push_key.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
@@ -67,14 +55,10 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
let timer = tokio::time::Instant::now();
let results = services()
services()
.sending
.db
.queued_requests(&Destination::Push(user_id.into(), push_key));
let query_time = timer.elapsed();
(results, query_time)
.queued_requests(&Destination::Push(user_id.into(), push_key))
},
(Some(_), Some(_), Some(_), Some(_)) => {
return Ok(RoomMessageEventContent::text_plain(
@@ -91,11 +75,11 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
};
let queued_requests = results.collect::<Result<Vec<(_, _)>>>();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{queued_requests:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{queued_requests:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```"
)))
},
Sending::ActiveRequestsFor {
appservice_id,
@@ -110,7 +94,8 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
let (results, query_time) = match (appservice_id, server_name, user_id, push_key) {
let timer = tokio::time::Instant::now();
let results = match (appservice_id, server_name, user_id, push_key) {
(Some(appservice_id), None, None, None) => {
if appservice_id.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
@@ -119,25 +104,15 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
let timer = tokio::time::Instant::now();
let results = services()
services()
.sending
.db
.active_requests_for(&Destination::Appservice(appservice_id));
let query_time = timer.elapsed();
(results, query_time)
},
(None, Some(server_name), None, None) => {
let timer = tokio::time::Instant::now();
let results = services()
.sending
.db
.active_requests_for(&Destination::Normal(server_name.into()));
let query_time = timer.elapsed();
(results, query_time)
.active_requests_for(&Destination::Appservice(appservice_id))
},
(None, Some(server_name), None, None) => services()
.sending
.db
.active_requests_for(&Destination::Normal(server_name.into())),
(None, None, Some(user_id), Some(push_key)) => {
if push_key.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
@@ -146,14 +121,10 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
));
}
let timer = tokio::time::Instant::now();
let results = services()
services()
.sending
.db
.active_requests_for(&Destination::Push(user_id.into(), push_key));
let query_time = timer.elapsed();
(results, query_time)
.active_requests_for(&Destination::Push(user_id.into(), push_key))
},
(Some(_), Some(_), Some(_), Some(_)) => {
return Ok(RoomMessageEventContent::text_plain(
@@ -170,11 +141,11 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
};
let active_requests = results.collect::<Result<Vec<(_, _)>>>();
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{active_requests:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{active_requests:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
)))
},
Sending::GetLatestEduCount {
server_name,
@@ -183,10 +154,9 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
let results = services().sending.db.get_latest_educount(&server_name);
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
}
}
+5 -7
View File
@@ -4,19 +4,17 @@ use super::Users;
use crate::{services, Result};
/// All the getters and iterators in key_value/users.rs
pub(crate) async fn users(subcommand: Users) -> Result<RoomMessageEventContent> {
pub(super) async fn users(subcommand: Users) -> Result<RoomMessageEventContent> {
match subcommand {
Users::Iter => {
let timer = tokio::time::Instant::now();
let results = services().users.db.iter();
let users = results.collect::<Vec<_>>();
let query_time = timer.elapsed();
let users = results.collect::<Vec<_>>();
Ok(RoomMessageEventContent::text_html(
format!("Query completed in {query_time:?}:\n\n```\n{users:?}```"),
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{users:?}\n</code></pre>"),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{users:#?}\n```"
)))
},
}
}
+24 -14
View File
@@ -1,21 +1,29 @@
mod room_alias_commands;
mod room_commands;
mod room_directory_commands;
mod room_info_commands;
mod room_moderation_commands;
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, RoomOrAliasId};
use self::room_commands::list;
use crate::Result;
pub(crate) mod room_alias_commands;
pub(crate) mod room_commands;
pub(crate) mod room_directory_commands;
pub(crate) mod room_info_commands;
pub(crate) mod room_moderation_commands;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum RoomCommand {
pub(super) enum RoomCommand {
/// - List all rooms the server knows about
List {
page: Option<usize>,
/// Excludes rooms that we have federation disabled with
#[arg(long)]
exclude_disabled: bool,
/// Excludes rooms that we have banned
#[arg(long)]
exclude_banned: bool,
},
#[command(subcommand)]
@@ -37,7 +45,7 @@ pub(crate) enum RoomCommand {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum RoomInfoCommand {
pub(super) enum RoomInfoCommand {
/// - List joined members in a room
ListJoinedMembers {
room_id: Box<RoomId>,
@@ -54,7 +62,7 @@ pub(crate) enum RoomInfoCommand {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum RoomAliasCommand {
pub(super) enum RoomAliasCommand {
/// - Make an alias point to a room.
Set {
#[arg(short, long)]
@@ -90,7 +98,7 @@ pub(crate) enum RoomAliasCommand {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum RoomDirectoryCommand {
pub(super) enum RoomDirectoryCommand {
/// - Publish a room to the room directory
Publish {
/// The room id of the room to publish
@@ -111,7 +119,7 @@ pub(crate) enum RoomDirectoryCommand {
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum RoomModerationCommand {
pub(super) enum RoomModerationCommand {
/// - Bans a room from local users joining and evicts all our local users
/// from the room. Also blocks any invites (local and remote) for the
/// banned room.
@@ -167,7 +175,7 @@ pub(crate) enum RoomModerationCommand {
ListBannedRooms,
}
pub(crate) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
RoomCommand::Info(command) => room_info_commands::process(command, body).await?,
@@ -179,6 +187,8 @@ pub(crate) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<Roo
RoomCommand::List {
page,
} => list(body, page).await?,
exclude_disabled,
exclude_banned,
} => list(body, page, exclude_disabled, exclude_banned).await?,
})
}
+1 -1
View File
@@ -5,7 +5,7 @@ use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId};
use super::RoomAliasCommand;
use crate::{escape_html, services, Result};
pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
let server_user = &services().globals.server_user;
match command {
+32 -4
View File
@@ -1,18 +1,46 @@
use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
use ruma::events::room::message::RoomMessageEventContent;
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMessageEventContent> {
pub(super) async fn list(
_body: Vec<&str>, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
) -> Result<RoomMessageEventContent> {
// TODO: i know there's a way to do this with clap, but i can't seem to find it
let page = page.unwrap_or(1);
let mut rooms = services()
.rooms
.metadata
.iter_ids()
.filter_map(Result::ok)
.map(|id: OwnedRoomId| get_room_info(&id))
.filter_map(|room_id| {
room_id
.ok()
.filter(|room_id| {
if exclude_disabled
&& services()
.rooms
.metadata
.is_disabled(room_id)
.unwrap_or(false)
{
return false;
}
if exclude_banned
&& services()
.rooms
.metadata
.is_banned(room_id)
.unwrap_or(false)
{
return false;
}
true
})
.map(|room_id| get_room_info(&room_id))
})
.collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1);
rooms.reverse();
+1 -1
View File
@@ -5,7 +5,7 @@ use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
use super::RoomDirectoryCommand;
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
RoomDirectoryCommand::Publish {
room_id,
+6 -30
View File
@@ -1,12 +1,10 @@
use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
use service::services;
use super::RoomInfoCommand;
use crate::{escape_html, Result};
use crate::Result;
pub(crate) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
RoomInfoCommand::ListJoinedMembers {
room_id,
@@ -57,26 +55,7 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
.join("\n")
);
let output_html = format!(
"<table><caption>{} Members in Room \"{}\" </caption>\n<tr><th>MXID</th>\t<th>Display \
Name</th></tr>\n{}</table>",
member_info.len(),
room_name,
member_info
.iter()
.fold(String::new(), |mut output, (mxid, displayname)| {
writeln!(
output,
"<tr><td>{}</td>\t<td>{}</td></tr>",
mxid,
escape_html(displayname.as_ref())
)
.expect("should be able to write to string buffer");
output
})
);
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
Ok(RoomMessageEventContent::notice_markdown(output_plain))
}
async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
@@ -84,10 +63,7 @@ async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomM
return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set."));
};
let output_html = format!("<p>Room topic:</p>\n<hr>\n{}<hr>", escape_html(&room_topic));
Ok(RoomMessageEventContent::text_html(
format!("Room topic:\n\n```{room_topic}\n```"),
output_html,
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"Room topic:\n\n```{room_topic}\n```"
)))
}
+57 -70
View File
@@ -1,15 +1,11 @@
use std::fmt::Write;
use api::client::{get_alias_helper, leave_room};
use ruma::{
events::room::message::RoomMessageEventContent, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId,
};
use api::client::leave_room;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
use tracing::{debug, error, info, warn};
use super::{super::Service, RoomModerationCommand};
use crate::{escape_html, get_room_info, services, user_is_local, Result};
use crate::{get_room_info, services, user_is_local, Result};
pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
RoomModerationCommand::BanRoom {
force,
@@ -78,13 +74,18 @@ async fn ban_room(
} else {
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
match get_alias_helper(room_alias, None).await {
Ok(response) => {
debug!("Got federation response fetching room ID for room {room}: {:?}", response);
response.room_id
match services()
.rooms
.alias
.resolve_alias(&room_alias, None)
.await
{
Ok((room_id, servers)) => {
debug!(?room_id, ?servers, "Got federation response fetching room ID for {room}");
room_id
},
Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
return Ok(RoomMessageEventContent::notice_plain(format!(
"Failed to resolve room alias {room} to a room ID: {e}"
)));
},
@@ -111,19 +112,17 @@ async fn ban_room(
user.ok().filter(|local_user| {
user_is_local(local_user)
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
// who are in the admin room to the list of local users (would
// fail auth check)
&& (user_is_local(local_user)
// since this is a force operation, assume user is an admin
// if somehow this fails
&& services()
.users
.is_admin(local_user)
.unwrap_or(true)) // since this is a force
// operation, assume user
// is an admin if somehow
// this fails
.unwrap_or(true))
})
})
.collect::<Vec<OwnedUserId>>()
{
}) {
debug!(
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
&local_user, &room_id
@@ -150,9 +149,7 @@ async fn ban_room(
.is_admin(local_user)
.unwrap_or(false))
})
})
.collect::<Vec<OwnedUserId>>()
{
}) {
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(&local_user, &room_id, None).await {
error!(
@@ -188,7 +185,10 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
));
}
let rooms_s = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
let rooms_s = body
.clone()
.drain(1..body.len().saturating_sub(1))
.collect::<Vec<_>>();
let admin_room_alias = &services().globals.admin_alias;
@@ -239,13 +239,19 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
ID over federation"
);
match get_alias_helper(room_alias, None).await {
Ok(response) => {
match services()
.rooms
.alias
.resolve_alias(&room_alias, None)
.await
{
Ok((room_id, servers)) => {
debug!(
"Got federation response fetching room ID for room {room}: {:?}",
response
?room_id,
?servers,
"Got federation response fetching room ID for {room}",
);
response.room_id
room_id
},
Err(e) => {
// don't fail if force blocking
@@ -311,23 +317,19 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
.filter_map(|user| {
user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name()
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
// additional wrapped check here is to avoid adding remote
// users who are in the admin room to the list of local
// users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
// since this is a force operation, assume user is an
// admin if somehow this fails
&& services()
.users
.is_admin(local_user)
.unwrap_or(true)) // since this is a
// force operation,
// assume user is
// an admin if
// somehow this
// fails
.unwrap_or(true))
})
})
.collect::<Vec<OwnedUserId>>()
{
}) {
debug!(
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
&local_user, room_id
@@ -344,8 +346,9 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
.filter_map(|user| {
user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name()
// additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check)
// additional wrapped check here is to avoid adding remote
// users who are in the admin room to the list of local
// users (would fail auth check)
&& (local_user.server_name()
== services().globals.server_name()
&& !services()
@@ -353,9 +356,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
.is_admin(local_user)
.unwrap_or(false))
})
})
.collect::<Vec<OwnedUserId>>()
{
}) {
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(&local_user, &room_id, None).await {
error!(
@@ -429,10 +430,15 @@ async fn unban_room(
} else {
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
match get_alias_helper(room_alias, None).await {
Ok(response) => {
debug!("Got federation response fetching room ID for room {room}: {:?}", response);
response.room_id
match services()
.rooms
.alias
.resolve_alias(&room_alias, None)
.await
{
Ok((room_id, servers)) => {
debug!(?room_id, ?servers, "Got federation response fetching room ID for room {room}");
room_id
},
Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
@@ -493,26 +499,7 @@ async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent>
.join("\n")
);
let output_html = format!(
"<table><caption>Rooms Banned ({}) \
</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
rooms.len(),
rooms
.iter()
.fold(String::new(), |mut output, (id, members, name)| {
writeln!(
output,
"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
id,
members,
escape_html(name.as_ref())
)
.expect("should be able to write to string buffer");
output
})
);
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
Ok(RoomMessageEventContent::notice_markdown(output_plain))
},
Err(e) => {
error!("Failed to list banned rooms: {}", e);
+107
View File
@@ -0,0 +1,107 @@
use conduit::{utils::time, warn, Err, Result};
use ruma::events::room::message::RoomMessageEventContent;
use crate::services;
pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let elapsed = services()
.server
.started
.elapsed()
.expect("standard duration");
let result = time::pretty(elapsed);
Ok(RoomMessageEventContent::notice_plain(format!("{result}.")))
}
pub(super) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
// Construct and send the response
Ok(RoomMessageEventContent::text_plain(format!("{}", services().globals.config)))
}
pub(super) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let services_usage = services().memory_usage().await?;
let database_usage = services().db.db.memory_usage()?;
let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
Ok(RoomMessageEventContent::text_plain(format!(
"Services:\n{services_usage}\nDatabase:\n{database_usage}{allocator_usage}",
)))
}
pub(super) async fn clear_caches(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
services().clear_cache().await;
Ok(RoomMessageEventContent::text_plain("Done."))
}
pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let result = services().globals.db.backup_list()?;
if result.is_empty() {
Ok(RoomMessageEventContent::text_plain("No backups found."))
} else {
Ok(RoomMessageEventContent::text_plain(result))
}
}
pub(super) async fn backup_database(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let mut result = services()
.server
.runtime()
.spawn_blocking(move || match services().globals.db.backup() {
Ok(()) => String::new(),
Err(e) => (*e).to_string(),
})
.await
.unwrap();
if result.is_empty() {
result = services().globals.db.backup_list()?;
}
Ok(RoomMessageEventContent::text_plain(&result))
}
pub(super) async fn list_database_files(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let result = services().globals.db.file_list()?;
Ok(RoomMessageEventContent::notice_markdown(result))
}
pub(super) async fn admin_notice(_body: Vec<&str>, message: Vec<String>) -> Result<RoomMessageEventContent> {
let message = message.join(" ");
services().admin.send_text(&message).await;
Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins"))
}
#[cfg(conduit_mods)]
pub(super) async fn reload(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
services().server.reload()?;
Ok(RoomMessageEventContent::notice_plain("Reloading server..."))
}
#[cfg(unix)]
pub(super) async fn restart(_body: Vec<&str>, force: bool) -> Result<RoomMessageEventContent> {
use conduit::utils::sys::current_exe_deleted;
if !force && current_exe_deleted() {
return Err!(
"The server cannot be restarted because the executable changed. If this is expected use --force to \
override."
);
}
services().server.restart()?;
Ok(RoomMessageEventContent::notice_plain("Restarting server..."))
}
pub(super) async fn shutdown(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
warn!("shutdown command");
services().server.shutdown()?;
Ok(RoomMessageEventContent::notice_plain("Shutting down server..."))
}
+37 -25
View File
@@ -1,17 +1,14 @@
pub(crate) mod server_commands;
mod commands;
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::server_commands::{
backup_database, clear_database_caches, clear_service_caches, list_backups, list_database_files, memory_usage,
show_config, uptime,
};
use crate::Result;
use self::commands::*;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum ServerCommand {
pub(super) enum ServerCommand {
/// - Time elapsed since startup
Uptime,
@@ -21,17 +18,8 @@ pub(crate) enum ServerCommand {
/// - Print database memory usage statistics
MemoryUsage,
/// - Clears all of Conduit's database caches with index smaller than the
/// amount
ClearDatabaseCaches {
amount: u32,
},
/// - Clears all of Conduit's service caches with index smaller than the
/// amount
ClearServiceCaches {
amount: u32,
},
/// - Clears all of Conduwuit's caches
ClearCaches,
/// - Performs an online backup of the database (only available for RocksDB
/// at the moment)
@@ -42,21 +30,45 @@ pub(crate) enum ServerCommand {
/// - List database files
ListDatabaseFiles,
/// - Send a message to the admin room.
AdminNotice {
message: Vec<String>,
},
#[cfg(conduit_mods)]
/// - Hot-reload the server
Reload,
#[cfg(unix)]
/// - Restart the server
Restart {
#[arg(short, long)]
force: bool,
},
/// - Shutdown the server
Shutdown,
}
pub(crate) async fn process(command: ServerCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: ServerCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
ServerCommand::Uptime => uptime(body).await?,
ServerCommand::ShowConfig => show_config(body).await?,
ServerCommand::MemoryUsage => memory_usage(body).await?,
ServerCommand::ClearDatabaseCaches {
amount,
} => clear_database_caches(body, amount).await?,
ServerCommand::ClearServiceCaches {
amount,
} => clear_service_caches(body, amount).await?,
ServerCommand::ClearCaches => clear_caches(body).await?,
ServerCommand::ListBackups => list_backups(body).await?,
ServerCommand::BackupDatabase => backup_database(body).await?,
ServerCommand::ListDatabaseFiles => list_database_files(body).await?,
ServerCommand::AdminNotice {
message,
} => admin_notice(body, message).await?,
#[cfg(conduit_mods)]
ServerCommand::Reload => reload(body).await?,
#[cfg(unix)]
ServerCommand::Restart {
force,
} => restart(body, force).await?,
ServerCommand::Shutdown => shutdown(body).await?,
})
}
-98
View File
@@ -1,98 +0,0 @@
use ruma::events::room::message::RoomMessageEventContent;
use crate::{services, Result};
pub(crate) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let seconds = services()
.server
.started
.elapsed()
.expect("standard duration")
.as_secs();
let result = format!(
"up {} days, {} hours, {} minutes, {} seconds.",
seconds / 86400,
(seconds % 86400) / 60 / 60,
(seconds % 3600) / 60,
seconds % 60,
);
Ok(RoomMessageEventContent::notice_html(String::new(), result))
}
pub(crate) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
// Construct and send the response
Ok(RoomMessageEventContent::text_plain(format!("{}", services().globals.config)))
}
pub(crate) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let response0 = services().memory_usage().await;
let response1 = services().globals.db.memory_usage();
let response2 = conduit::alloc::memory_usage();
Ok(RoomMessageEventContent::text_plain(format!(
"Services:\n{response0}\n\nDatabase:\n{response1}\n{}",
if !response2.is_empty() {
format!("Allocator:\n {response2}")
} else {
String::new()
}
)))
}
pub(crate) async fn clear_database_caches(_body: Vec<&str>, amount: u32) -> Result<RoomMessageEventContent> {
services().globals.db.clear_caches(amount);
Ok(RoomMessageEventContent::text_plain("Done."))
}
pub(crate) async fn clear_service_caches(_body: Vec<&str>, amount: u32) -> Result<RoomMessageEventContent> {
services().clear_caches(amount).await;
Ok(RoomMessageEventContent::text_plain("Done."))
}
pub(crate) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let result = services().globals.db.backup_list()?;
if result.is_empty() {
Ok(RoomMessageEventContent::text_plain("No backups found."))
} else {
Ok(RoomMessageEventContent::text_plain(result))
}
}
pub(crate) async fn backup_database(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
if !cfg!(feature = "rocksdb") {
return Ok(RoomMessageEventContent::text_plain(
"Only RocksDB supports online backups in conduwuit.",
));
}
let mut result = services()
.server
.runtime()
.spawn_blocking(move || match services().globals.db.backup() {
Ok(()) => String::new(),
Err(e) => (*e).to_string(),
})
.await
.unwrap();
if result.is_empty() {
result = services().globals.db.backup_list()?;
}
Ok(RoomMessageEventContent::text_plain(&result))
}
pub(crate) async fn list_database_files(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
if !cfg!(feature = "rocksdb") {
return Ok(RoomMessageEventContent::text_plain(
"Only RocksDB supports listing files in conduwuit.",
));
}
let result = services().globals.db.file_list()?;
Ok(RoomMessageEventContent::notice_html(String::new(), result))
}
-14
View File
@@ -1,14 +0,0 @@
use ruma::events::room::message::RoomMessageEventContent;
use crate::Result;
#[cfg_attr(test, derive(Debug))]
#[derive(clap::Subcommand)]
pub(crate) enum TesterCommands {
Tester,
}
pub(crate) async fn process(command: TesterCommands, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
TesterCommands::Tester => RoomMessageEventContent::notice_plain(String::from("completed")),
})
}
+26
View File
@@ -0,0 +1,26 @@
#![cfg(test)]
#[test]
fn get_help_short() { get_help_inner("-h"); }
#[test]
fn get_help_long() { get_help_inner("--help"); }
#[test]
fn get_help_subcommand() { get_help_inner("help"); }
fn get_help_inner(input: &str) {
use clap::Parser;
use crate::handler::AdminCommand;
let Err(error) = AdminCommand::try_parse_from(["argv[0] doesn't matter", input]) else {
panic!("no error!");
};
let error = error.to_string();
// Search for a handful of keywords that suggest the help printed properly
assert!(error.contains("Usage:"));
assert!(error.contains("Commands:"));
assert!(error.contains("Options:"));
}
@@ -1,42 +1,38 @@
use std::{collections::BTreeMap, fmt::Write as _};
use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname};
use conduit::utils;
use conduit::{utils, Result};
use ruma::{
events::{
room::message::RoomMessageEventContent,
tag::{TagEvent, TagEventContent, TagInfo},
RoomAccountDataEventType,
},
OwnedRoomId, OwnedUserId, RoomId,
OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId,
};
use tracing::{error, info, warn};
use crate::{
escape_html, get_room_info, services,
utils::{parse_active_local_user_id, parse_local_user_id},
Result,
};
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
pub(crate) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
match services().users.list_local_users() {
Ok(users) => {
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
plain_msg += &users.join("\n");
plain_msg += users.join("\n").as_str();
plain_msg += "\n```";
let mut html_msg = format!("<p>Found {} local user account(s):</p><pre><code>", users.len());
html_msg += &users.join("\n");
html_msg += "\n</code></pre>";
Ok(RoomMessageEventContent::text_html(&plain_msg, &html_msg))
Ok(RoomMessageEventContent::notice_markdown(plain_msg))
},
Err(e) => Ok(RoomMessageEventContent::text_plain(e.to_string())),
}
}
pub(crate) async fn create(
pub(super) async fn create(
_body: Vec<&str>, username: String, password: Option<String>,
) -> Result<RoomMessageEventContent> {
// Validate user id
@@ -99,7 +95,7 @@ pub(crate) async fn create(
if let Some(room_id_server_name) = room.server_name() {
match join_room_by_id_helper(
Some(&user_id),
&user_id,
room,
Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
@@ -127,7 +123,7 @@ pub(crate) async fn create(
)))
}
pub(crate) async fn deactivate(
pub(super) async fn deactivate(
_body: Vec<&str>, no_leave_rooms: bool, user_id: String,
) -> Result<RoomMessageEventContent> {
// Validate user id
@@ -166,7 +162,7 @@ pub(crate) async fn deactivate(
)))
}
pub(crate) async fn reset_password(_body: Vec<&str>, username: String) -> Result<RoomMessageEventContent> {
pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(&username)?;
if user_id == services().globals.server_user {
@@ -190,7 +186,7 @@ pub(crate) async fn reset_password(_body: Vec<&str>, username: String) -> Result
}
}
pub(crate) async fn deactivate_all(
pub(super) async fn deactivate_all(
body: Vec<&str>, no_leave_rooms: bool, force: bool,
) -> Result<RoomMessageEventContent> {
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
@@ -199,7 +195,10 @@ pub(crate) async fn deactivate_all(
));
}
let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
let usernames = body
.clone()
.drain(1..body.len().saturating_sub(1))
.collect::<Vec<_>>();
let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
let mut admins = Vec::new();
@@ -284,7 +283,7 @@ pub(crate) async fn deactivate_all(
}
}
pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> {
pub(super) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> {
// Validate user id
let user_id = parse_local_user_id(&user_id)?;
@@ -335,7 +334,36 @@ pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
}
pub(crate) async fn put_room_tag(
pub(super) async fn force_join_room(
_body: Vec<&str>, user_id: String, room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(&user_id)?;
let room_id = services().rooms.alias.resolve(&room_id).await?;
assert!(service::user_is_local(&user_id), "Parsed user_id must be a local user");
join_room_by_id_helper(&user_id, &room_id, None, &[], None).await?;
Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been joined to {room_id}.",
)))
}
pub(super) async fn make_user_admin(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(&user_id)?;
let displayname = services()
.users
.displayname(&user_id)?
.unwrap_or_else(|| user_id.to_string());
assert!(service::user_is_local(&user_id), "Parsed user_id must be a local user");
service::admin::make_user_admin(&user_id, displayname).await?;
Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been granted admin privileges.",
)))
}
pub(super) async fn put_room_tag(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(&user_id)?;
@@ -370,7 +398,7 @@ pub(crate) async fn put_room_tag(
)))
}
pub(crate) async fn delete_room_tag(
pub(super) async fn delete_room_tag(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(&user_id)?;
@@ -402,7 +430,7 @@ pub(crate) async fn delete_room_tag(
)))
}
pub(crate) async fn get_room_tags(
pub(super) async fn get_room_tags(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>,
) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(&user_id)?;
@@ -420,8 +448,8 @@ pub(crate) async fn get_room_tags(
|e| serde_json::from_str(e.get()).expect("Bad account data in database for user {user_id}"),
);
Ok(RoomMessageEventContent::text_html(
format!("<pre><code>\n{:?}\n</code></pre>", tags_event.content.tags),
format!("```\n{:?}\n```", tags_event.content.tags),
))
Ok(RoomMessageEventContent::notice_markdown(format!(
"```\n{:#?}\n```",
tags_event.content.tags
)))
}
+25 -8
View File
@@ -1,15 +1,14 @@
pub(crate) mod user_commands;
mod commands;
use clap::Subcommand;
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
use user_commands::{delete_room_tag, get_room_tags, put_room_tag};
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId, RoomId};
use self::user_commands::{create, deactivate, deactivate_all, list, list_joined_rooms, reset_password};
use crate::Result;
use self::commands::*;
#[cfg_attr(test, derive(Debug))]
#[derive(Subcommand)]
pub(crate) enum UserCommand {
pub(super) enum UserCommand {
/// - Create a new user
Create {
/// Username of the new user
@@ -50,7 +49,7 @@ pub(crate) enum UserCommand {
/// Markdown code block below the command.
DeactivateAll {
#[arg(short, long)]
/// Remove users from their joined rooms
/// Does not leave any rooms the user is in on deactivation
no_leave_rooms: bool,
#[arg(short, long)]
/// Also deactivate admin accounts and will assume leave all rooms too
@@ -66,6 +65,17 @@ pub(crate) enum UserCommand {
user_id: String,
},
/// - Manually join a local user to a room.
ForceJoinRoom {
user_id: String,
room_id: OwnedRoomOrAliasId,
},
/// - Grant server-admin privileges to a user.
MakeUserAdmin {
user_id: String,
},
/// - Puts a room tag for the specified user and room ID.
///
/// This is primarily useful if you'd like to set your admin room
@@ -93,7 +103,7 @@ pub(crate) enum UserCommand {
},
}
pub(crate) async fn process(command: UserCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(command: UserCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
UserCommand::List => list(body).await?,
UserCommand::Create {
@@ -114,6 +124,13 @@ pub(crate) async fn process(command: UserCommand, body: Vec<&str>) -> Result<Roo
UserCommand::ListJoinedRooms {
user_id,
} => list_joined_rooms(body, user_id).await?,
UserCommand::ForceJoinRoom {
user_id,
room_id,
} => force_join_room(body, user_id, room_id).await?,
UserCommand::MakeUserAdmin {
user_id,
} => make_user_admin(body, user_id).await?,
UserCommand::PutRoomTag {
user_id,
room_id,
+5 -6
View File
@@ -1,5 +1,4 @@
pub(crate) use conduit::utils::HtmlEscape;
use conduit_core::Error;
use conduit_core::{err, Err};
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
use service::user_is_local;
@@ -34,7 +33,7 @@ pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
/// Parses user ID
pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> {
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name())
.map_err(|e| Error::Err(format!("The supplied username is not a valid username: {e}")))
.map_err(|e| err!("The supplied username is not a valid username: {e}"))
}
/// Parses user ID as our local user
@@ -42,7 +41,7 @@ pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> {
let user_id = parse_user_id(user_id)?;
if !user_is_local(&user_id) {
return Err(Error::Err(String::from("User does not belong to our server.")));
return Err!("User {user_id:?} does not belong to our server.");
}
Ok(user_id)
@@ -53,11 +52,11 @@ pub(crate) fn parse_active_local_user_id(user_id: &str) -> Result<OwnedUserId> {
let user_id = parse_local_user_id(user_id)?;
if !services().users.exists(&user_id)? {
return Err(Error::Err(String::from("User does not exist on this server.")));
return Err!("User {user_id:?} does not exist on this server.");
}
if services().users.is_deactivated(&user_id)? {
return Err(Error::Err(String::from("User is deactivated.")));
return Err!("User {user_id:?} is deactivated.");
}
Ok(user_id)
+3 -2
View File
@@ -18,7 +18,7 @@ crate-type = [
[features]
element_hacks = []
dev_release_log_level = []
#dev_release_log_level = []
release_max_log_level = [
"tracing/max_level_trace",
"tracing/release_max_level_info",
@@ -41,9 +41,11 @@ bytes.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
conduit-service.workspace = true
const-str.workspace = true
futures-util.workspace = true
hmac.workspace = true
http.workspace = true
http-body-util.workspace = true
hyper.workspace = true
image.workspace = true
ipaddress.workspace = true
@@ -56,7 +58,6 @@ serde_html_form.workspace = true
serde_json.workspace = true
serde.workspace = true
sha-1.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
webpage.workspace = true
+27 -21
View File
@@ -40,9 +40,9 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
///
/// Note: This will not reserve the username, so the username might become
/// invalid when trying to register
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
pub(crate) async fn get_register_available_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_username_availability::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_username_availability::v3::Request>,
) -> Result<get_username_availability::v3::Response> {
// Validate user id
let user_id = UserId::parse_with_server_name(body.username.to_lowercase(), services().globals.server_name())
@@ -89,9 +89,9 @@ pub(crate) async fn get_register_available_route(
/// - If `inhibit_login` is false: Creates a device and returns device id and
/// access_token
#[allow(clippy::doc_markdown)]
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "register")]
pub(crate) async fn register_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<register::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<register::v3::Request>,
) -> Result<register::v3::Response> {
if !services().globals.allow_registration() && body.appservice_info.is_none() {
info!(
@@ -302,14 +302,14 @@ pub(crate) async fn register_route(
services()
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client_ip}."
"New user \"{user_id}\" registered on this server from IP {client}."
)))
.await;
}
// log in conduit admin channel if a guest registered
if body.appservice_info.is_none() && is_guest && services().globals.log_guest_registrations() {
info!("New guest user \"{user_id}\" registered on this server from IP.");
info!("New guest user \"{user_id}\" registered on this server.");
if let Some(device_display_name) = &body.initial_device_display_name {
if body
@@ -321,7 +321,7 @@ pub(crate) async fn register_route(
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \
server from IP {client_ip}."
server from IP {client}."
)))
.await;
} else {
@@ -329,7 +329,7 @@ pub(crate) async fn register_route(
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
{client_ip}.",
{client}.",
)))
.await;
}
@@ -337,8 +337,7 @@ pub(crate) async fn register_route(
services()
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
{client_ip}.",
"Guest user \"{user_id}\" with no device display name registered on this server from IP {client}.",
)))
.await;
}
@@ -354,10 +353,7 @@ pub(crate) async fn register_route(
.room_joined_count(&admin_room)?
== Some(1)
{
services()
.admin
.make_user_admin(&user_id, displayname)
.await?;
service::admin::make_user_admin(&user_id, displayname).await?;
warn!("Granting {user_id} admin privileges as the first user");
}
@@ -380,7 +376,7 @@ pub(crate) async fn register_route(
if let Some(room_id_server_name) = room.server_name() {
if let Err(e) = join_room_by_id_helper(
Some(&user_id),
&user_id,
room,
Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
@@ -423,11 +419,16 @@ pub(crate) async fn register_route(
/// last seen ts)
/// - Forgets to-device events
/// - Triggers device list updates
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
pub(crate) async fn change_password_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<change_password::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<change_password::v3::Request>,
) -> Result<change_password::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
// Authentication for this endpoint was made optional, but we need
// authentication currently
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))?;
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let mut uiaainfo = UiaaInfo {
@@ -512,11 +513,16 @@ pub(crate) async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoa
/// - Forgets all to-device events
/// - Triggers device list updates
/// - Removes ability to log in again
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
pub(crate) async fn deactivate_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<deactivate::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<deactivate::v3::Request>,
) -> Result<deactivate::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
// Authentication for this endpoint was made optional, but we need
// authentication currently
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))?;
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let mut uiaainfo = UiaaInfo {
+17 -145
View File
@@ -1,22 +1,14 @@
use rand::seq::SliceRandom;
use ruma::{
api::{
appservice,
client::{
alias::{create_alias, delete_alias, get_alias},
error::ErrorKind,
},
federation,
api::client::{
alias::{create_alias, delete_alias, get_alias},
error::ErrorKind,
},
OwnedRoomAliasId, OwnedServerName, RoomAliasId, RoomId,
OwnedServerName, RoomAliasId, RoomId,
};
use tracing::debug;
use crate::{
debug_info, debug_warn,
service::{appservice::RegistrationInfo, server_is_ours},
services, Error, Result, Ruma,
};
use crate::{service::server_is_ours, services, Error, Result, Ruma};
/// # `PUT /_matrix/client/v3/directory/room/{roomAlias}`
///
@@ -24,7 +16,7 @@ use crate::{
pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
alias_checks(&body.room_alias, &body.appservice_info).await?;
service::rooms::alias::appservice_checks(&body.room_alias, &body.appservice_info).await?;
// this isn't apart of alias_checks or delete alias route because we should
// allow removing forbidden room aliases
@@ -61,7 +53,7 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
alias_checks(&body.room_alias, &body.appservice_info).await?;
service::rooms::alias::appservice_checks(&body.room_alias, &body.appservice_info).await?;
if services()
.rooms
@@ -87,124 +79,20 @@ pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) ->
///
/// Resolve an alias locally or over federation.
pub(crate) async fn get_alias_route(body: Ruma<get_alias::v3::Request>) -> Result<get_alias::v3::Response> {
get_alias_helper(body.body.room_alias, None).await
}
let room_alias = body.body.room_alias;
let servers = None;
pub async fn get_alias_helper(
room_alias: OwnedRoomAliasId, servers: Option<Vec<OwnedServerName>>,
) -> Result<get_alias::v3::Response> {
debug!("get_alias_helper servers: {servers:?}");
if !server_is_ours(room_alias.server_name())
&& (!servers
.as_ref()
.is_some_and(|servers| servers.contains(&services().globals.server_name().to_owned()))
|| servers.as_ref().is_none())
{
let mut response = services()
.sending
.send_federation_request(
room_alias.server_name(),
federation::query::get_room_information::v1::Request {
room_alias: room_alias.clone(),
},
)
.await;
debug!("room alias server_name get_alias_helper response: {response:?}");
if let Err(ref e) = response {
debug_info!(
"Server {} of the original room alias failed to assist in resolving room alias: {e}",
room_alias.server_name()
);
}
if response.as_ref().is_ok_and(|resp| resp.servers.is_empty()) || response.as_ref().is_err() {
if let Some(servers) = servers {
for server in servers {
response = services()
.sending
.send_federation_request(
&server,
federation::query::get_room_information::v1::Request {
room_alias: room_alias.clone(),
},
)
.await;
debug!("Got response from server {server} for room aliases: {response:?}");
if let Ok(ref response) = response {
if !response.servers.is_empty() {
break;
}
debug_warn!(
"Server {server} responded with room aliases, but was empty? Response: {response:?}"
);
}
}
}
}
if let Ok(response) = response {
let room_id = response.room_id;
let mut pre_servers = response.servers;
// since the room alis server responded, insert it into the list
pre_servers.push(room_alias.server_name().into());
let servers = room_available_servers(&room_id, &room_alias, &Some(pre_servers));
debug!(
"room alias servers from federation response for room ID {room_id} and room alias {room_alias}: \
{servers:?}"
);
return Ok(get_alias::v3::Response::new(room_id, servers));
}
return Err(Error::BadRequest(
ErrorKind::NotFound,
"No servers could assist in resolving the room alias",
));
}
let mut room_id = None;
match services().rooms.alias.resolve_local_alias(&room_alias)? {
Some(r) => room_id = Some(r),
None => {
for appservice in services().appservice.read().await.values() {
if appservice.aliases.is_match(room_alias.as_str())
&& matches!(
services()
.sending
.send_appservice_request(
appservice.registration.clone(),
appservice::query::query_room_alias::v1::Request {
room_alias: room_alias.clone(),
},
)
.await,
Ok(Some(_opt_result))
) {
room_id = Some(
services()
.rooms
.alias
.resolve_local_alias(&room_alias)?
.ok_or_else(|| Error::bad_config("Room does not exist."))?,
);
break;
}
}
},
};
let Some(room_id) = room_id else {
let Ok((room_id, pre_servers)) = services()
.rooms
.alias
.resolve_alias(&room_alias, servers.as_ref())
.await
else {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room with alias not found."));
};
let servers = room_available_servers(&room_id, &room_alias, &None);
debug!("room alias servers for room ID {room_id} and room alias {room_alias}");
let servers = room_available_servers(&room_id, &room_alias, &pre_servers);
debug!(?room_alias, ?room_id, "available servers: {servers:?}");
Ok(get_alias::v3::Response::new(room_id, servers))
}
@@ -250,19 +138,3 @@ fn room_available_servers(
servers
}
async fn alias_checks(room_alias: &RoomAliasId, appservice_info: &Option<RegistrationInfo>) -> Result<()> {
if !server_is_ours(room_alias.server_name()) {
return Err(Error::BadRequest(ErrorKind::InvalidParam, "Alias is from another server."));
}
if let Some(ref info) = appservice_info {
if !info.aliases.is_match(room_alias.as_str()) {
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace."));
}
} else if services().appservice.is_exclusive_alias(room_alias).await {
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias reserved by appservice."));
}
Ok(())
}
+1 -2
View File
@@ -13,8 +13,7 @@ use crate::{services, Error, Result, Ruma};
/// Allows loading room history around an event.
///
/// - Only works if the user is joined (TODO: always allow, but only show events
/// if the user was
/// joined, depending on history_visibility)
/// if the user was joined, depending on history_visibility)
pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> Result<get_context::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
+52 -33
View File
@@ -1,4 +1,5 @@
use axum_client_ip::InsecureClientIp;
use conduit::{err, info, warn, Error, Result};
use ruma::{
api::{
client::{
@@ -11,26 +12,24 @@ use ruma::{
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork},
events::{
room::{
avatar::RoomAvatarEventContent,
create::RoomCreateEventContent,
join_rules::{JoinRule, RoomJoinRulesEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
StateEventType,
},
uint, ServerName, UInt,
uint, RoomId, ServerName, UInt, UserId,
};
use tracing::{error, info, warn};
use crate::{service::server_is_ours, services, Error, Result, Ruma};
use crate::{service::server_is_ours, services, Ruma};
/// # `POST /_matrix/client/v3/publicRooms`
///
/// Lists the public rooms on this server.
///
/// - Rooms are ordered by the number of joined members
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
pub(crate) async fn get_public_rooms_filtered_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_public_rooms_filtered::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_public_rooms_filtered::v3::Request>,
) -> Result<get_public_rooms_filtered::v3::Response> {
if let Some(server) = &body.server {
if services()
@@ -66,9 +65,9 @@ pub(crate) async fn get_public_rooms_filtered_route(
/// Lists the public rooms on this server.
///
/// - Rooms are ordered by the number of joined members
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
pub(crate) async fn get_public_rooms_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_public_rooms::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_public_rooms::v3::Request>,
) -> Result<get_public_rooms::v3::Response> {
if let Some(server) = &body.server {
if services()
@@ -107,11 +106,9 @@ pub(crate) async fn get_public_rooms_route(
/// # `PUT /_matrix/client/r0/directory/list/room/{roomId}`
///
/// Sets the visibility of a given room in the room directory.
///
/// - TODO: Access control checks
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
pub(crate) async fn set_room_visibility_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<set_room_visibility::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<set_room_visibility::v3::Request>,
) -> Result<set_room_visibility::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -120,6 +117,13 @@ pub(crate) async fn set_room_visibility_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
}
if !user_can_publish_room(sender_user, &body.room_id)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"User is not allowed to publish this room",
));
}
match &body.visibility {
room::Visibility::Public => {
if services().globals.config.lockdown_public_room_directory && !services().users.is_admin(sender_user)? {
@@ -256,15 +260,10 @@ pub(crate) async fn get_public_rooms_filtered_helper(
avatar_url: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomAvatar, "")?
.map(|s| {
serde_json::from_str(s.content.get())
.map(|c: RoomAvatarEventContent| c.url)
.map_err(|_| Error::bad_database("Invalid room avatar event in database."))
})
.transpose()?
// url is now an Option<String> so we must flatten
.flatten(),
.get_avatar(&room_id)?
.into_option()
.unwrap_or_default()
.url,
join_rule: services()
.rooms
.state_accessor
@@ -277,8 +276,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
_ => None,
})
.map_err(|e| {
error!("Invalid room join rule event in database: {}", e);
Error::BadDatabase("Invalid room join rule event in database.")
err!(Database(error!("Invalid room join rule event in database: {e}")))
})
})
.transpose()?
@@ -287,15 +285,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
room_type: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")?
.map(|s| {
serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(|e| {
error!("Invalid room create event in database: {}", e);
Error::BadDatabase("Invalid room create event in database.")
})
})
.transpose()?
.and_then(|e| e.room_type),
.get_room_type(&room_id)?,
room_id,
};
Ok(chunk)
@@ -368,3 +358,32 @@ pub(crate) async fn get_public_rooms_filtered_helper(
total_room_count_estimate: Some(total_room_count_estimate),
})
}
/// Check whether the user can publish to the room directory via power levels of
/// room history visibility event or room creator
fn user_can_publish_room(user_id: &UserId, room_id: &RoomId) -> Result<bool> {
if let Some(event) =
services()
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
{
serde_json::from_str(event.content.get())
.map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels"))
.map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content).user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
})
} else if let Some(event) =
services()
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
{
Ok(event.sender == user_id)
} else {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"User is not allowed to publish this room",
));
}
}
+10 -15
View File
@@ -1,9 +1,9 @@
use std::{
cmp,
collections::{hash_map, BTreeMap, HashMap, HashSet},
time::{Duration, Instant},
time::Instant,
};
use conduit::{utils, utils::math::continue_exponential_backoff_secs, Error, Result};
use futures_util::{stream::FuturesUnordered, StreamExt};
use ruma::{
api::{
@@ -18,15 +18,11 @@ use ruma::{
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
};
use serde_json::json;
use service::user_is_local;
use tracing::debug;
use super::SESSION_ID_LENGTH;
use crate::{
service::user_is_local,
services,
utils::{self},
Error, Result, Ruma,
};
use crate::{services, Ruma};
/// # `POST /_matrix/client/r0/keys/upload`
///
@@ -334,7 +330,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
.globals
.bad_query_ratelimiter
.write()
.await
.expect("locked")
.entry(id)
{
hash_map::Entry::Vacant(e) => {
@@ -353,15 +349,14 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
.globals
.bad_query_ratelimiter
.read()
.await
.expect("locked")
.get(server)
{
// Exponential backoff
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_elapsed_duration = cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
if time.elapsed() < min_elapsed_duration {
debug!("Backing off query from {:?}", server);
const MIN: u64 = 5 * 60;
const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
debug!("Backing off query from {server:?}");
return (server, Err(Error::BadServerResponse("bad query, still backing off")));
}
}
+61 -29
View File
@@ -1,5 +1,9 @@
#![allow(deprecated)]
use std::{io::Cursor, sync::Arc, time::Duration};
use axum_client_ip::InsecureClientIp;
use conduit::{debug, error, utils::math::ruma_from_usize, warn};
use image::io::Reader as ImgReader;
use ipaddress::IPAddress;
use reqwest::Url;
@@ -10,7 +14,6 @@ use ruma::api::client::{
get_media_preview,
},
};
use tracing::{debug, error, warn};
use webpage::HTML;
use crate::{
@@ -42,7 +45,7 @@ pub(crate) async fn get_media_config_route(
_body: Ruma<get_media_config::v3::Request>,
) -> Result<get_media_config::v3::Response> {
Ok(get_media_config::v3::Response {
upload_size: services().globals.max_request_size().into(),
upload_size: ruma_from_usize(services().globals.config.max_request_size),
})
}
@@ -62,18 +65,22 @@ pub(crate) async fn get_media_config_v1_route(
/// # `GET /_matrix/media/v3/preview_url`
///
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
pub(crate) async fn get_media_preview_route(
body: Ruma<get_media_preview::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_media_preview::v3::Request>,
) -> Result<get_media_preview::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let url = &body.url;
if !url_preview_allowed(url) {
warn!(%sender_user, "URL is not allowed to be previewed: {url}");
return Err(Error::BadRequest(ErrorKind::forbidden(), "URL is not allowed to be previewed"));
}
match get_url_preview(url).await {
Ok(preview) => {
let res = serde_json::value::to_raw_value(&preview).map_err(|e| {
error!("Failed to convert UrlPreviewData into a serde json value: {}", e);
error!(%sender_user, "Failed to convert UrlPreviewData into a serde json value: {e}");
Error::BadRequest(
ErrorKind::LimitExceeded {
retry_after: Some(RetryAfter::Delay(Duration::from_secs(5))),
@@ -85,7 +92,7 @@ pub(crate) async fn get_media_preview_route(
Ok(get_media_preview::v3::Response::from_raw_value(res))
},
Err(e) => {
warn!("Failed to generate a URL preview: {e}");
warn!(%sender_user, "Failed to generate a URL preview: {e}");
// there doesn't seem to be an agreed-upon error code in the spec.
// the only response codes in the preview_url spec page are 200 and 429.
@@ -106,10 +113,13 @@ pub(crate) async fn get_media_preview_route(
/// See <https://spec.matrix.org/legacy/legacy/#id27>
///
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
pub(crate) async fn get_media_preview_v1_route(
body: Ruma<get_media_preview::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_media_preview::v3::Request>,
) -> Result<RumaResponse<get_media_preview::v3::Response>> {
get_media_preview_route(body).await.map(RumaResponse)
get_media_preview_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `POST /_matrix/media/v3/upload`
@@ -118,8 +128,9 @@ pub(crate) async fn get_media_preview_v1_route(
///
/// - Some metadata will be saved in the database
/// - Media will be saved in the media/ directory
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
pub(crate) async fn create_content_route(
body: Ruma<create_content::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -133,7 +144,7 @@ pub(crate) async fn create_content_route(
.media
.create(
Some(sender_user.clone()),
mxc.clone(),
&mxc,
body.filename
.as_ref()
.map(|filename| {
@@ -165,10 +176,13 @@ pub(crate) async fn create_content_route(
///
/// - Some metadata will be saved in the database
/// - Media will be saved in the media/ directory
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
pub(crate) async fn create_content_v1_route(
body: Ruma<create_content::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<create_content::v3::Request>,
) -> Result<RumaResponse<create_content::v3::Response>> {
create_content_route(body).await.map(RumaResponse)
create_content_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}`
@@ -179,16 +193,20 @@ pub(crate) async fn create_content_v1_route(
/// - Only redirects if `allow_redirect` is true
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> Result<get_content::v3::Response> {
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
pub(crate) async fn get_content_route(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content::v3::Request>,
) -> Result<get_content::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content,
content_type,
file,
content_disposition,
}) = services().media.get(mxc.clone()).await?
}) = services().media.get(&mxc).await?
{
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
let file = content.expect("content");
Ok(get_content::v3::Response {
file,
@@ -241,10 +259,13 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
/// - 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")]
pub(crate) async fn get_content_v1_route(
body: Ruma<get_content::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content::v3::Request>,
) -> Result<RumaResponse<get_content::v3::Response>> {
get_content_route(body).await.map(RumaResponse)
get_content_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}`
@@ -255,16 +276,17 @@ pub(crate) async fn get_content_v1_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")]
pub(crate) async fn get_content_as_filename_route(
body: Ruma<get_content_as_filename::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_as_filename::v3::Request>,
) -> Result<get_content_as_filename::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content,
content_type,
file,
content_disposition,
}) = services().media.get(mxc.clone()).await?
}) = services().media.get(&mxc).await?
{
let content_disposition = Some(make_content_disposition(
&content_type,
@@ -272,6 +294,7 @@ pub(crate) async fn get_content_as_filename_route(
Some(body.filename.clone()),
));
let file = content.expect("content");
Ok(get_content_as_filename::v3::Response {
file,
content_type,
@@ -326,10 +349,13 @@ pub(crate) async fn get_content_as_filename_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")]
pub(crate) async fn get_content_as_filename_v1_route(
body: Ruma<get_content_as_filename::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_as_filename::v3::Request>,
) -> Result<RumaResponse<get_content_as_filename::v3::Response>> {
get_content_as_filename_route(body).await.map(RumaResponse)
get_content_as_filename_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}`
@@ -340,19 +366,20 @@ pub(crate) async fn get_content_as_filename_v1_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")]
pub(crate) async fn get_content_thumbnail_route(
body: Ruma<get_content_thumbnail::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_thumbnail::v3::Request>,
) -> Result<get_content_thumbnail::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
if let Some(FileMeta {
content,
content_type,
file,
content_disposition,
}) = services()
.media
.get_thumbnail(
mxc.clone(),
&mxc,
body.width
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
@@ -363,6 +390,7 @@ pub(crate) async fn get_content_thumbnail_route(
.await?
{
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
let file = content.expect("content");
Ok(get_content_thumbnail::v3::Response {
file,
@@ -396,6 +424,7 @@ pub(crate) async fn get_content_thumbnail_route(
media_id: body.media_id.clone(),
timeout_ms: body.timeout_ms,
allow_redirect: body.allow_redirect,
animated: body.animated,
},
)
.await
@@ -405,7 +434,7 @@ pub(crate) async fn get_content_thumbnail_route(
.media
.upload_thumbnail(
None,
mxc,
&mxc,
None,
get_thumbnail_response.content_type.as_deref(),
body.width.try_into().expect("all UInts are valid u32s"),
@@ -450,10 +479,13 @@ pub(crate) async fn get_content_thumbnail_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")]
pub(crate) async fn get_content_thumbnail_v1_route(
body: Ruma<get_content_thumbnail::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_content_thumbnail::v3::Request>,
) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> {
get_content_thumbnail_route(body).await.map(RumaResponse)
get_content_thumbnail_route(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
async fn get_remote_content(
@@ -494,7 +526,7 @@ async fn get_remote_content(
.media
.create(
None,
mxc.to_owned(),
mxc,
content_disposition.as_deref(),
content_response.content_type.as_deref(),
&content_response.file,
@@ -520,7 +552,7 @@ async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPrevie
services()
.media
.create(None, mxc.clone(), None, None, &image)
.create(None, &mxc, None, None, &image)
.await?;
let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
+75 -139
View File
@@ -1,12 +1,15 @@
use std::{
cmp,
collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
net::IpAddr,
sync::Arc,
time::{Duration, Instant},
time::Instant,
};
use axum_client_ip::InsecureClientIp;
use conduit::{
debug, debug_warn, error, info, trace, utils, utils::math::continue_exponential_backoff_secs, warn, Error,
PduEvent, Result,
};
use ruma::{
api::{
client::{
@@ -32,17 +35,17 @@ use ruma::{
OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
};
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
use tokio::sync::{MutexGuard, RwLock};
use tracing::{debug, error, info, trace, warn};
use tokio::sync::RwLock;
use super::get_alias_helper;
use crate::{
client::{update_avatar_url, update_displayname},
service::{
pdu::{gen_event_id_canonical_json, PduBuilder},
rooms::state::RoomMutexGuard,
sending::convert_to_outgoing_federation_event,
server_is_ours, user_is_local,
},
services, utils, Error, PduEvent, Result, Ruma,
services, Ruma,
};
/// Checks if the room is banned in any way possible and the sender user is not
@@ -164,7 +167,7 @@ async fn banned_room_check(
/// rules locally
/// - If the server does not know about the room: asks other servers over
/// federation
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client_ip), name = "join")]
pub(crate) async fn join_room_by_id_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<join_room_by_id::v3::Request>,
) -> Result<join_room_by_id::v3::Response> {
@@ -199,7 +202,7 @@ pub(crate) async fn join_room_by_id_route(
}
join_room_by_id_helper(
body.sender_user.as_deref(),
sender_user,
&body.room_id,
body.reason.clone(),
&servers,
@@ -217,16 +220,16 @@ pub(crate) async fn join_room_by_id_route(
/// - If the server does not know about the room: use the server name query
/// param if specified. if not specified, asks other servers over federation
/// via room alias server name and room ID server name
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "join")]
pub(crate) async fn join_room_by_id_or_alias_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<join_room_by_id_or_alias::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<join_room_by_id_or_alias::v3::Request>,
) -> Result<join_room_by_id_or_alias::v3::Response> {
let sender_user = body.sender_user.as_deref().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(sender_user, Some(&room_id), room_id.server_name(), client_ip).await?;
banned_room_check(sender_user, Some(&room_id), room_id.server_name(), client).await?;
let mut servers = body.server_name.clone();
servers.extend(
@@ -258,17 +261,24 @@ pub(crate) async fn join_room_by_id_or_alias_route(
(servers, room_id)
},
Err(room_alias) => {
let response = get_alias_helper(room_alias.clone(), Some(body.server_name.clone())).await?;
let response = services()
.rooms
.alias
.resolve_alias(&room_alias, Some(&body.server_name.clone()))
.await?;
let (room_id, mut pre_servers) = response;
banned_room_check(sender_user, Some(&response.room_id), Some(room_alias.server_name()), client_ip).await?;
banned_room_check(sender_user, Some(&room_id), Some(room_alias.server_name()), client).await?;
let mut servers = body.server_name;
servers.extend(response.servers);
if let Some(pre_servers) = &mut pre_servers {
servers.append(pre_servers);
}
servers.extend(
services()
.rooms
.state_cache
.servers_invite_via(&response.room_id)
.servers_invite_via(&room_id)
.filter_map(Result::ok),
);
@@ -276,7 +286,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
services()
.rooms
.state_cache
.invite_state(sender_user, &response.room_id)?
.invite_state(sender_user, &room_id)?
.unwrap_or_default()
.iter()
.filter_map(|event| serde_json::from_str(event.json().get()).ok())
@@ -286,12 +296,12 @@ pub(crate) async fn join_room_by_id_or_alias_route(
.map(|user| user.server_name().to_owned()),
);
(servers, response.room_id)
(servers, room_id)
},
};
let join_room_response = join_room_by_id_helper(
Some(sender_user),
sender_user,
&room_id,
body.reason.clone(),
&servers,
@@ -320,9 +330,9 @@ pub(crate) async fn leave_room_route(body: Ruma<leave_room::v3::Request>) -> Res
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
///
/// Tries to send an invite event into the room.
#[tracing::instrument(skip_all, fields(%client_ip))]
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
pub(crate) async fn invite_user_route(
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<invite_user::v3::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<invite_user::v3::Request>,
) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -337,7 +347,7 @@ pub(crate) async fn invite_user_route(
));
}
banned_room_check(sender_user, Some(&body.room_id), body.room_id.server_name(), client_ip).await?;
banned_room_check(sender_user, Some(&body.room_id), body.room_id.server_name(), client).await?;
if let invite_user::v3::InvitationRecipient::UserId {
user_id,
@@ -356,6 +366,8 @@ pub(crate) async fn invite_user_route(
pub(crate) async fn kick_user_route(body: Ruma<kick_user::v3::Request>) -> Result<kick_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let mut event: RoomMemberEventContent = serde_json::from_str(
services()
.rooms
@@ -373,17 +385,6 @@ pub(crate) async fn kick_user_route(body: Ruma<kick_user::v3::Request>) -> Resul
event.membership = MembershipState::Leave;
event.reason.clone_from(&body.reason);
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(body.room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
services()
.rooms
.timeline
@@ -412,6 +413,8 @@ pub(crate) async fn kick_user_route(body: Ruma<kick_user::v3::Request>) -> Resul
pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<ban_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let event = services()
.rooms
.state_accessor
@@ -442,17 +445,6 @@ pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<
},
)?;
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(body.room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
services()
.rooms
.timeline
@@ -481,6 +473,8 @@ pub(crate) async fn ban_user_route(body: Ruma<ban_user::v3::Request>) -> Result<
pub(crate) async fn unban_user_route(body: Ruma<unban_user::v3::Request>) -> Result<unban_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
let mut event: RoomMemberEventContent = serde_json::from_str(
services()
.rooms
@@ -496,17 +490,6 @@ pub(crate) async fn unban_user_route(body: Ruma<unban_user::v3::Request>) -> Res
event.reason.clone_from(&body.reason);
event.join_authorized_via_users_server = None;
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(body.room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
services()
.rooms
.timeline
@@ -658,44 +641,36 @@ pub(crate) async fn joined_members_route(
}
pub async fn join_room_by_id_helper(
sender_user: Option<&UserId>, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
third_party_signed: Option<&ThirdPartySigned>,
) -> Result<join_room_by_id::v3::Response> {
let sender_user = sender_user.expect("user is authenticated");
let state_lock = services().rooms.state.mutex.lock(room_id).await;
if matches!(services().rooms.state_cache.is_joined(sender_user, room_id), Ok(true)) {
info!("{sender_user} is already joined in {room_id}");
debug_warn!("{sender_user} is already joined in {room_id}");
return Ok(join_room_by_id::v3::Response {
room_id: room_id.into(),
});
}
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(room_id.to_owned())
.or_default(),
);
let state_lock = mutex_state.lock().await;
// Ask a remote server if we are not participating in this room
if !services()
if services()
.rooms
.state_cache
.server_in_room(services().globals.server_name(), room_id)?
|| servers.is_empty()
|| (servers.len() == 1 && server_is_ours(&servers[0]))
{
join_room_by_id_helper_remote(sender_user, room_id, reason, servers, third_party_signed, state_lock).await
} else {
join_room_by_id_helper_local(sender_user, room_id, reason, servers, third_party_signed, state_lock).await
} else {
// Ask a remote server if we are not participating in this room
join_room_by_id_helper_remote(sender_user, room_id, reason, servers, third_party_signed, state_lock).await
}
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote")]
async fn join_room_by_id_helper_remote(
sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>, state_lock: MutexGuard<'_, ()>,
_third_party_signed: Option<&ThirdPartySigned>, state_lock: RoomMutexGuard,
) -> Result<join_room_by_id::v3::Response> {
info!("Joining {room_id} over federation.");
@@ -796,7 +771,7 @@ async fn join_room_by_id_helper_remote(
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.to_owned(),
pdu: PduEvent::convert_to_outgoing_federation_event(join_event.clone()),
pdu: convert_to_outgoing_federation_event(join_event.clone()),
omit_members: false,
},
)
@@ -805,14 +780,9 @@ async fn join_room_by_id_helper_remote(
info!("send_join finished");
if join_authorized_via_users_server.is_some() {
use RoomVersionId::*;
match &room_version_id {
RoomVersionId::V1
| RoomVersionId::V2
| RoomVersionId::V3
| RoomVersionId::V4
| RoomVersionId::V5
| RoomVersionId::V6
| RoomVersionId::V7 => {
V1 | V2 | V3 | V4 | V5 | V6 | V7 => {
warn!(
"Found `join_authorised_via_users_server` but room {} is version {}. Ignoring.",
room_id, &room_version_id
@@ -820,7 +790,7 @@ async fn join_room_by_id_helper_remote(
},
// only room versions 8 and above using `join_authorized_via_users_server` (restricted joins) need to
// validate and send signatures
RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 | RoomVersionId::V11 => {
V8 | V9 | V10 | V11 => {
if let Some(signed_raw) = &send_join_response.room_state.event {
info!(
"There is a signed event. This room is probably using restricted joins. Adding signature to \
@@ -1028,11 +998,12 @@ async fn join_room_by_id_helper_remote(
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
async fn join_room_by_id_helper_local(
sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>, state_lock: MutexGuard<'_, ()>,
_third_party_signed: Option<&ThirdPartySigned>, state_lock: RoomMutexGuard,
) -> Result<join_room_by_id::v3::Response> {
info!("We can join locally");
debug!("We can join locally");
let join_rules_event =
services()
@@ -1072,7 +1043,7 @@ async fn join_room_by_id_helper_local(
.filter(|user| user_is_local(user))
.collect::<Vec<OwnedUserId>>();
let mut authorized_user: Option<OwnedUserId> = None;
let mut join_authorized_via_users_server: Option<OwnedUserId> = None;
if restriction_rooms.iter().any(|restriction_room_id| {
services()
@@ -1086,10 +1057,9 @@ async fn join_room_by_id_helper_local(
.rooms
.state_accessor
.user_can_invite(room_id, &user, sender_user, &state_lock)
.await
.unwrap_or(false)
{
authorized_user = Some(user);
join_authorized_via_users_server = Some(user);
break;
}
}
@@ -1103,7 +1073,7 @@ async fn join_room_by_id_helper_local(
third_party_invite: None,
blurhash: services().users.blurhash(sender_user)?,
reason: reason.clone(),
join_authorized_via_users_server: authorized_user,
join_authorized_via_users_server,
};
// Try normal join first
@@ -1133,7 +1103,7 @@ async fn join_room_by_id_helper_local(
.iter()
.any(|server_name| !server_is_ours(server_name))
{
info!("We couldn't do the join locally, maybe federation can help to satisfy the restricted join requirements");
warn!("We couldn't do the join locally, maybe federation can help to satisfy the restricted join requirements");
let (make_join_response, remote_server) = make_join_request(sender_user, room_id, servers).await?;
let room_version_id = match make_join_response.room_version {
@@ -1225,7 +1195,7 @@ async fn join_room_by_id_helper_local(
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.to_owned(),
pdu: PduEvent::convert_to_outgoing_federation_event(join_event.clone()),
pdu: convert_to_outgoing_federation_event(join_event.clone()),
omit_members: false,
},
)
@@ -1298,16 +1268,12 @@ async fn make_join_request(
make_join_counter = make_join_counter.saturating_add(1);
if let Err(ref e) = make_join_response {
trace!("make_join ErrorKind string: {:?}", e.error_code().to_string());
trace!("make_join ErrorKind string: {:?}", e.kind().to_string());
// converting to a string is necessary (i think) because ruma is forcing us to
// fill in the struct for M_INCOMPATIBLE_ROOM_VERSION
if e.error_code()
.to_string()
.contains("M_INCOMPATIBLE_ROOM_VERSION")
|| e.error_code()
.to_string()
.contains("M_UNSUPPORTED_ROOM_VERSION")
if e.kind().to_string().contains("M_INCOMPATIBLE_ROOM_VERSION")
|| e.kind().to_string().contains("M_UNSUPPORTED_ROOM_VERSION")
{
incompatible_room_version_count = incompatible_room_version_count.saturating_add(1);
}
@@ -1360,7 +1326,7 @@ pub async fn validate_and_add_event_id(
.globals
.bad_event_ratelimiter
.write()
.await
.expect("locked")
.entry(id)
{
Entry::Vacant(e) => {
@@ -1376,15 +1342,14 @@ pub async fn validate_and_add_event_id(
.globals
.bad_event_ratelimiter
.read()
.await
.expect("locked")
.get(&event_id)
{
// Exponential backoff
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
let min_elapsed_duration = cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
if time.elapsed() < min_elapsed_duration {
debug!("Backing off from {}", event_id);
const MIN: u64 = 60 * 5;
const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
debug!("Backing off from {event_id}");
return Err(Error::BadServerResponse("bad event, still backing off"));
}
}
@@ -1413,17 +1378,7 @@ pub(crate) async fn invite_helper(
if !user_is_local(user_id) {
let (pdu, pdu_json, invite_room_state) = {
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(room_id.to_owned())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
let content = to_raw_value(&RoomMemberEventContent {
avatar_url: services().users.avatar_url(user_id)?,
displayname: None,
@@ -1466,7 +1421,7 @@ pub(crate) async fn invite_helper(
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
event: PduEvent::convert_to_outgoing_federation_event(pdu_json.clone()),
event: convert_to_outgoing_federation_event(pdu_json.clone()),
invite_room_state,
via: services().rooms.state_cache.servers_route_via(room_id).ok(),
},
@@ -1535,16 +1490,7 @@ pub(crate) async fn invite_helper(
));
}
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(room_id.to_owned())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
services()
.rooms
@@ -1638,16 +1584,7 @@ pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<Strin
true,
)?;
} else {
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(room_id.to_owned())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
let member_event =
services()
@@ -1726,8 +1663,7 @@ async fn remote_leave_room(user_id: &UserId, room_id: &RoomId) -> Result<()> {
.filter_map(|event: serde_json::Value| event.get("sender").cloned())
.filter_map(|sender| sender.as_str().map(ToOwned::to_owned))
.filter_map(|sender| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned())
.collect::<HashSet<OwnedServerName>>(),
.map(|user| user.server_name().to_owned()),
);
debug!("servers in remote_leave_room: {servers:?}");
@@ -1821,7 +1757,7 @@ async fn remote_leave_room(user_id: &UserId, room_id: &RoomId) -> Result<()> {
federation::membership::create_leave_event::v2::Request {
room_id: room_id.to_owned(),
event_id,
pdu: PduEvent::convert_to_outgoing_federation_event(leave_event.clone()),
pdu: convert_to_outgoing_federation_event(leave_event.clone()),
},
)
.await?;
+3 -16
View File
@@ -1,7 +1,4 @@
use std::{
collections::{BTreeMap, HashSet},
sync::Arc,
};
use std::collections::{BTreeMap, HashSet};
use conduit::PduCount;
use ruma::{
@@ -32,16 +29,7 @@ pub(crate) async fn send_message_event_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_deref();
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(body.room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let state_lock = services().rooms.state.mutex.lock(&body.room_id).await;
// Forbid m.room.encrypted if encryption is disabled
if MessageLikeEventType::RoomEncrypted == body.event_type && !services().globals.allow_encryption() {
@@ -115,8 +103,7 @@ pub(crate) async fn send_message_event_route(
/// Allows paginating through room history.
///
/// - Only works if the user is joined (TODO: always allow, but only show events
/// where the user was
/// joined, depending on `history_visibility`)
/// where the user was joined, depending on `history_visibility`)
pub(crate) async fn get_message_events_route(
body: Ruma<get_message_events::v3::Request>,
) -> Result<get_message_events::v3::Response> {
+2 -1
View File
@@ -11,6 +11,7 @@ pub(super) mod keys;
pub(super) mod media;
pub(super) mod membership;
pub(super) mod message;
pub(super) mod openid;
pub(super) mod presence;
pub(super) mod profile;
pub(super) mod push;
@@ -35,7 +36,6 @@ pub(super) mod user_directory;
pub(super) mod voip;
pub(super) use account::*;
pub use alias::get_alias_helper;
pub(super) use alias::*;
pub(super) use backup::*;
pub(super) use capabilities::*;
@@ -49,6 +49,7 @@ pub(super) use media::*;
pub(super) use membership::*;
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, validate_and_add_event_id};
pub(super) use message::*;
pub(super) use openid::*;
pub(super) use presence::*;
pub(super) use profile::*;
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
+41
View File
@@ -0,0 +1,41 @@
use std::time::Duration;
use conduit::utils;
use ruma::{
api::client::{account, error::ErrorKind},
authentication::TokenType,
};
use super::TOKEN_LENGTH;
use crate::{services, Error, Result, Ruma};
/// # `POST /_matrix/client/v3/user/{userId}/openid/request_token`
///
/// Request an OpenID token to verify identity with third-party services.
///
/// - The token generated is only valid for the OpenID API
pub(crate) async fn create_openid_token_route(
body: Ruma<account::request_openid_token::v3::Request>,
) -> Result<account::request_openid_token::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if sender_user != &body.user_id {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Not allowed to request OpenID tokens on behalf of other users",
));
}
let access_token = utils::random_string(TOKEN_LENGTH);
let expires_in = services()
.users
.create_openid_token(&body.user_id, &access_token)?;
Ok(account::request_openid_token::v3::Response {
access_token,
token_type: TokenType::Bearer,
matrix_server_name: services().globals.config.server_name.clone(),
expires_in: Duration::from_secs(expires_in),
})
}
+6 -18
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use ruma::{
api::{
client::{
@@ -52,8 +50,8 @@ pub(crate) async fn set_displayname_route(
///
/// Returns the displayname of the user.
///
/// - If user is on another server and we do not have a local copy already
/// fetch displayname over federation
/// - If user is on another server and we do not have a local copy already fetch
/// displayname over federation
pub(crate) async fn get_displayname_route(
body: Ruma<get_display_name::v3::Request>,
) -> Result<get_display_name::v3::Response> {
@@ -142,8 +140,8 @@ pub(crate) async fn set_avatar_url_route(
///
/// Returns the `avatar_url` and `blurhash` of the user.
///
/// - If user is on another server and we do not have a local copy already
/// fetch `avatar_url` and blurhash over federation
/// - If user is on another server and we do not have a local copy already fetch
/// `avatar_url` and blurhash over federation
pub(crate) async fn get_avatar_url_route(
body: Ruma<get_avatar_url::v3::Request>,
) -> Result<get_avatar_url::v3::Response> {
@@ -201,7 +199,7 @@ pub(crate) async fn get_avatar_url_route(
/// Returns the displayname, avatar_url and blurhash of the user.
///
/// - If user is on another server and we do not have a local copy already,
/// fetch profile over federation.
/// fetch profile over federation.
pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> Result<get_profile::v3::Response> {
if !user_is_local(&body.user_id) {
// Create and update our local copy of the user
@@ -355,17 +353,7 @@ pub async fn update_avatar_url(
pub async fn update_all_rooms(all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: OwnedUserId) {
for (pdu_builder, room_id) in all_joined_rooms {
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let state_lock = services().rooms.state.mutex.lock(room_id).await;
if let Err(e) = services()
.rooms
.timeline

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