Compare commits

..

146 Commits

Author SHA1 Message Date
June Clementine Strawberry e5049cae4a update complement test results
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-02 22:02:11 -05:00
Jason Volk f1af7ec08c fix unused import in release mode
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-03 02:36:24 +00:00
June Clementine Strawberry 3b8a90ad13 delete more stuff to free up runner space
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-02 20:56:27 -05:00
strawberry 7a349fdc58 use latest sccache, bump nix flake lock, cache everything in rust-cache
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-02 19:13:27 -05:00
strawberry 6c8a1b5e9b bump ruwuma
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-02 18:48:04 -05:00
strawberry a5d70f7356 add some m.room.member checks on putting direct state events
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-02 18:16:00 -05:00
Jason Volk 50cadbee96 add faster shutdown point in unix socket listener
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-02 08:20:02 +00:00
Jason Volk b1182fe8a4 improve some router tracing spans
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-02 08:20:02 +00:00
Jason Volk 77d7e8a3ad add json array to serde_json::Value db deserialization without Raw 2025-01-02 06:52:32 +00:00
strawberry 72797532b6 fix MSC4133 fields not being returned as original types
Signed-off-by: strawberry <strawberry@puppygock.gay>
2025-01-01 23:28:01 -05:00
Jason Volk b4ef646485 translate 5xx to 404 on remote media request
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk b8f8f68634 improve memory-usage output w/ more byte-sizes
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 33635e11d1 bump ruma
tweak tracing instrument

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 1a71798859 add Expected trait to utils; use (already transitive) num-traits.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 7e4453620e move exponential backoff util to different submod
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 2259e2c82f batch queries to maximize throughput
query-side streams for first level of callsites

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 1792711d09 support core affinity mask
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 0b2fca5ad9 add complement diff from e2f6753a3280 2025-01-01 23:28:01 -05:00
June Clementine Strawberry 0e110bb48b fix flipped room ver check on send_join
Signed-off-by: June Clementine Strawberry <strawberry@puppygock.gay>
2025-01-01 23:28:01 -05:00
Jason Volk 3ffe2cd56e reduce backoff warning from infolog
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk c5b94be5b8 remove dev_release_log_level cfg
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 2bbb28bb88 get device info with libc using major/minor
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 203cf57fdf add console log event format hook
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 9c6b5b4407 add faster interruption to resolver (#649)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk a1fc4d49ac reduce non-debug startup warnings
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk b56e480b3c integrate some std io error kinds with our status code abstraction
associate rocksdb error kinds with std io error kinds

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 7c8eeaf4ea simplify multi_get_or_create/related stream implementations
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 9eb99f8070 abstract async shutdown waiting loop
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 0a9b6c136f refactor for ruma identifiers optimizations
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk d91570d0e6 add Error variant for FeatureDisabled
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 3a2c5be4f4 reduce exponential backoff for prev fetch
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk e21403a4d4 use VecDeque for todo queues
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 74eb30c106 add LIFO scheduling for database frontend pool workers
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 6458f4b195 refactor various Arc<EventId> to OwnedEventId
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 5a335933b8 pass stream width to ruma state res
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 3b95af9a18 move stream width related into unit
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk a407909d09 reduce unnecessary info-level spans to debug
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 7a6d657558 configurable dynamic stream concurrency scalar
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk b195107053 optimize for multi-queue storage topologies with affinity
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk e5a1309583 additional interruption points to hasten shutdown
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 03f2ac9caf simplify usage of mpmc channels which don't require receiver lock
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 5fdb8895b1 add utils to detect device io characteristics
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 749f29aaab add and/and_then to BoolExt
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk a3d87be22f enable atomic_flush for database columns
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk aefc4b4e69 add more program options for tokio
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 503210c3bf toggle worker_affinity feature from program argument
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 674acc8657 add program options for tokio histogram settings
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk af3d6a2e37 shard sender into multiple task workers by destination hash
rename Destination::Normal variant

tracing instruments

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 98e6c81e49 offload remaining db iterator initial seeks on cache miss
consume task budget on cache hit

Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Jason Volk 14341bb906 simplify and improve db iter State init interface
Signed-off-by: Jason Volk <jason@zemos.net>
2025-01-01 23:28:01 -05:00
Neil Svedberg 5bce0a3a46 Improve documentation for configuration
Although the configuration file was mostly wrapped to a line-width of
80, some lines were wrapped slightly shorter. I fixed this.

In general, all sentences were changed to start with a capital letter
and end with a period or other punctuation mark.

Many of the documentation commets read as, "config option to do XYZ". I
shortened these to simply "do XYZ".
2025-01-01 16:31:30 -05:00
Neek c53e9e07f2 update cohost link
added an archive.org link to the cohost post as it will fully shut down at the end of the year
2024-12-27 13:57:18 -05:00
nexy7574 7f645ff0e9 Make some doc changes to admin media delete-past-remote-media
Adjust before/after doc to clarify internationally

Courtesy of @Aranjedeath

Run `cargo fmt`, properly

Fix stupid doc command issue

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-20 13:56:40 -05:00
Jade Ellis 5b5735f653 update ruwuma 2024-12-19 21:03:12 -05:00
Jade Ellis 025950139e do not try to push to GHCR on pull requests from forks 2024-12-19 21:02:34 -05:00
Jade Ellis 0602e38ada remove logging 2024-12-19 21:02:34 -05:00
Jade Ellis 8f73caae0b fix github actions runs on forks
This commit contains two separate fixes:

1) use lowercased github username/repository for the docker
repository name.
This is because image registries only accept
lowercase in image repository names, but github stores the
repository and username in a case-sensitive manner. This broke
image uploads for me, as my username has uppercase chars.

2) change run conditions for some steps.
It will no longer attempt to set up SSH web publishing if the SSH
private key is not set.
It will also run the image registry upload steps if registry
usernames are missing, instead skipping individual uploads where
the token for that registry is missing.

Finally, it simplifies the sccache run conditions to use the
`SCCACHE_GHA_ENABLED` env variable, rather than duplicating that
logic.

Signed-off-by: Jade Ellis <jade@ellis.link>
2024-12-19 21:02:34 -05:00
admin@dimensionproject.net 802395bdb7 add password argument to reset-password admin cmd
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-18 20:46:01 -05:00
Jason Volk 60a952508e use macro for error constructions
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-18 14:16:29 -05:00
Jason Volk 4d46df2af5 abort tasks for non-async pool shudown
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-18 14:16:23 -05:00
strawberry 7b8320e0eb bump rocksdb to v9.9.3
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-18 13:41:30 -05:00
Jason Volk 6c42ac2726 add num_workers() convenience to core metrics
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-18 13:32:13 -05:00
Jason Volk 71673b2a88 add worker_affinity feature
split runtime init from main.rs

Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-18 13:31:58 -05:00
Jason Volk 5f7636f177 set dbpool thread name (gated by tokio_unstable)
Signed-off-by: Jason Volk <jason@zemos.net>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-18 13:31:41 -05:00
Jason Volk f78104a959 relax Send on input iterator
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-18 13:29:51 -05:00
Jason Volk ad8cbcaac1 check cache prior to offloading iterator seek
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-18 13:29:43 -05:00
strawberry f54a62dda0 Revert "Send read reciept and typing indicator EDUs to appservices with receive_ephemeral"
This reverts commit 3675c941f8.
2024-12-18 11:26:18 -05:00
strawberry 9040ad054e Revert "misc typing cleanup"
This reverts commit 4dd809fdc4.
2024-12-18 11:26:04 -05:00
strawberry 4dd809fdc4 misc typing cleanup
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-17 23:13:35 -05:00
Jade Ellis 3675c941f8 Send read reciept and typing indicator EDUs to appservices with receive_ephemeral 2024-12-17 22:49:45 -05:00
Jade Ellis 5b6ff3869c Fix typo in test from rename 2024-12-17 19:29:46 +00:00
AsenHu 9ec35cf684 skip uploading JSON files in release workflow 2024-12-17 03:01:40 +00:00
AsenHu 63c4975483 fix bug 2024-12-17 02:08:52 +00:00
strawberry 5813419f4b free up ci runner space on both jobs
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 22:49:34 -05:00
strawberry 8dcdb4b09a update some more docs
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 22:48:22 -05:00
strawberry 7ad710d96a bump complement
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 18:00:49 -05:00
strawberry c8c9d73b20 enable edns0 on hickory resolver config
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 17:24:45 -05:00
strawberry 004671b437 dont copy join_authorized_via_users_server on leaving rooms
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 15:58:53 -05:00
strawberry aeae67a7ee bump ruwuma to stop sending empty content rulesets
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 15:20:28 -05:00
strawberry 74c5bfd311 dont allow m.room.server_acl to be redacted
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 14:31:11 -05:00
strawberry 2ba0400758 return proper error for attempting to update m.room.create
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 14:23:13 -05:00
strawberry 468071336b ping online presence on read updates like synapse
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 14:13:51 -05:00
strawberry 9c6908b8c1 bump ruwuma, implement MSC4076, add more pusher HTTP URL validation
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 14:05:28 -05:00
strawberry c8349988aa ping online presence on typing updates like synapse
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 02:53:32 -05:00
strawberry 6c96acc482 use fast binary_search for some const slices
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 01:41:50 -05:00
strawberry 52693db477 hide messages from users servers on the forbidden_remote_server_names list
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 01:02:52 -05:00
strawberry 77e0b76408 apply new rustfmt.toml changes, fix some clippy lints
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-15 01:00:41 -05:00
strawberry 0317cc8cc5 rename conduit to conduwuit finally
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-14 22:24:45 -05:00
strawberry c6bf8f5ea1 improve gh actions security using zizmor
https://github.com/woodruffw/zizmor
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-14 21:00:33 -05:00
strawberry e4489a5d20 bump rust-rocksdb fork
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-13 21:59:37 -05:00
strawberry a9f6bb6169 remove jemalloc_prof and jemalloc_stats from all-features builds (exc. debug)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-13 13:35:36 -05:00
strawberry 1b9c331049 use database::Json() to serialise MSC4133 profile value before insert
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-13 11:13:21 -05:00
strawberry 17273b1cea upload CI binaries to webserver for easy direct download
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-13 11:09:29 -05:00
Asen c5aca8036d add auto upload assets, upload binary to webserver
Co-authored-by: strawberry <strawberry@puppygock.gay>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-13 01:57:49 -05:00
strawberry b73d558cba start producing haswell target optimised x86 binaries for rocksdb fast CRC32 support
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-13 01:57:45 -05:00
strawberry 76a5a67b6f fix a couple rust 2024 lints introduced in rust 1.82.0
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-12 10:22:30 -05:00
strawberry e0446181c5 remove unnecessary args in pack_receipts
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 22:07:53 -05:00
strawberry a26b1c5722 send private read receipt on sliding stink (sync)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 22:02:48 -05:00
strawberry c9bb943a34 mark device list updates when a device is deleted/removed
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 21:25:40 -05:00
strawberry 353dc17af4 construct private read receipt properly
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 21:25:07 -05:00
strawberry 66a82447da log hostname in DNS error tracing spans
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 21:24:53 -05:00
strawberry 3e64a5c907 delete pointless bad_query_ratelimiter harming e2ee performance
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 19:46:34 -05:00
strawberry 5d51adc29d update complement test results
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 19:17:23 -05:00
strawberry 0740d7259a document modifying systemd unit if changing database path
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 18:22:51 -05:00
strawberry af08d3f7bd document 'DNS No connections available' troubleshooting
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 18:12:27 -05:00
strawberry 9ca964a97e update stored default push rules when removing legacy mentions
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 17:36:51 -05:00
strawberry 39abd9a827 (temp) drop sccache version down in CI to 0.8.2
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 16:38:29 -05:00
strawberry ac0545f773 always set limited true on left timeline events in sync (for now)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 16:38:29 -05:00
strawberry 80f9536d21 rename config.rs to account_data.rs, refactor, add some missing checks
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 16:38:29 -05:00
strawberry ef8392cbbe allow empty POST request bodies (synapse behaviour) to improve compatibility
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 16:38:25 -05:00
strawberry 1e3bb02ced bump cargo.lock, bump some deps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 14:26:15 -05:00
strawberry badb83484f fix private read receipt support
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-11 14:25:32 -05:00
Jason Volk 34f9e3260f additional sync v3 refactoring/optimizations and tracing instruments
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-10 06:20:30 +00:00
Jason Volk aba88ccead misc tracing span tweaks
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-10 06:20:30 +00:00
Jason Volk 5fc8e90e02 fix runtime metrics command
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-10 05:40:45 +00:00
Jason Volk cd67ca6c57 fix .gitignore to allow /target to be a symlink
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-10 05:40:45 +00:00
strawberry 9cb71e212a nix: bump flake.lock finally after 3 months
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 17:03:54 -05:00
strawberry 0ff37b829c update default systemd units to use network-online.target fully
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 13:33:24 -05:00
strawberry b6ac3649ee use fork of complement
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 13:04:13 -05:00
strawberry e1bd6a7c2d nix: try bumping to latest complement
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:52:06 -05:00
strawberry faf48405aa bump rocksdb to v9.8.4, enable track_and_verify_wals_in_manifest by default
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:52:06 -05:00
strawberry ab4a283870 update complement config
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:28:14 -05:00
strawberry 9d59f777d2 refactor fed membership endpoints, add missing checks, some cleanup, reduce line width
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:28:14 -05:00
strawberry 61670370ed add rocksdb paranoid_file_checks config option, add some more config checks
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:28:14 -05:00
strawberry 1606441d09 disable admin_room_notices for complement, update welcome text
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:28:14 -05:00
strawberry cf71aeef0b fix+improve membership endpoint UX, dont reuse old membership reasons, small parallelisation
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:28:14 -05:00
strawberry a1b0369033 reduce line width on banned_room_check
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:28:14 -05:00
strawberry c070edc189 fix profile updates reusing old membership content + small parallelise + remove unnecessary Result
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:28:14 -05:00
strawberry 35e9d9b02e fix duplicate timezone keys being sent on profile lookup requests
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-12-09 12:28:14 -05:00
Jason Volk a752fb9943 rename mod with tracing inits due to macro issues
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 20:33:25 +00:00
Jason Volk f70fdca828 remove jemalloc_stats from main default features
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 20:33:25 +00:00
Jason Volk 82133ee2ea bump rust-rocksdb
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:27 +00:00
Jason Volk ff56ee7413 add back the consume_budget on cache hits
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:27 +00:00
Jason Volk 6aa35260e6 misc database options tweaks
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:27 +00:00
Jason Volk 139a1ac504 configure the tokio runtime tunables
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:27 +00:00
Jason Volk 65a370836c derive specific ReadOptions for iterators
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:27 +00:00
Jason Volk aa6d0fcaa7 add unwrap_or_default() to future TryExt extension
start an OptionFuture extension

Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:27 +00:00
Jason Volk d921b82376 add options for console tracing EnvFilter details
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:26 +00:00
Jason Volk da984d49cf remove unnecessary tracing of pdu conversions
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:26 +00:00
Jason Volk b7a41f283f optimize sync v3 asynchronicity
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:26 +00:00
Jason Volk b77a1eb079 move cork to remove_to_device_events fn
add cork around write-heavy database routine called in loop; cleanup

Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:23 +00:00
Jason Volk 23cf2b2236 add is_err() to TryFuture extension
add fold_default to Future tools extension

Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:23 +00:00
Jason Volk de3b137df8 eliminate future wrapping stream for all_pdus()
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:23 +00:00
Jason Volk 8e8c6bfe07 abstract supported room versions apis
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:23 +00:00
Jason Volk f0a1aaf7bc extend x-platform support for binding URL previews to interfaces via address
Signed-off-by: Jason Volk <jason@zemos.net>
2024-12-08 07:12:23 +00:00
Jade Ellis 52cee65748 add support for binding to a specific interface for url previews
This is helpful to, for example, bind to an interface that can
only access the public internet. The resulting setup is less
maintenance-heavy / error-prone than manually maintaining a deny/
allowlist to protect internal resources.

Signed-off-by: Jade Ellis <jade@ellis.link>
2024-12-07 23:27:56 +00:00
389 changed files with 14246 additions and 8735 deletions
+1
View File
@@ -21,3 +21,4 @@ indent_size = 2
[*.rs]
indent_style = tab
max_line_length = 98
+385 -113
View File
@@ -52,16 +52,47 @@ env:
accept-flake-config = true
# complement uses libolm
NIXPKGS_ALLOW_INSECURE: 1
WEB_UPLOAD_SSH_USERNAME: ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
GH_SHA: ${{ github.sha }}
GH_REF_NAME: ${{ github.ref_name }}
permissions:
packages: write
contents: read
permissions: {}
jobs:
tests:
name: Test
runs-on: ubuntu-24.04
steps:
- name: Setup SSH web publish
env:
web_upload_ssh_private_key: ${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
mkdir -p -v ~/.ssh
echo "${{ secrets.WEB_UPLOAD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
echo "${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
cat >>~/.ssh/config <<END
Host website
HostName ${{ secrets.WEB_UPLOAD_SSH_HOSTNAME }}
User ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking yes
AddKeysToAgent no
ForwardX11 no
BatchMode yes
END
echo "Checking connection"
ssh -q website "echo test"
echo "Creating commit rev directory on web server"
ssh -q website "rm -rf /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/$GITHUB_SHA/"
ssh -q website "mkdir -v /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/$GITHUB_SHA/"
- name: Install liburing
run: |
sudo apt install liburing-dev -y
@@ -72,11 +103,13 @@ jobs:
sudo docker image prune --all --force || true
sudo apt purge -y 'php.*' '^mongodb-.*' '^mysql-.*' azure-cli google-cloud-cli google-chrome-stable firefox powershell microsoft-edge-stable || true
sudo apt clean
sudo rm -v -rf /usr/local/games /usr/local/sqlpackage /usr/local/share/powershell /usr/local/share/edge_driver /usr/local/share/gecko_driver /usr/local/share/chromium /usr/local/share/chromedriver-linux64 /usr/lib/google-cloud-sdk /usr/lib/jvm /usr/lib/mono /usr/lib/heroku
sudo rm -rf /usr/local/lib/android /usr/local/julia* /usr/local/games /usr/local/sqlpackage /usr/local/share/powershell /usr/local/share/edge_driver /usr/local/share/gecko_driver /usr/local/share/chromium /usr/local/share/chromedriver-linux64 /usr/lib/google-cloud-sdk /usr/lib/jvm /usr/lib/mono /usr/local/lib/heroku /usr/lib/heroku /usr/local/share/boost /usr/share/dotnet /usr/local/bin/cmake* /usr/local/bin/stack /usr/local/bin/terraform /opt/microsoft/powershell /opt/hostedtoolcache/CodeQL /opt/hostedtoolcache/go /opt/hostedtoolcache/PyPy /usr/local/bin/sam || true
set -o pipefail
- name: Sync repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Tag comparison check
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
@@ -84,7 +117,7 @@ jobs:
# 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 != ${{ github.ref_name }} ]; then
if [ ${LATEST_TAG} != ${GH_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
@@ -133,8 +166,8 @@ jobs:
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
run: |
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 }}
extra-substituters = ${ATTIC_ENDPOINT}
extra-trusted-public-keys = ${ATTIC_PUBLIC_KEY}
EOF
- name: Prepare build environment
@@ -153,13 +186,15 @@ jobs:
# use sccache for Rust
- name: Run sccache-cache
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
if: (env.SCCACHE_GHA_ENABLED == 'true')
uses: mozilla-actions/sccache-action@main
# use rust-cache
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
cache-on-failure: "true"
cache-targets: "true"
- name: Run CI tests
env:
@@ -181,6 +216,7 @@ jobs:
name: complement_oci_image.tar.gz
path: complement_oci_image.tar.gz
if-no-files-found: error
compression-level: 0
- name: Upload Complement logs
uses: actions/upload-artifact@v4
@@ -201,9 +237,11 @@ jobs:
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
env:
GH_JOB_STATUS: ${{ job.status }}
if: success() || failure()
run: |
if [ ${{ job.status }} == 'success' ]; then
if [ ${GH_JOB_STATUS} == 'success' ]; then
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
else
echo '# CI failure' >> $GITHUB_STEP_SUMMARY
@@ -224,15 +262,51 @@ jobs:
build:
name: Build
runs-on: ubuntu-24.04
needs: tests
strategy:
matrix:
include:
- target: aarch64-linux-musl
- target: x86_64-linux-musl
steps:
- name: Free up a bit of runner space
run: |
set +o pipefail
sudo docker image prune --all --force || true
sudo apt purge -y 'php.*' '^mongodb-.*' '^mysql-.*' azure-cli google-cloud-cli google-chrome-stable firefox powershell microsoft-edge-stable || true
sudo apt clean
sudo rm -rf /usr/local/lib/android /usr/local/julia* /usr/local/games /usr/local/sqlpackage /usr/local/share/powershell /usr/local/share/edge_driver /usr/local/share/gecko_driver /usr/local/share/chromium /usr/local/share/chromedriver-linux64 /usr/lib/google-cloud-sdk /usr/lib/jvm /usr/lib/mono /usr/local/lib/heroku /usr/lib/heroku /usr/local/share/boost /usr/share/dotnet /usr/local/bin/cmake* /usr/local/bin/stack /usr/local/bin/terraform /opt/microsoft/powershell /opt/hostedtoolcache/CodeQL /opt/hostedtoolcache/go /opt/hostedtoolcache/PyPy /usr/local/bin/sam || true
set -o pipefail
- name: Sync repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup SSH web publish
env:
web_upload_ssh_private_key: ${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
mkdir -p -v ~/.ssh
echo "${{ secrets.WEB_UPLOAD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
echo "${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
cat >>~/.ssh/config <<END
Host website
HostName ${{ secrets.WEB_UPLOAD_SSH_HOSTNAME }}
User ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking yes
AddKeysToAgent no
ForwardX11 no
BatchMode yes
END
echo "Checking connection"
ssh -q website "echo test"
- uses: nixbuild/nix-quick-install-action@master
@@ -277,8 +351,8 @@ jobs:
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
run: |
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 }}
extra-substituters = ${ATTIC_ENDPOINT}
extra-trusted-public-keys = ${ATTIC_PUBLIC_KEY}
EOF
- name: Prepare build environment
@@ -290,15 +364,17 @@ jobs:
# use sccache for Rust
- name: Run sccache-cache
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
if: (env.SCCACHE_GHA_ENABLED == 'true')
uses: mozilla-actions/sccache-action@main
# use rust-cache
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
cache-on-failure: "true"
cache-targets: "true"
- name: Build static ${{ matrix.target }}
- name: Build static ${{ matrix.target }}-all-features
run: |
if [[ ${{ matrix.target }} == "x86_64-linux-musl" ]]
then
@@ -314,22 +390,40 @@ jobs:
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
cp -v -f result/bin/conduwuit target/release/conduwuit
cp -v -f result/bin/conduwuit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
direnv exec . cargo deb --verbose --no-build --no-strip -p conduwuit --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: Build static x86_64-linux-musl-all-features-x86_64-haswell-optimised
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
CARGO_DEB_TARGET_TUPLE="x86_64-unknown-linux-musl"
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
bin/nix-build-and-cache just .#static-x86_64-linux-musl-all-features-x86_64-haswell-optimised
mkdir -v -p target/release/
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
cp -v -f result/bin/conduwuit target/release/conduwuit
cp -v -f result/bin/conduwuit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
direnv exec . cargo deb --verbose --no-build --no-strip -p conduwuit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/x86_64-linux-musl-x86_64-haswell-optimised.deb
mv -v target/release/conduwuit static-x86_64-linux-musl-x86_64-haswell-optimised
mv -v target/release/x86_64-linux-musl-x86_64-haswell-optimised.deb x86_64-linux-musl-x86_64-haswell-optimised.deb
# quick smoke test of the x86_64 static release binary
- name: Run x86_64 static release binary
- name: Quick smoke test the x86_64 static release binary
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
# GH actions default runners are x86_64 only
if file result/bin/conduit | grep x86-64; then
result/bin/conduit --version
if file result/bin/conduwuit | grep x86-64; then
result/bin/conduwuit --version
result/bin/conduwuit --help
result/bin/conduwuit -Oserver_name="'$(date -u +%s).local'" -Odatabase_path="'/tmp/$(date -u +%s)'" --execute "server admin-notice awawawawawawawawawawa" --execute "server memory-usage" --execute "server shutdown"
fi
- name: Build static debug ${{ matrix.target }}
- name: Build static debug ${{ matrix.target }}-all-features
run: |
if [[ ${{ matrix.target }} == "x86_64-linux-musl" ]]
then
@@ -347,10 +441,9 @@ jobs:
# so we need to coerce cargo-deb into thinking this is a release binary
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 }}-debug.deb
cp -v -f result/bin/conduwuit target/release/conduwuit
cp -v -f result/bin/conduwuit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
direnv exec . cargo deb --verbose --no-build --no-strip -p conduwuit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}-debug.deb
mv -v target/release/conduwuit static-${{ matrix.target }}-debug
mv -v target/release/${{ matrix.target }}-debug.deb ${{ matrix.target }}-debug.deb
@@ -358,8 +451,8 @@ jobs:
- name: Run x86_64 static debug binary
run: |
# GH actions default runners are x86_64 only
if file result/bin/conduit | grep x86-64; then
result/bin/conduit --version
if file result/bin/conduwuit | grep x86-64; then
result/bin/conduwuit --version
fi
# check validity of produced deb package, invalid debs will error on these commands
@@ -372,14 +465,22 @@ jobs:
dpkg-deb --info ${{ matrix.target }}.deb
dpkg-deb --info ${{ matrix.target }}-debug.deb
- name: Upload static-${{ matrix.target }}
- name: Upload static-x86_64-linux-musl-all-features-x86_64-haswell-optimised to GitHub
uses: actions/upload-artifact@v4
if: ${{ matrix.target == 'x86_64-linux-musl' }}
with:
name: static-x86_64-linux-musl-x86_64-haswell-optimised
path: static-x86_64-linux-musl-x86_64-haswell-optimised
if-no-files-found: error
- name: Upload static-${{ matrix.target }}-all-features to GitHub
uses: actions/upload-artifact@v4
with:
name: static-${{ matrix.target }}
path: static-${{ matrix.target }}
if-no-files-found: error
- name: Upload deb ${{ matrix.target }}
- name: Upload static deb ${{ matrix.target }}-all-features to GitHub
uses: actions/upload-artifact@v4
with:
name: deb-${{ matrix.target }}
@@ -387,14 +488,42 @@ jobs:
if-no-files-found: error
compression-level: 0
- name: Upload static-${{ matrix.target }}-debug
- name: Upload static-x86_64-linux-musl-all-features-x86_64-haswell-optimised to webserver
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp static-x86_64-linux-musl-x86_64-haswell-optimised website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/static-x86_64-linux-musl-x86_64-haswell-optimised
fi
- name: Upload static-${{ matrix.target }}-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp static-${{ matrix.target }} website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/static-${{ matrix.target }}
fi
- name: Upload static deb x86_64-linux-musl-all-features-x86_64-haswell-optimised to webserver
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp x86_64-linux-musl-x86_64-haswell-optimised.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/x86_64-linux-musl-x86_64-haswell-optimised.deb
fi
- name: Upload static deb ${{ matrix.target }}-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp ${{ matrix.target }}.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/${{ matrix.target }}.deb
fi
- name: Upload static-${{ matrix.target }}-debug-all-features to GitHub
uses: actions/upload-artifact@v4
with:
name: static-${{ matrix.target }}-debug
path: static-${{ matrix.target }}-debug
if-no-files-found: error
- name: Upload deb ${{ matrix.target }}-debug
- name: Upload static deb ${{ matrix.target }}-debug-all-features to GitHub
uses: actions/upload-artifact@v4
with:
name: deb-${{ matrix.target }}-debug
@@ -402,19 +531,40 @@ jobs:
if-no-files-found: error
compression-level: 0
- name: Build OCI image ${{ matrix.target }}
- name: Upload static-${{ matrix.target }}-debug-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp static-${{ matrix.target }}-debug website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/static-${{ matrix.target }}-debug
fi
- name: Upload static deb ${{ matrix.target }}-debug-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp ${{ matrix.target }}-debug.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/${{ matrix.target }}-debug.deb
fi
- name: Build OCI image ${{ matrix.target }}-all-features
run: |
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
- name: Build debug OCI image ${{ matrix.target }}
- name: Build OCI image x86_64-linux-musl-all-features-x86_64-haswell-optimised
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
bin/nix-build-and-cache just .#oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised
cp -v -f result oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz
- name: Build debug OCI image ${{ matrix.target }}-all-features
run: |
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features-debug
cp -v -f result oci-image-${{ matrix.target }}-debug.tar.gz
- name: Upload OCI image ${{ matrix.target }}
- name: Upload OCI image ${{ matrix.target }}-all-features to GitHub
uses: actions/upload-artifact@v4
with:
name: oci-image-${{ matrix.target }}
@@ -422,7 +572,7 @@ jobs:
if-no-files-found: error
compression-level: 0
- name: Upload OCI image ${{ matrix.target }}-debug
- name: Upload OCI image ${{ matrix.target }}-debug-all-features to GitHub
uses: actions/upload-artifact@v4
with:
name: oci-image-${{ matrix.target }}-debug
@@ -430,6 +580,27 @@ jobs:
if-no-files-found: error
compression-level: 0
- name: Upload OCI image x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz to webserver
if: ${{ matrix.target == 'x86_64-linux-musl' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz
fi
- name: Upload OCI image ${{ matrix.target }}-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp oci-image-${{ matrix.target }}.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/oci-image-${{ matrix.target }}.tar.gz
fi
- name: Upload OCI image ${{ matrix.target }}-debug-all-features to webserver
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp oci-image-${{ matrix.target }}-debug.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/oci-image-${{ matrix.target }}-debug.tar.gz
fi
build_mac_binaries:
name: Build MacOS Binaries
strategy:
@@ -439,13 +610,42 @@ jobs:
steps:
- name: Sync repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup SSH web publish
env:
web_upload_ssh_private_key: ${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
run: |
mkdir -p -v ~/.ssh
echo "${{ secrets.WEB_UPLOAD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
echo "${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
cat >>~/.ssh/config <<END
Host website
HostName ${{ secrets.WEB_UPLOAD_SSH_HOSTNAME }}
User ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking yes
AddKeysToAgent no
ForwardX11 no
BatchMode yes
END
echo "Checking connection"
ssh -q website "echo test"
- name: Tag comparison check
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`)
if [ $LATEST_TAG != ${{ github.ref_name }} ]; then
if [ ${LATEST_TAG} != ${GH_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
@@ -453,20 +653,22 @@ jobs:
# use sccache for Rust
- name: Run sccache-cache
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
if: (env.SCCACHE_GHA_ENABLED == 'true')
uses: mozilla-actions/sccache-action@main
# use rust-cache
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
cache-on-failure: "true"
cache-targets: "true"
# Nix can't do portable macOS builds yet
- name: Build macOS x86_64 binary
if: ${{ matrix.os == 'macos-13' }}
run: |
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short HEAD)" cargo build --release
cp -v -f target/release/conduit conduwuit-macos-x86_64
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short ${{ github.sha }})" cargo build --release
cp -v -f target/release/conduwuit conduwuit-macos-x86_64
otool -L conduwuit-macos-x86_64
# quick smoke test of the x86_64 macOS binary
@@ -478,8 +680,8 @@ jobs:
- name: Build macOS arm64 binary
if: ${{ matrix.os == 'macos-latest' }}
run: |
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short HEAD)" cargo build --release
cp -v -f target/release/conduit conduwuit-macos-arm64
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short ${{ github.sha }})" cargo build --release
cp -v -f target/release/conduwuit conduwuit-macos-arm64
otool -L conduwuit-macos-arm64
# quick smoke test of the arm64 macOS binary
@@ -488,6 +690,20 @@ jobs:
run: |
./conduwuit-macos-arm64 --version
- name: Upload macOS x86_64 binary to webserver
if: ${{ matrix.os == 'macos-13' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp conduwuit-macos-x86_64 website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/conduwuit-macos-x86_64
fi
- name: Upload macOS arm64 binary to webserver
if: ${{ matrix.os == 'macos-latest' }}
run: |
if [ ! -z $WEB_UPLOAD_SSH_USERNAME ]; then
scp conduwuit-macos-arm64 website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${GH_SHA}/conduwuit-macos-arm64
fi
- name: Upload macOS x86_64 binary
if: ${{ matrix.os == 'macos-13' }}
uses: actions/upload-artifact@v4
@@ -503,21 +719,34 @@ jobs:
name: conduwuit-macos-arm64
path: conduwuit-macos-arm64
if-no-files-found: error
variables:
outputs:
github_repository: ${{ steps.var.outputs.github_repository }}
runs-on: "ubuntu-latest"
steps:
- name: Setting global variables
uses: actions/github-script@v7
id: var
with:
script: |
core.setOutput('github_repository', '${{ github.repository }}'.toLowerCase())
docker:
name: Docker publish
runs-on: ubuntu-24.04
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 != '') && github.event.pull_request.user.login != 'renovate[bot]'
needs: [build, variables]
permissions:
packages: write
contents: read
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && github.event.pull_request.user.login != 'renovate[bot]'
env:
DOCKER_ARM64: docker.io/${{ 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') && !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') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
DOCKER_ARM64: docker.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
DOCKER_AMD64: docker.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
DOCKER_TAG: docker.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
DOCKER_BRANCH: docker.io/${{ needs.variables.outputs.github_repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GHCR_ARM64: ghcr.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
GHCR_AMD64: ghcr.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
GHCR_TAG: ghcr.io/${{ needs.variables.outputs.github_repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
GHCR_BRANCH: ghcr.io/${{ needs.variables.outputs.github_repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
GLCR_ARM64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
GLCR_AMD64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
GLCR_TAG: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
@@ -525,6 +754,7 @@ jobs:
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
GHCR_ENABLED: "${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && 'true' || 'false' }}"
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
@@ -560,95 +790,137 @@ jobs:
mv -v oci-image-aarch64-linux-musl-debug/*.tar.gz oci-image-arm64v8-debug.tar.gz
- name: Load and push amd64 image
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
docker load -i oci-image-amd64.tar.gz
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_AMD64 }}
docker tag $(docker images -q conduit:main) ${{ env.GHCR_AMD64 }}
docker tag $(docker images -q conduit:main) ${{ env.GLCR_AMD64 }}
docker push ${{ env.DOCKER_AMD64 }}
docker push ${{ env.GHCR_AMD64 }}
docker push ${{ env.GLCR_AMD64 }}
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_AMD64}
docker push ${DOCKER_AMD64}
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_AMD64}
docker push ${GHCR_AMD64}
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_AMD64}
docker push ${GLCR_AMD64}
fi
- name: Load and push arm64 image
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
docker load -i oci-image-arm64v8.tar.gz
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_ARM64 }}
docker tag $(docker images -q conduit:main) ${{ env.GHCR_ARM64 }}
docker tag $(docker images -q conduit:main) ${{ env.GLCR_ARM64 }}
docker push ${{ env.DOCKER_ARM64 }}
docker push ${{ env.GHCR_ARM64 }}
docker push ${{ env.GLCR_ARM64 }}
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_ARM64}
docker push ${DOCKER_ARM64}
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_ARM64}
docker push ${GHCR_ARM64}
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_ARM64}
docker push ${GLCR_ARM64}
fi
- name: Load and push amd64 debug image
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
docker load -i oci-image-amd64-debug.tar.gz
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_AMD64 }}-debug
docker tag $(docker images -q conduit:main) ${{ env.GHCR_AMD64 }}-debug
docker tag $(docker images -q conduit:main) ${{ env.GLCR_AMD64 }}-debug
docker push ${{ env.DOCKER_AMD64 }}-debug
docker push ${{ env.GHCR_AMD64 }}-debug
docker push ${{ env.GLCR_AMD64 }}-debug
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_AMD64}-debug
docker push ${DOCKER_AMD64}-debug
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_AMD64}-debug
docker push ${GHCR_AMD64}-debug
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_AMD64}-debug
docker push ${GLCR_AMD64}-debug
fi
- name: Load and push arm64 debug image
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
docker load -i oci-image-arm64v8-debug.tar.gz
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_ARM64 }}-debug
docker tag $(docker images -q conduit:main) ${{ env.GHCR_ARM64 }}-debug
docker tag $(docker images -q conduit:main) ${{ env.GLCR_ARM64 }}-debug
docker push ${{ env.DOCKER_ARM64 }}-debug
docker push ${{ env.GHCR_ARM64 }}-debug
docker push ${{ env.GLCR_ARM64 }}-debug
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${DOCKER_ARM64}-debug
docker push ${DOCKER_ARM64}-debug
fi
if [ $GHCR_ENABLED = "true" ]; then
docker tag $(docker images -q conduwuit:main) ${GHCR_ARM64}-debug
docker push ${GHCR_ARM64}-debug
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker tag $(docker images -q conduwuit:main) ${GLCR_ARM64}-debug
docker push ${GLCR_ARM64}-debug
fi
- name: Create Docker combined manifests
run: |
# Dockerhub Container Registry
docker manifest create ${{ env.DOCKER_TAG }} --amend ${{ env.DOCKER_ARM64 }} --amend ${{ env.DOCKER_AMD64 }}
docker manifest create ${{ env.DOCKER_BRANCH }} --amend ${{ env.DOCKER_ARM64 }} --amend ${{ env.DOCKER_AMD64 }}
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker manifest create ${DOCKER_TAG} --amend ${DOCKER_ARM64} --amend ${DOCKER_AMD64}
docker manifest create ${DOCKER_BRANCH} --amend ${DOCKER_ARM64} --amend ${DOCKER_AMD64}
fi
# GitHub Container Registry
docker manifest create ${{ env.GHCR_TAG }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
docker manifest create ${{ env.GHCR_BRANCH }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
if [ $GHCR_ENABLED = "true" ]; then
docker manifest create ${GHCR_TAG} --amend ${GHCR_ARM64} --amend ${GHCR_AMD64}
docker manifest create ${GHCR_BRANCH} --amend ${GHCR_ARM64} --amend ${GHCR_AMD64}
fi
# GitLab Container Registry
docker manifest create ${{ env.GLCR_TAG }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
docker manifest create ${{ env.GLCR_BRANCH }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
if [ ! -z $GITLAB_TOKEN ]; then
docker manifest create ${GLCR_TAG} --amend ${GLCR_ARM64} --amend ${GLCR_AMD64}
docker manifest create ${GLCR_BRANCH} --amend ${GLCR_ARM64} --amend ${GLCR_AMD64}
fi
- name: Create Docker combined debug manifests
run: |
# Dockerhub Container Registry
docker manifest create ${{ env.DOCKER_TAG }}-debug --amend ${{ env.DOCKER_ARM64 }}-debug --amend ${{ env.DOCKER_AMD64 }}-debug
docker manifest create ${{ env.DOCKER_BRANCH }}-debug --amend ${{ env.DOCKER_ARM64 }}-debug --amend ${{ env.DOCKER_AMD64 }}-debug
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker manifest create ${DOCKER_TAG}-debug --amend ${DOCKER_ARM64}-debug --amend ${DOCKER_AMD64}-debug
docker manifest create ${DOCKER_BRANCH}-debug --amend ${DOCKER_ARM64}-debug --amend ${DOCKER_AMD64}-debug
fi
# GitHub Container Registry
docker manifest create ${{ env.GHCR_TAG }}-debug --amend ${{ env.GHCR_ARM64 }}-debug --amend ${{ env.GHCR_AMD64 }}-debug
docker manifest create ${{ env.GHCR_BRANCH }}-debug --amend ${{ env.GHCR_ARM64 }}-debug --amend ${{ env.GHCR_AMD64 }}-debug
if [ $GHCR_ENABLED = "true" ]; then
docker manifest create ${GHCR_TAG}-debug --amend ${GHCR_ARM64}-debug --amend ${GHCR_AMD64}-debug
docker manifest create ${GHCR_BRANCH}-debug --amend ${GHCR_ARM64}-debug --amend ${GHCR_AMD64}-debug
fi
# GitLab Container Registry
docker manifest create ${{ env.GLCR_TAG }}-debug --amend ${{ env.GLCR_ARM64 }}-debug --amend ${{ env.GLCR_AMD64 }}-debug
docker manifest create ${{ env.GLCR_BRANCH }}-debug --amend ${{ env.GLCR_ARM64 }}-debug --amend ${{ env.GLCR_AMD64 }}-debug
if [ ! -z $GITLAB_TOKEN ]; then
docker manifest create ${GLCR_TAG}-debug --amend ${GLCR_ARM64}-debug --amend ${GLCR_AMD64}-debug
docker manifest create ${GLCR_BRANCH}-debug --amend ${GLCR_ARM64}-debug --amend ${GLCR_AMD64}-debug
fi
- name: Push manifests to Docker registries
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
docker manifest push ${{ env.DOCKER_TAG }}
docker manifest push ${{ env.DOCKER_BRANCH }}
docker manifest push ${{ env.GHCR_TAG }}
docker manifest push ${{ env.GHCR_BRANCH }}
docker manifest push ${{ env.GLCR_TAG }}
docker manifest push ${{ env.GLCR_BRANCH }}
docker manifest push ${{ env.DOCKER_TAG }}-debug
docker manifest push ${{ env.DOCKER_BRANCH }}-debug
docker manifest push ${{ env.GHCR_TAG }}-debug
docker manifest push ${{ env.GHCR_BRANCH }}-debug
docker manifest push ${{ env.GLCR_TAG }}-debug
docker manifest push ${{ env.GLCR_BRANCH }}-debug
if [ ! -z $DOCKERHUB_TOKEN ]; then
docker manifest push ${DOCKER_TAG}
docker manifest push ${DOCKER_BRANCH}
docker manifest push ${DOCKER_TAG}-debug
docker manifest push ${DOCKER_BRANCH}-debug
fi
if [ $GHCR_ENABLED = "true" ]; then
docker manifest push ${GHCR_TAG}
docker manifest push ${GHCR_BRANCH}
docker manifest push ${GHCR_TAG}-debug
docker manifest push ${GHCR_BRANCH}-debug
fi
if [ ! -z $GITLAB_TOKEN ]; then
docker manifest push ${GLCR_TAG}
docker manifest push ${GLCR_BRANCH}
docker manifest push ${GLCR_TAG}-debug
docker manifest push ${GLCR_BRANCH}-debug
fi
- name: Add Image Links to Job Summary
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
run: |
echo "- \`docker pull ${{ env.DOCKER_TAG }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.GHCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.GLCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.DOCKER_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.GHCR_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${{ env.GLCR_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
if [ ! -z $DOCKERHUB_TOKEN ]; then
echo "- \`docker pull ${DOCKER_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${DOCKER_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
fi
if [ $GHCR_ENABLED = "true" ]; then
echo "- \`docker pull ${GHCR_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GHCR_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
fi
if [ ! -z $GITLAB_TOKEN ]; then
echo "- \`docker pull ${GLCR_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`docker pull ${GLCR_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
fi
+6 -2
View File
@@ -36,6 +36,8 @@ concurrency:
group: "pages"
cancel-in-progress: false
permissions: {}
jobs:
docs:
name: Documentation and GitHub Pages
@@ -61,6 +63,8 @@ jobs:
- name: Sync repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup GitHub Pages
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') && (github.event_name != 'pull_request')
@@ -109,8 +113,8 @@ jobs:
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
run: |
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 }}
extra-substituters = ${ATTIC_ENDPOINT}
extra-trusted-public-keys = ${ATTIC_PUBLIC_KEY}
EOF
- name: Prepare build environment
+118
View File
@@ -0,0 +1,118 @@
name: Upload Release Assets
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Tag to release'
required: true
type: string
action_id:
description: 'Action ID of the CI run'
required: true
type: string
permissions: {}
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write
env:
GH_EVENT_NAME: ${{ github.event_name }}
GH_EVENT_INPUTS_ACTION_ID: ${{ github.event.inputs.action_id }}
GH_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }}
GH_REPOSITORY: ${{ github.repository }}
GH_SHA: ${{ github.sha }}
GH_TAG: ${{ github.event.release.tag_name }}
steps:
- name: get latest ci id
id: get_ci_id
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${GH_EVENT_NAME}" == "workflow_dispatch" ]; then
id="${GH_EVENT_INPUTS_ACTION_ID}"
tag="${GH_EVENT_INPUTS_TAG}"
else
# get all runs of the ci workflow
json=$(gh api "repos/${GH_REPOSITORY}/actions/workflows/ci.yml/runs")
# find first run that is github sha and status is completed
id=$(echo "$json" | jq ".workflow_runs[] | select(.head_sha == \"${GH_SHA}\" and .status == \"completed\") | .id" | head -n 1)
if [ ! "$id" ]; then
echo "No completed runs found"
echo "ci_id=0" >> "$GITHUB_OUTPUT"
exit 0
fi
tag="${GH_TAG}"
fi
echo "ci_id=$id" >> "$GITHUB_OUTPUT"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- name: get latest ci artifacts
if: steps.get_ci_id.outputs.ci_id != 0
uses: actions/download-artifact@v4
env:
GH_TOKEN: ${{ github.token }}
with:
merge-multiple: true
run-id: ${{ steps.get_ci_id.outputs.ci_id }}
github-token: ${{ github.token }}
- run: |
ls
- name: upload release assets
if: steps.get_ci_id.outputs.ci_id != 0
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.get_ci_id.outputs.tag }}
run: |
for file in $(find . -type f); do
case "$file" in
*json*) echo "Skipping $file...";;
*) echo "Uploading $file..."; gh release upload $TAG "$file" --clobber --repo="${GH_REPOSITORY}" || echo "Something went wrong, skipping.";;
esac
done
- name: upload release assets to website
if: steps.get_ci_id.outputs.ci_id != 0
env:
TAG: ${{ steps.get_ci_id.outputs.tag }}
run: |
mkdir -p -v ~/.ssh
echo "${{ secrets.WEB_UPLOAD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
echo "${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
cat >>~/.ssh/config <<END
Host website
HostName ${{ secrets.WEB_UPLOAD_SSH_HOSTNAME }}
User ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking yes
AddKeysToAgent no
ForwardX11 no
BatchMode yes
END
echo "Creating tag directory on web server"
ssh -q website "rm -rf /var/www/girlboss.ceo/~strawberry/conduwuit/releases/$TAG/"
ssh -q website "mkdir -v /var/www/girlboss.ceo/~strawberry/conduwuit/releases/$TAG/"
for file in $(find . -type f); do
case "$file" in
*json*) echo "Skipping $file...";;
*) echo "Uploading $file to website"; scp $file website:/var/www/girlboss.ceo/~strawberry/conduwuit/releases/$TAG/$file;;
esac
done
+1 -1
View File
@@ -30,7 +30,7 @@ modules.xml
.nfs*
# Rust
/target/
/target
### vscode ###
.vscode/*
Generated
+265 -291
View File
File diff suppressed because it is too large Load Diff
+66 -43
View File
@@ -23,7 +23,7 @@ rust-version = "1.83.0"
version = "0.5.0"
[workspace.metadata.crane]
name = "conduit"
name = "conduwuit"
[workspace.dependencies.arrayvec]
version = "0.7.4"
@@ -35,6 +35,7 @@ features = [
"const_generics",
"const_new",
"serde",
"union",
"write",
]
@@ -45,7 +46,7 @@ version = "0.5.7"
version = "0.2.9"
[workspace.dependencies.cargo_toml]
version = "0.20"
version = "0.21"
default-features = false
features = ["features"]
@@ -79,13 +80,13 @@ version = "0.8.5"
# Used for the http request / response body type for Ruma endpoints used with reqwest
[workspace.dependencies.bytes]
version = "1.8.0"
version = "1.9.0"
[workspace.dependencies.http-body-util]
version = "0.1.2"
[workspace.dependencies.http]
version = "1.1.0"
version = "1.2.0"
[workspace.dependencies.regex]
version = "1.11.1"
@@ -138,7 +139,7 @@ features = [
]
[workspace.dependencies.rustls]
version = "0.23.16"
version = "0.23.19"
default-features = false
features = ["aws_lc_rs"]
@@ -153,7 +154,7 @@ features = [
]
[workspace.dependencies.serde]
version = "1.0.215"
version = "1.0.216"
default-features = false
features = ["rc"]
@@ -199,7 +200,7 @@ default-features = false
version = "0.1.41"
default-features = false
[workspace.dependencies.tracing-subscriber]
version = "0.3.18"
version = "=0.3.18"
default-features = false
features = ["env-filter", "std", "tracing", "tracing-log", "ansi", "fmt"]
[workspace.dependencies.tracing-core]
@@ -211,9 +212,9 @@ default-features = false
version = "2.0.1"
default-features = false
# used for conduit's CLI and admin room command parsing
# used for conduwuit's CLI and admin room command parsing
[workspace.dependencies.clap]
version = "4.5.21"
version = "4.5.23"
default-features = false
features = [
"derive",
@@ -231,7 +232,7 @@ default-features = false
features = ["std", "async-await"]
[workspace.dependencies.tokio]
version = "1.41.1"
version = "1.42.0"
default-features = false
features = [
"fs",
@@ -242,13 +243,14 @@ features = [
"time",
"rt-multi-thread",
"io-util",
"tracing",
]
[workspace.dependencies.tokio-metrics]
version = "0.3.1"
version = "0.4.0"
[workspace.dependencies.libloading]
version = "0.8.5"
version = "0.8.6"
# Validating urls in config, was already a transitive dependency
[workspace.dependencies.url]
@@ -294,12 +296,12 @@ default-features = false
features = ["env", "toml"]
[workspace.dependencies.hickory-resolver]
version = "0.24.1"
version = "0.24.2"
default-features = false
# Used for conduit::Error type
# Used for conduwuit::Error type
[workspace.dependencies.thiserror]
version = "2.0.3"
version = "2.0.7"
default-features = false
# Used when hashing the state
@@ -334,7 +336,7 @@ version = "0.1.2"
[workspace.dependencies.ruma]
git = "https://github.com/girlbossceo/ruwuma"
#branch = "conduwuit-changes"
rev = "1a550585bf025cce48ef8b734339245092bc986e"
rev = "c4f55b39900b33b2d443dd12a6a2dab50961fdfb"
features = [
"compat",
"rand",
@@ -350,7 +352,6 @@ features = [
"compat-upload-signatures",
"identifiers-validation",
"unstable-unspecified",
"unstable-msc2409",
"unstable-msc2448",
"unstable-msc2666",
"unstable-msc2867",
@@ -366,6 +367,7 @@ features = [
"unstable-msc4121",
"unstable-msc4125",
"unstable-msc4186",
"unstable-msc4203", # sending to-device events to appservices
"unstable-msc4210", # remove legacy mentions
"unstable-extensible-events",
]
@@ -410,7 +412,7 @@ features = ["rt-tokio"]
# optional sentry metrics for crash/panic reporting
[workspace.dependencies.sentry]
version = "0.34.0"
version = "0.35.0"
default-features = false
features = [
"backtrace",
@@ -426,9 +428,9 @@ features = [
]
[workspace.dependencies.sentry-tracing]
version = "0.34.0"
version = "0.35.0"
[workspace.dependencies.sentry-tower]
version = "0.34.0"
version = "0.35.0"
# jemalloc usage
[workspace.dependencies.tikv-jemalloc-sys]
@@ -480,7 +482,7 @@ default-features = false
version = "0.1"
[workspace.dependencies.syn]
version = "2.0.87"
version = "2.0.90"
default-features = false
features = ["full", "extra-traits"]
@@ -493,6 +495,15 @@ version = "1.0.89"
[workspace.dependencies.bytesize]
version = "1.3.0"
[workspace.dependencies.core_affinity]
version = "0.8.1"
[workspace.dependencies.libc]
version = "0.2"
[workspace.dependencies.num-traits]
version = "0.2"
#
# Patches
#
@@ -519,42 +530,54 @@ rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
git = "https://github.com/girlbossceo/rustyline-async"
rev = "deaeb0694e2083f53d363b648da06e10fc13900c"
# adds LIFO queue scheduling; this should be updated with PR progress.
[patch.crates-io.event-listener]
git = "https://github.com/girlbossceo/event-listener"
rev = "fe4aebeeaae435af60087ddd56b573a2e0be671d"
[patch.crates-io.async-channel]
git = "https://github.com/girlbossceo/async-channel"
rev = "92e5e74063bf2a3b10414bcc8a0d68b235644280"
[patch.crates-io.core_affinity]
git = "https://github.com/girlbossceo/core_affinity_rs"
rev = "9c8e51510c35077df888ee72a36b4b05637147da"
#
# Our crates
#
[workspace.dependencies.conduit-router]
package = "conduit_router"
[workspace.dependencies.conduwuit-router]
package = "conduwuit_router"
path = "src/router"
default-features = false
[workspace.dependencies.conduit-admin]
package = "conduit_admin"
[workspace.dependencies.conduwuit-admin]
package = "conduwuit_admin"
path = "src/admin"
default-features = false
[workspace.dependencies.conduit-api]
package = "conduit_api"
[workspace.dependencies.conduwuit-api]
package = "conduwuit_api"
path = "src/api"
default-features = false
[workspace.dependencies.conduit-service]
package = "conduit_service"
[workspace.dependencies.conduwuit-service]
package = "conduwuit_service"
path = "src/service"
default-features = false
[workspace.dependencies.conduit-database]
package = "conduit_database"
[workspace.dependencies.conduwuit-database]
package = "conduwuit_database"
path = "src/database"
default-features = false
[workspace.dependencies.conduit-core]
package = "conduit_core"
[workspace.dependencies.conduwuit-core]
package = "conduwuit_core"
path = "src/core"
default-features = false
[workspace.dependencies.conduit-macros]
package = "conduit_macros"
[workspace.dependencies.conduwuit-macros]
package = "conduwuit_macros"
path = "src/macros"
default-features = false
@@ -613,7 +636,7 @@ codegen-units = 32
# '-Clink-arg=-Wl,--no-gc-sections',
#]
[profile.release-max-perf.package.conduit_macros]
[profile.release-max-perf.package.conduwuit_macros]
inherits = "release-max-perf.build-override"
#rustflags = [
# '-Crelocation-model=pic',
@@ -647,7 +670,7 @@ panic = "unwind"
debug-assertions = true
incremental = true
#rustflags = [
# '--cfg', 'conduit_mods',
# '--cfg', 'conduwuit_mods',
# '-Ztime-passes',
# '-Zmir-opt-level=0',
# '-Zvalidate-mir=false',
@@ -664,11 +687,11 @@ incremental = true
# '-Clink-arg=-Wl,-z,lazy',
#]
[profile.dev.package.conduit_core]
[profile.dev.package.conduwuit_core]
inherits = "dev"
incremental = false
#rustflags = [
# '--cfg', 'conduit_mods',
# '--cfg', 'conduwuit_mods',
# '-Ztime-passes',
# '-Zmir-opt-level=0',
# '-Ztls-model=initial-exec',
@@ -685,10 +708,10 @@ incremental = false
# '-Clink-arg=-Wl,-z,nodelete',
#]
[profile.dev.package.conduit]
[profile.dev.package.conduwuit]
inherits = "dev"
#rustflags = [
# '--cfg', 'conduit_mods',
# '--cfg', 'conduwuit_mods',
# '-Ztime-passes',
# '-Zmir-opt-level=0',
# '-Zvalidate-mir=false',
@@ -710,7 +733,7 @@ incremental = false
codegen-units = 1
opt-level = 'z'
#rustflags = [
# '--cfg', 'conduit_mods',
# '--cfg', 'conduwuit_mods',
# '-Ztls-model=initial-exec',
# '-Cprefer-dynamic=true',
# '-Zstaticlib-prefer-dynamic=true',
@@ -731,7 +754,7 @@ incremental = false
codegen-units = 1
opt-level = 'z'
#rustflags = [
# '--cfg', 'conduit_mods',
# '--cfg', 'conduwuit_mods',
# '-Ztls-model=global-dynamic',
# '-Cprefer-dynamic=true',
# '-Zstaticlib-prefer-dynamic=true',
+7 -1
View File
@@ -66,6 +66,12 @@ is no harm or additional steps required for using conduwuit. See the
[Migrating from Conduit](https://conduwuit.puppyirl.gay/deploying/generic.html#migrating-from-conduit) section
on the generic deploying guide.
Note that as of conduwuit version 0.5.0, backwards compatibility with Conduit is
no longer supported. We only support migrating *from* Conduit, not back to
Conduit like before. If you are truly finding yourself wanting to migrate back
to Conduit, we would appreciate all your feedback and if we can assist with
any issues or concerns.
<!-- ANCHOR_END: body -->
<!-- ANCHOR: footer -->
@@ -91,7 +97,7 @@ heavily appreciated! 💜🥺
Original repo and Matrix room picture was from bran (<3). Current banner image
and logo is directly from [this cohost
post](https://cohost.org/RatBaby/post/1028290-finally-a-flag-for).
post](https://web.archive.org/web/20241126004041/https://cohost.org/RatBaby/post/1028290-finally-a-flag-for).
#### Is it conduwuit or Conduwuit?
+2 -1
View File
@@ -1,6 +1,7 @@
[Unit]
Description=conduwuit Matrix homeserver
After=network.target
Wants=network-online.target
After=network-online.target
Documentation=https://conduwuit.puppyirl.gay/
RequiresMountsFor=/var/lib/private/conduwuit
+10 -6
View File
@@ -3,13 +3,17 @@ cognitive-complexity-threshold = 94 # TODO reduce me ALARA
excessive-nesting-threshold = 11 # TODO reduce me to 4 or 5
future-size-threshold = 7745 # TODO reduce me ALARA
stack-size-threshold = 196608 # reduce me ALARA
too-many-lines-threshold = 700 # TODO reduce me to <= 100
too-many-lines-threshold = 780 # TODO reduce me to <= 100
type-complexity-threshold = 250 # reduce me to ~200
disallowed-macros = [
{ path = "log::error", reason = "use conduit_core::error" },
{ path = "log::warn", reason = "use conduit_core::warn" },
{ path = "log::info", reason = "use conduit_core::info" },
{ path = "log::debug", reason = "use conduit_core::debug" },
{ path = "log::trace", reason = "use conduit_core::trace" },
{ path = "log::error", reason = "use conduwuit_core::error" },
{ path = "log::warn", reason = "use conduwuit_core::warn" },
{ path = "log::info", reason = "use conduwuit_core::info" },
{ path = "log::debug", reason = "use conduwuit_core::debug" },
{ path = "log::trace", reason = "use conduwuit_core::trace" },
]
disallowed-methods = [
{ path = "tokio::spawn", reason = "use and pass conduuwit_core::server::Server::runtime() to spawn from" },
]
+370 -244
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -9,6 +9,8 @@ It is recommended to see the [generic deployment guide](../deploying/generic.md)
for further information if needed as usage of the Debian package is generally
related.
No `apt` repository is currently offered yet, it is in the works/development.
### Configuration
When installed, the example config is placed at `/etc/conduwuit/conduwuit.toml`
+2 -1
View File
@@ -1,7 +1,8 @@
[Unit]
Description=conduwuit Matrix homeserver
Documentation=https://conduwuit.puppyirl.gay/
Wants=network-online.target
After=network-online.target
Documentation=https://conduwuit.puppyirl.gay/
[Service]
DynamicUser=yes
+1 -1
View File
@@ -27,7 +27,7 @@ malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
[dependencies.rust-rocksdb]
git = "https://github.com/girlbossceo/rust-rocksdb-zaidoon1"
rev = "4bce1bb97d8be6f0d47245c99d465ca9cef33aad"
rev = "123d6302fed23fc706344becb2f19623265a83f8"
#branch = "master"
default-features = false
+59 -58
View File
@@ -1,61 +1,62 @@
pub use rust_rocksdb::*;
#[cfg_attr(not(conduit_mods), link(name = "rocksdb"))]
#[cfg_attr(conduit_mods, link(name = "rocksdb", kind = "static"))]
extern "C" {
pub fn rocksdb_list_column_families();
pub fn rocksdb_logger_create_stderr_logger();
pub fn rocksdb_options_set_info_log();
pub fn rocksdb_get_options_from_string();
pub fn rocksdb_writebatch_create();
pub fn rocksdb_writebatch_destroy();
pub fn rocksdb_writebatch_put_cf();
pub fn rocksdb_writebatch_delete_cf();
pub fn rocksdb_iter_value();
pub fn rocksdb_iter_seek_to_last();
pub fn rocksdb_iter_seek_for_prev();
pub fn rocksdb_iter_seek_to_first();
pub fn rocksdb_iter_next();
pub fn rocksdb_iter_prev();
pub fn rocksdb_iter_seek();
pub fn rocksdb_iter_valid();
pub fn rocksdb_iter_get_error();
pub fn rocksdb_iter_key();
pub fn rocksdb_iter_destroy();
pub fn rocksdb_livefiles();
pub fn rocksdb_livefiles_count();
pub fn rocksdb_livefiles_destroy();
pub fn rocksdb_livefiles_column_family_name();
pub fn rocksdb_livefiles_name();
pub fn rocksdb_livefiles_size();
pub fn rocksdb_livefiles_level();
pub fn rocksdb_livefiles_smallestkey();
pub fn rocksdb_livefiles_largestkey();
pub fn rocksdb_livefiles_entries();
pub fn rocksdb_livefiles_deletions();
pub fn rocksdb_put_cf();
pub fn rocksdb_delete_cf();
pub fn rocksdb_get_pinned_cf();
pub fn rocksdb_create_column_family();
pub fn rocksdb_get_latest_sequence_number();
pub fn rocksdb_batched_multi_get_cf();
pub fn rocksdb_cancel_all_background_work();
pub fn rocksdb_repair_db();
pub fn rocksdb_list_column_families_destroy();
pub fn rocksdb_flush();
pub fn rocksdb_flush_wal();
pub fn rocksdb_open_column_families();
pub fn rocksdb_open_for_read_only_column_families();
pub fn rocksdb_open_as_secondary_column_families();
pub fn rocksdb_open_column_families_with_ttl();
pub fn rocksdb_open();
pub fn rocksdb_open_for_read_only();
pub fn rocksdb_open_with_ttl();
pub fn rocksdb_open_as_secondary();
pub fn rocksdb_write();
pub fn rocksdb_create_iterator_cf();
pub fn rocksdb_backup_engine_create_new_backup_flush();
pub fn rocksdb_backup_engine_options_create();
pub fn rocksdb_write_buffer_manager_destroy();
pub fn rocksdb_options_set_ttl();
#[cfg_attr(not(conduwuit_mods), link(name = "rocksdb"))]
#[cfg_attr(conduwuit_mods, link(name = "rocksdb", kind = "static"))]
unsafe extern "C" {
pub unsafe fn rocksdb_list_column_families();
pub unsafe fn rocksdb_logger_create_stderr_logger();
pub unsafe fn rocksdb_logger_create_callback_logger();
pub unsafe fn rocksdb_options_set_info_log();
pub unsafe fn rocksdb_get_options_from_string();
pub unsafe fn rocksdb_writebatch_create();
pub unsafe fn rocksdb_writebatch_destroy();
pub unsafe fn rocksdb_writebatch_put_cf();
pub unsafe fn rocksdb_writebatch_delete_cf();
pub unsafe fn rocksdb_iter_value();
pub unsafe fn rocksdb_iter_seek_to_last();
pub unsafe fn rocksdb_iter_seek_for_prev();
pub unsafe fn rocksdb_iter_seek_to_first();
pub unsafe fn rocksdb_iter_next();
pub unsafe fn rocksdb_iter_prev();
pub unsafe fn rocksdb_iter_seek();
pub unsafe fn rocksdb_iter_valid();
pub unsafe fn rocksdb_iter_get_error();
pub unsafe fn rocksdb_iter_key();
pub unsafe fn rocksdb_iter_destroy();
pub unsafe fn rocksdb_livefiles();
pub unsafe fn rocksdb_livefiles_count();
pub unsafe fn rocksdb_livefiles_destroy();
pub unsafe fn rocksdb_livefiles_column_family_name();
pub unsafe fn rocksdb_livefiles_name();
pub unsafe fn rocksdb_livefiles_size();
pub unsafe fn rocksdb_livefiles_level();
pub unsafe fn rocksdb_livefiles_smallestkey();
pub unsafe fn rocksdb_livefiles_largestkey();
pub unsafe fn rocksdb_livefiles_entries();
pub unsafe fn rocksdb_livefiles_deletions();
pub unsafe fn rocksdb_put_cf();
pub unsafe fn rocksdb_delete_cf();
pub unsafe fn rocksdb_get_pinned_cf();
pub unsafe fn rocksdb_create_column_family();
pub unsafe fn rocksdb_get_latest_sequence_number();
pub unsafe fn rocksdb_batched_multi_get_cf();
pub unsafe fn rocksdb_cancel_all_background_work();
pub unsafe fn rocksdb_repair_db();
pub unsafe fn rocksdb_list_column_families_destroy();
pub unsafe fn rocksdb_flush();
pub unsafe fn rocksdb_flush_wal();
pub unsafe fn rocksdb_open_column_families();
pub unsafe fn rocksdb_open_for_read_only_column_families();
pub unsafe fn rocksdb_open_as_secondary_column_families();
pub unsafe fn rocksdb_open_column_families_with_ttl();
pub unsafe fn rocksdb_open();
pub unsafe fn rocksdb_open_for_read_only();
pub unsafe fn rocksdb_open_with_ttl();
pub unsafe fn rocksdb_open_as_secondary();
pub unsafe fn rocksdb_write();
pub unsafe fn rocksdb_create_iterator_cf();
pub unsafe fn rocksdb_backup_engine_create_new_backup_flush();
pub unsafe fn rocksdb_backup_engine_options_create();
pub unsafe fn rocksdb_write_buffer_manager_destroy();
pub unsafe fn rocksdb_options_set_ttl();
}
+1 -1
View File
@@ -42,7 +42,7 @@ The syntax of this is a standard admin command without the prefix such as
An example output of a success is:
```
INFO conduit_service::admin::startup: Startup command #0 completed:
INFO conduwuit_service::admin::startup: Startup command #0 completed:
Created user with user_id: @june:girlboss.ceo and password: `<redacted>`
```
+4 -1
View File
@@ -24,6 +24,9 @@ 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
OCI image `.tar.gz` files are also hosted directly at when uploaded by CI with a
commit hash/revision or a tagged release: <https://pup.systems/~strawberry/conduwuit/>
Use
```bash
@@ -41,7 +44,7 @@ docker run -d -p 8448:6167 \
-v db:/var/lib/conduwuit/ \
-e CONDUWUIT_SERVER_NAME="your.server.name" \
-e CONDUWUIT_ALLOW_REGISTRATION=false \
--name conduit $LINK
--name conduwuit $LINK
```
or you can use [docker compose](#docker-compose).
+58 -16
View File
@@ -1,6 +1,6 @@
# Generic deployment documentation
> ## Getting help
> ### Getting help
>
> If you run into any problems while setting up conduwuit, ask us in
> `#conduwuit:puppygock.gay` or [open an issue on
@@ -8,29 +8,50 @@
## Installing conduwuit
You may simply download the binary that fits your machine. Run `uname -m` to see
what you need.
### Static prebuilt binary
You may simply download the binary that fits your machine architecture (x86_64
or aarch64). Run `uname -m` to see what you need.
Prebuilt fully static musl binaries can be downloaded from the latest tagged
release [here](https://github.com/girlbossceo/conduwuit/releases/latest) or
`main` CI branch workflow artifact output. These also include Debian/Ubuntu packages.
`main` CI branch workflow artifact output. These also include Debian/Ubuntu
packages.
Binaries are also available on my website directly at: <https://pup.systems/~strawberry/conduwuit/>
These can be curl'd directly from. `ci-bins` are CI workflow binaries by commit
hash/revision, and `releases` are tagged releases. Sort by descending last
modified for the latest.
These binaries have jemalloc and io_uring statically linked and included with
them, so no additional dynamic dependencies need to be installed.
For the **best** performance; if using an `x86_64` CPU made in the last ~15 years,
we recommend using the `-haswell-` optimised binaries. This sets
`-march=haswell` which is the most compatible and highest performance with
optimised binaries. The database backend, RocksDB, most benefits from this as it
will then use hardware accelerated CRC32 hashing/checksumming which is critical
for performance.
### Compiling
Alternatively, you may compile the binary yourself. We recommend using
Nix (or [Lix](https://lix.systems)) to build conduwuit as this has the most guaranteed
reproducibiltiy and easiest to get a build environment and output going. This also
allows easy cross-compilation.
Nix (or [Lix](https://lix.systems)) to build conduwuit as this has the most
guaranteed reproducibiltiy and easiest to get a build environment and output
going. This also allows easy cross-compilation.
You can run the `nix build -L .#static-x86_64-linux-musl-all-features` or
`nix build -L .#static-aarch64-linux-musl-all-features` commands based
on architecture to cross-compile the necessary static binary located at
`result/bin/conduit`. This is reproducible with the static binaries produced in our CI.
`result/bin/conduwuit`. This is reproducible with the static binaries produced
in our CI.
Otherwise, follow standard Rust project build guides (installing git and cloning
the repo, getting the Rust toolchain via rustup, installing LLVM toolchain +
libclang for RocksDB, installing liburing for io_uring and RocksDB, etc).
If wanting to build using standard Rust toolchains, make sure you install:
- `liburing-dev` on the compiling machine, and `liburing` on the target host
- LLVM and libclang for RocksDB
You can build conduwuit using `cargo build --release --all-features`
## Migrating from Conduit
@@ -38,6 +59,12 @@ As mentioned in the README, there is little to no steps needed to migrate
from Conduit. As long as you are using the RocksDB database backend, just
replace the binary / container image / etc.
**WARNING**: As of conduwuit 0.5.0, all database and backwards compatibility
with Conduit is no longer supported. We only support migrating *from* Conduit,
not back to Conduit like before. If you are truly finding yourself wanting to
migrate back to Conduit, we would appreciate all your feedback and if we can
assist with any issues or concerns.
**Note**: If you are relying on Conduit's "automatic delegation" feature,
this will **NOT** work on conduwuit and you must configure delegation manually.
This is not a mistake and no support for this feature will be added.
@@ -89,11 +116,25 @@ on the network level, consider something like NextDNS or Pi-Hole.
## Setting up a systemd service
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.
Two example systemd units for conduwuit can be found
[on the configuration page](../configuration/examples.md#debian-systemd-unit-file).
You may need to change the `ExecStart=` path to where you placed the conduwuit
binary if it is not `/usr/bin/conduwuit`.
On systems where rsyslog is used alongside journald (i.e. Red Hat-based distros and OpenSUSE), put `$EscapeControlCharactersOnReceive off` inside `/etc/rsyslog.conf` to allow color in logs.
On systems where rsyslog is used alongside journald (i.e. Red Hat-based distros
and OpenSUSE), put `$EscapeControlCharactersOnReceive off` inside
`/etc/rsyslog.conf` to allow color in logs.
If you are using a different `database_path` other than the systemd unit
configured default `/var/lib/conduwuit`, you need to add your path to the
systemd unit's `ReadWritePaths=`. This can be done by either directly editing
`conduwuit.service` and reloading systemd, or running `systemctl edit conduwuit.service`
and entering the following:
```
[Service]
ReadWritePaths=/path/to/custom/database/path
```
## Creating the conduwuit configuration file
@@ -101,7 +142,8 @@ 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 the config. You need to change at least the server name.**
**Please take a moment to read the config. You need to change at least the
server name.**
RocksDB is the only supported database backend.
+6
View File
@@ -29,6 +29,12 @@ conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
```
If needed, we have a binary cache on Cachix but it is only limited to 5GB:
```
https://conduwuit.cachix.org
conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
```
If specifying a Git remote URL in your flake, you can use any remotes that
are specified on the README (the mirrors), such as the GitHub: `github:girlbossceo/conduwuit`
+1 -1
View File
@@ -52,7 +52,7 @@ the said workspace crate(s) must define the feature there in its `Cargo.toml`.
So, if this is adding a feature to the API such as `woof`, you define the feature
in the `api` crate's `Cargo.toml` as `woof = []`. The feature definition in `main`'s
`Cargo.toml` will be `woof = ["conduit-api/woof"]`.
`Cargo.toml` will be `woof = ["conduwuit-api/woof"]`.
The rationale for this is due to Rust / Cargo not supporting
["workspace level features"][9], we must make a choice of; either scattering
+50 -12
View File
@@ -22,23 +22,59 @@ conduwuit has moderation admin commands for:
Any commands with `-list` in them will require a codeblock in the message with
each object being newline delimited. An example of doing this is:
```` !admin rooms moderation ban-list-of-rooms ``` !roomid1:server.name
!roomid2:server.name !roomid3:server.name ``` ````
````
!admin rooms moderation ban-list-of-rooms
```
!roomid1:server.name
#badroomalias1:server.name
!roomid2:server.name
!roomid3:server.name
#badroomalias2:server.name
```
````
## Database
## Database (RocksDB)
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.
Generally there is very little you need to do. [Compaction][rocksdb-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, due to
creating unnecessary I/O amplification. RocksDB is built with io_uring support
via liburing for improved read performance.
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](troubleshooting.md).
### Compression
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.
the RocksDB section in the [example config](configuration/examples.md).
RocksDB troubleshooting can be found [in the RocksDB section of
troubleshooting](troubleshooting.md).
btrfs users have reported that database compression does not need to be disabled
on conduwuit as the filesystem already does not attempt to compress. This can be
validated by using `filefrag -v` on a `.SST` file in your database, and ensure
the `physical_offset` matches (no filesystem compression). It is very important
to ensure no additional filesystem compression takes place as this can render
unbuffered Direct IO inoperable, significantly slowing down read and write
performance. See <https://btrfs.readthedocs.io/en/latest/Compression.html#compatibility>
> Compression is done using the COW mechanism so its incompatible with
> nodatacow. Direct IO read works on compressed files but will fall back to
> buffered writes and leads to no compression even if force compression is set.
> Currently nodatasum and compression dont work together.
### Files in database
Do not touch any of the files in the database directory. This must be said due
to users being mislead by the `.log` files in the RocksDB directory, thinking
they're server logs or database logs, however they are critical RocksDB files
related to WAL tracking.
The only safe files that can be deleted are the `LOG` files (all caps). These
are the real RocksDB telemetry/log files, however conduwuit has already
configured to only store up to 3 RocksDB `LOG` files due to generall being
useless for average users unless troubleshooting something low-level. If you
would like to store nearly none at all, see the `rocksdb_max_log_files`
config option.
## Backups
@@ -95,3 +131,5 @@ 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.
[rocksdb-compaction]: https://github.com/facebook/rocksdb/wiki/Compaction
+51 -10
View File
@@ -41,18 +41,56 @@ workarounds for this are:
- Don't use Docker's default DNS setup and instead allow the container to use
and communicate with your host's DNS servers (host's `/etc/resolv.conf`)
#### DNS No connections available error message
If you receive spurious amounts of error logs saying "DNS No connections
available", this is due to your DNS server (servers from `/etc/resolv.conf`)
being overloaded and unable to handle typical Matrix federation volume. Some
users have reported that the upstream servers are rate-limiting them as well
when they get this error (e.g. popular upstreams like Google DNS).
Matrix federation is extremely heavy and sends wild amounts of DNS requests.
Unfortunately this is by design and has only gotten worse with more
server/destination resolution steps. Synapse also expects a very perfect DNS
setup.
There are some ways you can reduce the amount of DNS queries, but ultimately
the best solution/fix is selfhosting a high quality caching DNS server like
[Unbound][unbound-arch] without any upstream resolvers, and without DNSSEC
validation enabled.
DNSSEC validation is highly recommended to be **disabled** due to DNSSEC being
very computationally expensive, and is extremely susceptible to denial of
service, especially on Matrix. Many servers also strangely have broken DNSSEC
setups and will result in non-functional federation.
conduwuit cannot provide a "works-for-everyone" Unbound DNS setup guide, but
the [official Unbound tuning guide][unbound-tuning] and the [Unbound Arch Linux wiki page][unbound-arch]
may be of interest. Disabling DNSSEC on Unbound is commenting out trust-anchors
config options and removing the `validator` module.
**Avoid** using `systemd-resolved` as it does **not** perform very well under
high load, and we have identified its DNS caching to not be very effective.
dnsmasq can possibly work, but it does **not** support TCP fallback which can be
problematic when receiving large DNS responses such as from large SRV records.
If you still want to use dnsmasq, make sure you **disable** `dns_tcp_fallback`
in conduwuit config.
Raising `dns_cache_entries` in conduwuit config from the default can also assist
in DNS caching, but a full-fledged external caching resolver is better and more
reliable.
If you don't have IPv6 connectivity, changing `ip_lookup_strategy` to match
your setup can help reduce unnecessary AAAA queries
(`1 - Ipv4Only (Only query for A records, no AAAA/IPv6)`).
If your DNS server supports it, some users have reported enabling
`query_over_tcp_only` to force only TCP querying by default has improved DNS
reliability at a slight performance cost due to TCP overhead.
## RocksDB / database issues
#### 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 and reduces system CPU
usage, but at least FUSE and possibly ZFS are filesystems 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
@@ -149,3 +187,6 @@ If you are a developer, you can also view the raw jemalloc statistics with
`!admin debug memory-stats`. Please note that this output is extremely large
which may only be visible in the conduwuit console CLI due to PDU size limits,
and is not easy for non-developers to understand.
[unbound-tuning]: https://unbound.docs.nlnetlabs.nl/en/latest/topics/core/performance.html
[unbound-arch]: https://wiki.archlinux.org/title/Unbound
Generated
+123 -493
View File
@@ -5,15 +5,16 @@
"crane": "crane",
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"nix-github-actions": "nix-github-actions",
"nixpkgs": "nixpkgs",
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1729116596,
"narHash": "sha256-NnLMLIXGZtAscUF4dCShksuQ1nOGF6Y2dEeyj0rBbUg=",
"lastModified": 1731270564,
"narHash": "sha256-6KMC/NH/VWP5Eb+hA56hz0urel3jP6Y6cF2PX6xaTkk=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "2b05b7d986cf6009b1c1ef7daa4961cd1a658782",
"rev": "47752427561f1c34debb16728a210d378f0ece36",
"type": "github"
},
"original": {
@@ -26,16 +27,16 @@
"cachix": {
"inputs": {
"devenv": "devenv",
"flake-compat": "flake-compat_3",
"flake-compat": "flake-compat_2",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1728672398,
"narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
"lastModified": 1733424942,
"narHash": "sha256-5t7Sl6EkOaoP4FvzLmH7HFDbdl9SizmLh53RjDQCbWQ=",
"owner": "cachix",
"repo": "cachix",
"rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
"rev": "8b6b0e4694b9aa78b2ea4c93bff6e1a222dc7e4a",
"type": "github"
},
"original": {
@@ -47,72 +48,31 @@
},
"cachix_2": {
"inputs": {
"devenv": "devenv_2",
"devenv": [
"cachix",
"devenv"
],
"flake-compat": [
"cachix",
"devenv",
"flake-compat"
"devenv"
],
"git-hooks": [
"cachix",
"devenv",
"pre-commit-hooks"
"devenv"
],
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
]
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1726520618,
"narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=",
"lastModified": 1728672398,
"narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
"owner": "cachix",
"repo": "cachix",
"rev": "695525f9086542dfb09fde0871dbf4174abbf634",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "cachix",
"type": "github"
}
},
"cachix_3": {
"inputs": {
"devenv": "devenv_3",
"flake-compat": [
"cachix",
"devenv",
"cachix",
"devenv",
"flake-compat"
],
"nixpkgs": [
"cachix",
"devenv",
"cachix",
"devenv",
"nixpkgs"
],
"pre-commit-hooks": [
"cachix",
"devenv",
"cachix",
"devenv",
"pre-commit-hooks"
]
},
"locked": {
"lastModified": 1712055811,
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
"owner": "cachix",
"repo": "cachix",
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
"rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
@@ -120,15 +80,15 @@
"complement": {
"flake": false,
"locked": {
"lastModified": 1724347376,
"narHash": "sha256-y0e/ULDJ92IhNQZsS/06g0s+AYZ82aJfrIO9qEse94c=",
"owner": "matrix-org",
"lastModified": 1734303596,
"narHash": "sha256-HjDRyLR4MBqQ3IjfMM6eE+8ayztXlbz3gXdyDmFla68=",
"owner": "girlbossceo",
"repo": "complement",
"rev": "39733c1b2f8314800776748cc7164f9a34650686",
"rev": "14cc5be797b774f1a2b9f826f38181066d4952b8",
"type": "github"
},
"original": {
"owner": "matrix-org",
"owner": "girlbossceo",
"ref": "main",
"repo": "complement",
"type": "github"
@@ -157,11 +117,11 @@
},
"crane_2": {
"locked": {
"lastModified": 1729741221,
"narHash": "sha256-8AHZZXs1lFkERfBY0C8cZGElSo33D/et7NKEpLRmvzo=",
"lastModified": 1734808813,
"narHash": "sha256-3aH/0Y6ajIlfy7j52FGZ+s4icVX0oHhqBzRdlOeztqg=",
"owner": "ipetkov",
"repo": "crane",
"rev": "f235b656ee5b2bfd6d94c3bfd67896a575d4a6ed",
"rev": "72e2d02dbac80c8c86bf6bf3e785536acf8ee926",
"type": "github"
},
"original": {
@@ -178,100 +138,26 @@
"cachix",
"flake-compat"
],
"nix": "nix_3",
"nixpkgs": [
"cachix",
"nixpkgs"
],
"pre-commit-hooks": [
"git-hooks": [
"cachix",
"git-hooks"
]
},
"locked": {
"lastModified": 1727963652,
"narHash": "sha256-os0EDjn7QVXL6RtHNb9TrZLXVm2Tc5/nZKk3KpbTzd8=",
"owner": "cachix",
"repo": "devenv",
"rev": "cb0052e25dbcc8267b3026160dc73cddaac7d5fd",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"devenv_2": {
"inputs": {
"cachix": "cachix_3",
"flake-compat": [
"cachix",
"devenv",
"cachix",
"flake-compat"
],
"nix": "nix_2",
"nixpkgs": [
"cachix",
"devenv",
"cachix",
"nixpkgs"
],
"pre-commit-hooks": [
"cachix",
"devenv",
"cachix",
"git-hooks"
]
},
"locked": {
"lastModified": 1723156315,
"narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=",
"owner": "cachix",
"repo": "devenv",
"rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"devenv_3": {
"inputs": {
"flake-compat": [
"cachix",
"devenv",
"cachix",
"devenv",
"cachix",
"flake-compat"
],
"nix": "nix",
"nixpkgs": "nixpkgs_2",
"poetry2nix": "poetry2nix",
"pre-commit-hooks": [
"nixpkgs": [
"cachix",
"devenv",
"cachix",
"devenv",
"cachix",
"pre-commit-hooks"
"nixpkgs"
]
},
"locked": {
"lastModified": 1708704632,
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
"lastModified": 1733323168,
"narHash": "sha256-d5DwB4MZvlaQpN6OQ4SLYxb5jA4UH5EtV5t5WOtjLPU=",
"owner": "cachix",
"repo": "devenv",
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
"rev": "efa9010b8b1cfd5dd3c7ed1e172a470c3b84a064",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "python-rewrite",
"repo": "devenv",
"type": "github"
}
@@ -284,11 +170,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1729751566,
"narHash": "sha256-99u/hrgBdi8bxSXZc9ZbNkR5EL1htrkbd3lsbKzS60g=",
"lastModified": 1735799625,
"narHash": "sha256-lFadwWDvVIub11bwfZhsh2WUByf9LOi6yjsSUMmE0xk=",
"owner": "nix-community",
"repo": "fenix",
"rev": "f32a2d484091a6dc98220b1f4a2c2d60b7c97c64",
"rev": "a9d84a1545814910cb4ab0515ed6921e8b07ee95",
"type": "github"
},
"original": {
@@ -317,11 +203,11 @@
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
@@ -333,27 +219,11 @@
"flake-compat_3": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"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",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
@@ -412,44 +282,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"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=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -473,11 +310,11 @@
"nixpkgs-stable": "nixpkgs-stable_2"
},
"locked": {
"lastModified": 1727854478,
"narHash": "sha256-/odH2nUMAwkMgOS2nG2z0exLQNJS4S2LfMW0teqU7co=",
"lastModified": 1733318908,
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "5f58871c9657b5fc0a7f65670fe2ba99c26c1d79",
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
"type": "github"
},
"original": {
@@ -527,11 +364,11 @@
"liburing": {
"flake": false,
"locked": {
"lastModified": 1725659644,
"narHash": "sha256-WjnpmopfvFoUbubIu9bki+Y6P4YXDfvnW4+72hniq3g=",
"lastModified": 1733603756,
"narHash": "sha256-eTKnZDZ1Ex++v+BI0DBcUBmCXAO/tE8hxK9MiyztZkU=",
"owner": "axboe",
"repo": "liburing",
"rev": "0fe5c09195c0918f89582dd6ff098a58a0bdf62a",
"rev": "c3d5d6270cd5ed48d817fc1e8e95f7c8b222f2ff",
"type": "github"
},
"original": {
@@ -542,123 +379,26 @@
}
},
"nix": {
"inputs": {
"flake-compat": "flake-compat_2",
"nixpkgs": [
"cachix",
"devenv",
"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,
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
"type": "github"
},
"original": {
"owner": "numtide",
"ref": "main",
"repo": "nix-filter",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"cachix",
"devenv",
"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",
"cachix",
"devenv",
"flake-compat"
],
"nixpkgs": [
"cachix",
"devenv",
"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"
}
},
"nix_3": {
"inputs": {
"flake-compat": [
"cachix",
"devenv",
"flake-compat"
"devenv"
],
"flake-parts": "flake-parts_2",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_3",
"nixpkgs-23-11": "nixpkgs-23-11",
"nixpkgs-regression": "nixpkgs-regression_3",
"pre-commit-hooks": "pre-commit-hooks"
"nixpkgs-23-11": [
"cachix",
"devenv"
],
"nixpkgs-regression": [
"cachix",
"devenv"
],
"pre-commit-hooks": [
"cachix",
"devenv"
]
},
"locked": {
"lastModified": 1727438425,
@@ -675,6 +415,43 @@
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"type": "github"
},
"original": {
"owner": "numtide",
"ref": "main",
"repo": "nix-filter",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1726042813,
@@ -691,70 +468,6 @@
"type": "github"
}
},
"nixpkgs-23-11": {
"locked": {
"lastModified": 1717159533,
"narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
"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-regression_3": {
"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": 1724316499,
@@ -773,11 +486,11 @@
},
"nixpkgs-stable_2": {
"locked": {
"lastModified": 1720386169,
"narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
"lastModified": 1730741070,
"narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
"rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
"type": "github"
},
"original": {
@@ -789,16 +502,16 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1692808169,
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
"lastModified": 1730531603,
"narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
"rev": "7ffd9ae656aec493492b44d0ddfb28e79a1ea25d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
@@ -821,11 +534,11 @@
},
"nixpkgs_4": {
"locked": {
"lastModified": 1727802920,
"narHash": "sha256-HP89HZOT0ReIbI7IJZJQoJgxvB2Tn28V6XS3MNKnfLs=",
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "27e30d177e57d912d614c88c622dcfdb2e6e6515",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"type": "github"
},
"original": {
@@ -837,11 +550,11 @@
},
"nixpkgs_5": {
"locked": {
"lastModified": 1725534445,
"narHash": "sha256-Yd0FK9SkWy+ZPuNqUgmVPXokxDgMJoGuNpMEtkfcf84=",
"lastModified": 1735685343,
"narHash": "sha256-h1CpBzdJDNtSUb5QMyfFHKHocTTky+4McgQEBQBM+xA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9bb1e7571aadf31ddb4af77fc64b2d59580f9a39",
"rev": "81934660d6e9ea54d2f0cdee821e8533b10c221a",
"type": "github"
},
"original": {
@@ -851,87 +564,19 @@
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"cachix",
"devenv",
"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": [
"cachix",
"devenv",
"nix"
],
"flake-utils": "flake-utils_2",
"gitignore": [
"cachix",
"devenv",
"nix"
],
"nixpkgs": [
"cachix",
"devenv",
"nix",
"nixpkgs"
],
"nixpkgs-stable": [
"cachix",
"devenv",
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712897695,
"narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1731690620,
"narHash": "sha256-Xd4TJYqPERMJLXaGa6r6Ny1Wlw8Uy5Cyf/8q7nS58QM=",
"lastModified": 1734469478,
"narHash": "sha256-IcQ4N8xADYal79K+ONmNq4RLlIwdgUqgrVzgNgiIaG8=",
"owner": "girlbossceo",
"repo": "rocksdb",
"rev": "292446aa2bc41699204d817a1e4b091679a886eb",
"rev": "8b4808e7de2fbb5d119d8d72cdca76d8ab84bc47",
"type": "github"
},
"original": {
"owner": "girlbossceo",
"ref": "v9.7.4",
"ref": "v9.9.3",
"repo": "rocksdb",
"type": "github"
}
@@ -943,8 +588,8 @@
"complement": "complement",
"crane": "crane_2",
"fenix": "fenix",
"flake-compat": "flake-compat_4",
"flake-utils": "flake-utils_3",
"flake-compat": "flake-compat_3",
"flake-utils": "flake-utils",
"liburing": "liburing",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_5",
@@ -954,11 +599,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1729715509,
"narHash": "sha256-jUDN4e1kObbksb4sc+57NEeujBEDRdLCOu9wiE3RZdM=",
"lastModified": 1735742096,
"narHash": "sha256-q3a80h8Jf8wfmPURUgRR46nQCB3I5fhZ+/swulTF5HY=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "40492e15d49b89cf409e2c5536444131fac49429",
"rev": "7e639ee3dda6ed9cecc79d41f6d38235121e483d",
"type": "github"
},
"original": {
@@ -982,21 +627,6 @@
"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",
+104 -16
View File
@@ -2,14 +2,14 @@
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; };
complement = { url = "github:girlbossceo/complement?ref=main"; flake = false; };
crane = { url = "github:ipetkov/crane?ref=master"; };
fenix = { url = "github:nix-community/fenix?ref=main"; inputs.nixpkgs.follows = "nixpkgs"; };
flake-compat = { url = "github:edolstra/flake-compat?ref=master"; flake = false; };
flake-utils.url = "github:numtide/flake-utils?ref=main";
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixpkgs-unstable";
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.7.4"; flake = false; };
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.9.3"; flake = false; };
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
};
@@ -18,7 +18,6 @@
let
pkgsHost = import inputs.nixpkgs{
inherit system;
config.permittedInsecurePackages = [ "olm-3.2.16" ];
};
pkgsHostStatic = pkgsHost.pkgsStatic;
@@ -73,9 +72,16 @@
"-DWITH_TESTS=1"
# we use rust-rocksdb via C interface and dont need C++ RTTI
"-DUSE_RTTI=1"
# this doesn't exist in RocksDB, and USE_SSE is deprecated for
# PORTABLE=$(march)
"-DFORCE_SSE42=1"
# PORTABLE will get set in main/default.nix
"-DPORTABLE=1"
]
old.cmakeFlags
++ [
# no real reason to have snappy, no one uses this
"-DWITH_SNAPPY=0"
# we dont need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=0"
# we dont need trace tools
@@ -118,9 +124,9 @@
# code.
COMPLEMENT_SRC = inputs.complement.outPath;
# Needed for Complement
CGO_CFLAGS = "-I${scope.pkgs.olm}/include";
CGO_LDFLAGS = "-L${scope.pkgs.olm}/lib";
# Needed for Complement: <https://github.com/golang/go/issues/52690>
CGO_CFLAGS = "-Wl,--no-gc-sections";
CGO_LDFLAGS = "-Wl,--no-gc-sections";
};
# Development tools
@@ -202,6 +208,10 @@
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
];
};
all-features-debug = scopeHost.main.override {
@@ -227,6 +237,10 @@
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
];
};
};
@@ -273,6 +287,15 @@
value = scopeCrossStatic.main;
}
# An output for a statically-linked binary with x86_64 haswell
# target optimisations
{
name = "${binaryName}-x86_64-haswell-optimised";
value = scopeCrossStatic.main.override {
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
}
# An output for a statically-linked unstripped debug ("dev") binary
{
name = "${binaryName}-debug";
@@ -303,10 +326,34 @@
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
];
};
}
# An output for a statically-linked binary with `--all-features` and with x86_64 haswell
# target optimisations
{
name = "${binaryName}-all-features-x86_64-haswell-optimised";
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
];
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
}
# An output for a statically-linked unstripped debug ("dev") binary with `--all-features`
{
name = "${binaryName}-all-features-debug";
@@ -338,6 +385,17 @@
value = scopeCrossStatic.oci-image;
}
# An output for an OCI image based on that binary with x86_64 haswell
# target optimisations
{
name = "oci-image-${crossSystem}-x86_64-haswell-optimised";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
};
}
# An output for an OCI image based on that unstripped debug ("dev") binary
{
name = "oci-image-${crossSystem}-debug";
@@ -361,26 +419,52 @@
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
];
};
};
}
# An output for an OCI image based on that binary with `--all-features` and with x86_64 haswell
# target optimisations
{
name = "oci-image-${crossSystem}-all-features-x86_64-haswell-optimised";
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"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
];
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
};
}
# An output for an OCI image based on that unstripped debug ("dev") binary with `--all-features`
{
name = "oci-image-${crossSystem}-all-features-debug";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
];
};
};
}
@@ -422,6 +506,10 @@
"hardened_malloc"
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
];
};
}));
+14 -1
View File
@@ -9,14 +9,27 @@ database_path = "/database"
log = "trace,h2=warn,hyper=warn"
port = [8008, 8448]
trusted_servers = []
only_query_trusted_key_servers = false
query_trusted_key_servers_first = false
query_trusted_key_servers_first_on_join = 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 = ["*"]
url_preview_domain_explicit_denylist = ["*"]
media_compat_file_link = false
media_startup_check = false
rocksdb_direct_io = false
prune_missing_media = false
log_colors = false
admin_room_notices = false
allow_check_for_updates = false
allow_unstable_room_versions = true
rocksdb_log_level = "debug"
rocksdb_max_log_files = 1
rocksdb_recovery_mode = 0
rocksdb_paranoid_file_checks = true
log_guest_registrations = false
allow_legacy_media = true
startup_netburst = false
[global.tls]
certs = "/certificate.crt"
+6 -28
View File
@@ -1,6 +1,5 @@
{ lib
, pkgsBuildHost
, pkgsBuildTarget
, rust
, stdenv
}:
@@ -23,25 +22,13 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
[ "-C" "relocation-model=static" ]
++ lib.optionals
(stdenv.buildPlatform.config != stdenv.hostPlatform.config)
[ "-l" "c" ]
++ lib.optionals
# This check has to match the one [here][0]. We only need to set
# these flags when using a different linker. Don't ask me why,
# though, because I don't know. All I know is it breaks otherwise.
#
# [0]: https://github.com/NixOS/nixpkgs/blob/5cdb38bb16c6d0a38779db14fcc766bc1b2394d6/pkgs/build-support/rust/lib/default.nix#L37-L40
(
# Nixpkgs doesn't check for x86_64 here but we do, because I
# observed a failure building statically for x86_64 without
# including it here. Linkers are weird.
(stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isx86_64)
&& stdenv.hostPlatform.isStatic
&& !stdenv.hostPlatform.isDarwin
&& !stdenv.cc.bintools.isLLVM
)
[
"-l"
"c"
"-l"
"stdc++"
"-L"
"${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib"
]
@@ -58,7 +45,6 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
(
let
inherit (rust.lib) envVars;
shouldUseLLD = platform: platform.isAarch64 && platform.isStatic && !stdenv.hostPlatform.isDarwin;
in
lib.optionalAttrs
(stdenv.targetPlatform.rust.rustcTarget
@@ -66,30 +52,22 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
(
let
inherit (stdenv.targetPlatform.rust) cargoEnvVarTarget;
linkerForTarget = if shouldUseLLD stdenv.targetPlatform
&& !stdenv.cc.bintools.isLLVM # whether stdenv's linker is lld already
then "${pkgsBuildTarget.llvmPackages.bintools}/bin/${stdenv.cc.targetPrefix}ld.lld"
else envVars.ccForTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForTarget;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = linkerForTarget;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForTarget;
}
)
//
(
let
inherit (stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget;
linkerForHost = if shouldUseLLD stdenv.targetPlatform
&& !stdenv.cc.bintools.isLLVM
then "${pkgsBuildHost.llvmPackages.bintools}/bin/${stdenv.cc.targetPrefix}ld.lld"
else envVars.ccForHost;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForHost;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForHost;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = linkerForHost;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForHost;
CARGO_BUILD_TARGET = rustcTarget;
}
)
+18 -6
View File
@@ -6,7 +6,6 @@
, libiconv
, liburing
, pkgsBuildHost
, pkgsBuildTarget
, rocksdb
, removeReferencesTo
, rust
@@ -14,12 +13,17 @@
, stdenv
# Options (keep sorted)
, default_features ? true
, disable_release_max_log_level ? false
, all_features ? false
, default_features ? true
, disable_features ? []
, disable_release_max_log_level ? false
, features ? []
, profile ? "release"
# rocksdb compiled with -march=haswell and target-cpu=haswell rustflag
# haswell is pretty much any x86 cpu made in the last 12 years, and
# supports modern CPU extensions that rocksdb can make use of.
# disable if trying to make a portable x86_64 build for very old hardware
, x86_64_haswell_target_optimised ? false
}:
let
@@ -80,6 +84,13 @@ buildDepsOnlyEnv =
enableLiburing = enableLiburing;
}).overrideAttrs (old: {
enableLiburing = enableLiburing;
cmakeFlags = (if x86_64_haswell_target_optimised then (lib.subtractLists [
# dont make a portable build if x86_64_haswell_target_optimised is enabled
"-DPORTABLE=1"
] old.cmakeFlags
++ [ "-DPORTABLE=haswell" ]) else ([ "-DPORTABLE=1" ])
)
++ old.cmakeFlags;
});
in
{
@@ -96,7 +107,6 @@ buildDepsOnlyEnv =
inherit
lib
pkgsBuildHost
pkgsBuildTarget
rust
stdenv;
});
@@ -107,7 +117,9 @@ 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 x86_64_haswell_target_optimised
" -Ctarget-cpu=haswell";
};
@@ -176,7 +188,7 @@ commonAttrs = {
#
# <https://github.com/input-output-hk/haskell.nix/issues/829>
postInstall = with pkgsBuildHost; ''
find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${libgcc} -t ${llvm} -t ${libllvm} -t ${rustc.unwrapped} -t ${rustc} -t ${libidn2} -t ${libunistring} '{}' +
find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${llvm} -t ${rustc.unwrapped} -t ${rustc} '{}' +
'';
};
in
+9 -3
View File
@@ -2,9 +2,8 @@ array_width = 80
chain_width = 60
comment_width = 80
condense_wildcard_suffixes = true
edition = "2021"
edition = "2024"
fn_call_width = 80
fn_params_layout = "Compressed"
fn_single_line = true
format_code_in_doc_comments = true
format_macro_bodies = true
@@ -14,13 +13,20 @@ group_imports = "StdExternalCrate"
hard_tabs = true
hex_literal_case = "Upper"
imports_granularity = "Crate"
match_arm_blocks = false
match_arm_leading_pipes = "Always"
match_block_trailing_comma = true
max_width = 120
max_width = 98
newline_style = "Unix"
normalize_comments = false
overflow_delimited_expr = true
reorder_impl_items = true
reorder_imports = true
single_line_if_else_max_width = 60
single_line_let_else_max_width = 80
struct_lit_width = 40
tab_spaces = 4
unstable_features = true
use_field_init_shorthand = true
use_small_heuristics = "Off"
use_try_shorthand = true
+6 -7
View File
@@ -1,5 +1,5 @@
[package]
name = "conduit_admin"
name = "conduwuit_admin"
categories.workspace = true
description.workspace = true
edition.workspace = true
@@ -17,7 +17,6 @@ crate-type = [
]
[features]
#dev_release_log_level = []
release_max_log_level = [
"tracing/max_level_trace",
"tracing/release_max_level_info",
@@ -27,11 +26,11 @@ release_max_log_level = [
[dependencies]
clap.workspace = true
conduit-api.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
conduit-macros.workspace = true
conduit-service.workspace = true
conduwuit-api.workspace = true
conduwuit-core.workspace = true
conduwuit-database.workspace = true
conduwuit-macros.workspace = true
conduwuit-service.workspace = true
const-str.workspace = true
futures.workspace = true
log.workspace = true
+19 -15
View File
@@ -1,15 +1,16 @@
use clap::Parser;
use conduit::Result;
use conduwuit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use crate::{
appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command, debug,
debug::DebugCommand, federation, federation::FederationCommand, media, media::MediaCommand, query,
query::QueryCommand, room, room::RoomCommand, server, server::ServerCommand, user, user::UserCommand,
appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command,
debug, debug::DebugCommand, federation, federation::FederationCommand, media,
media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand, server,
server::ServerCommand, user, user::UserCommand,
};
#[derive(Debug, Parser)]
#[command(name = "conduwuit", version = conduit::version())]
#[command(name = "conduwuit", version = conduwuit::version())]
pub(super) enum AdminCommand {
#[command(subcommand)]
/// - Commands for managing appservices
@@ -49,18 +50,21 @@ pub(super) enum AdminCommand {
}
#[tracing::instrument(skip_all, name = "command")]
pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
command: AdminCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
use AdminCommand::*;
Ok(match command {
Appservices(command) => appservice::process(command, context).await?,
Media(command) => media::process(command, context).await?,
Users(command) => user::process(command, context).await?,
Rooms(command) => room::process(command, context).await?,
Federation(command) => federation::process(command, context).await?,
Server(command) => server::process(command, context).await?,
Debug(command) => debug::process(command, context).await?,
Query(command) => query::process(command, context).await?,
Check(command) => check::process(command, context).await?,
| Appservices(command) => appservice::process(command, context).await?,
| Media(command) => media::process(command, context).await?,
| Users(command) => user::process(command, context).await?,
| Rooms(command) => room::process(command, context).await?,
| Federation(command) => federation::process(command, context).await?,
| Server(command) => server::process(command, context).await?,
| Debug(command) => debug::process(command, context).await?,
| Query(command) => query::process(command, context).await?,
| Check(command) => check::process(command, context).await?,
})
}
+23 -13
View File
@@ -4,7 +4,9 @@ use crate::{admin_command, Result};
#[admin_command]
pub(super) async fn register(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -14,55 +16,63 @@ pub(super) async fn register(&self) -> Result<RoomMessageEventContent> {
let appservice_config_body = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config_body);
match parsed_config {
Ok(registration) => match self
| Ok(registration) => match self
.services
.appservice
.register_appservice(&registration, &appservice_config_body)
.await
{
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
| Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
"Appservice registered with ID: {}",
registration.id
))),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Failed to register appservice: {e}"
))),
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Could not parse appservice config as YAML: {e}"
))),
}
}
#[admin_command]
pub(super) async fn unregister(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
pub(super) async fn unregister(
&self,
appservice_identifier: String,
) -> Result<RoomMessageEventContent> {
match self
.services
.appservice
.unregister_appservice(&appservice_identifier)
.await
{
Ok(()) => Ok(RoomMessageEventContent::text_plain("Appservice unregistered.")),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
| Ok(()) => Ok(RoomMessageEventContent::text_plain("Appservice unregistered.")),
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Failed to unregister appservice: {e}"
))),
}
}
#[admin_command]
pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
pub(super) async fn show_appservice_config(
&self,
appservice_identifier: String,
) -> Result<RoomMessageEventContent> {
match self
.services
.appservice
.get_registration(&appservice_identifier)
.await
{
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```",);
| 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```",);
Ok(RoomMessageEventContent::notice_markdown(output))
},
None => Ok(RoomMessageEventContent::text_plain("Appservice does not exist.")),
| None => Ok(RoomMessageEventContent::text_plain("Appservice does not exist.")),
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use crate::admin_command_dispatch;
+5 -4
View File
@@ -1,5 +1,5 @@
use conduit::Result;
use conduit_macros::implement;
use conduwuit::Result;
use conduwuit_macros::implement;
use futures::StreamExt;
use ruma::events::room::message::RoomMessageEventContent;
@@ -19,8 +19,9 @@ pub(super) async fn check_all_users(&self) -> Result<RoomMessageEventContent> {
let ok_count = users.iter().filter(|_user| true).count();
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:?}\n```"
"Database query completed in {query_time:?}:\n\n```\nTotal entries: \
{total:?}\nFailure/Invalid user count: {err_count:?}\nSuccess/Valid user count: \
{ok_count:?}\n```"
);
Ok(RoomMessageEventContent::notice_markdown(message))
+6 -3
View File
@@ -1,7 +1,7 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use crate::Command;
@@ -11,8 +11,11 @@ pub(super) enum CheckCommand {
AllUsers,
}
pub(super) async fn process(command: CheckCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
command: CheckCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
Ok(match command {
CheckCommand::AllUsers => context.check_all_users().await?,
| CheckCommand::AllUsers => context.check_all_users().await?,
})
}
+1 -1
View File
@@ -1,6 +1,6 @@
use std::time::SystemTime;
use conduit_service::Services;
use conduwuit_service::Services;
use ruma::EventId;
pub(crate) struct Command<'a> {
+158 -92
View File
@@ -2,16 +2,18 @@ use std::{
collections::HashMap,
fmt::Write,
iter::once,
sync::Arc,
time::{Instant, SystemTime},
};
use conduit::{debug_error, err, info, trace, utils, utils::string::EMPTY, warn, Error, PduEvent, Result};
use conduwuit::{
debug_error, err, info, trace, utils, utils::string::EMPTY, warn, Error, PduEvent, Result,
};
use futures::{FutureExt, StreamExt};
use ruma::{
api::{client::error::ErrorKind, federation::event::get_room_state},
events::room::message::RoomMessageEventContent,
CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName,
CanonicalJsonObject, EventId, OwnedEventId, OwnedRoomOrAliasId, RoomId, RoomVersionId,
ServerName,
};
use service::rooms::state_compressor::HashSetCompressStateEvent;
use tracing_subscriber::EnvFilter;
@@ -26,7 +28,10 @@ pub(super) async fn echo(&self, message: Vec<String>) -> Result<RoomMessageEvent
}
#[admin_command]
pub(super) async fn get_auth_chain(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
pub(super) async fn get_auth_chain(
&self,
event_id: Box<EventId>,
) -> Result<RoomMessageEventContent> {
let Ok(event) = self.services.rooms.timeline.get_pdu_json(&event_id).await else {
return Ok(RoomMessageEventContent::notice_plain("Event not found."));
};
@@ -68,20 +73,26 @@ pub(super) async fn parse_pdu(&self) -> Result<RoomMessageEventContent> {
let string = self.body[1..self.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) => {
let event_id = EventId::parse(format!("${hash}"));
| Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
| Ok(hash) => {
let event_id = OwnedEventId::parse(format!("${hash}"));
match serde_json::from_value::<PduEvent>(serde_json::to_value(value).expect("value is json")) {
Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
match serde_json::from_value::<PduEvent>(
serde_json::to_value(value).expect("value is json"),
) {
| Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!(
"EventId: {event_id:?}\n{pdu:#?}"
))),
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"EventId: {event_id:?}\nCould not parse event: {e}"
))),
}
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))),
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Could not parse PDU JSON: {e:?}"
))),
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Invalid json in command body: {e}"
))),
}
@@ -103,8 +114,9 @@ pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessage
}
match pdu_json {
Ok(json) => {
let json_text = serde_json::to_string_pretty(&json).expect("canonical json is valid json");
| Ok(json) => {
let json_text =
serde_json::to_string_pretty(&json).expect("canonical json is valid json");
Ok(RoomMessageEventContent::notice_markdown(format!(
"{}\n```json\n{}\n```",
if outlier {
@@ -115,13 +127,15 @@ pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessage
json_text
)))
},
Err(_) => Ok(RoomMessageEventContent::text_plain("PDU not found locally.")),
| Err(_) => Ok(RoomMessageEventContent::text_plain("PDU not found locally.")),
}
}
#[admin_command]
pub(super) async fn get_remote_pdu_list(
&self, server: Box<ServerName>, force: bool,
&self,
server: Box<ServerName>,
force: bool,
) -> Result<RoomMessageEventContent> {
if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain(
@@ -131,8 +145,8 @@ pub(super) async fn get_remote_pdu_list(
if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
the database.",
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for \
fetching local PDUs from the database.",
));
}
@@ -184,7 +198,9 @@ pub(super) async fn get_remote_pdu_list(
#[admin_command]
pub(super) async fn get_remote_pdu(
&self, event_id: Box<EventId>, server: Box<ServerName>,
&self,
event_id: Box<EventId>,
server: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain(
@@ -194,30 +210,32 @@ pub(super) async fn get_remote_pdu(
if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for \
fetching local PDUs.",
));
}
match self
.services
.sending
.send_federation_request(
&server,
ruma::api::federation::event::get_event::v1::Request {
event_id: event_id.clone().into(),
include_unredacted_content: None,
},
)
.send_federation_request(&server, ruma::api::federation::event::get_event::v1::Request {
event_id: event_id.clone().into(),
include_unredacted_content: None,
})
.await
{
Ok(response) => {
let json: CanonicalJsonObject = serde_json::from_str(response.pdu.get()).map_err(|e| {
warn!(
"Requested event ID {event_id} from server but failed to convert from RawValue to \
CanonicalJsonObject (malformed event/response?): {e}"
);
Error::BadRequest(ErrorKind::Unknown, "Received response from server but failed to parse PDU")
})?;
| Ok(response) => {
let json: CanonicalJsonObject =
serde_json::from_str(response.pdu.get()).map_err(|e| {
warn!(
"Requested event ID {event_id} from server but failed to convert from \
RawValue to CanonicalJsonObject (malformed event/response?): {e}"
);
Error::BadRequest(
ErrorKind::Unknown,
"Received response from server but failed to parse PDU",
)
})?;
trace!("Attempting to parse PDU: {:?}", &response.pdu);
let _parsed_pdu = {
@@ -229,8 +247,8 @@ pub(super) async fn get_remote_pdu(
.await;
let (event_id, value, room_id) = match parsed_result {
Ok(t) => t,
Err(e) => {
| Ok(t) => t,
| Err(e) => {
warn!("Failed to parse PDU: {e}");
info!("Full PDU: {:?}", &response.pdu);
return Ok(RoomMessageEventContent::text_plain(format!(
@@ -250,21 +268,27 @@ pub(super) async fn get_remote_pdu(
.boxed()
.await?;
let json_text = serde_json::to_string_pretty(&json).expect("canonical json is valid json");
let json_text =
serde_json::to_string_pretty(&json).expect("canonical json is valid json");
Ok(RoomMessageEventContent::notice_markdown(format!(
"{}\n```json\n{}\n```",
"Got PDU from specified server and handled as backfilled PDU successfully. Event body:", json_text
"Got PDU from specified server and handled as backfilled PDU successfully. \
Event body:",
json_text
)))
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Remote server did not have PDU or failed sending request to remote server: {e}"
))),
}
}
#[admin_command]
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
pub(super) async fn get_room_state(
&self,
room: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> {
let room_id = self.services.rooms.alias.resolve(&room).await?;
let room_state: Vec<_> = self
.services
@@ -285,7 +309,8 @@ pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<Ro
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",
"Failed to convert room state events to pretty JSON, possible invalid room state \
events in our database",
)
})?;
@@ -305,10 +330,13 @@ pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEv
match self
.services
.sending
.send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
.send_federation_request(
&server,
ruma::api::federation::discovery::get_server_version::v1::Request {},
)
.await
{
Ok(response) => {
| Ok(response) => {
let ping_time = timer.elapsed();
let json_text_res = serde_json::to_string_pretty(&response.server);
@@ -323,8 +351,11 @@ pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEv
"Got non-JSON response which took {ping_time:?} time:\n{response:?}"
)))
},
Err(e) => {
warn!("Failed sending federation request to specified server from ping debug command: {e}");
| Err(e) => {
warn!(
"Failed sending federation request to specified server from ping debug command: \
{e}"
);
Ok(RoomMessageEventContent::text_plain(format!(
"Failed sending federation request to specified server:\n\n{e}",
)))
@@ -347,13 +378,17 @@ pub(super) async fn force_device_list_updates(&self) -> Result<RoomMessageEventC
}
#[admin_command]
pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result<RoomMessageEventContent> {
pub(super) async fn change_log_level(
&self,
filter: Option<String>,
reset: bool,
) -> Result<RoomMessageEventContent> {
let handles = &["console"];
if reset {
let old_filter_layer = match EnvFilter::try_new(&self.services.globals.config.log) {
Ok(s) => s,
Err(e) => {
| Ok(s) => s,
| Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Log level from config appears to be invalid now: {e}"
)));
@@ -367,13 +402,13 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
.reload
.reload(&old_filter_layer, Some(handles))
{
Ok(()) => {
| Ok(()) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Successfully changed log level back to config value {}",
self.services.globals.config.log
)));
},
Err(e) => {
| Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to modify and reload the global tracing log level: {e}"
)));
@@ -383,8 +418,8 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
if let Some(filter) = filter {
let new_filter_layer = match EnvFilter::try_new(filter) {
Ok(s) => s,
Err(e) => {
| Ok(s) => s,
| Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Invalid log level filter specified: {e}"
)));
@@ -398,10 +433,10 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
.reload
.reload(&new_filter_layer, Some(handles))
{
Ok(()) => {
| Ok(()) => {
return Ok(RoomMessageEventContent::text_plain("Successfully changed log level"));
},
Err(e) => {
| Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to modify and reload the global tracing log level: {e}"
)));
@@ -414,7 +449,9 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
#[admin_command]
pub(super) async fn sign_json(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -423,21 +460,24 @@ pub(super) async fn sign_json(&self) -> Result<RoomMessageEventContent> {
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) {
Ok(mut value) => {
| Ok(mut value) => {
self.services
.server_keys
.sign_json(&mut value)
.expect("our request json is what ruma expects");
let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
let json_text =
serde_json::to_string_pretty(&value).expect("canonical json is valid json");
Ok(RoomMessageEventContent::text_plain(json_text))
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
}
}
#[admin_command]
pub(super) async fn verify_json(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -446,13 +486,13 @@ pub(super) async fn verify_json(&self) -> Result<RoomMessageEventContent> {
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str::<CanonicalJsonObject>(&string) {
Ok(value) => match self.services.server_keys.verify_json(&value, None).await {
Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
| Ok(value) => match self.services.server_keys.verify_json(&value, None).await {
| Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Signature verification failed: {e}"
))),
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
}
}
@@ -462,9 +502,10 @@ pub(super) async fn verify_pdu(&self, event_id: Box<EventId>) -> Result<RoomMess
event.remove("event_id");
let msg = match self.services.server_keys.verify_event(&event, None).await {
Ok(ruma::signatures::Verified::Signatures) => "signatures OK, but content hash failed (redaction).",
Ok(ruma::signatures::Verified::All) => "signatures and hashes OK.",
Err(e) => return Err(e),
| Ok(ruma::signatures::Verified::Signatures) =>
"signatures OK, but content hash failed (redaction).",
| Ok(ruma::signatures::Verified::All) => "signatures and hashes OK.",
| Err(e) => return Err(e),
};
Ok(RoomMessageEventContent::notice_plain(msg))
@@ -472,7 +513,10 @@ pub(super) async fn verify_pdu(&self, event_id: Box<EventId>) -> Result<RoomMess
#[admin_command]
#[tracing::instrument(skip(self))]
pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
pub(super) async fn first_pdu_in_room(
&self,
room_id: Box<RoomId>,
) -> Result<RoomMessageEventContent> {
if !self
.services
.rooms
@@ -498,7 +542,10 @@ pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<Roo
#[admin_command]
#[tracing::instrument(skip(self))]
pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
pub(super) async fn latest_pdu_in_room(
&self,
room_id: Box<RoomId>,
) -> Result<RoomMessageEventContent> {
if !self
.services
.rooms
@@ -525,7 +572,9 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<Ro
#[admin_command]
#[tracing::instrument(skip(self))]
pub(super) async fn force_set_room_state_from_server(
&self, room_id: Box<RoomId>, server_name: Box<ServerName>,
&self,
room_id: Box<RoomId>,
server_name: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
if !self
.services
@@ -549,18 +598,15 @@ pub(super) async fn force_set_room_state_from_server(
let room_version = self.services.rooms.state.get_room_version(&room_id).await?;
let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
let mut state: HashMap<u64, OwnedEventId> = HashMap::new();
let remote_state_response = self
.services
.sending
.send_federation_request(
&server_name,
get_room_state::v1::Request {
room_id: room_id.clone().into(),
event_id: first_pdu.event_id.clone().into(),
},
)
.send_federation_request(&server_name, get_room_state::v1::Request {
room_id: room_id.clone().into(),
event_id: first_pdu.event_id.clone(),
})
.await?;
for pdu in remote_state_response.pdus.clone() {
@@ -571,8 +617,8 @@ pub(super) async fn force_set_room_state_from_server(
.parse_incoming_pdu(&pdu)
.await
{
Ok(t) => t,
Err(e) => {
| Ok(t) => t,
| Err(e) => {
warn!("Could not parse PDU, ignoring: {e}");
continue;
},
@@ -631,7 +677,7 @@ pub(super) async fn force_set_room_state_from_server(
.services
.rooms
.event_handler
.resolve_state(room_id.clone().as_ref(), &room_version, state)
.resolve_state(&room_id, &room_version, state)
.await?;
info!("Forcing new room state");
@@ -654,8 +700,8 @@ pub(super) async fn force_set_room_state_from_server(
.await?;
info!(
"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
m.room.member state"
"Updating joined counts for room just in case (e.g. we may have found a difference in \
the room's m.room.member state"
);
self.services
.rooms
@@ -672,9 +718,13 @@ pub(super) async fn force_set_room_state_from_server(
#[admin_command]
pub(super) async fn get_signing_keys(
&self, server_name: Option<Box<ServerName>>, notary: Option<Box<ServerName>>, query: bool,
&self,
server_name: Option<Box<ServerName>>,
notary: Option<Box<ServerName>>,
query: bool,
) -> Result<RoomMessageEventContent> {
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
let server_name =
server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
if let Some(notary) = notary {
let signing_keys = self
@@ -706,8 +756,12 @@ pub(super) async fn get_signing_keys(
}
#[admin_command]
pub(super) async fn get_verify_keys(&self, server_name: Option<Box<ServerName>>) -> Result<RoomMessageEventContent> {
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
pub(super) async fn get_verify_keys(
&self,
server_name: Option<Box<ServerName>>,
) -> Result<RoomMessageEventContent> {
let server_name =
server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
let keys = self
.services
@@ -727,7 +781,9 @@ pub(super) async fn get_verify_keys(&self, server_name: Option<Box<ServerName>>)
#[admin_command]
pub(super) async fn resolve_true_destination(
&self, server_name: Box<ServerName>, no_cache: bool,
&self,
server_name: Box<ServerName>,
no_cache: bool,
) -> Result<RoomMessageEventContent> {
if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain(
@@ -737,7 +793,8 @@ pub(super) async fn resolve_true_destination(
if server_name == self.services.globals.config.server_name {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for \
fetching local PDUs.",
));
}
@@ -754,7 +811,7 @@ pub(super) async fn resolve_true_destination(
#[admin_command]
pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
let html_body = conduit::alloc::memory_stats();
let html_body = conduwuit::alloc::memory_stats();
if html_body.is_none() {
return Ok(RoomMessageEventContent::text_plain(
@@ -773,7 +830,14 @@ pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
let out = self.services.server.metrics.runtime_metrics().map_or_else(
|| "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"),
|metrics| {
format!(
"```rs\nnum_workers: {}\nnum_alive_tasks: {}\nglobal_queue_depth: {}\n```",
metrics.num_workers(),
metrics.num_alive_tasks(),
metrics.global_queue_depth()
)
},
);
Ok(RoomMessageEventContent::text_markdown(out))
@@ -839,7 +903,9 @@ pub(super) async fn list_dependencies(&self, names: bool) -> Result<RoomMessageE
#[admin_command]
pub(super) async fn database_stats(
&self, property: Option<String>, map: Option<String>,
&self,
property: Option<String>,
map: Option<String>,
) -> Result<RoomMessageEventContent> {
let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned());
let map_name = map.as_ref().map_or(EMPTY, String::as_str);
+1 -1
View File
@@ -2,7 +2,7 @@ mod commands;
pub(crate) mod tester;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName};
use self::tester::TesterCommand;
+1 -1
View File
@@ -1,4 +1,4 @@
use conduit::Err;
use conduwuit::Err;
use ruma::events::room::message::RoomMessageEventContent;
use crate::{admin_command, admin_command_dispatch, Result};
+24 -11
View File
@@ -1,8 +1,10 @@
use std::fmt::Write;
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
use ruma::{
events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId,
};
use crate::{admin_command, get_room_info};
@@ -38,7 +40,10 @@ pub(super) async fn incoming_federation(&self) -> Result<RoomMessageEventContent
}
#[admin_command]
pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>) -> Result<RoomMessageEventContent> {
pub(super) async fn fetch_support_well_known(
&self,
server_name: Box<ServerName>,
) -> Result<RoomMessageEventContent> {
let response = self
.services
.client
@@ -60,16 +65,20 @@ pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>
}
let json: serde_json::Value = match serde_json::from_str(&text) {
Ok(json) => json,
Err(_) => {
return Ok(RoomMessageEventContent::text_plain("Response text/body is not valid JSON."));
| Ok(json) => json,
| Err(_) => {
return Ok(RoomMessageEventContent::text_plain(
"Response text/body is not valid JSON.",
));
},
};
let pretty_json: String = match serde_json::to_string_pretty(&json) {
Ok(json) => json,
Err(_) => {
return Ok(RoomMessageEventContent::text_plain("Response text/body is not valid JSON."));
| Ok(json) => json,
| Err(_) => {
return Ok(RoomMessageEventContent::text_plain(
"Response text/body is not valid JSON.",
));
},
};
@@ -79,10 +88,14 @@ pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>
}
#[admin_command]
pub(super) async fn remote_user_in_rooms(&self, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
pub(super) async fn remote_user_in_rooms(
&self,
user_id: Box<UserId>,
) -> Result<RoomMessageEventContent> {
if user_id.server_name() == self.services.globals.config.server_name {
return Ok(RoomMessageEventContent::text_plain(
"User belongs to our server, please use `list-joined-rooms` user admin command instead.",
"User belongs to our server, please use `list-joined-rooms` user admin command \
instead.",
));
}
+1 -1
View File
@@ -1,7 +1,7 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::{RoomId, ServerName, UserId};
use crate::admin_command_dispatch;
+86 -37
View File
@@ -1,16 +1,21 @@
use std::time::Duration;
use conduit::{debug, debug_info, debug_warn, error, info, trace, utils::time::parse_timepoint_ago, Result};
use conduit_service::media::Dim;
use conduwuit::{
debug, debug_info, debug_warn, error, info, trace, utils::time::parse_timepoint_ago, Result,
};
use conduwuit_service::media::Dim;
use ruma::{
events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, OwnedMxcUri, OwnedServerName, ServerName,
events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, OwnedMxcUri,
OwnedServerName, ServerName,
};
use crate::{admin_command, utils::parse_local_user_id};
#[admin_command]
pub(super) async fn delete(
&self, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
&self,
mxc: Option<Box<MxcUri>>,
event_id: Option<Box<EventId>>,
) -> Result<RoomMessageEventContent> {
if event_id.is_some() && mxc.is_some() {
return Ok(RoomMessageEventContent::text_plain(
@@ -52,7 +57,10 @@ pub(super) async fn delete(
let final_url = url.to_string().replace('"', "");
mxc_urls.push(final_url);
} else {
info!("Found a URL in the event ID {event_id} but did not start with mxc://, ignoring");
info!(
"Found a URL in the event ID {event_id} but did not start with \
mxc://, ignoring"
);
}
}
@@ -67,17 +75,24 @@ pub(super) async fn delete(
debug!("Found a thumbnail_url in info key: {thumbnail_url}");
if thumbnail_url.to_string().starts_with("\"mxc://") {
debug!("Pushing thumbnail URL {thumbnail_url} to list of MXCs to delete");
let final_thumbnail_url = thumbnail_url.to_string().replace('"', "");
debug!(
"Pushing thumbnail URL {thumbnail_url} to list of MXCs \
to delete"
);
let final_thumbnail_url =
thumbnail_url.to_string().replace('"', "");
mxc_urls.push(final_thumbnail_url);
} else {
info!(
"Found a thumbnail URL in the event ID {event_id} but did not start with \
mxc://, ignoring"
"Found a thumbnail URL in the event ID {event_id} but \
did not start with mxc://, ignoring"
);
}
} else {
info!("No \"thumbnail_url\" key in \"info\" key, assuming no thumbnails.");
info!(
"No \"thumbnail_url\" key in \"info\" key, assuming no \
thumbnails."
);
}
}
}
@@ -98,8 +113,8 @@ pub(super) async fn delete(
mxc_urls.push(final_url);
} else {
info!(
"Found a URL in the event ID {event_id} but did not start with mxc://, \
ignoring"
"Found a URL in the event ID {event_id} but did not \
start with mxc://, ignoring"
);
}
} else {
@@ -109,13 +124,14 @@ pub(super) async fn delete(
}
} else {
return Ok(RoomMessageEventContent::text_plain(
"Event ID does not have a \"content\" key or failed parsing the event ID JSON.",
"Event ID does not have a \"content\" key or failed parsing the event \
ID JSON.",
));
}
} else {
return Ok(RoomMessageEventContent::text_plain(
"Event ID does not have a \"content\" key, this is not a message or an event type that contains \
media.",
"Event ID does not have a \"content\" key, this is not a message or an \
event type that contains media.",
));
}
} else {
@@ -126,7 +142,9 @@ pub(super) async fn delete(
if mxc_urls.is_empty() {
info!("Parsed event ID {event_id} but did not contain any MXC URLs.");
return Ok(RoomMessageEventContent::text_plain("Parsed event ID but found no MXC URLs."));
return Ok(RoomMessageEventContent::text_plain(
"Parsed event ID but found no MXC URLs.",
));
}
let mut mxc_deletion_count: usize = 0;
@@ -138,11 +156,11 @@ pub(super) async fn delete(
.delete(&mxc_url.as_str().try_into()?)
.await
{
Ok(()) => {
| Ok(()) => {
debug_info!("Successfully deleted {mxc_url} from filesystem and database");
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
},
Err(e) => {
| Err(e) => {
debug_warn!("Failed to delete {mxc_url}, ignoring error and skipping: {e}");
continue;
},
@@ -150,19 +168,22 @@ pub(super) async fn delete(
}
return Ok(RoomMessageEventContent::text_plain(format!(
"Deleted {mxc_deletion_count} total MXCs from our database and the filesystem from event ID {event_id}."
"Deleted {mxc_deletion_count} total MXCs from our database and the filesystem from \
event ID {event_id}."
)));
}
Ok(RoomMessageEventContent::text_plain(
"Please specify either an MXC using --mxc or an event ID using --event-id of the message containing an image. \
See --help for details.",
"Please specify either an MXC using --mxc or an event ID using --event-id of the \
message containing an image. See --help for details.",
))
}
#[admin_command]
pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -192,11 +213,11 @@ pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
for mxc in &mxc_list {
trace!(%failed_parsed_mxcs, %mxc_deletion_count, "Deleting MXC {mxc} in bulk");
match self.services.media.delete(mxc).await {
Ok(()) => {
| Ok(()) => {
debug_info!("Successfully deleted {mxc} from filesystem and database");
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
},
Err(e) => {
| Err(e) => {
debug_warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
continue;
},
@@ -204,14 +225,18 @@ pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
}
Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem. \
{failed_parsed_mxcs} MXCs failed to be parsed from the database.",
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database \
and the filesystem. {failed_parsed_mxcs} MXCs failed to be parsed from the database.",
)))
}
#[admin_command]
pub(super) async fn delete_past_remote_media(
&self, duration: String, before: bool, after: bool, yes_i_want_to_delete_local_media: bool,
&self,
duration: String,
before: bool,
after: bool,
yes_i_want_to_delete_local_media: bool,
) -> Result<RoomMessageEventContent> {
if before && after {
return Ok(RoomMessageEventContent::text_plain(
@@ -224,7 +249,12 @@ pub(super) async fn delete_past_remote_media(
let deleted_count = self
.services
.media
.delete_all_remote_media_at_after_time(duration, before, after, yes_i_want_to_delete_local_media)
.delete_all_remote_media_at_after_time(
duration,
before,
after,
yes_i_want_to_delete_local_media,
)
.await?;
Ok(RoomMessageEventContent::text_plain(format!(
@@ -233,7 +263,10 @@ pub(super) async fn delete_past_remote_media(
}
#[admin_command]
pub(super) async fn delete_all_from_user(&self, username: String) -> Result<RoomMessageEventContent> {
pub(super) async fn delete_all_from_user(
&self,
username: String,
) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &username)?;
let deleted_count = self.services.media.delete_from_user(&user_id).await?;
@@ -245,7 +278,9 @@ pub(super) async fn delete_all_from_user(&self, username: String) -> Result<Room
#[admin_command]
pub(super) async fn delete_all_from_server(
&self, server_name: Box<ServerName>, yes_i_want_to_delete_local_media: bool,
&self,
server_name: Box<ServerName>,
yes_i_want_to_delete_local_media: bool,
) -> Result<RoomMessageEventContent> {
if server_name == self.services.globals.server_name() && !yes_i_want_to_delete_local_media {
return Ok(RoomMessageEventContent::text_plain(
@@ -260,20 +295,26 @@ pub(super) async fn delete_all_from_server(
.await
.inspect_err(|e| error!("Failed to get MXC URIs from our database: {e}"))
else {
return Ok(RoomMessageEventContent::text_plain("Failed to get MXC URIs from our database"));
return Ok(RoomMessageEventContent::text_plain(
"Failed to get MXC URIs from our database",
));
};
let mut deleted_count: usize = 0;
for mxc in all_mxcs {
let Ok(mxc_server_name) = mxc.server_name().inspect_err(|e| {
debug_warn!("Failed to parse MXC {mxc} server name from database, ignoring error and skipping: {e}");
debug_warn!(
"Failed to parse MXC {mxc} server name from database, ignoring error and \
skipping: {e}"
);
}) else {
continue;
};
if mxc_server_name != server_name
|| (self.services.globals.server_is_ours(mxc_server_name) && !yes_i_want_to_delete_local_media)
|| (self.services.globals.server_is_ours(mxc_server_name)
&& !yes_i_want_to_delete_local_media)
{
trace!("skipping MXC URI {mxc}");
continue;
@@ -282,10 +323,10 @@ pub(super) async fn delete_all_from_server(
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
match self.services.media.delete(&mxc).await {
Ok(()) => {
| Ok(()) => {
deleted_count = deleted_count.saturating_add(1);
},
Err(e) => {
| Err(e) => {
debug_warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
continue;
},
@@ -307,7 +348,10 @@ pub(super) async fn get_file_info(&self, mxc: OwnedMxcUri) -> Result<RoomMessage
#[admin_command]
pub(super) async fn get_remote_file(
&self, mxc: OwnedMxcUri, server: Option<OwnedServerName>, timeout: u32,
&self,
mxc: OwnedMxcUri,
server: Option<OwnedServerName>,
timeout: u32,
) -> Result<RoomMessageEventContent> {
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
let timeout = Duration::from_millis(timeout.into());
@@ -327,7 +371,12 @@ pub(super) async fn get_remote_file(
#[admin_command]
pub(super) async fn get_remote_thumbnail(
&self, mxc: OwnedMxcUri, server: Option<OwnedServerName>, timeout: u32, width: u32, height: u32,
&self,
mxc: OwnedMxcUri,
server: Option<OwnedServerName>,
timeout: u32,
width: u32,
height: u32,
) -> Result<RoomMessageEventContent> {
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
let timeout = Duration::from_millis(timeout.into());
+9 -9
View File
@@ -1,7 +1,7 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::{EventId, MxcUri, OwnedMxcUri, OwnedServerName, ServerName};
use crate::admin_command_dispatch;
@@ -26,23 +26,23 @@ pub(super) enum MediaCommand {
/// filesystem. This will always ignore errors.
DeleteList,
/// - Deletes all remote media in the last/after "X" time using filesystem
/// metadata first created at date, or fallback to last modified date.
/// This will always ignore errors by default.
///
/// Synapse
/// - Deletes all remote (and optionally local) media created before or
/// after \[duration] time using filesystem metadata first created at
/// date, or fallback to last modified date. This will always ignore
/// errors by default.
DeletePastRemoteMedia {
/// - The duration (at or after/before), e.g. "5m" to delete all media
/// in the past or up to 5 minutes
/// - The relative time (e.g. 30s, 5m, 7d) within which to search
duration: String,
/// - Only delete media created more recently than \[duration] ago
#[arg(long, short)]
before: bool,
/// - Only delete media created after \[duration] ago
#[arg(long, short)]
after: bool,
/// Long argument to delete local media
/// - Long argument to additionally delete local media
#[arg(long)]
yes_i_want_to_delete_local_media: bool,
},
+8 -8
View File
@@ -18,12 +18,12 @@ pub(crate) mod room;
pub(crate) mod server;
pub(crate) mod user;
extern crate conduit_api as api;
extern crate conduit_core as conduit;
extern crate conduit_service as service;
extern crate conduwuit_api as api;
extern crate conduwuit_core as conduwuit;
extern crate conduwuit_service as service;
pub(crate) use conduit::Result;
pub(crate) use conduit_macros::{admin_command, admin_command_dispatch};
pub(crate) use conduwuit::Result;
pub(crate) use conduwuit_macros::{admin_command, admin_command_dispatch};
pub(crate) use crate::{
command::Command,
@@ -32,9 +32,9 @@ pub(crate) use crate::{
pub(crate) const PAGE_SIZE: usize = 100;
conduit::mod_ctor! {}
conduit::mod_dtor! {}
conduit::rustc_flags_capture! {}
conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {}
conduwuit::rustc_flags_capture! {}
/// Install the admin command processor
pub async fn init(admin_service: &service::admin::Service) {
+26 -17
View File
@@ -6,7 +6,7 @@ use std::{
};
use clap::{CommandFactory, Parser};
use conduit::{
use conduwuit::{
debug, error,
log::{
capture,
@@ -53,8 +53,8 @@ async fn handle_command(services: Arc<Services>, command: CommandInput) -> Proce
async fn process_command(services: Arc<Services>, input: &CommandInput) -> ProcessorResult {
let (command, args, body) = match parse(&services, input) {
Err(error) => return Err(error),
Ok(parsed) => parsed,
| Err(error) => return Err(error),
| Ok(parsed) => parsed,
};
let context = Command {
@@ -68,7 +68,8 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
}
fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult {
let link = "Please submit a [bug report](https://github.com/girlbossceo/conduwuit/issues/new). 🥺";
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:?}");
@@ -76,7 +77,11 @@ fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult {
}
// Parse and process a message from the admin room
async fn process(context: &Command<'_>, command: AdminCommand, args: &[String]) -> ProcessorResult {
async fn process(
context: &Command<'_>,
command: AdminCommand,
args: &[String],
) -> ProcessorResult {
let (capture, logs) = capture_create(context);
let capture_scope = capture.start();
@@ -100,11 +105,12 @@ async fn process(context: &Command<'_>, command: AdminCommand, args: &[String])
drop(logs);
match result {
Ok(content) => {
write!(&mut output, "{0}", content.body()).expect("failed to format command result to output buffer");
| Ok(content) => {
write!(&mut output, "{0}", content.body())
.expect("failed to format command result to output buffer");
Ok(Some(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id)))
},
Err(error) => {
| Err(error) => {
write!(&mut output, "Command failed with error:\n```\n{error:#?}\n```")
.expect("failed to format command result to output");
Err(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id))
@@ -128,8 +134,9 @@ fn capture_create(context: &Command<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
.and_then(LevelFilter::into_level)
.unwrap_or(Level::DEBUG);
let filter =
move |data: capture::Data<'_>| data.level() <= log_level && data.our_modules() && data.scope.contains(&"admin");
let filter = move |data: capture::Data<'_>| {
data.level() <= log_level && data.our_modules() && data.scope.contains(&"admin")
};
let logs = Arc::new(Mutex::new(
collect_stream(|s| markdown_table_head(s)).expect("markdown table header"),
@@ -146,14 +153,15 @@ fn capture_create(context: &Command<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
// Parse chat messages from the admin room into an AdminCommand object
fn parse<'a>(
services: &Arc<Services>, input: &'a CommandInput,
services: &Arc<Services>,
input: &'a CommandInput,
) -> Result<(AdminCommand, Vec<String>, Vec<&'a str>), CommandOutput> {
let lines = input.command.lines().filter(|line| !line.trim().is_empty());
let command_line = lines.clone().next().expect("command missing first line");
let body = lines.skip(1).collect();
match parse_command(command_line) {
Ok((command, args)) => Ok((command, args, body)),
Err(error) => {
| Ok((command, args)) => Ok((command, args, body)),
| Err(error) => {
let message = error
.to_string()
.replace("server.name", services.globals.server_name().as_str());
@@ -255,11 +263,12 @@ fn parse_line(command_line: &str) -> Vec<String> {
argv
}
fn reply(mut content: RoomMessageEventContent, reply_id: Option<&EventId>) -> RoomMessageEventContent {
fn reply(
mut content: RoomMessageEventContent,
reply_id: Option<&EventId>,
) -> RoomMessageEventContent {
content.relates_to = reply_id.map(|event_id| Reply {
in_reply_to: InReplyTo {
event_id: event_id.to_owned(),
},
in_reply_to: InReplyTo { event_id: event_id.to_owned() },
});
content
+7 -12
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, UserId};
@@ -30,15 +30,14 @@ pub(crate) enum AccountDataCommand {
}
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(super) async fn process(subcommand: AccountDataCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
subcommand: AccountDataCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
AccountDataCommand::ChangesSince {
user_id,
since,
room_id,
} => {
| AccountDataCommand::ChangesSince { user_id, since, room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.account_data
@@ -51,11 +50,7 @@ pub(super) async fn process(subcommand: AccountDataCommand, context: &Command<'_
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
AccountDataCommand::Get {
user_id,
kind,
room_id,
} => {
| AccountDataCommand::Get { user_id, kind, room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.account_data
+7 -6
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use crate::Command;
@@ -18,13 +18,14 @@ pub(crate) enum AppserviceCommand {
}
/// All the getters and iterators from src/database/key_value/appservice.rs
pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
subcommand: AppserviceCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
AppserviceCommand::GetRegistration {
appservice_id,
} => {
| AppserviceCommand::GetRegistration { appservice_id } => {
let timer = tokio::time::Instant::now();
let results = services.appservice.get_registration(&appservice_id).await;
@@ -34,7 +35,7 @@ pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
AppserviceCommand::All => {
| AppserviceCommand::All => {
let timer = tokio::time::Instant::now();
let results = services.appservice.all().await;
let query_time = timer.elapsed();
+9 -8
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::{events::room::message::RoomMessageEventContent, ServerName};
use crate::Command;
@@ -21,11 +21,14 @@ pub(crate) enum GlobalsCommand {
}
/// All the getters and iterators from src/database/key_value/globals.rs
pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
subcommand: GlobalsCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
GlobalsCommand::DatabaseVersion => {
| GlobalsCommand::DatabaseVersion => {
let timer = tokio::time::Instant::now();
let results = services.globals.db.database_version().await;
let query_time = timer.elapsed();
@@ -34,7 +37,7 @@ pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
GlobalsCommand::CurrentCount => {
| GlobalsCommand::CurrentCount => {
let timer = tokio::time::Instant::now();
let results = services.globals.db.current_count();
let query_time = timer.elapsed();
@@ -43,7 +46,7 @@ pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
GlobalsCommand::LastCheckForUpdatesId => {
| GlobalsCommand::LastCheckForUpdatesId => {
let timer = tokio::time::Instant::now();
let results = services.updates.last_check_for_updates_id().await;
let query_time = timer.elapsed();
@@ -52,9 +55,7 @@ pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
GlobalsCommand::SigningKeysFor {
origin,
} => {
| GlobalsCommand::SigningKeysFor { origin } => {
let timer = tokio::time::Instant::now();
let results = services.server_keys.verify_keys_for(&origin).await;
let query_time = timer.elapsed();
+4 -3
View File
@@ -10,12 +10,13 @@ mod sending;
mod users;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use self::{
account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
presence::PresenceCommand, pusher::PusherCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand,
presence::PresenceCommand, pusher::PusherCommand, resolver::ResolverCommand,
room_alias::RoomAliasCommand, room_state_cache::RoomStateCacheCommand,
sending::SendingCommand, users::UsersCommand,
};
use crate::admin_command_dispatch;
+7 -8
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, UserId};
@@ -23,13 +23,14 @@ pub(crate) enum PresenceCommand {
}
/// All the getters and iterators in key_value/presence.rs
pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
subcommand: PresenceCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
PresenceCommand::GetPresence {
user_id,
} => {
| PresenceCommand::GetPresence { user_id } => {
let timer = tokio::time::Instant::now();
let results = services.presence.db.get_presence(&user_id).await;
let query_time = timer.elapsed();
@@ -38,9 +39,7 @@ pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>)
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
PresenceCommand::PresenceSince {
since,
} => {
| PresenceCommand::PresenceSince { since } => {
let timer = tokio::time::Instant::now();
let results: Vec<(_, _, _)> = services
.presence
+6 -5
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::{events::room::message::RoomMessageEventContent, UserId};
use crate::Command;
@@ -13,13 +13,14 @@ pub(crate) enum PusherCommand {
},
}
pub(super) async fn process(subcommand: PusherCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
subcommand: PusherCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
PusherCommand::GetPushers {
user_id,
} => {
| PusherCommand::GetPushers { user_id } => {
let timer = tokio::time::Instant::now();
let results = services.pusher.get_pushers(&user_id).await;
let query_time = timer.elapsed();
+7 -18
View File
@@ -1,7 +1,7 @@
use std::fmt::Write;
use clap::Subcommand;
use conduit::{utils::time, Result};
use conduwuit::{utils::time, Result};
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
use crate::{admin_command, admin_command_dispatch};
@@ -22,20 +22,16 @@ pub(crate) enum ResolverCommand {
}
#[admin_command]
async fn destinations_cache(&self, server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> {
async fn destinations_cache(
&self,
server_name: Option<OwnedServerName>,
) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedDest;
let mut out = String::new();
writeln!(out, "| Server Name | Destination | Hostname | Expires |")?;
writeln!(out, "| ----------- | ----------- | -------- | ------- |")?;
let row = |(
name,
&CachedDest {
ref dest,
ref host,
expire,
},
)| {
let row = |(name, &CachedDest { ref dest, ref host, expire })| {
let expire = time::format(expire, "%+");
writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line");
};
@@ -64,14 +60,7 @@ async fn overrides_cache(&self, server_name: Option<String>) -> Result<RoomMessa
let mut out = String::new();
writeln!(out, "| Server Name | IP | Port | Expires |")?;
writeln!(out, "| ----------- | --- | ----:| ------- |")?;
let row = |(
name,
&CachedOverride {
ref ips,
port,
expire,
},
)| {
let row = |(name, &CachedOverride { ref ips, port, expire })| {
let expire = time::format(expire, "%+");
writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line");
};
+8 -9
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
@@ -24,13 +24,14 @@ pub(crate) enum RoomAliasCommand {
}
/// All the getters and iterators in src/database/key_value/rooms/alias.rs
pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
subcommand: RoomAliasCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
RoomAliasCommand::ResolveLocalAlias {
alias,
} => {
| RoomAliasCommand::ResolveLocalAlias { alias } => {
let timer = tokio::time::Instant::now();
let results = services.rooms.alias.resolve_local_alias(&alias).await;
let query_time = timer.elapsed();
@@ -39,9 +40,7 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>)
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomAliasCommand::LocalAliasesForRoom {
room_id,
} => {
| RoomAliasCommand::LocalAliasesForRoom { room_id } => {
let timer = tokio::time::Instant::now();
let aliases: Vec<_> = services
.rooms
@@ -56,7 +55,7 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>)
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
)))
},
RoomAliasCommand::AllLocalAliases => {
| RoomAliasCommand::AllLocalAliases => {
let timer = tokio::time::Instant::now();
let aliases = services
.rooms
+19 -54
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
@@ -77,15 +77,13 @@ pub(crate) enum RoomStateCacheCommand {
}
pub(super) async fn process(
subcommand: RoomStateCacheCommand, context: &Command<'_>,
subcommand: RoomStateCacheCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
RoomStateCacheCommand::ServerInRoom {
server,
room_id,
} => {
| RoomStateCacheCommand::ServerInRoom { server, room_id } => {
let timer = tokio::time::Instant::now();
let result = services
.rooms
@@ -98,9 +96,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
)))
},
RoomStateCacheCommand::RoomServers {
room_id,
} => {
| RoomStateCacheCommand::RoomServers { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -115,9 +111,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::ServerRooms {
server,
} => {
| RoomStateCacheCommand::ServerRooms { server } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -132,9 +126,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::RoomMembers {
room_id,
} => {
| RoomStateCacheCommand::RoomMembers { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -149,9 +141,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::LocalUsersInRoom {
room_id,
} => {
| RoomStateCacheCommand::LocalUsersInRoom { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -166,9 +156,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::ActiveLocalUsersInRoom {
room_id,
} => {
| RoomStateCacheCommand::ActiveLocalUsersInRoom { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -183,9 +171,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::RoomJoinedCount {
room_id,
} => {
| RoomStateCacheCommand::RoomJoinedCount { room_id } => {
let timer = tokio::time::Instant::now();
let results = services.rooms.state_cache.room_joined_count(&room_id).await;
let query_time = timer.elapsed();
@@ -194,9 +180,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::RoomInvitedCount {
room_id,
} => {
| RoomStateCacheCommand::RoomInvitedCount { room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
@@ -209,9 +193,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::RoomUserOnceJoined {
room_id,
} => {
| RoomStateCacheCommand::RoomUserOnceJoined { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -226,9 +208,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::RoomMembersInvited {
room_id,
} => {
| RoomStateCacheCommand::RoomMembersInvited { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -243,10 +223,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::GetInviteCount {
room_id,
user_id,
} => {
| RoomStateCacheCommand::GetInviteCount { room_id, user_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
@@ -259,10 +236,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::GetLeftCount {
room_id,
user_id,
} => {
| RoomStateCacheCommand::GetLeftCount { room_id, user_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
@@ -275,9 +249,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::RoomsJoined {
user_id,
} => {
| RoomStateCacheCommand::RoomsJoined { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -292,9 +264,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::RoomsInvited {
user_id,
} => {
| RoomStateCacheCommand::RoomsInvited { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -308,9 +278,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::RoomsLeft {
user_id,
} => {
| RoomStateCacheCommand::RoomsLeft { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
@@ -324,10 +292,7 @@ pub(super) async fn process(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
RoomStateCacheCommand::InviteState {
user_id,
room_id,
} => {
| RoomStateCacheCommand::InviteState { user_id, room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
+51 -42
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, ServerName, UserId};
use service::sending::Destination;
@@ -62,11 +62,14 @@ pub(crate) enum SendingCommand {
}
/// All the getters and iterators in key_value/sending.rs
pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
subcommand: SendingCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
SendingCommand::ActiveRequests => {
| SendingCommand::ActiveRequests => {
let timer = tokio::time::Instant::now();
let results = services.sending.db.active_requests();
let active_requests = results.collect::<Vec<_>>().await;
@@ -76,25 +79,29 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
)))
},
SendingCommand::QueuedRequests {
| SendingCommand::QueuedRequests {
appservice_id,
server_name,
user_id,
push_key,
} => {
if appservice_id.is_none() && server_name.is_none() && user_id.is_none() && push_key.is_none() {
if appservice_id.is_none()
&& server_name.is_none()
&& user_id.is_none()
&& push_key.is_none()
{
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via arguments. See \
--help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. See --help for more details.",
));
}
let timer = tokio::time::Instant::now();
let results = match (appservice_id, server_name, user_id, push_key) {
(Some(appservice_id), None, None, None) => {
| (Some(appservice_id), None, None, None) => {
if appservice_id.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via \
arguments. See --help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. See --help for more details.",
));
}
@@ -103,15 +110,15 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
.db
.queued_requests(&Destination::Appservice(appservice_id))
},
(None, Some(server_name), None, None) => services
| (None, Some(server_name), None, None) => services
.sending
.db
.queued_requests(&Destination::Normal(server_name.into())),
(None, None, Some(user_id), Some(push_key)) => {
.queued_requests(&Destination::Federation(server_name.into())),
| (None, None, Some(user_id), Some(push_key)) => {
if push_key.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via \
arguments. See --help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. See --help for more details.",
));
}
@@ -120,16 +127,16 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
.db
.queued_requests(&Destination::Push(user_id.into(), push_key))
},
(Some(_), Some(_), Some(_), Some(_)) => {
| (Some(_), Some(_), Some(_), Some(_)) => {
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via arguments. \
Not all of them See --help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. Not all of them See --help for more details.",
));
},
_ => {
| _ => {
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via arguments. \
See --help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. See --help for more details.",
));
},
};
@@ -141,26 +148,30 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
"Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```"
)))
},
SendingCommand::ActiveRequestsFor {
| SendingCommand::ActiveRequestsFor {
appservice_id,
server_name,
user_id,
push_key,
} => {
if appservice_id.is_none() && server_name.is_none() && user_id.is_none() && push_key.is_none() {
if appservice_id.is_none()
&& server_name.is_none()
&& user_id.is_none()
&& push_key.is_none()
{
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via arguments. See \
--help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. See --help for more details.",
));
}
let timer = tokio::time::Instant::now();
let results = match (appservice_id, server_name, user_id, push_key) {
(Some(appservice_id), None, None, None) => {
| (Some(appservice_id), None, None, None) => {
if appservice_id.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via \
arguments. See --help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. See --help for more details.",
));
}
@@ -169,15 +180,15 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
.db
.active_requests_for(&Destination::Appservice(appservice_id))
},
(None, Some(server_name), None, None) => services
| (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)) => {
.active_requests_for(&Destination::Federation(server_name.into())),
| (None, None, Some(user_id), Some(push_key)) => {
if push_key.is_empty() {
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via \
arguments. See --help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. See --help for more details.",
));
}
@@ -186,16 +197,16 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
.db
.active_requests_for(&Destination::Push(user_id.into(), push_key))
},
(Some(_), Some(_), Some(_), Some(_)) => {
| (Some(_), Some(_), Some(_), Some(_)) => {
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via arguments. \
Not all of them See --help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. Not all of them See --help for more details.",
));
},
_ => {
| _ => {
return Ok(RoomMessageEventContent::text_plain(
"An appservice ID, server name, or a user ID with push key must be specified via arguments. \
See --help for more details.",
"An appservice ID, server name, or a user ID with push key must be \
specified via arguments. See --help for more details.",
));
},
};
@@ -207,9 +218,7 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
)))
},
SendingCommand::GetLatestEduCount {
server_name,
} => {
| SendingCommand::GetLatestEduCount { server_name } => {
let timer = tokio::time::Instant::now();
let results = services.sending.db.get_latest_educount(&server_name).await;
let query_time = timer.elapsed();
+45 -11
View File
@@ -1,7 +1,9 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use futures::stream::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, OwnedDeviceId, OwnedRoomId, OwnedUserId};
use ruma::{
events::room::message::RoomMessageEventContent, OwnedDeviceId, OwnedRoomId, OwnedUserId,
};
use crate::{admin_command, admin_command_dispatch};
@@ -91,7 +93,11 @@ pub(crate) enum UsersCommand {
#[admin_command]
async fn get_backup_session(
&self, user_id: OwnedUserId, version: String, room_id: OwnedRoomId, session_id: String,
&self,
user_id: OwnedUserId,
version: String,
room_id: OwnedRoomId,
session_id: String,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result = self
@@ -108,7 +114,10 @@ async fn get_backup_session(
#[admin_command]
async fn get_room_backups(
&self, user_id: OwnedUserId, version: String, room_id: OwnedRoomId,
&self,
user_id: OwnedUserId,
version: String,
room_id: OwnedRoomId,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result = self
@@ -124,7 +133,11 @@ async fn get_room_backups(
}
#[admin_command]
async fn get_all_backups(&self, user_id: OwnedUserId, version: String) -> Result<RoomMessageEventContent> {
async fn get_all_backups(
&self,
user_id: OwnedUserId,
version: String,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result = self.services.key_backups.get_all(&user_id, &version).await;
let query_time = timer.elapsed();
@@ -135,7 +148,11 @@ async fn get_all_backups(&self, user_id: OwnedUserId, version: String) -> Result
}
#[admin_command]
async fn get_backup_algorithm(&self, user_id: OwnedUserId, version: String) -> Result<RoomMessageEventContent> {
async fn get_backup_algorithm(
&self,
user_id: OwnedUserId,
version: String,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result = self
.services
@@ -150,7 +167,10 @@ async fn get_backup_algorithm(&self, user_id: OwnedUserId, version: String) -> R
}
#[admin_command]
async fn get_latest_backup_version(&self, user_id: OwnedUserId) -> Result<RoomMessageEventContent> {
async fn get_latest_backup_version(
&self,
user_id: OwnedUserId,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result = self
.services
@@ -244,7 +264,11 @@ async fn list_devices_metadata(&self, user_id: OwnedUserId) -> Result<RoomMessag
}
#[admin_command]
async fn get_device_metadata(&self, user_id: OwnedUserId, device_id: OwnedDeviceId) -> Result<RoomMessageEventContent> {
async fn get_device_metadata(
&self,
user_id: OwnedUserId,
device_id: OwnedDeviceId,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let device = self
.services
@@ -270,7 +294,11 @@ async fn get_devices_version(&self, user_id: OwnedUserId) -> Result<RoomMessageE
}
#[admin_command]
async fn count_one_time_keys(&self, user_id: OwnedUserId, device_id: OwnedDeviceId) -> Result<RoomMessageEventContent> {
async fn count_one_time_keys(
&self,
user_id: OwnedUserId,
device_id: OwnedDeviceId,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result = self
.services
@@ -285,7 +313,11 @@ async fn count_one_time_keys(&self, user_id: OwnedUserId, device_id: OwnedDevice
}
#[admin_command]
async fn get_device_keys(&self, user_id: OwnedUserId, device_id: OwnedDeviceId) -> Result<RoomMessageEventContent> {
async fn get_device_keys(
&self,
user_id: OwnedUserId,
device_id: OwnedDeviceId,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result = self
.services
@@ -327,7 +359,9 @@ async fn get_master_key(&self, user_id: OwnedUserId) -> Result<RoomMessageEventC
#[admin_command]
async fn get_to_device_events(
&self, user_id: OwnedUserId, device_id: OwnedDeviceId,
&self,
user_id: OwnedUserId,
device_id: OwnedDeviceId,
) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now();
let result = self
+81 -69
View File
@@ -1,9 +1,11 @@
use std::fmt::Write;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId};
use ruma::{
events::room::message::RoomMessageEventContent, OwnedRoomAliasId, OwnedRoomId, RoomId,
};
use crate::{escape_html, Command};
@@ -42,82 +44,92 @@ pub(crate) enum RoomAliasCommand {
},
}
pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
command: RoomAliasCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
let server_user = &services.globals.server_user;
match command {
RoomAliasCommand::Set {
ref room_alias_localpart,
..
}
| RoomAliasCommand::Remove {
ref room_alias_localpart,
}
| RoomAliasCommand::Which {
ref room_alias_localpart,
} => {
let room_alias_str = format!("#{}:{}", room_alias_localpart, services.globals.server_name());
let room_alias = match RoomAliasId::parse_box(room_alias_str) {
Ok(alias) => alias,
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))),
| RoomAliasCommand::Set { ref room_alias_localpart, .. }
| RoomAliasCommand::Remove { ref room_alias_localpart }
| RoomAliasCommand::Which { ref room_alias_localpart } => {
let room_alias_str =
format!("#{}:{}", room_alias_localpart, services.globals.server_name());
let room_alias = match OwnedRoomAliasId::parse(room_alias_str) {
| Ok(alias) => alias,
| Err(err) =>
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to parse alias: {err}"
))),
};
match command {
RoomAliasCommand::Set {
force,
room_id,
..
} => match (force, services.rooms.alias.resolve_local_alias(&room_alias).await) {
(true, Ok(id)) => match services
.rooms
.alias
.set_alias(&room_alias, &room_id, server_user)
{
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
"Successfully overwrote alias (formerly {id})"
| RoomAliasCommand::Set { force, room_id, .. } =>
match (force, services.rooms.alias.resolve_local_alias(&room_alias).await) {
| (true, Ok(id)) => {
match services.rooms.alias.set_alias(
&room_alias,
&room_id,
server_user,
) {
| Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
"Successfully overwrote alias (formerly {id})"
))),
| Err(err) => Ok(RoomMessageEventContent::text_plain(format!(
"Failed to remove alias: {err}"
))),
}
},
| (false, Ok(id)) => Ok(RoomMessageEventContent::text_plain(format!(
"Refusing to overwrite in use alias for {id}, use -f or --force to \
overwrite"
))),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
| (_, Err(_)) => {
match services.rooms.alias.set_alias(
&room_alias,
&room_id,
server_user,
) {
| Ok(()) => Ok(RoomMessageEventContent::text_plain(
"Successfully set alias",
)),
| Err(err) => Ok(RoomMessageEventContent::text_plain(format!(
"Failed to remove alias: {err}"
))),
}
},
},
(false, Ok(id)) => Ok(RoomMessageEventContent::text_plain(format!(
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
))),
(_, Err(_)) => match services
.rooms
.alias
.set_alias(&room_alias, &room_id, server_user)
{
Ok(()) => Ok(RoomMessageEventContent::text_plain("Successfully set alias")),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
| RoomAliasCommand::Remove { .. } =>
match services.rooms.alias.resolve_local_alias(&room_alias).await {
| Ok(id) => match services
.rooms
.alias
.remove_alias(&room_alias, server_user)
.await
{
| Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
"Removed alias from {id}"
))),
| Err(err) => Ok(RoomMessageEventContent::text_plain(format!(
"Failed to remove alias: {err}"
))),
},
| Err(_) =>
Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
},
},
RoomAliasCommand::Remove {
..
} => match services.rooms.alias.resolve_local_alias(&room_alias).await {
Ok(id) => match services
.rooms
.alias
.remove_alias(&room_alias, server_user)
.await
{
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!("Removed alias from {id}"))),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
| RoomAliasCommand::Which { .. } =>
match services.rooms.alias.resolve_local_alias(&room_alias).await {
| Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
"Alias resolves to {id}"
))),
| Err(_) =>
Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
},
Err(_) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
},
RoomAliasCommand::Which {
..
} => match services.rooms.alias.resolve_local_alias(&room_alias).await {
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))),
Err(_) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
},
RoomAliasCommand::List {
..
} => unreachable!(),
| RoomAliasCommand::List { .. } => unreachable!(),
}
},
RoomAliasCommand::List {
room_id,
} => {
| RoomAliasCommand::List { room_id } =>
if let Some(room_id) = room_id {
let aliases: Vec<OwnedRoomAliasId> = services
.rooms
@@ -128,7 +140,8 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) ->
.await;
let plain_list = aliases.iter().fold(String::new(), |mut output, alias| {
writeln!(output, "- {alias}").expect("should be able to write to string buffer");
writeln!(output, "- {alias}")
.expect("should be able to write to string buffer");
output
});
@@ -176,7 +189,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) ->
let plain = format!("Aliases:\n{plain_list}");
let html = format!("Aliases:\n<ul>{html_list}</ul>");
Ok(RoomMessageEventContent::text_html(plain, html))
}
},
},
}
}
+10 -4
View File
@@ -1,4 +1,4 @@
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
@@ -6,7 +6,11 @@ use crate::{admin_command, get_room_info, PAGE_SIZE};
#[admin_command]
pub(super) async fn list_rooms(
&self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool, no_details: bool,
&self,
page: Option<usize>,
exclude_disabled: bool,
exclude_banned: bool,
no_details: 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);
@@ -16,10 +20,12 @@ pub(super) async fn list_rooms(
.metadata
.iter_ids()
.filter_map(|room_id| async move {
(!exclude_disabled || !self.services.rooms.metadata.is_disabled(room_id).await).then_some(room_id)
(!exclude_disabled || !self.services.rooms.metadata.is_disabled(room_id).await)
.then_some(room_id)
})
.filter_map(|room_id| async move {
(!exclude_banned || !self.services.rooms.metadata.is_banned(room_id).await).then_some(room_id)
(!exclude_banned || !self.services.rooms.metadata.is_banned(room_id).await)
.then_some(room_id)
})
.then(|room_id| get_room_info(self.services, room_id))
.collect::<Vec<_>>()
+11 -12
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
@@ -25,24 +25,21 @@ pub(crate) enum RoomDirectoryCommand {
},
}
pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
pub(super) async fn process(
command: RoomDirectoryCommand,
context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
match command {
RoomDirectoryCommand::Publish {
room_id,
} => {
| RoomDirectoryCommand::Publish { room_id } => {
services.rooms.directory.set_public(&room_id);
Ok(RoomMessageEventContent::notice_plain("Room published"))
},
RoomDirectoryCommand::Unpublish {
room_id,
} => {
| RoomDirectoryCommand::Unpublish { room_id } => {
services.rooms.directory.set_not_public(&room_id);
Ok(RoomMessageEventContent::notice_plain("Room unpublished"))
},
RoomDirectoryCommand::List {
page,
} => {
| RoomDirectoryCommand::List { page } => {
// 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: Vec<_> = services
@@ -70,7 +67,9 @@ pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>
"Rooms (page {page}):\n```\n{}\n```",
rooms
.iter()
.map(|(id, members, name)| format!("{id} | Members: {members} | Name: {name}"))
.map(|(id, members, name)| format!(
"{id} | Members: {members} | Name: {name}"
))
.collect::<Vec<_>>()
.join("\n")
);
+6 -2
View File
@@ -1,5 +1,5 @@
use clap::Subcommand;
use conduit::{utils::ReadyExt, Result};
use conduwuit::{utils::ReadyExt, Result};
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
@@ -27,7 +27,11 @@ pub(crate) enum RoomInfoCommand {
}
#[admin_command]
async fn list_joined_members(&self, room_id: Box<RoomId>, local_only: bool) -> Result<RoomMessageEventContent> {
async fn list_joined_members(
&self,
room_id: Box<RoomId>,
local_only: bool,
) -> Result<RoomMessageEventContent> {
let room_name = self
.services
.rooms
+3 -2
View File
@@ -5,11 +5,12 @@ mod info;
mod moderation;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::OwnedRoomId;
use self::{
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, moderation::RoomModerationCommand,
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand,
moderation::RoomModerationCommand,
};
use crate::admin_command_dispatch;
+143 -100
View File
@@ -1,12 +1,15 @@
use api::client::leave_room;
use clap::Subcommand;
use conduit::{
use conduwuit::{
debug, error, info,
utils::{IterStream, ReadyExt},
warn, Result,
};
use futures::StreamExt;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
use ruma::{
events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId,
RoomOrAliasId,
};
use crate::{admin_command, admin_command_dispatch, get_room_info};
@@ -75,7 +78,10 @@ pub(crate) enum RoomModerationCommand {
#[admin_command]
async fn ban_room(
&self, force: bool, disable_federation: bool, room: Box<RoomOrAliasId>,
&self,
force: bool,
disable_federation: bool,
room: Box<RoomOrAliasId>,
) -> Result<RoomMessageEventContent> {
debug!("Got room alias or ID: {}", room);
@@ -89,61 +95,67 @@ async fn ban_room(
let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) {
Ok(room_id) => room_id,
Err(e) => {
| Ok(room_id) => room_id,
| Err(e) =>
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to parse room ID {room}. Please note that this requires a full room ID \
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
)))
},
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
))),
};
debug!("Room specified is a room ID, banning room ID");
self.services.rooms.metadata.ban_room(room_id, true);
self.services.rooms.metadata.ban_room(&room_id, true);
room_id
room_id.to_owned()
} else if room.is_room_alias_id() {
let room_alias = match RoomAliasId::parse(&room) {
Ok(room_alias) => room_alias,
Err(e) => {
| Ok(room_alias) => room_alias,
| Err(e) =>
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to parse room ID {room}. Please note that this requires a full room ID \
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
)))
},
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
))),
};
debug!(
"Room specified is not a room ID, attempting to resolve room alias to a room ID locally, if not using \
get_alias_helper to fetch room ID remotely"
"Room specified is not a room ID, attempting to resolve room alias to a room ID \
locally, if not using get_alias_helper to fetch room ID remotely"
);
let room_id = if let Ok(room_id) = self
.services
.rooms
.alias
.resolve_local_alias(&room_alias)
.resolve_local_alias(room_alias)
.await
{
room_id
} else {
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
debug!(
"We don't have this room alias to a room ID locally, attempting to fetch room \
ID over federation"
);
match self
.services
.rooms
.alias
.resolve_alias(&room_alias, None)
.resolve_alias(room_alias, None)
.await
{
Ok((room_id, servers)) => {
debug!(?room_id, ?servers, "Got federation response fetching room ID for {room}");
| Ok((room_id, servers)) => {
debug!(
?room_id,
?servers,
"Got federation response fetching room ID for {room_id}"
);
room_id
},
Err(e) => {
| Err(e) => {
return Ok(RoomMessageEventContent::notice_plain(format!(
"Failed to resolve room alias {room} to a room ID: {e}"
"Failed to resolve room alias {room_alias} to a room ID: {e}"
)));
},
}
@@ -154,8 +166,9 @@ async fn ban_room(
room_id
} else {
return Ok(RoomMessageEventContent::text_plain(
"Room specified is not a room ID or room alias. Please note that this requires a full room ID \
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`)",
"Room specified is not a room ID or room alias. Please note that this requires a \
full room ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`)",
));
};
@@ -171,8 +184,8 @@ async fn ban_room(
while let Some(local_user) = users.next().await {
debug!(
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring all errors, evicting \
admins too)",
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring all \
errors, evicting admins too)",
);
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
@@ -196,12 +209,14 @@ async fn ban_room(
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
error!(
"Error attempting to make local user {} leave room {} during room banning: {}",
"Error attempting to make local user {} leave room {} during room banning: \
{}",
&local_user, &room_id, e
);
return Ok(RoomMessageEventContent::text_plain(format!(
"Error attempting to make local user {} leave room {} during room banning (room is still banned \
but not removing any more users): {}\nIf you would like to ignore errors, use --force",
"Error attempting to make local user {} leave room {} during room banning \
(room is still banned but not removing any more users): {}\nIf you would \
like to ignore errors, use --force",
&local_user, &room_id, e
)));
}
@@ -232,19 +247,26 @@ async fn ban_room(
if disable_federation {
self.services.rooms.metadata.disable_room(&room_id, true);
return Ok(RoomMessageEventContent::text_plain(
"Room banned, removed all our local users, and disabled incoming federation with room.",
"Room banned, removed all our local users, and disabled incoming federation with \
room.",
));
}
Ok(RoomMessageEventContent::text_plain(
"Room banned and removed all our local users, use `!admin federation disable-room` to stop receiving new \
inbound federation events as well if needed.",
"Room banned and removed all our local users, use `!admin federation disable-room` to \
stop receiving new inbound federation events as well if needed.",
))
}
#[admin_command]
async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
async fn ban_list_of_rooms(
&self,
force: bool,
disable_federation: bool,
) -> Result<RoomMessageEventContent> {
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -264,9 +286,10 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
for &room in &rooms_s {
match <&RoomOrAliasId>::try_from(room) {
Ok(room_alias_or_id) => {
| Ok(room_alias_or_id) => {
if let Ok(admin_room_id) = self.services.admin.get_admin_room().await {
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) {
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias)
{
info!("User specified admin room in bulk ban list, ignoring");
continue;
}
@@ -274,51 +297,52 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
if room_alias_or_id.is_room_id() {
let room_id = match RoomId::parse(room_alias_or_id) {
Ok(room_id) => room_id,
Err(e) => {
| Ok(room_id) => room_id,
| Err(e) => {
if force {
// ignore rooms we failed to parse if we're force banning
warn!(
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
logging here: {e}"
"Error parsing room \"{room}\" during bulk room banning, \
ignoring error and logging here: {e}"
);
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
"{room} is not a valid room ID or room alias, please fix the \
list and try again: {e}"
)));
},
};
room_ids.push(room_id);
room_ids.push(room_id.to_owned());
}
if room_alias_or_id.is_room_alias_id() {
match RoomAliasId::parse(room_alias_or_id) {
Ok(room_alias) => {
| Ok(room_alias) => {
let room_id = if let Ok(room_id) = self
.services
.rooms
.alias
.resolve_local_alias(&room_alias)
.resolve_local_alias(room_alias)
.await
{
room_id
} else {
debug!(
"We don't have this room alias to a room ID locally, attempting to fetch room ID \
over federation"
"We don't have this room alias to a room ID locally, \
attempting to fetch room ID over federation"
);
match self
.services
.rooms
.alias
.resolve_alias(&room_alias, None)
.resolve_alias(room_alias, None)
.await
{
Ok((room_id, servers)) => {
| Ok((room_id, servers)) => {
debug!(
?room_id,
?servers,
@@ -326,15 +350,19 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
);
room_id
},
Err(e) => {
| Err(e) => {
// don't fail if force blocking
if force {
warn!("Failed to resolve room alias {room} to a room ID: {e}");
warn!(
"Failed to resolve room alias {room} to a room \
ID: {e}"
);
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to resolve room alias {room} to a room ID: {e}"
"Failed to resolve room alias {room} to a room ID: \
{e}"
)));
},
}
@@ -342,34 +370,37 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
room_ids.push(room_id);
},
Err(e) => {
| Err(e) => {
if force {
// ignore rooms we failed to parse if we're force deleting
error!(
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
logging here: {e}"
"Error parsing room \"{room}\" during bulk room banning, \
ignoring error and logging here: {e}"
);
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
"{room} is not a valid room ID or room alias, please fix the \
list and try again: {e}"
)));
},
}
}
},
Err(e) => {
| Err(e) => {
if force {
// ignore rooms we failed to parse if we're force deleting
error!(
"Error parsing room \"{room}\" during bulk room banning, ignoring error and logging here: {e}"
"Error parsing room \"{room}\" during bulk room banning, ignoring error \
and logging here: {e}"
);
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
"{room} is not a valid room ID or room alias, please fix the list and try \
again: {e}"
)));
},
}
@@ -393,8 +424,8 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
while let Some(local_user) = users.next().await {
debug!(
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring all errors, evicting \
admins too)",
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring \
all errors, evicting admins too)",
);
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
@@ -418,14 +449,15 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
debug!("Attempting leave for user {local_user} in room {room_id}");
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
error!(
"Error attempting to make local user {local_user} leave room {room_id} during bulk room \
banning: {e}",
"Error attempting to make local user {local_user} leave room {room_id} \
during bulk room banning: {e}",
);
return Ok(RoomMessageEventContent::text_plain(format!(
"Error attempting to make local user {} leave room {} during room banning (room is still \
banned but not removing any more users and not banning any more rooms): {}\nIf you would \
like to ignore errors, use --force",
"Error attempting to make local user {} leave room {} during room \
banning (room is still banned but not removing any more users and not \
banning any more rooms): {}\nIf you would like to ignore errors, use \
--force",
&local_user, &room_id, e
)));
}
@@ -458,8 +490,8 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
if disable_federation {
Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and disabled incoming \
federation with the room."
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, \
and disabled incoming federation with the room."
)))
} else {
Ok(RoomMessageEventContent::text_plain(format!(
@@ -469,62 +501,72 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
}
#[admin_command]
async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
async fn unban_room(
&self,
enable_federation: bool,
room: Box<RoomOrAliasId>,
) -> Result<RoomMessageEventContent> {
let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) {
Ok(room_id) => room_id,
Err(e) => {
| Ok(room_id) => room_id,
| Err(e) =>
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to parse room ID {room}. Please note that this requires a full room ID \
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
)))
},
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
))),
};
debug!("Room specified is a room ID, unbanning room ID");
self.services.rooms.metadata.ban_room(room_id, false);
self.services.rooms.metadata.ban_room(&room_id, false);
room_id
room_id.to_owned()
} else if room.is_room_alias_id() {
let room_alias = match RoomAliasId::parse(&room) {
Ok(room_alias) => room_alias,
Err(e) => {
| Ok(room_alias) => room_alias,
| Err(e) =>
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to parse room ID {room}. Please note that this requires a full room ID \
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
)))
},
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
))),
};
debug!(
"Room specified is not a room ID, attempting to resolve room alias to a room ID locally, if not using \
get_alias_helper to fetch room ID remotely"
"Room specified is not a room ID, attempting to resolve room alias to a room ID \
locally, if not using get_alias_helper to fetch room ID remotely"
);
let room_id = if let Ok(room_id) = self
.services
.rooms
.alias
.resolve_local_alias(&room_alias)
.resolve_local_alias(room_alias)
.await
{
room_id
} else {
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
debug!(
"We don't have this room alias to a room ID locally, attempting to fetch room \
ID over federation"
);
match self
.services
.rooms
.alias
.resolve_alias(&room_alias, None)
.resolve_alias(room_alias, None)
.await
{
Ok((room_id, servers)) => {
debug!(?room_id, ?servers, "Got federation response fetching room ID for room {room}");
| Ok((room_id, servers)) => {
debug!(
?room_id,
?servers,
"Got federation response fetching room ID for room {room}"
);
room_id
},
Err(e) => {
| Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to resolve room alias {room} to a room ID: {e}"
)));
@@ -537,8 +579,9 @@ async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) ->
room_id
} else {
return Ok(RoomMessageEventContent::text_plain(
"Room specified is not a room ID or room alias. Please note that this requires a full room ID \
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`)",
"Room specified is not a room ID or room alias. Please note that this requires a \
full room ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`)",
));
};
@@ -548,8 +591,8 @@ async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) ->
}
Ok(RoomMessageEventContent::text_plain(
"Room unbanned, you may need to re-enable federation with the room using enable-room if this is a remote room \
to make it fully functional.",
"Room unbanned, you may need to re-enable federation with the room using enable-room if \
this is a remote room to make it fully functional.",
))
}
+15 -23
View File
@@ -1,6 +1,6 @@
use std::{fmt::Write, sync::Arc};
use conduit::{info, utils::time, warn, Err, Result};
use conduwuit::{info, utils::time, warn, Err, Result};
use ruma::events::room::message::RoomMessageEventContent;
use crate::admin_command;
@@ -29,13 +29,12 @@ pub(super) async fn show_config(&self) -> Result<RoomMessageEventContent> {
#[admin_command]
pub(super) async fn list_features(
&self, available: bool, enabled: bool, comma: bool,
&self,
available: bool,
enabled: bool,
comma: bool,
) -> Result<RoomMessageEventContent> {
let delim = if comma {
","
} else {
" "
};
let delim = if comma { "," } else { " " };
if enabled && !available {
let features = info::rustc::features().join(delim);
let out = format!("`\n{features}\n`");
@@ -53,16 +52,8 @@ pub(super) async fn list_features(
let available = info::cargo::features();
for feature in available {
let active = enabled.contains(&feature.as_str());
let emoji = if active {
""
} else {
""
};
let remark = if active {
"[enabled]"
} else {
""
};
let emoji = if active { "" } else { "" };
let remark = if active { "[enabled]" } else { "" };
writeln!(features, "{emoji} {feature} {remark}")?;
}
@@ -73,7 +64,8 @@ pub(super) async fn list_features(
pub(super) async fn memory_usage(&self) -> Result<RoomMessageEventContent> {
let services_usage = self.services.memory_usage().await?;
let database_usage = self.services.db.db.memory_usage()?;
let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
let allocator_usage =
conduwuit::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}",
@@ -106,8 +98,8 @@ pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> {
.server
.runtime()
.spawn_blocking(move || match globals.db.backup() {
Ok(()) => String::new(),
Err(e) => e.to_string(),
| Ok(()) => String::new(),
| Err(e) => e.to_string(),
})
.await?;
@@ -143,12 +135,12 @@ pub(super) async fn reload_mods(&self) -> Result<RoomMessageEventContent> {
#[admin_command]
#[cfg(unix)]
pub(super) async fn restart(&self, force: bool) -> Result<RoomMessageEventContent> {
use conduit::utils::sys::current_exe_deleted;
use conduwuit::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."
"The server cannot be restarted because the executable changed. If this is expected \
use --force to override."
);
}
+1 -1
View File
@@ -1,7 +1,7 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use crate::admin_command_dispatch;
+129 -70
View File
@@ -1,12 +1,12 @@
use std::{collections::BTreeMap, fmt::Write as _};
use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room};
use conduit::{
use conduwuit::{
debug_warn, error, info, is_equal_to,
utils::{self, ReadyExt},
warn, PduBuilder, Result,
};
use conduit_api::client::{leave_all_rooms, update_avatar_url, update_displayname};
use conduwuit_api::client::{leave_all_rooms, update_avatar_url, update_displayname};
use futures::StreamExt;
use ruma::{
events::{
@@ -47,12 +47,18 @@ pub(super) async fn list_users(&self) -> Result<RoomMessageEventContent> {
}
#[admin_command]
pub(super) async fn create_user(&self, username: String, password: Option<String>) -> Result<RoomMessageEventContent> {
pub(super) async fn create_user(
&self,
username: String,
password: Option<String>,
) -> Result<RoomMessageEventContent> {
// Validate user id
let user_id = parse_local_user_id(self.services, &username)?;
if self.services.users.exists(&user_id).await {
return Ok(RoomMessageEventContent::text_plain(format!("Userid {user_id} already exists")));
return Ok(RoomMessageEventContent::text_plain(format!(
"Userid {user_id} already exists"
)));
}
if user_id.is_historical() {
@@ -120,7 +126,9 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
.server_in_room(self.services.globals.server_name(), &room_id)
.await
{
warn!("Skipping room {room} to automatically join as we have never joined before.");
warn!(
"Skipping room {room} to automatically join as we have never joined before."
);
continue;
}
@@ -130,25 +138,31 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
&user_id,
&room_id,
Some("Automatically joining this room upon registration".to_owned()),
&[self.services.globals.server_name().to_owned(), room_server_name.to_owned()],
&[
self.services.globals.server_name().to_owned(),
room_server_name.to_owned(),
],
None,
&None,
)
.await
{
Ok(_response) => {
| Ok(_response) => {
info!("Automatically joined room {room} for user {user_id}");
},
Err(e) => {
| Err(e) => {
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Failed to automatically join room {room} for user {user_id}: {e}"
"Failed to automatically join room {room} for user {user_id}: \
{e}"
)))
.await
.ok();
// don't return this error so we don't fail registrations
error!("Failed to automatically join room {room} for user {user_id}: {e}");
error!(
"Failed to automatically join room {room} for user {user_id}: {e}"
);
},
};
}
@@ -181,7 +195,11 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
}
#[admin_command]
pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) -> Result<RoomMessageEventContent> {
pub(super) async fn deactivate(
&self,
no_leave_rooms: bool,
user_id: String,
) -> Result<RoomMessageEventContent> {
// Validate user id
let user_id = parse_local_user_id(self.services, &user_id)?;
@@ -213,8 +231,8 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
.await;
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms).await?;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await;
leave_all_rooms(self.services, &user_id).await;
}
@@ -224,34 +242,45 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
}
#[admin_command]
pub(super) async fn reset_password(&self, username: String) -> Result<RoomMessageEventContent> {
pub(super) async fn reset_password(
&self,
username: String,
password: Option<String>,
) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &username)?;
if user_id == self.services.globals.server_user {
return Ok(RoomMessageEventContent::text_plain(
"Not allowed to set the password for the server account. Please use the emergency password config option.",
"Not allowed to set the password for the server account. Please use the emergency \
password config option.",
));
}
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
let new_password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
match self
.services
.users
.set_password(&user_id, Some(new_password.as_str()))
{
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
| Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
"Successfully reset the password for user {user_id}: `{new_password}`"
))),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Couldn't reset the password for user {user_id}: {e}"
))),
}
}
#[admin_command]
pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
pub(super) async fn deactivate_all(
&self,
no_leave_rooms: bool,
force: bool,
) -> Result<RoomMessageEventContent> {
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -269,7 +298,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
for username in usernames {
match parse_active_local_user_id(self.services, username).await {
Ok(user_id) => {
| Ok(user_id) => {
if self.services.users.is_admin(&user_id).await && !force {
self.services
.admin
@@ -296,7 +325,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
user_ids.push(user_id);
},
Err(e) => {
| Err(e) => {
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
@@ -313,7 +342,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
for user_id in user_ids {
match self.services.users.deactivate_account(&user_id).await {
Ok(()) => {
| Ok(()) => {
deactivation_count = deactivation_count.saturating_add(1);
if !no_leave_rooms {
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
@@ -327,19 +356,18 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
.await;
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
update_displayname(self.services, &user_id, None, &all_joined_rooms)
.await
.ok();
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms)
.await
.ok();
.await;
leave_all_rooms(self.services, &user_id).await;
}
},
Err(e) => {
| Err(e) => {
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
.send_message(RoomMessageEventContent::text_plain(format!(
"Failed deactivating user: {e}"
)))
.await
.ok();
},
@@ -352,8 +380,8 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
)))
} else {
Ok(RoomMessageEventContent::text_plain(format!(
"Deactivated {deactivation_count} accounts.\nSkipped admin accounts: {}. Use --force to deactivate admin \
accounts",
"Deactivated {deactivation_count} accounts.\nSkipped admin accounts: {}. Use \
--force to deactivate admin accounts",
admins.join(", ")
)))
}
@@ -395,9 +423,13 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result<RoomMess
#[admin_command]
pub(super) async fn force_join_list_of_local_users(
&self, room_id: OwnedRoomOrAliasId, yes_i_want_to_do_this: bool,
&self,
room_id: OwnedRoomOrAliasId,
yes_i_want_to_do_this: bool,
) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
@@ -406,8 +438,8 @@ pub(super) async fn force_join_list_of_local_users(
if !yes_i_want_to_do_this {
return Ok(RoomMessageEventContent::notice_markdown(
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force bulk join all \
specified local users.",
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force \
bulk join all specified local users.",
));
}
@@ -466,7 +498,7 @@ pub(super) async fn force_join_list_of_local_users(
for username in usernames {
match parse_active_local_user_id(self.services, username).await {
Ok(user_id) => {
| Ok(user_id) => {
// don't make the server service account join
if user_id == self.services.globals.server_user {
self.services
@@ -481,7 +513,7 @@ pub(super) async fn force_join_list_of_local_users(
user_ids.push(user_id);
},
Err(e) => {
| Err(e) => {
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
@@ -509,10 +541,10 @@ pub(super) async fn force_join_list_of_local_users(
)
.await
{
Ok(_res) => {
| Ok(_res) => {
successful_joins = successful_joins.saturating_add(1);
},
Err(e) => {
| Err(e) => {
debug_warn!("Failed force joining {user_id} to {room_id} during bulk join: {e}");
failed_joins = failed_joins.saturating_add(1);
},
@@ -520,18 +552,21 @@ pub(super) async fn force_join_list_of_local_users(
}
Ok(RoomMessageEventContent::notice_markdown(format!(
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins failed.",
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
failed.",
)))
}
#[admin_command]
pub(super) async fn force_join_all_local_users(
&self, room_id: OwnedRoomOrAliasId, yes_i_want_to_do_this: bool,
&self,
room_id: OwnedRoomOrAliasId,
yes_i_want_to_do_this: bool,
) -> Result<RoomMessageEventContent> {
if !yes_i_want_to_do_this {
return Ok(RoomMessageEventContent::notice_markdown(
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force bulk join all local \
users.",
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force \
bulk join all local users.",
));
}
@@ -602,10 +637,10 @@ pub(super) async fn force_join_all_local_users(
)
.await
{
Ok(_res) => {
| Ok(_res) => {
successful_joins = successful_joins.saturating_add(1);
},
Err(e) => {
| Err(e) => {
debug_warn!("Failed force joining {user_id} to {room_id} during bulk join: {e}");
failed_joins = failed_joins.saturating_add(1);
},
@@ -613,13 +648,16 @@ pub(super) async fn force_join_all_local_users(
}
Ok(RoomMessageEventContent::notice_markdown(format!(
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins failed.",
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
failed.",
)))
}
#[admin_command]
pub(super) async fn force_join_room(
&self, user_id: String, room_id: OwnedRoomOrAliasId,
&self,
user_id: String,
room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &user_id)?;
let (room_id, servers) = self
@@ -633,7 +671,8 @@ pub(super) async fn force_join_room(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
join_room_by_id_helper(self.services, &user_id, &room_id, None, &servers, None, &None).await?;
join_room_by_id_helper(self.services, &user_id, &room_id, None, &servers, None, &None)
.await?;
Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been joined to {room_id}.",
@@ -642,7 +681,9 @@ pub(super) async fn force_join_room(
#[admin_command]
pub(super) async fn force_leave_room(
&self, user_id: String, room_id: OwnedRoomOrAliasId,
&self,
user_id: String,
room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
@@ -660,7 +701,9 @@ pub(super) async fn force_leave_room(
#[admin_command]
pub(super) async fn force_demote(
&self, user_id: String, room_id: OwnedRoomOrAliasId,
&self,
user_id: String,
room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
@@ -676,14 +719,19 @@ pub(super) async fn force_demote(
.services
.rooms
.state_accessor
.room_state_get_content::<RoomPowerLevelsEventContent>(&room_id, &StateEventType::RoomPowerLevels, "")
.room_state_get_content::<RoomPowerLevelsEventContent>(
&room_id,
&StateEventType::RoomPowerLevels,
"",
)
.await
.ok();
let user_can_demote_self = room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone()).user_can_change_user_power_level(&user_id, &user_id)
RoomPowerLevels::from(power_levels_content.clone())
.user_can_change_user_power_level(&user_id, &user_id)
}) || self
.services
.rooms
@@ -714,7 +762,8 @@ pub(super) async fn force_demote(
.await?;
Ok(RoomMessageEventContent::notice_markdown(format!(
"User {user_id} demoted themselves to the room default power level in {room_id} - {event_id}"
"User {user_id} demoted themselves to the room default power level in {room_id} - \
{event_id}"
)))
}
@@ -735,7 +784,10 @@ pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessag
#[admin_command]
pub(super) async fn put_room_tag(
&self, user_id: String, room_id: Box<RoomId>, tag: String,
&self,
user_id: String,
room_id: Box<RoomId>,
tag: String,
) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
@@ -745,9 +797,7 @@ pub(super) async fn put_room_tag(
.get_room(&room_id, &user_id, RoomAccountDataEventType::Tag)
.await
.unwrap_or(TagEvent {
content: TagEventContent {
tags: BTreeMap::new(),
},
content: TagEventContent { tags: BTreeMap::new() },
});
tags_event
@@ -772,7 +822,10 @@ pub(super) async fn put_room_tag(
#[admin_command]
pub(super) async fn delete_room_tag(
&self, user_id: String, room_id: Box<RoomId>, tag: String,
&self,
user_id: String,
room_id: Box<RoomId>,
tag: String,
) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
@@ -782,9 +835,7 @@ pub(super) async fn delete_room_tag(
.get_room(&room_id, &user_id, RoomAccountDataEventType::Tag)
.await
.unwrap_or(TagEvent {
content: TagEventContent {
tags: BTreeMap::new(),
},
content: TagEventContent { tags: BTreeMap::new() },
});
tags_event.content.tags.remove(&tag.clone().into());
@@ -800,12 +851,17 @@ pub(super) async fn delete_room_tag(
.await?;
Ok(RoomMessageEventContent::text_plain(format!(
"Successfully updated room account data for {user_id} and room {room_id}, deleting room tag {tag}"
"Successfully updated room account data for {user_id} and room {room_id}, deleting room \
tag {tag}"
)))
}
#[admin_command]
pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
pub(super) async fn get_room_tags(
&self,
user_id: String,
room_id: Box<RoomId>,
) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
let tags_event = self
@@ -814,9 +870,7 @@ pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>)
.get_room(&room_id, &user_id, RoomAccountDataEventType::Tag)
.await
.unwrap_or(TagEvent {
content: TagEventContent {
tags: BTreeMap::new(),
},
content: TagEventContent { tags: BTreeMap::new() },
});
Ok(RoomMessageEventContent::notice_markdown(format!(
@@ -826,7 +880,10 @@ pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>)
}
#[admin_command]
pub(super) async fn redact_event(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
pub(super) async fn redact_event(
&self,
event_id: Box<EventId>,
) -> Result<RoomMessageEventContent> {
let Ok(event) = self
.services
.rooms
@@ -845,7 +902,9 @@ pub(super) async fn redact_event(&self, event_id: Box<EventId>) -> Result<RoomMe
let sender_user = event.sender;
if !self.services.globals.user_is_local(&sender_user) {
return Ok(RoomMessageEventContent::text_plain("This command only works on local users."));
return Ok(RoomMessageEventContent::text_plain(
"This command only works on local users.",
));
}
let reason = format!(
@@ -863,7 +922,7 @@ pub(super) async fn redact_event(&self, event_id: Box<EventId>) -> Result<RoomMe
PduBuilder {
redacts: Some(event.event_id.clone()),
..PduBuilder::timeline(&RoomRedactionEventContent {
redacts: Some(event.event_id.clone().into()),
redacts: Some(event.event_id.clone()),
reason: Some(reason),
})
},
+3 -1
View File
@@ -1,7 +1,7 @@
mod commands;
use clap::Subcommand;
use conduit::Result;
use conduwuit::Result;
use ruma::{EventId, OwnedRoomOrAliasId, RoomId};
use crate::admin_command_dispatch;
@@ -22,6 +22,8 @@ pub(super) enum UserCommand {
ResetPassword {
/// Username of the user for whom the password should be reset
username: String,
/// New password for the user, if unspecified one is generated
password: Option<String>,
},
/// - Deactivate a user
+9 -3
View File
@@ -1,4 +1,4 @@
use conduit_core::{err, Err, Result};
use conduwuit_core::{err, Err, Result};
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
use service::Services;
@@ -8,7 +8,10 @@ pub(crate) fn escape_html(s: &str) -> String {
.replace('>', "&gt;")
}
pub(crate) async fn get_room_info(services: &Services, room_id: &RoomId) -> (OwnedRoomId, u64, String) {
pub(crate) async fn get_room_info(
services: &Services,
room_id: &RoomId,
) -> (OwnedRoomId, u64, String) {
(
room_id.into(),
services
@@ -44,7 +47,10 @@ pub(crate) fn parse_local_user_id(services: &Services, user_id: &str) -> Result<
}
/// Parses user ID that is an active (not guest or deactivated) local user
pub(crate) async fn parse_active_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
pub(crate) async fn parse_active_local_user_id(
services: &Services,
user_id: &str,
) -> Result<OwnedUserId> {
let user_id = parse_local_user_id(services, user_id)?;
if !services.users.exists(&user_id).await {
+4 -5
View File
@@ -1,5 +1,5 @@
[package]
name = "conduit_api"
name = "conduwuit_api"
categories.workspace = true
description.workspace = true
edition.workspace = true
@@ -18,7 +18,6 @@ crate-type = [
[features]
element_hacks = []
#dev_release_log_level = []
release_max_log_level = [
"tracing/max_level_trace",
"tracing/release_max_level_info",
@@ -41,9 +40,9 @@ axum-extra.workspace = true
axum.workspace = true
base64.workspace = true
bytes.workspace = true
conduit-core.workspace = true
conduit-database.workspace = true
conduit-service.workspace = true
conduwuit-core.workspace = true
conduwuit-database.workspace = true
conduwuit-service.workspace = true
const-str.workspace = true
futures.workspace = true
hmac.workspace = true
+120 -91
View File
@@ -2,16 +2,19 @@ use std::fmt::Write;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{debug_info, error, info, is_equal_to, utils, utils::ReadyExt, warn, Error, PduBuilder, Result};
use conduwuit::{
debug_info, error, info, is_equal_to, utils, utils::ReadyExt, warn, Error, PduBuilder, Result,
};
use futures::{FutureExt, StreamExt};
use register::RegistrationKind;
use ruma::{
api::client::{
account::{
change_password, check_registration_token_validity, deactivate, get_3pids, get_username_availability,
change_password, check_registration_token_validity, deactivate, get_3pids,
get_username_availability,
register::{self, LoginType},
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, whoami,
ThirdPartyIdRemovalStatus,
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
whoami, ThirdPartyIdRemovalStatus,
},
error::ErrorKind,
uiaa::{AuthFlow, AuthType, UiaaInfo},
@@ -45,7 +48,8 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
/// invalid when trying to register
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
pub(crate) async fn get_register_available_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_username_availability::v3::Request>,
) -> Result<get_username_availability::v3::Response> {
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
@@ -66,7 +70,8 @@ pub(crate) async fn get_register_available_route(
let user_id = UserId::parse_with_server_name(body_username, services.globals.server_name())
.ok()
.filter(|user_id| {
(!user_id.is_historical() || is_matrix_appservice_irc) && services.globals.user_is_local(user_id)
(!user_id.is_historical() || is_matrix_appservice_irc)
&& services.globals.user_is_local(user_id)
})
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
@@ -86,9 +91,7 @@ pub(crate) async fn get_register_available_route(
// TODO add check for appservice namespaces
// If no if check is true we have an username that's available to be used.
Ok(get_username_availability::v3::Response {
available: true,
})
Ok(get_username_availability::v3::Response { available: true })
}
/// # `POST /_matrix/client/v3/register`
@@ -111,12 +114,14 @@ pub(crate) async fn get_register_available_route(
#[allow(clippy::doc_markdown)]
#[tracing::instrument(skip_all, fields(%client), name = "register")]
pub(crate) async fn register_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp, body: Ruma<register::v3::Request>,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<register::v3::Request>,
) -> Result<register::v3::Response> {
if !services.globals.allow_registration() && body.appservice_info.is_none() {
info!(
"Registration disabled and request not from known appservice, rejecting registration attempt for username \
\"{}\"",
"Registration disabled and request not from known appservice, rejecting \
registration attempt for username \"{}\"",
body.username.as_deref().unwrap_or("")
);
return Err(Error::BadRequest(ErrorKind::forbidden(), "Registration has been disabled."));
@@ -126,11 +131,12 @@ pub(crate) async fn register_route(
if is_guest
&& (!services.globals.allow_guest_registration()
|| (services.globals.allow_registration() && services.globals.registration_token.is_some()))
|| (services.globals.allow_registration()
&& services.globals.registration_token.is_some()))
{
info!(
"Guest registration disabled / registration enabled with token configured, rejecting guest registration \
attempt, initial device name: \"{}\"",
"Guest registration disabled / registration enabled with token configured, \
rejecting guest registration attempt, initial device name: \"{}\"",
body.initial_device_display_name.as_deref().unwrap_or("")
);
return Err(Error::BadRequest(
@@ -143,21 +149,25 @@ pub(crate) async fn register_route(
// generic user error.
if is_guest && services.users.count().await < 2 {
warn!(
"Guest account attempted to register before a real admin user has been registered, rejecting \
registration. Guest's initial device name: \"{}\"",
"Guest account attempted to register before a real admin user has been registered, \
rejecting registration. Guest's initial device name: \"{}\"",
body.initial_device_display_name.as_deref().unwrap_or("")
);
return Err(Error::BadRequest(ErrorKind::forbidden(), "Registration temporarily disabled."));
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Registration temporarily disabled.",
));
}
let user_id = match (&body.username, is_guest) {
(Some(username), false) => {
| (Some(username), false) => {
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
let is_matrix_appservice_irc = body.appservice_info.as_ref().is_some_and(|appservice| {
appservice.registration.id == "irc"
|| appservice.registration.id.contains("matrix-appservice-irc")
|| appservice.registration.id.contains("matrix_appservice_irc")
});
let is_matrix_appservice_irc =
body.appservice_info.as_ref().is_some_and(|appservice| {
appservice.registration.id == "irc"
|| appservice.registration.id.contains("matrix-appservice-irc")
|| appservice.registration.id.contains("matrix_appservice_irc")
});
// don't force the username lowercase if it's from matrix-appservice-irc
let body_username = if is_matrix_appservice_irc {
@@ -166,15 +176,23 @@ pub(crate) async fn register_route(
username.to_lowercase()
};
let proposed_user_id = UserId::parse_with_server_name(body_username, services.globals.server_name())
.ok()
.filter(|user_id| {
(!user_id.is_historical() || is_matrix_appservice_irc) && services.globals.user_is_local(user_id)
})
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
let proposed_user_id =
UserId::parse_with_server_name(body_username, services.globals.server_name())
.ok()
.filter(|user_id| {
(!user_id.is_historical() || is_matrix_appservice_irc)
&& services.globals.user_is_local(user_id)
})
.ok_or(Error::BadRequest(
ErrorKind::InvalidUsername,
"Username is invalid.",
))?;
if services.users.exists(&proposed_user_id).await {
return Err(Error::BadRequest(ErrorKind::UserInUse, "Desired user ID is already taken."));
return Err(Error::BadRequest(
ErrorKind::UserInUse,
"Desired user ID is already taken.",
));
}
if services
@@ -187,7 +205,7 @@ pub(crate) async fn register_route(
proposed_user_id
},
_ => loop {
| _ => loop {
let proposed_user_id = UserId::parse_with_server_name(
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
services.globals.server_name(),
@@ -228,9 +246,7 @@ pub(crate) async fn register_route(
} else {
// No registration token necessary, but clients must still go through the flow
uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Dummy],
}],
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
completed: Vec::new(),
params: Box::default(),
session: None,
@@ -244,7 +260,8 @@ pub(crate) async fn register_route(
let (worked, uiaainfo) = services
.uiaa
.try_auth(
&UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
&UserId::parse_with_server_name("", services.globals.server_name())
.expect("we know this is valid"),
"".into(),
auth,
&uiaainfo,
@@ -257,7 +274,8 @@ pub(crate) async fn register_route(
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services.uiaa.create(
&UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
&UserId::parse_with_server_name("", services.globals.server_name())
.expect("we know this is valid"),
"".into(),
&uiaainfo,
&json,
@@ -268,11 +286,7 @@ pub(crate) async fn register_route(
}
}
let password = if is_guest {
None
} else {
body.password.as_deref()
};
let password = if is_guest { None } else { body.password.as_deref() };
// Create user
services.users.create(&user_id, password)?;
@@ -282,7 +296,9 @@ pub(crate) async fn register_route(
// If `new_user_displayname_suffix` is set, registration will push whatever
// content is set to the user's display name with a space before it
if !services.globals.new_user_displayname_suffix().is_empty() && body.appservice_info.is_none() {
if !services.globals.new_user_displayname_suffix().is_empty()
&& body.appservice_info.is_none()
{
write!(displayname, " {}", services.globals.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer");
}
@@ -319,12 +335,8 @@ pub(crate) async fn register_route(
}
// Generate new device id if the user didn't specify one
let device_id = if is_guest {
None
} else {
body.device_id.clone()
}
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
let device_id = if is_guest { None } else { body.device_id.clone() }
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Generate new token for the device
let token = utils::random_string(TOKEN_LENGTH);
@@ -349,15 +361,16 @@ pub(crate) async fn register_route(
if body.appservice_info.is_none() && !is_guest {
if !device_display_name.is_empty() {
info!(
"New user \"{user_id}\" registered on this server with device display name: \"{device_display_name}\""
"New user \"{user_id}\" registered on this server with device display name: \
\"{device_display_name}\""
);
if services.globals.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client} and device display name \
\"{device_display_name}\""
"New user \"{user_id}\" registered on this server from IP {client} and \
device display name \"{device_display_name}\""
)))
.await
.ok();
@@ -386,8 +399,8 @@ pub(crate) async fn register_route(
services
.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}"
"Guest user \"{user_id}\" with device display name \
\"{device_display_name}\" registered on this server from IP {client}"
)))
.await
.ok();
@@ -398,8 +411,8 @@ 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}",
"Guest user \"{user_id}\" with no device display name registered on \
this server from IP {client}",
)))
.await
.ok();
@@ -430,7 +443,10 @@ pub(crate) async fn register_route(
{
for room in &services.globals.config.auto_join_rooms {
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
error!("Failed to resolve room alias to room ID when attempting to auto join {room}, skipping");
error!(
"Failed to resolve room alias to room ID when attempting to auto join \
{room}, skipping"
);
continue;
};
@@ -440,7 +456,9 @@ pub(crate) async fn register_route(
.server_in_room(services.globals.server_name(), &room_id)
.await
{
warn!("Skipping room {room} to automatically join as we have never joined before.");
warn!(
"Skipping room {room} to automatically join as we have never joined before."
);
continue;
}
@@ -494,7 +512,8 @@ pub(crate) async fn register_route(
/// - Triggers device list updates
#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
pub(crate) async fn change_password_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<change_password::v3::Request>,
) -> Result<change_password::v3::Response> {
// Authentication for this endpoint was made optional, but we need
@@ -506,9 +525,7 @@ pub(crate) async fn change_password_route(
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Password],
}],
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
@@ -572,7 +589,8 @@ pub(crate) async fn change_password_route(
///
/// Note: Also works for Application Services
pub(crate) async fn whoami_route(
State(services): State<crate::State>, body: Ruma<whoami::v3::Request>,
State(services): State<crate::State>,
body: Ruma<whoami::v3::Request>,
) -> Result<whoami::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device_id = body.sender_device.clone();
@@ -580,7 +598,8 @@ pub(crate) async fn whoami_route(
Ok(whoami::v3::Response {
user_id: sender_user.clone(),
device_id,
is_guest: services.users.is_deactivated(sender_user).await? && body.appservice_info.is_none(),
is_guest: services.users.is_deactivated(sender_user).await?
&& body.appservice_info.is_none(),
})
}
@@ -597,7 +616,8 @@ pub(crate) async fn whoami_route(
/// - Removes ability to log in again
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
pub(crate) async fn deactivate_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<deactivate::v3::Request>,
) -> Result<deactivate::v3::Response> {
// Authentication for this endpoint was made optional, but we need
@@ -609,9 +629,7 @@ pub(crate) async fn deactivate_route(
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Password],
}],
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
@@ -648,8 +666,8 @@ pub(crate) async fn deactivate_route(
.collect()
.await;
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await?;
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await?;
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await;
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await;
full_user_deactivate(&services, sender_user, &all_joined_rooms).await?;
@@ -675,7 +693,9 @@ pub(crate) async fn deactivate_route(
/// Get a list of third party identifiers associated with this account.
///
/// - Currently always returns empty list
pub(crate) async fn third_party_route(body: Ruma<get_3pids::v3::Request>) -> Result<get_3pids::v3::Response> {
pub(crate) async fn third_party_route(
body: Ruma<get_3pids::v3::Request>,
) -> Result<get_3pids::v3::Response> {
let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(get_3pids::v3::Response::new(Vec::new()))
@@ -720,7 +740,8 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
/// Currently does not have any ratelimiting, and this isn't very practical as
/// there is only one registration token allowed.
pub(crate) async fn check_registration_token_validity(
State(services): State<crate::State>, body: Ruma<check_registration_token_validity::v1::Request>,
State(services): State<crate::State>,
body: Ruma<check_registration_token_validity::v1::Request>,
) -> Result<check_registration_token_validity::v1::Response> {
let Some(reg_token) = services.globals.registration_token.clone() else {
return Err(Error::BadRequest(
@@ -729,9 +750,7 @@ pub(crate) async fn check_registration_token_validity(
));
};
Ok(check_registration_token_validity::v1::Response {
valid: reg_token == body.token,
})
Ok(check_registration_token_validity::v1::Response { valid: reg_token == body.token })
}
/// Runs through all the deactivation steps:
@@ -742,16 +761,20 @@ pub(crate) async fn check_registration_token_validity(
/// - Removing all profile data
/// - Leaving all rooms (and forgets all of them)
pub async fn full_user_deactivate(
services: &Services, user_id: &UserId, all_joined_rooms: &[OwnedRoomId],
services: &Services,
user_id: &UserId,
all_joined_rooms: &[OwnedRoomId],
) -> Result<()> {
services.users.deactivate_account(user_id).await?;
super::update_displayname(services, user_id, None, all_joined_rooms).await?;
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await?;
services.users.deactivate_account(user_id).await.ok();
super::update_displayname(services, user_id, None, all_joined_rooms).await;
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await;
services
.users
.all_profile_keys(user_id)
.ready_for_each(|(profile_key, _)| services.users.set_profile_key(user_id, &profile_key, None))
.ready_for_each(|(profile_key, _)| {
services.users.set_profile_key(user_id, &profile_key, None);
})
.await;
for room_id in all_joined_rooms {
@@ -760,20 +783,26 @@ pub async fn full_user_deactivate(
let room_power_levels = services
.rooms
.state_accessor
.room_state_get_content::<RoomPowerLevelsEventContent>(room_id, &StateEventType::RoomPowerLevels, "")
.room_state_get_content::<RoomPowerLevelsEventContent>(
room_id,
&StateEventType::RoomPowerLevels,
"",
)
.await
.ok();
let user_can_demote_self = room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone()).user_can_change_user_power_level(user_id, user_id)
}) || services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender == user_id);
let user_can_demote_self =
room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone())
.user_can_change_user_power_level(user_id, user_id)
}) || services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender == user_id);
if user_can_demote_self {
let mut power_levels_content = room_power_levels.unwrap_or_default();
+159
View File
@@ -0,0 +1,159 @@
use axum::extract::State;
use conduwuit::{err, Err};
use ruma::{
api::client::config::{
get_global_account_data, get_room_account_data, set_global_account_data,
set_room_account_data,
},
events::{
AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent,
GlobalAccountDataEventType, RoomAccountDataEventType,
},
serde::Raw,
RoomId, UserId,
};
use serde::Deserialize;
use serde_json::{json, value::RawValue as RawJsonValue};
use crate::{service::Services, Result, Ruma};
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
///
/// Sets some account data for the sender user.
pub(crate) async fn set_global_account_data_route(
State(services): State<crate::State>,
body: Ruma<set_global_account_data::v3::Request>,
) -> Result<set_global_account_data::v3::Response> {
let sender_user = body.sender_user();
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot set account data for other users.")));
}
set_account_data(
&services,
None,
&body.user_id,
&body.event_type.to_string(),
body.data.json(),
)
.await?;
Ok(set_global_account_data::v3::Response {})
}
/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
///
/// Sets some room account data for the sender user.
pub(crate) async fn set_room_account_data_route(
State(services): State<crate::State>,
body: Ruma<set_room_account_data::v3::Request>,
) -> Result<set_room_account_data::v3::Response> {
let sender_user = body.sender_user();
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot set account data for other users.")));
}
set_account_data(
&services,
Some(&body.room_id),
&body.user_id,
&body.event_type.to_string(),
body.data.json(),
)
.await?;
Ok(set_room_account_data::v3::Response {})
}
/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
///
/// Gets some account data for the sender user.
pub(crate) async fn get_global_account_data_route(
State(services): State<crate::State>,
body: Ruma<get_global_account_data::v3::Request>,
) -> Result<get_global_account_data::v3::Response> {
let sender_user = body.sender_user();
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot get account data of other users.")));
}
let account_data: ExtractGlobalEventContent = services
.account_data
.get_global(&body.user_id, body.event_type.clone())
.await
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
Ok(get_global_account_data::v3::Response { account_data: account_data.content })
}
/// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
///
/// Gets some room account data for the sender user.
pub(crate) async fn get_room_account_data_route(
State(services): State<crate::State>,
body: Ruma<get_room_account_data::v3::Request>,
) -> Result<get_room_account_data::v3::Response> {
let sender_user = body.sender_user();
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot get account data of other users.")));
}
let account_data: ExtractRoomEventContent = services
.account_data
.get_room(&body.room_id, &body.user_id, body.event_type.clone())
.await
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
Ok(get_room_account_data::v3::Response { account_data: account_data.content })
}
async fn set_account_data(
services: &Services,
room_id: Option<&RoomId>,
sender_user: &UserId,
event_type_s: &str,
data: &RawJsonValue,
) -> Result {
if event_type_s == RoomAccountDataEventType::FullyRead.to_cow_str() {
return Err!(Request(BadJson(
"This endpoint cannot be used for marking a room as fully read (setting \
m.fully_read)"
)));
}
if event_type_s == GlobalAccountDataEventType::PushRules.to_cow_str() {
return Err!(Request(BadJson(
"This endpoint cannot be used for setting/configuring push rules."
)));
}
let data: serde_json::Value = serde_json::from_str(data.get())
.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
services
.account_data
.update(
room_id,
sender_user,
event_type_s.into(),
&json!({
"type": event_type_s,
"content": data,
}),
)
.await
}
#[derive(Deserialize)]
struct ExtractRoomEventContent {
content: Raw<AnyRoomAccountDataEventContent>,
}
#[derive(Deserialize)]
struct ExtractGlobalEventContent {
content: Raw<AnyGlobalAccountDataEventContent>,
}
+13 -6
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduit::{debug, Err, Result};
use conduwuit::{debug, Err, Result};
use futures::StreamExt;
use rand::seq::SliceRandom;
use ruma::{
@@ -14,7 +14,8 @@ use crate::Ruma;
///
/// Creates a new room alias on this server.
pub(crate) async fn create_alias_route(
State(services): State<crate::State>, body: Ruma<create_alias::v3::Request>,
State(services): State<crate::State>,
body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -58,7 +59,8 @@ pub(crate) async fn create_alias_route(
///
/// - TODO: Update canonical alias event
pub(crate) async fn delete_alias_route(
State(services): State<crate::State>, body: Ruma<delete_alias::v3::Request>,
State(services): State<crate::State>,
body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -83,11 +85,13 @@ pub(crate) async fn delete_alias_route(
///
/// Resolve an alias locally or over federation.
pub(crate) async fn get_alias_route(
State(services): State<crate::State>, body: Ruma<get_alias::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_alias::v3::Request>,
) -> Result<get_alias::v3::Response> {
let room_alias = body.body.room_alias;
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias, None).await else {
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias, None).await
else {
return Err!(Request(NotFound("Room with alias not found.")));
};
@@ -98,7 +102,10 @@ pub(crate) async fn get_alias_route(
}
async fn room_available_servers(
services: &Services, room_id: &RoomId, room_alias: &RoomAliasId, pre_servers: Vec<OwnedServerName>,
services: &Services,
room_id: &RoomId,
room_alias: &RoomAliasId,
pre_servers: Vec<OwnedServerName>,
) -> Vec<OwnedServerName> {
// find active servers in room state cache to suggest
let mut servers: Vec<OwnedServerName> = services
+7 -9
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduit::{err, Err, Result};
use conduwuit::{err, Err, Result};
use ruma::api::{appservice::ping, client::appservice::request_ping};
use crate::Ruma;
@@ -9,12 +9,12 @@ use crate::Ruma;
/// Ask the homeserver to ping the application service to ensure the connection
/// works.
pub(crate) async fn appservice_ping(
State(services): State<crate::State>, body: Ruma<request_ping::v1::Request>,
State(services): State<crate::State>,
body: Ruma<request_ping::v1::Request>,
) -> Result<request_ping::v1::Response> {
let appservice_info = body
.appservice_info
.as_ref()
.ok_or_else(|| err!(Request(Forbidden("This endpoint can only be called by appservices."))))?;
let appservice_info = body.appservice_info.as_ref().ok_or_else(|| {
err!(Request(Forbidden("This endpoint can only be called by appservices.")))
})?;
if body.appservice_id != appservice_info.registration.id {
return Err!(Request(Forbidden(
@@ -41,7 +41,5 @@ pub(crate) async fn appservice_ping(
.await?
.expect("We already validated if an appservice URL exists above");
Ok(request_ping::v1::Response {
duration: timer.elapsed(),
})
Ok(request_ping::v1::Response { duration: timer.elapsed() })
}
+44 -33
View File
@@ -1,11 +1,12 @@
use axum::extract::State;
use conduit::{err, Err};
use conduwuit::{err, Err};
use ruma::{
api::client::backup::{
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
get_latest_backup_info, update_backup_version,
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session,
create_backup_version, delete_backup_keys, delete_backup_keys_for_room,
delete_backup_keys_for_session, delete_backup_version, get_backup_info, get_backup_keys,
get_backup_keys_for_room, get_backup_keys_for_session, get_latest_backup_info,
update_backup_version,
},
UInt,
};
@@ -16,15 +17,14 @@ use crate::{Result, Ruma};
///
/// Creates a new backup.
pub(crate) async fn create_backup_version_route(
State(services): State<crate::State>, body: Ruma<create_backup_version::v3::Request>,
State(services): State<crate::State>,
body: Ruma<create_backup_version::v3::Request>,
) -> Result<create_backup_version::v3::Response> {
let version = services
.key_backups
.create_backup(body.sender_user(), &body.algorithm)?;
Ok(create_backup_version::v3::Response {
version,
})
Ok(create_backup_version::v3::Response { version })
}
/// # `PUT /_matrix/client/r0/room_keys/version/{version}`
@@ -32,7 +32,8 @@ pub(crate) async fn create_backup_version_route(
/// Update information about an existing backup. Only `auth_data` can be
/// modified.
pub(crate) async fn update_backup_version_route(
State(services): State<crate::State>, body: Ruma<update_backup_version::v3::Request>,
State(services): State<crate::State>,
body: Ruma<update_backup_version::v3::Request>,
) -> Result<update_backup_version::v3::Response> {
services
.key_backups
@@ -46,7 +47,8 @@ pub(crate) async fn update_backup_version_route(
///
/// Get information about the latest backup version.
pub(crate) async fn get_latest_backup_info_route(
State(services): State<crate::State>, body: Ruma<get_latest_backup_info::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_latest_backup_info::v3::Request>,
) -> Result<get_latest_backup_info::v3::Response> {
let (version, algorithm) = services
.key_backups
@@ -75,13 +77,16 @@ pub(crate) async fn get_latest_backup_info_route(
///
/// Get information about an existing backup.
pub(crate) async fn get_backup_info_route(
State(services): State<crate::State>, body: Ruma<get_backup_info::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_backup_info::v3::Request>,
) -> Result<get_backup_info::v3::Response> {
let algorithm = services
.key_backups
.get_backup(body.sender_user(), &body.version)
.await
.map_err(|_| err!(Request(NotFound("Key backup does not exist at version {:?}", body.version))))?;
.map_err(|_| {
err!(Request(NotFound("Key backup does not exist at version {:?}", body.version)))
})?;
Ok(get_backup_info::v3::Response {
algorithm,
@@ -105,7 +110,8 @@ pub(crate) async fn get_backup_info_route(
/// - Deletes both information about the backup, as well as all key data related
/// to the backup
pub(crate) async fn delete_backup_version_route(
State(services): State<crate::State>, body: Ruma<delete_backup_version::v3::Request>,
State(services): State<crate::State>,
body: Ruma<delete_backup_version::v3::Request>,
) -> Result<delete_backup_version::v3::Response> {
services
.key_backups
@@ -124,7 +130,8 @@ pub(crate) async fn delete_backup_version_route(
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_route(
State(services): State<crate::State>, body: Ruma<add_backup_keys::v3::Request>,
State(services): State<crate::State>,
body: Ruma<add_backup_keys::v3::Request>,
) -> Result<add_backup_keys::v3::Response> {
if services
.key_backups
@@ -168,7 +175,8 @@ pub(crate) async fn add_backup_keys_route(
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_for_room_route(
State(services): State<crate::State>, body: Ruma<add_backup_keys_for_room::v3::Request>,
State(services): State<crate::State>,
body: Ruma<add_backup_keys_for_room::v3::Request>,
) -> Result<add_backup_keys_for_room::v3::Response> {
if services
.key_backups
@@ -210,7 +218,8 @@ pub(crate) async fn add_backup_keys_for_room_route(
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub(crate) async fn add_backup_keys_for_session_route(
State(services): State<crate::State>, body: Ruma<add_backup_keys_for_session::v3::Request>,
State(services): State<crate::State>,
body: Ruma<add_backup_keys_for_session::v3::Request>,
) -> Result<add_backup_keys_for_session::v3::Response> {
if services
.key_backups
@@ -251,56 +260,56 @@ pub(crate) async fn add_backup_keys_for_session_route(
///
/// Retrieves all keys from the backup.
pub(crate) async fn get_backup_keys_route(
State(services): State<crate::State>, body: Ruma<get_backup_keys::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_backup_keys::v3::Request>,
) -> Result<get_backup_keys::v3::Response> {
let rooms = services
.key_backups
.get_all(body.sender_user(), &body.version)
.await;
Ok(get_backup_keys::v3::Response {
rooms,
})
Ok(get_backup_keys::v3::Response { rooms })
}
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Retrieves all keys from the backup for a given room.
pub(crate) async fn get_backup_keys_for_room_route(
State(services): State<crate::State>, body: Ruma<get_backup_keys_for_room::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_backup_keys_for_room::v3::Request>,
) -> Result<get_backup_keys_for_room::v3::Response> {
let sessions = services
.key_backups
.get_room(body.sender_user(), &body.version, &body.room_id)
.await;
Ok(get_backup_keys_for_room::v3::Response {
sessions,
})
Ok(get_backup_keys_for_room::v3::Response { sessions })
}
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Retrieves a key from the backup.
pub(crate) async fn get_backup_keys_for_session_route(
State(services): State<crate::State>, body: Ruma<get_backup_keys_for_session::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_backup_keys_for_session::v3::Request>,
) -> Result<get_backup_keys_for_session::v3::Response> {
let key_data = services
.key_backups
.get_session(body.sender_user(), &body.version, &body.room_id, &body.session_id)
.await
.map_err(|_| err!(Request(NotFound(debug_error!("Backup key not found for this user's session.")))))?;
.map_err(|_| {
err!(Request(NotFound(debug_error!("Backup key not found for this user's session."))))
})?;
Ok(get_backup_keys_for_session::v3::Response {
key_data,
})
Ok(get_backup_keys_for_session::v3::Response { key_data })
}
/// # `DELETE /_matrix/client/r0/room_keys/keys`
///
/// Delete the keys from the backup.
pub(crate) async fn delete_backup_keys_route(
State(services): State<crate::State>, body: Ruma<delete_backup_keys::v3::Request>,
State(services): State<crate::State>,
body: Ruma<delete_backup_keys::v3::Request>,
) -> Result<delete_backup_keys::v3::Response> {
services
.key_backups
@@ -324,7 +333,8 @@ pub(crate) async fn delete_backup_keys_route(
///
/// Delete the keys from the backup for a given room.
pub(crate) async fn delete_backup_keys_for_room_route(
State(services): State<crate::State>, body: Ruma<delete_backup_keys_for_room::v3::Request>,
State(services): State<crate::State>,
body: Ruma<delete_backup_keys_for_room::v3::Request>,
) -> Result<delete_backup_keys_for_room::v3::Response> {
services
.key_backups
@@ -348,7 +358,8 @@ pub(crate) async fn delete_backup_keys_for_room_route(
///
/// Delete a key from the backup.
pub(crate) async fn delete_backup_keys_for_session_route(
State(services): State<crate::State>, body: Ruma<delete_backup_keys_for_session::v3::Request>,
State(services): State<crate::State>,
body: Ruma<delete_backup_keys_for_session::v3::Request>,
) -> Result<delete_backup_keys_for_session::v3::Response> {
services
.key_backups
+10 -26
View File
@@ -3,8 +3,8 @@ use std::collections::BTreeMap;
use axum::extract::State;
use ruma::{
api::client::discovery::get_capabilities::{
self, Capabilities, GetLoginTokenCapability, RoomVersionStability, RoomVersionsCapability,
ThirdPartyIdChangesCapability,
self, Capabilities, GetLoginTokenCapability, RoomVersionStability,
RoomVersionsCapability, ThirdPartyIdChangesCapability,
},
RoomVersionId,
};
@@ -17,44 +17,28 @@ use crate::{Result, Ruma};
/// Get information on the supported feature set and other relevent capabilities
/// of this server.
pub(crate) async fn get_capabilities_route(
State(services): State<crate::State>, _body: Ruma<get_capabilities::v3::Request>,
State(services): State<crate::State>,
_body: Ruma<get_capabilities::v3::Request>,
) -> Result<get_capabilities::v3::Response> {
let available: BTreeMap<RoomVersionId, RoomVersionStability> = services
.globals
.unstable_room_versions
.iter()
.map(|unstable_room_version| (unstable_room_version.clone(), RoomVersionStability::Unstable))
.chain(
services
.globals
.stable_room_versions
.iter()
.map(|stable_room_version| (stable_room_version.clone(), RoomVersionStability::Stable)),
)
.collect();
let available: BTreeMap<RoomVersionId, RoomVersionStability> =
services.server.available_room_versions().collect();
let mut capabilities = Capabilities::default();
capabilities.room_versions = RoomVersionsCapability {
default: services.globals.default_room_version(),
default: services.server.config.default_room_version.clone(),
available,
};
// we do not implement 3PID stuff
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability {
enabled: false,
};
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability { enabled: false };
// we dont support generating tokens yet
capabilities.get_login_token = GetLoginTokenCapability {
enabled: false,
};
capabilities.get_login_token = GetLoginTokenCapability { enabled: false };
// MSC4133 capability
capabilities
.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))
.expect("this is valid JSON we created");
Ok(get_capabilities::v3::Response {
capabilities,
})
Ok(get_capabilities::v3::Response { capabilities })
}
-124
View File
@@ -1,124 +0,0 @@
use axum::extract::State;
use conduit::err;
use ruma::{
api::client::{
config::{get_global_account_data, get_room_account_data, set_global_account_data, set_room_account_data},
error::ErrorKind,
},
events::{AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent},
serde::Raw,
OwnedUserId, RoomId,
};
use serde::Deserialize;
use serde_json::{json, value::RawValue as RawJsonValue};
use crate::{service::Services, Error, Result, Ruma};
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
///
/// Sets some account data for the sender user.
pub(crate) async fn set_global_account_data_route(
State(services): State<crate::State>, body: Ruma<set_global_account_data::v3::Request>,
) -> Result<set_global_account_data::v3::Response> {
set_account_data(
&services,
None,
body.sender_user.as_ref(),
&body.event_type.to_string(),
body.data.json(),
)
.await?;
Ok(set_global_account_data::v3::Response {})
}
/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
///
/// Sets some room account data for the sender user.
pub(crate) async fn set_room_account_data_route(
State(services): State<crate::State>, body: Ruma<set_room_account_data::v3::Request>,
) -> Result<set_room_account_data::v3::Response> {
set_account_data(
&services,
Some(&body.room_id),
body.sender_user.as_ref(),
&body.event_type.to_string(),
body.data.json(),
)
.await?;
Ok(set_room_account_data::v3::Response {})
}
/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
///
/// Gets some account data for the sender user.
pub(crate) async fn get_global_account_data_route(
State(services): State<crate::State>, body: Ruma<get_global_account_data::v3::Request>,
) -> Result<get_global_account_data::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let account_data: ExtractGlobalEventContent = services
.account_data
.get_global(sender_user, body.event_type.clone())
.await
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
Ok(get_global_account_data::v3::Response {
account_data: account_data.content,
})
}
/// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
///
/// Gets some room account data for the sender user.
pub(crate) async fn get_room_account_data_route(
State(services): State<crate::State>, body: Ruma<get_room_account_data::v3::Request>,
) -> Result<get_room_account_data::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let account_data: ExtractRoomEventContent = services
.account_data
.get_room(&body.room_id, sender_user, body.event_type.clone())
.await
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
Ok(get_room_account_data::v3::Response {
account_data: account_data.content,
})
}
async fn set_account_data(
services: &Services, room_id: Option<&RoomId>, sender_user: Option<&OwnedUserId>, event_type: &str,
data: &RawJsonValue,
) -> Result<()> {
let sender_user = sender_user.as_ref().expect("user is authenticated");
let data: serde_json::Value =
serde_json::from_str(data.get()).map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
services
.account_data
.update(
room_id,
sender_user,
event_type.into(),
&json!({
"type": event_type,
"content": data,
}),
)
.await?;
Ok(())
}
#[derive(Deserialize)]
struct ExtractRoomEventContent {
content: Raw<AnyRoomAccountDataEventContent>,
}
#[derive(Deserialize)]
struct ExtractGlobalEventContent {
content: Raw<AnyGlobalAccountDataEventContent>,
}
+13 -10
View File
@@ -1,7 +1,7 @@
use std::iter::once;
use axum::extract::State;
use conduit::{
use conduwuit::{
at, err, ref_at,
utils::{
future::TryExtExt,
@@ -32,7 +32,8 @@ const LIMIT_DEFAULT: usize = 10;
/// - Only works if the user is joined (TODO: always allow, but only show events
/// if the user was joined, depending on history_visibility)
pub(crate) async fn get_context_route(
State(services): State<crate::State>, body: Ruma<get_context::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_context::v3::Request>,
) -> Result<get_context::v3::Response> {
let filter = &body.filter;
let sender = body.sender();
@@ -50,9 +51,8 @@ pub(crate) async fn get_context_route(
// members for "inline" profiles on the timeline to work properly
let lazy_load_enabled = matches!(filter.lazy_load_options, LazyLoadOptions::Enabled { .. });
let lazy_load_redundant = if let LazyLoadOptions::Enabled {
include_redundant_members,
} = filter.lazy_load_options
let lazy_load_redundant = if let LazyLoadOptions::Enabled { include_redundant_members } =
filter.lazy_load_options
{
include_redundant_members
} else {
@@ -91,10 +91,11 @@ pub(crate) async fn get_context_route(
return Err!(Request(Forbidden("You don't have permission to view this event.")));
}
let events_before = services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(base_token));
let events_before =
services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(base_token));
let events_after = services
.rooms
@@ -166,7 +167,9 @@ pub(crate) async fn get_context_route(
.filter(|&user_id: &&UserId| lazy.contains(user_id))
.map(|_| event_id)
})
.broad_filter_map(|event_id: &OwnedEventId| services.rooms.timeline.get_pdu(event_id).ok())
.broad_filter_map(|event_id: &OwnedEventId| {
services.rooms.timeline.get_pdu(event_id).ok()
})
.map(|pdu| pdu.to_state_event())
.collect()
.await;
+15 -18
View File
@@ -1,6 +1,6 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{err, Err};
use conduwuit::{err, Err};
use futures::StreamExt;
use ruma::{
api::client::{
@@ -18,7 +18,8 @@ use crate::{utils, Error, Result, Ruma};
///
/// Get metadata on all devices of the sender user.
pub(crate) async fn get_devices_route(
State(services): State<crate::State>, body: Ruma<get_devices::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_devices::v3::Request>,
) -> Result<get_devices::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -28,16 +29,15 @@ pub(crate) async fn get_devices_route(
.collect()
.await;
Ok(get_devices::v3::Response {
devices,
})
Ok(get_devices::v3::Response { devices })
}
/// # `GET /_matrix/client/r0/devices/{deviceId}`
///
/// Get metadata on a single device of the sender user.
pub(crate) async fn get_device_route(
State(services): State<crate::State>, body: Ruma<get_device::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_device::v3::Request>,
) -> Result<get_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -47,9 +47,7 @@ pub(crate) async fn get_device_route(
.await
.map_err(|_| err!(Request(NotFound("Device not found."))))?;
Ok(get_device::v3::Response {
device,
})
Ok(get_device::v3::Response { device })
}
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
@@ -57,7 +55,8 @@ pub(crate) async fn get_device_route(
/// Updates the metadata on a given device of the sender user.
#[tracing::instrument(skip_all, fields(%client), name = "update_device")]
pub(crate) async fn update_device_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<update_device::v3::Request>,
) -> Result<update_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -93,16 +92,15 @@ pub(crate) async fn update_device_route(
/// - Forgets to-device events
/// - Triggers device list updates
pub(crate) async fn delete_device_route(
State(services): State<crate::State>, body: Ruma<delete_device::v3::Request>,
State(services): State<crate::State>,
body: Ruma<delete_device::v3::Request>,
) -> Result<delete_device::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");
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Password],
}],
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
@@ -151,16 +149,15 @@ pub(crate) async fn delete_device_route(
/// - Forgets to-device events
/// - Triggers device list updates
pub(crate) async fn delete_devices_route(
State(services): State<crate::State>, body: Ruma<delete_devices::v3::Request>,
State(services): State<crate::State>,
body: Ruma<delete_devices::v3::Request>,
) -> Result<delete_devices::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");
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Password],
}],
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
+55 -27
View File
@@ -1,11 +1,14 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{info, warn, Err, Error, Result};
use conduwuit::{info, warn, Err, Error, Result};
use futures::{StreamExt, TryFutureExt};
use ruma::{
api::{
client::{
directory::{get_public_rooms, get_public_rooms_filtered, get_room_visibility, set_room_visibility},
directory::{
get_public_rooms, get_public_rooms_filtered, get_room_visibility,
set_room_visibility,
},
error::ErrorKind,
room,
},
@@ -32,7 +35,8 @@ use crate::Ruma;
/// - Rooms are ordered by the number of joined members
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
pub(crate) async fn get_public_rooms_filtered_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms_filtered::v3::Request>,
) -> Result<get_public_rooms_filtered::v3::Response> {
if let Some(server) = &body.server {
@@ -57,7 +61,10 @@ pub(crate) async fn get_public_rooms_filtered_route(
.await
.map_err(|e| {
warn!(?body.server, "Failed to return /publicRooms: {e}");
Error::BadRequest(ErrorKind::Unknown, "Failed to return the requested server's public room list.")
Error::BadRequest(
ErrorKind::Unknown,
"Failed to return the requested server's public room list.",
)
})?;
Ok(response)
@@ -70,7 +77,8 @@ pub(crate) async fn get_public_rooms_filtered_route(
/// - Rooms are ordered by the number of joined members
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
pub(crate) async fn get_public_rooms_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms::v3::Request>,
) -> Result<get_public_rooms::v3::Response> {
if let Some(server) = &body.server {
@@ -95,7 +103,10 @@ pub(crate) async fn get_public_rooms_route(
.await
.map_err(|e| {
warn!(?body.server, "Failed to return /publicRooms: {e}");
Error::BadRequest(ErrorKind::Unknown, "Failed to return the requested server's public room list.")
Error::BadRequest(
ErrorKind::Unknown,
"Failed to return the requested server's public room list.",
)
})?;
Ok(get_public_rooms::v3::Response {
@@ -111,7 +122,8 @@ pub(crate) async fn get_public_rooms_route(
/// Sets the visibility of a given room in the room directory.
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
pub(crate) async fn set_room_visibility_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
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");
@@ -139,14 +151,14 @@ pub(crate) async fn set_room_visibility_route(
}
match &body.visibility {
room::Visibility::Public => {
| room::Visibility::Public => {
if services.globals.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
info!(
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
\"lockdown_public_room_directory\" is enabled",
"Non-admin user {sender_user} tried to publish {0} to the room directory \
while \"lockdown_public_room_directory\" is enabled",
body.room_id
);
@@ -154,8 +166,8 @@ pub(crate) async fn set_room_visibility_route(
services
.admin
.send_text(&format!(
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
\"lockdown_public_room_directory\" is enabled",
"Non-admin user {sender_user} tried to publish {0} to the room \
directory while \"lockdown_public_room_directory\" is enabled",
body.room_id
))
.await;
@@ -172,13 +184,16 @@ pub(crate) async fn set_room_visibility_route(
if services.globals.config.admin_room_notices {
services
.admin
.send_text(&format!("{sender_user} made {} public to the room directory", body.room_id))
.send_text(&format!(
"{sender_user} made {} public to the room directory",
body.room_id
))
.await;
}
info!("{sender_user} made {0} public to the room directory", body.room_id);
},
room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id),
_ => {
| room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id),
| _ => {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Room visibility type is not supported.",
@@ -193,7 +208,8 @@ pub(crate) async fn set_room_visibility_route(
///
/// Gets the visibility of a given room in the room directory.
pub(crate) async fn get_room_visibility_route(
State(services): State<crate::State>, body: Ruma<get_room_visibility::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_room_visibility::v3::Request>,
) -> Result<get_room_visibility::v3::Response> {
if !services.rooms.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
@@ -210,10 +226,16 @@ pub(crate) async fn get_room_visibility_route(
}
pub(crate) async fn get_public_rooms_filtered_helper(
services: &Services, server: Option<&ServerName>, limit: Option<UInt>, since: Option<&str>, filter: &Filter,
services: &Services,
server: Option<&ServerName>,
limit: Option<UInt>,
since: Option<&str>,
filter: &Filter,
_network: &RoomNetwork,
) -> Result<get_public_rooms_filtered::v3::Response> {
if let Some(other_server) = server.filter(|server_name| !services.globals.server_is_ours(server_name)) {
if let Some(other_server) =
server.filter(|server_name| !services.globals.server_is_ours(server_name))
{
let response = services
.sending
.send_federation_request(
@@ -245,9 +267,10 @@ pub(crate) async fn get_public_rooms_filtered_helper(
if let Some(s) = &since {
let mut characters = s.chars();
let backwards = match characters.next() {
Some('n') => false,
Some('p') => true,
_ => return Err(Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token")),
| Some('n') => false,
| Some('p') => true,
| _ =>
return Err(Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token")),
};
num_since = characters
@@ -337,7 +360,11 @@ pub(crate) async fn get_public_rooms_filtered_helper(
/// Check whether the user can publish to the room directory via power levels of
/// room history visibility event or room creator
async fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
async fn user_can_publish_room(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
) -> Result<bool> {
if let Ok(event) = services
.rooms
.state_accessor
@@ -347,7 +374,8 @@ async fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &
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)
RoomPowerLevels::from(content)
.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
})
} else if let Ok(event) = services
.rooms
@@ -406,10 +434,10 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "")
.map_ok(|c: RoomJoinRulesEventContent| match c.join_rule {
JoinRule::Public => PublicRoomJoinRule::Public,
JoinRule::Knock => "knock".into(),
JoinRule::KnockRestricted(_) => "knock_restricted".into(),
_ => "invite".into(),
| JoinRule::Public => PublicRoomJoinRule::Public,
| JoinRule::Knock => "knock".into(),
| JoinRule::KnockRestricted(_) => "knock_restricted".into(),
| _ => "invite".into(),
})
.await
.unwrap_or_default(),
+5 -3
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduit::err;
use conduwuit::err;
use ruma::api::client::filter::{create_filter, get_filter};
use crate::{Result, Ruma};
@@ -10,7 +10,8 @@ use crate::{Result, Ruma};
///
/// - A user can only access their own filters
pub(crate) async fn get_filter_route(
State(services): State<crate::State>, body: Ruma<get_filter::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_filter::v3::Request>,
) -> Result<get_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -26,7 +27,8 @@ pub(crate) async fn get_filter_route(
///
/// Creates a new filter to be used by other endpoints.
pub(crate) async fn create_filter_route(
State(services): State<crate::State>, body: Ruma<create_filter::v3::Request>,
State(services): State<crate::State>,
body: Ruma<create_filter::v3::Request>,
) -> Result<create_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+57 -75
View File
@@ -1,16 +1,16 @@
use std::{
collections::{hash_map, BTreeMap, HashMap, HashSet},
time::Instant,
};
use std::collections::{BTreeMap, HashMap, HashSet};
use axum::extract::State;
use conduit::{err, utils, utils::math::continue_exponential_backoff_secs, Err, Error, Result};
use conduwuit::{err, utils, Error, Result};
use futures::{stream::FuturesUnordered, StreamExt};
use ruma::{
api::{
client::{
error::ErrorKind,
keys::{claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures, upload_signing_keys},
keys::{
claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures,
upload_signing_keys,
},
uiaa::{AuthFlow, AuthType, UiaaInfo},
},
federation,
@@ -34,7 +34,8 @@ use crate::{
/// - If there are no device keys yet: Adds device keys (TODO: merge with
/// existing keys?)
pub(crate) async fn upload_keys_route(
State(services): State<crate::State>, body: Ruma<upload_keys::v3::Request>,
State(services): State<crate::State>,
body: Ruma<upload_keys::v3::Request>,
) -> Result<upload_keys::v3::Response> {
let (sender_user, sender_device) = body.sender();
@@ -78,7 +79,8 @@ pub(crate) async fn upload_keys_route(
/// - The master and self-signing keys contain signatures that the user is
/// allowed to see
pub(crate) async fn get_keys_route(
State(services): State<crate::State>, body: Ruma<get_keys::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_keys::v3::Request>,
) -> Result<get_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -96,7 +98,8 @@ pub(crate) async fn get_keys_route(
///
/// Claims one-time keys
pub(crate) async fn claim_keys_route(
State(services): State<crate::State>, body: Ruma<claim_keys::v3::Request>,
State(services): State<crate::State>,
body: Ruma<claim_keys::v3::Request>,
) -> Result<claim_keys::v3::Response> {
claim_keys_helper(&services, &body.one_time_keys).await
}
@@ -107,16 +110,15 @@ pub(crate) async fn claim_keys_route(
///
/// - Requires UIAA to verify password
pub(crate) async fn upload_signing_keys_route(
State(services): State<crate::State>, body: Ruma<upload_signing_keys::v3::Request>,
State(services): State<crate::State>,
body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::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");
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Password],
}],
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
@@ -164,7 +166,8 @@ pub(crate) async fn upload_signing_keys_route(
///
/// Uploads end-to-end key signatures from the sender user.
pub(crate) async fn upload_signatures_route(
State(services): State<crate::State>, body: Ruma<upload_signatures::v3::Request>,
State(services): State<crate::State>,
body: Ruma<upload_signatures::v3::Request>,
) -> Result<upload_signatures::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -177,7 +180,10 @@ pub(crate) async fn upload_signatures_route(
.get("signatures")
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Missing signatures field."))?
.get(sender_user.to_string())
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid user in signatures field."))?
.ok_or(Error::BadRequest(
ErrorKind::InvalidParam,
"Invalid user in signatures field.",
))?
.as_object()
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature."))?
.clone()
@@ -188,7 +194,10 @@ pub(crate) async fn upload_signatures_route(
signature
.1
.as_str()
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature value."))?
.ok_or(Error::BadRequest(
ErrorKind::InvalidParam,
"Invalid signature value.",
))?
.to_owned(),
);
@@ -212,7 +221,8 @@ pub(crate) async fn upload_signatures_route(
///
/// - TODO: left users
pub(crate) async fn get_key_changes_route(
State(services): State<crate::State>, body: Ruma<get_key_changes::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_key_changes::v3::Request>,
) -> Result<get_key_changes::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -258,8 +268,11 @@ pub(crate) async fn get_key_changes_route(
}
pub(crate) async fn get_keys_helper<F>(
services: &Services, sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
allowed_signatures: F, include_display_names: bool,
services: &Services,
sender_user: Option<&UserId>,
device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
allowed_signatures: F,
include_display_names: bool,
) -> Result<get_keys::v3::Response>
where
F: Fn(&UserId) -> bool + Send + Sync,
@@ -292,7 +305,9 @@ where
.users
.get_device_metadata(user_id, device_id)
.await
.map_err(|_| err!(Database("all_device_keys contained nonexistent device.")))?;
.map_err(|_| {
err!(Database("all_device_keys contained nonexistent device."))
})?;
add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
.map_err(|_| err!(Database("invalid device keys in database")))?;
@@ -310,7 +325,11 @@ where
.users
.get_device_metadata(user_id, device_id)
.await
.map_err(|_| err!(Request(InvalidParam("Tried to get keys for nonexistent device."))))?;
.map_err(|_| {
err!(Request(InvalidParam(
"Tried to get keys for nonexistent device."
)))
})?;
add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
.map_err(|_| err!(Database("invalid device keys in database")))?;
@@ -345,60 +364,28 @@ where
let mut failures = BTreeMap::new();
let back_off = |id| async {
match services
.globals
.bad_query_ratelimiter
.write()
.expect("locked")
.entry(id)
{
hash_map::Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
},
hash_map::Entry::Occupied(mut e) => {
*e.get_mut() = (Instant::now(), e.get().1.saturating_add(1));
},
}
};
let mut futures: FuturesUnordered<_> = get_over_federation
.into_iter()
.map(|(server, vec)| async move {
if let Some((time, tries)) = services
.globals
.bad_query_ratelimiter
.read()
.expect("locked")
.get(server)
{
// Exponential backoff
const MIN: u64 = 5 * 60;
const MAX: u64 = 60 * 60 * 24;
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
return (server, Err!(BadServerResponse("bad query from {server:?}, still backing off")));
}
}
let mut device_keys_input_fed = BTreeMap::new();
for (user_id, keys) in vec {
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
}
let request = federation::keys::get_keys::v1::Request {
device_keys: device_keys_input_fed,
};
let request =
federation::keys::get_keys::v1::Request { device_keys: device_keys_input_fed };
let response = services
.sending
.send_federation_request(server, request)
.await;
(server, Ok(response))
(server, response)
})
.collect();
while let Some((server, response)) = futures.next().await {
if let Ok(Ok(response)) = response {
if let Ok(response) = response {
for (user, master_key) in response.master_keys {
let (master_key_id, mut master_key) = parse_master_key(&user, &master_key)?;
@@ -416,8 +403,8 @@ where
.users
.add_cross_signing_keys(
&user, &raw, &None, &None,
false, /* Dont notify. A notification would trigger another key request resulting in an
* endless loop */
false, /* Dont notify. A notification would trigger another key request
* resulting in an endless loop */
)
.await?;
master_keys.insert(user.clone(), raw);
@@ -426,7 +413,6 @@ where
self_signing_keys.extend(response.self_signing_keys);
device_keys.extend(response.device_keys);
} else {
back_off(server.to_owned()).await;
failures.insert(server.to_string(), json!({}));
}
}
@@ -441,7 +427,8 @@ where
}
fn add_unsigned_device_display_name(
keys: &mut Raw<ruma::encryption::DeviceKeys>, metadata: ruma::api::client::device::Device,
keys: &mut Raw<ruma::encryption::DeviceKeys>,
metadata: ruma::api::client::device::Device,
include_display_names: bool,
) -> serde_json::Result<()> {
if let Some(display_name) = metadata.display_name {
@@ -466,7 +453,8 @@ fn add_unsigned_device_display_name(
}
pub(crate) async fn claim_keys_helper(
services: &Services, one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>>,
services: &Services,
one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>>,
) -> Result<claim_keys::v3::Response> {
let mut one_time_keys = BTreeMap::new();
@@ -508,12 +496,9 @@ pub(crate) async fn claim_keys_helper(
server,
services
.sending
.send_federation_request(
server,
federation::keys::claim_keys::v1::Request {
one_time_keys: one_time_keys_input_fed,
},
)
.send_federation_request(server, federation::keys::claim_keys::v1::Request {
one_time_keys: one_time_keys_input_fed,
})
.await,
)
})
@@ -521,17 +506,14 @@ pub(crate) async fn claim_keys_helper(
while let Some((server, response)) = futures.next().await {
match response {
Ok(keys) => {
| Ok(keys) => {
one_time_keys.extend(keys.one_time_keys);
},
Err(_e) => {
| Err(_e) => {
failures.insert(server.to_string(), json!({}));
},
}
}
Ok(claim_keys::v3::Response {
failures,
one_time_keys,
})
Ok(claim_keys::v3::Response { failures, one_time_keys })
}
+67 -18
View File
@@ -2,12 +2,12 @@ use std::time::Duration;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{
use conduwuit::{
err,
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
Err, Result,
};
use conduit_service::{
use conduwuit_service::{
media::{Dim, FileMeta, CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, MXC_LENGTH},
Services,
};
@@ -15,7 +15,8 @@ use reqwest::Url;
use ruma::{
api::client::{
authenticated_media::{
get_content, get_content_as_filename, get_content_thumbnail, get_media_config, get_media_preview,
get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
get_media_preview,
},
media::create_content,
},
@@ -26,7 +27,8 @@ use crate::Ruma;
/// # `GET /_matrix/client/v1/media/config`
pub(crate) async fn get_media_config_route(
State(services): State<crate::State>, _body: Ruma<get_media_config::v1::Request>,
State(services): State<crate::State>,
_body: Ruma<get_media_config::v1::Request>,
) -> Result<get_media_config::v1::Response> {
Ok(get_media_config::v1::Response {
upload_size: ruma_from_usize(services.globals.config.max_request_size),
@@ -39,9 +41,15 @@ pub(crate) async fn get_media_config_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")]
#[tracing::instrument(
name = "media_upload",
level = "debug",
skip_all,
fields(%client),
)]
pub(crate) async fn create_content_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> {
let user = body.sender_user.as_ref().expect("user is authenticated");
@@ -67,9 +75,15 @@ pub(crate) async fn create_content_route(
/// # `GET /_matrix/client/v1/media/thumbnail/{serverName}/{mediaId}`
///
/// Load media thumbnail from our server or over federation.
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
#[tracing::instrument(
name = "media_thumbnail_get",
level = "debug",
skip_all,
fields(%client),
)]
pub(crate) async fn get_content_thumbnail_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v1::Request>,
) -> Result<get_content_thumbnail::v1::Response> {
let user = body.sender_user.as_ref().expect("user is authenticated");
@@ -98,9 +112,15 @@ pub(crate) async fn get_content_thumbnail_route(
/// # `GET /_matrix/client/v1/media/download/{serverName}/{mediaId}`
///
/// Load media from our server or over federation.
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
#[tracing::instrument(
name = "media_get",
level = "debug",
skip_all,
fields(%client),
)]
pub(crate) async fn get_content_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content::v1::Request>,
) -> Result<get_content::v1::Response> {
let user = body.sender_user.as_ref().expect("user is authenticated");
@@ -128,9 +148,15 @@ pub(crate) async fn get_content_route(
/// # `GET /_matrix/client/v1/media/download/{serverName}/{mediaId}/{fileName}`
///
/// Load media from our server or over federation as fileName.
#[tracing::instrument(skip_all, fields(%client), name = "media_get_af")]
#[tracing::instrument(
name = "media_get_af",
level = "debug",
skip_all,
fields(%client),
)]
pub(crate) async fn get_content_as_filename_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_as_filename::v1::Request>,
) -> Result<get_content_as_filename::v1::Response> {
let user = body.sender_user.as_ref().expect("user is authenticated");
@@ -158,9 +184,15 @@ pub(crate) async fn get_content_as_filename_route(
/// # `GET /_matrix/client/v1/media/preview_url`
///
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
#[tracing::instrument(
name = "url_preview",
level = "debug",
skip_all,
fields(%client),
)]
pub(crate) async fn get_media_preview_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_media_preview::v1::Request>,
) -> Result<get_media_preview::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -198,7 +230,11 @@ pub(crate) async fn get_media_preview_route(
}
async fn fetch_thumbnail(
services: &Services, mxc: &Mxc<'_>, user: &UserId, timeout_ms: Duration, dim: &Dim,
services: &Services,
mxc: &Mxc<'_>,
user: &UserId,
timeout_ms: Duration,
dim: &Dim,
) -> Result<FileMeta> {
let FileMeta {
content,
@@ -220,7 +256,11 @@ async fn fetch_thumbnail(
}
async fn fetch_file(
services: &Services, mxc: &Mxc<'_>, user: &UserId, timeout_ms: Duration, filename: Option<&str>,
services: &Services,
mxc: &Mxc<'_>,
user: &UserId,
timeout_ms: Duration,
filename: Option<&str>,
) -> Result<FileMeta> {
let FileMeta {
content,
@@ -242,7 +282,11 @@ async fn fetch_file(
}
async fn fetch_thumbnail_meta(
services: &Services, mxc: &Mxc<'_>, user: &UserId, timeout_ms: Duration, dim: &Dim,
services: &Services,
mxc: &Mxc<'_>,
user: &UserId,
timeout_ms: Duration,
dim: &Dim,
) -> Result<FileMeta> {
if let Some(filemeta) = services.media.get_thumbnail(mxc, dim).await? {
return Ok(filemeta);
@@ -258,7 +302,12 @@ async fn fetch_thumbnail_meta(
.await
}
async fn fetch_file_meta(services: &Services, mxc: &Mxc<'_>, user: &UserId, timeout_ms: Duration) -> Result<FileMeta> {
async fn fetch_file_meta(
services: &Services,
mxc: &Mxc<'_>,
user: &UserId,
timeout_ms: Duration,
) -> Result<FileMeta> {
if let Some(filemeta) = services.media.get(mxc).await? {
return Ok(filemeta);
}
+59 -28
View File
@@ -2,17 +2,17 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{
use conduwuit::{
err,
utils::{content_disposition::make_content_disposition, math::ruma_from_usize},
Err, Result,
};
use conduit_service::media::{Dim, FileMeta, CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN};
use conduwuit_service::media::{Dim, FileMeta, CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN};
use reqwest::Url;
use ruma::{
api::client::media::{
create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
get_media_preview,
create_content, get_content, get_content_as_filename, get_content_thumbnail,
get_media_config, get_media_preview,
},
Mxc,
};
@@ -23,7 +23,8 @@ use crate::{client::create_content_route, Ruma, RumaResponse};
///
/// Returns max upload size.
pub(crate) async fn get_media_config_legacy_route(
State(services): State<crate::State>, _body: Ruma<get_media_config::v3::Request>,
State(services): State<crate::State>,
_body: Ruma<get_media_config::v3::Request>,
) -> Result<get_media_config::v3::Response> {
Ok(get_media_config::v3::Response {
upload_size: ruma_from_usize(services.globals.config.max_request_size),
@@ -38,7 +39,8 @@ pub(crate) async fn get_media_config_legacy_route(
///
/// Returns max upload size.
pub(crate) async fn get_media_config_legacy_legacy_route(
State(services): State<crate::State>, body: Ruma<get_media_config::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_media_config::v3::Request>,
) -> Result<RumaResponse<get_media_config::v3::Response>> {
get_media_config_legacy_route(State(services), body)
.await
@@ -50,7 +52,8 @@ pub(crate) async fn get_media_config_legacy_legacy_route(
/// Returns URL preview.
#[tracing::instrument(skip_all, fields(%client), name = "url_preview_legacy")]
pub(crate) async fn get_media_preview_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
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");
@@ -91,7 +94,8 @@ pub(crate) async fn get_media_preview_legacy_route(
///
/// Returns URL preview.
pub(crate) async fn get_media_preview_legacy_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_media_preview::v3::Request>,
) -> Result<RumaResponse<get_media_preview::v3::Response>> {
get_media_preview_legacy_route(State(services), InsecureClientIp(client), body)
@@ -110,7 +114,8 @@ pub(crate) async fn get_media_preview_legacy_legacy_route(
/// - Some metadata will be saved in the database
/// - Media will be saved in the media/ directory
pub(crate) async fn create_content_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<create_content::v3::Request>,
) -> Result<RumaResponse<create_content::v3::Response>> {
create_content_route(State(services), InsecureClientIp(client), body)
@@ -128,7 +133,8 @@ pub(crate) async fn create_content_legacy_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
pub(crate) async fn get_content_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content::v3::Request>,
) -> Result<get_content::v3::Response> {
let mxc = Mxc {
@@ -142,7 +148,8 @@ pub(crate) async fn get_content_legacy_route(
content_disposition,
}) = services.media.get(&mxc).await?
{
let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
let content_disposition =
make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
Ok(get_content::v3::Response {
file: content.expect("entire file contents"),
@@ -156,10 +163,15 @@ pub(crate) async fn get_content_legacy_route(
.media
.fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms)
.await
.map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?;
.map_err(|e| {
err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
})?;
let content_disposition =
make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None);
let content_disposition = make_content_disposition(
response.content_disposition.as_ref(),
response.content_type.as_deref(),
None,
);
Ok(get_content::v3::Response {
file: response.file,
@@ -187,7 +199,8 @@ pub(crate) async fn get_content_legacy_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
pub(crate) async fn get_content_legacy_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content::v3::Request>,
) -> Result<RumaResponse<get_content::v3::Response>> {
get_content_legacy_route(State(services), InsecureClientIp(client), body)
@@ -205,7 +218,8 @@ pub(crate) async fn get_content_legacy_legacy_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
pub(crate) async fn get_content_as_filename_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_as_filename::v3::Request>,
) -> Result<get_content_as_filename::v3::Response> {
let mxc = Mxc {
@@ -219,8 +233,11 @@ pub(crate) async fn get_content_as_filename_legacy_route(
content_disposition,
}) = services.media.get(&mxc).await?
{
let content_disposition =
make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), Some(&body.filename));
let content_disposition = make_content_disposition(
content_disposition.as_ref(),
content_type.as_deref(),
Some(&body.filename),
);
Ok(get_content_as_filename::v3::Response {
file: content.expect("entire file contents"),
@@ -234,10 +251,15 @@ pub(crate) async fn get_content_as_filename_legacy_route(
.media
.fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms)
.await
.map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?;
.map_err(|e| {
err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
})?;
let content_disposition =
make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None);
let content_disposition = make_content_disposition(
response.content_disposition.as_ref(),
response.content_type.as_deref(),
None,
);
Ok(get_content_as_filename::v3::Response {
content_disposition: Some(content_disposition),
@@ -264,7 +286,8 @@ pub(crate) async fn get_content_as_filename_legacy_route(
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
pub(crate) async fn get_content_as_filename_legacy_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_as_filename::v3::Request>,
) -> Result<RumaResponse<get_content_as_filename::v3::Response>> {
get_content_as_filename_legacy_route(State(services), InsecureClientIp(client), body)
@@ -282,7 +305,8 @@ pub(crate) async fn get_content_as_filename_legacy_legacy_route(
/// seconds
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get_legacy")]
pub(crate) async fn get_content_thumbnail_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v3::Request>,
) -> Result<get_content_thumbnail::v3::Response> {
let mxc = Mxc {
@@ -297,7 +321,8 @@ pub(crate) async fn get_content_thumbnail_legacy_route(
content_disposition,
}) = services.media.get_thumbnail(&mxc, &dim).await?
{
let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
let content_disposition =
make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
Ok(get_content_thumbnail::v3::Response {
file: content.expect("entire file contents"),
@@ -311,10 +336,15 @@ pub(crate) async fn get_content_thumbnail_legacy_route(
.media
.fetch_remote_thumbnail_legacy(&body)
.await
.map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?;
.map_err(|e| {
err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
})?;
let content_disposition =
make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None);
let content_disposition = make_content_disposition(
response.content_disposition.as_ref(),
response.content_type.as_deref(),
None,
);
Ok(get_content_thumbnail::v3::Response {
file: response.file,
@@ -341,7 +371,8 @@ pub(crate) async fn get_content_thumbnail_legacy_route(
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
/// seconds
pub(crate) async fn get_content_thumbnail_legacy_legacy_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v3::Request>,
) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> {
get_content_thumbnail_legacy_route(State(services), InsecureClientIp(client), body)
File diff suppressed because it is too large Load Diff
+63 -35
View File
@@ -1,7 +1,7 @@
use std::collections::HashSet;
use axum::extract::State;
use conduit::{
use conduwuit::{
at, is_equal_to,
utils::{
result::{FlatOk, LogErr},
@@ -27,23 +27,24 @@ use crate::Ruma;
pub(crate) type LazySet = HashSet<OwnedUserId>;
/// list of safe and common non-state events to ignore if the user is ignored
const IGNORED_MESSAGE_TYPES: &[TimelineEventType; 16] = &[
const IGNORED_MESSAGE_TYPES: &[TimelineEventType; 17] = &[
Audio,
CallInvite,
Emote,
File,
Image,
KeyVerificationStart,
Location,
PollStart,
UnstablePollStart,
Beacon,
Reaction,
RoomEncrypted,
RoomMessage,
Sticker,
CallInvite,
CallNotify,
RoomEncrypted,
Image,
File,
Audio,
Voice,
Video,
UnstablePollStart,
PollStart,
KeyVerificationStart,
Reaction,
Emote,
Location,
Voice,
CallNotify,
];
const LIMIT_MAX: usize = 100;
@@ -56,8 +57,10 @@ const LIMIT_DEFAULT: usize = 10;
/// - Only works if the user is joined (TODO: always allow, but only show events
/// where the user was joined, depending on `history_visibility`)
pub(crate) async fn get_message_events_route(
State(services): State<crate::State>, body: Ruma<get_message_events::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_message_events::v3::Request>,
) -> Result<get_message_events::v3::Response> {
debug_assert!(IGNORED_MESSAGE_TYPES.is_sorted(), "IGNORED_MESSAGE_TYPES is not sorted");
let sender = body.sender();
let (sender_user, sender_device) = sender;
let room_id = &body.room_id;
@@ -69,8 +72,8 @@ pub(crate) async fn get_message_events_route(
.map(str::parse)
.transpose()?
.unwrap_or_else(|| match body.dir {
Direction::Forward => PduCount::min(),
Direction::Backward => PduCount::max(),
| Direction::Forward => PduCount::min(),
| Direction::Backward => PduCount::max(),
});
let to: Option<PduCount> = body.to.as_deref().map(str::parse).flat_ok();
@@ -81,10 +84,12 @@ pub(crate) async fn get_message_events_route(
.unwrap_or(LIMIT_DEFAULT)
.min(LIMIT_MAX);
services
.rooms
.lazy_loading
.lazy_load_confirm_delivery(sender_user, sender_device, room_id, from);
services.rooms.lazy_loading.lazy_load_confirm_delivery(
sender_user,
sender_device,
room_id,
from,
);
if matches!(body.dir, Direction::Backward) {
services
@@ -98,14 +103,14 @@ pub(crate) async fn get_message_events_route(
}
let it = match body.dir {
Direction::Forward => services
| Direction::Forward => services
.rooms
.timeline
.pdus(Some(sender_user), room_id, Some(from))
.await?
.boxed(),
Direction::Backward => services
| Direction::Backward => services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(from))
@@ -141,10 +146,13 @@ pub(crate) async fn get_message_events_route(
if !cfg!(feature = "element_hacks") {
if let Some(next_token) = next_token {
services
.rooms
.lazy_loading
.lazy_load_mark_sent(sender_user, sender_device, room_id, lazy, next_token);
services.rooms.lazy_loading.lazy_load_mark_sent(
sender_user,
sender_device,
room_id,
lazy,
next_token,
);
}
}
@@ -162,7 +170,11 @@ pub(crate) async fn get_message_events_route(
})
}
async fn get_member_event(services: &Services, room_id: &RoomId, user_id: &UserId) -> Option<Raw<AnyStateEvent>> {
async fn get_member_event(
services: &Services,
room_id: &RoomId,
user_id: &UserId,
) -> Option<Raw<AnyStateEvent>> {
services
.rooms
.state_accessor
@@ -173,13 +185,17 @@ async fn get_member_event(services: &Services, room_id: &RoomId, user_id: &UserI
}
pub(crate) async fn update_lazy(
services: &Services, room_id: &RoomId, sender: (&UserId, &DeviceId), mut lazy: LazySet, item: &PdusIterItem,
services: &Services,
room_id: &RoomId,
sender: (&UserId, &DeviceId),
mut lazy: LazySet,
item: &PdusIterItem,
force: bool,
) -> LazySet {
let (_, event) = &item;
let (sender_user, sender_device) = sender;
/* TODO: Remove the not "element_hacks" check when these are resolved:
/* TODO: Remove the "element_hacks" check when these are resolved:
* https://github.com/vector-im/element-android/issues/3417
* https://github.com/vector-im/element-web/issues/21034
*/
@@ -204,7 +220,11 @@ pub(crate) async fn update_lazy(
lazy
}
pub(crate) async fn ignored_filter(services: &Services, item: PdusIterItem, user_id: &UserId) -> Option<PdusIterItem> {
pub(crate) async fn ignored_filter(
services: &Services,
item: PdusIterItem,
user_id: &UserId,
) -> Option<PdusIterItem> {
let (_, pdu) = &item;
// exclude Synapse's dummy events from bloating up response bodies. clients
@@ -213,8 +233,14 @@ pub(crate) async fn ignored_filter(services: &Services, item: PdusIterItem, user
return None;
}
if IGNORED_MESSAGE_TYPES.iter().any(is_equal_to!(&pdu.kind))
&& services.users.user_is_ignored(&pdu.sender, user_id).await
if IGNORED_MESSAGE_TYPES.binary_search(&pdu.kind).is_ok()
&& (services.users.user_is_ignored(&pdu.sender, user_id).await
|| services
.server
.config
.forbidden_remote_server_names
.iter()
.any(is_equal_to!(pdu.sender().server_name())))
{
return None;
}
@@ -223,7 +249,9 @@ pub(crate) async fn ignored_filter(services: &Services, item: PdusIterItem, user
}
pub(crate) async fn visibility_filter(
services: &Services, item: PdusIterItem, user_id: &UserId,
services: &Services,
item: PdusIterItem,
user_id: &UserId,
) -> Option<PdusIterItem> {
let (_, pdu) = &item;
+2 -2
View File
@@ -1,9 +1,9 @@
pub(super) mod account;
pub(super) mod account_data;
pub(super) mod alias;
pub(super) mod appservice;
pub(super) mod backup;
pub(super) mod capabilities;
pub(super) mod config;
pub(super) mod context;
pub(super) mod device;
pub(super) mod directory;
@@ -41,11 +41,11 @@ pub(super) mod well_known;
pub use account::full_user_deactivate;
pub(super) use account::*;
pub(super) use account_data::*;
pub(super) use alias::*;
pub(super) use appservice::*;
pub(super) use backup::*;
pub(super) use capabilities::*;
pub(super) use config::*;
pub(super) use context::*;
pub(super) use device::*;
pub(super) use directory::*;
+3 -2
View File
@@ -1,7 +1,7 @@
use std::time::Duration;
use axum::extract::State;
use conduit::utils;
use conduwuit::utils;
use ruma::{
api::client::{account, error::ErrorKind},
authentication::TokenType,
@@ -16,7 +16,8 @@ use crate::{Error, Result, Ruma};
///
/// - The token generated is only valid for the OpenID API
pub(crate) async fn create_openid_token_route(
State(services): State<crate::State>, body: Ruma<account::request_openid_token::v3::Request>,
State(services): State<crate::State>,
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");
+12 -4
View File
@@ -12,10 +12,14 @@ use crate::{Error, Result, Ruma};
///
/// Sets the presence state of the sender user.
pub(crate) async fn set_presence_route(
State(services): State<crate::State>, body: Ruma<set_presence::v3::Request>,
State(services): State<crate::State>,
body: Ruma<set_presence::v3::Request>,
) -> Result<set_presence::v3::Response> {
if !services.globals.allow_local_presence() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server"));
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Presence is disabled on this server",
));
}
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -40,10 +44,14 @@ pub(crate) async fn set_presence_route(
///
/// - Only works if you share a room with the user
pub(crate) async fn get_presence_route(
State(services): State<crate::State>, body: Ruma<get_presence::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_presence::v3::Request>,
) -> Result<get_presence::v3::Response> {
if !services.globals.allow_local_presence() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server"));
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Presence is disabled on this server",
));
}
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+124 -91
View File
@@ -1,19 +1,23 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduit::{
use conduwuit::{
pdu::PduBuilder,
utils::{stream::TryIgnore, IterStream},
warn, Err, Error, Result,
};
use futures::{StreamExt, TryStreamExt};
use futures::{future::join3, StreamExt, TryStreamExt};
use ruma::{
api::{
client::{
error::ErrorKind,
profile::{get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name},
profile::{
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
},
},
federation,
},
events::{room::member::RoomMemberEventContent, StateEventType},
events::room::member::{MembershipState, RoomMemberEventContent},
presence::PresenceState,
OwnedMxcUri, OwnedRoomId, UserId,
};
@@ -27,7 +31,8 @@ use crate::Ruma;
///
/// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn set_displayname_route(
State(services): State<crate::State>, body: Ruma<set_display_name::v3::Request>,
State(services): State<crate::State>,
body: Ruma<set_display_name::v3::Request>,
) -> Result<set_display_name::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -43,7 +48,8 @@ pub(crate) async fn set_displayname_route(
.collect()
.await;
update_displayname(&services, &body.user_id, body.displayname.clone(), &all_joined_rooms).await?;
update_displayname(&services, &body.user_id, body.displayname.clone(), &all_joined_rooms)
.await;
if services.globals.allow_local_presence() {
// Presence update
@@ -63,7 +69,8 @@ pub(crate) async fn set_displayname_route(
/// - 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(
State(services): State<crate::State>, body: Ruma<get_display_name::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_display_name::v3::Request>,
) -> Result<get_display_name::v3::Response> {
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
@@ -92,9 +99,7 @@ pub(crate) async fn get_displayname_route(
.users
.set_blurhash(&body.user_id, response.blurhash.clone());
return Ok(get_display_name::v3::Response {
displayname: response.displayname,
});
return Ok(get_display_name::v3::Response { displayname: response.displayname });
}
}
@@ -115,7 +120,8 @@ pub(crate) async fn get_displayname_route(
///
/// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn set_avatar_url_route(
State(services): State<crate::State>, body: Ruma<set_avatar_url::v3::Request>,
State(services): State<crate::State>,
body: Ruma<set_avatar_url::v3::Request>,
) -> Result<set_avatar_url::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -138,7 +144,7 @@ pub(crate) async fn set_avatar_url_route(
body.blurhash.clone(),
&all_joined_rooms,
)
.await?;
.await;
if services.globals.allow_local_presence() {
// Presence update
@@ -159,7 +165,8 @@ pub(crate) async fn set_avatar_url_route(
/// - 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(
State(services): State<crate::State>, body: Ruma<get_avatar_url::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_avatar_url::v3::Request>,
) -> Result<get_avatar_url::v3::Response> {
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
@@ -216,7 +223,8 @@ pub(crate) async fn get_avatar_url_route(
/// - If user is on another server and we do not have a local copy already,
/// fetch profile over federation.
pub(crate) async fn get_profile_route(
State(services): State<crate::State>, body: Ruma<get_profile::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_profile::v3::Request>,
) -> Result<get_profile::v3::Response> {
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
@@ -252,9 +260,11 @@ pub(crate) async fn get_profile_route(
.set_timezone(&body.user_id, response.tz.clone());
for (profile_key, profile_key_value) in &response.custom_profile_fields {
services
.users
.set_profile_key(&body.user_id, profile_key, Some(profile_key_value.clone()));
services.users.set_profile_key(
&body.user_id,
profile_key,
Some(profile_key_value.clone()),
);
}
return Ok(get_profile::v3::Response {
@@ -273,96 +283,66 @@ pub(crate) async fn get_profile_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
let mut custom_profile_fields: BTreeMap<String, serde_json::Value> = services
.users
.all_profile_keys(&body.user_id)
.collect()
.await;
// services.users.timezone will collect the MSC4175 timezone key if it exists
custom_profile_fields.remove("us.cloke.msc4175.tz");
custom_profile_fields.remove("m.tz");
Ok(get_profile::v3::Response {
avatar_url: services.users.avatar_url(&body.user_id).await.ok(),
blurhash: services.users.blurhash(&body.user_id).await.ok(),
displayname: services.users.displayname(&body.user_id).await.ok(),
tz: services.users.timezone(&body.user_id).await.ok(),
custom_profile_fields: services
.users
.all_profile_keys(&body.user_id)
.collect()
.await,
custom_profile_fields,
})
}
pub async fn update_displayname(
services: &Services, user_id: &UserId, displayname: Option<String>, all_joined_rooms: &[OwnedRoomId],
) -> Result<()> {
let current_display_name = services.users.displayname(user_id).await.ok();
services: &Services,
user_id: &UserId,
displayname: Option<String>,
all_joined_rooms: &[OwnedRoomId],
) {
let (current_avatar_url, current_blurhash, current_displayname) = join3(
services.users.avatar_url(user_id),
services.users.blurhash(user_id),
services.users.displayname(user_id),
)
.await;
if displayname == current_display_name {
return Ok(());
let current_avatar_url = current_avatar_url.ok();
let current_blurhash = current_blurhash.ok();
let current_displayname = current_displayname.ok();
if displayname == current_displayname {
return;
}
services.users.set_displayname(user_id, displayname.clone());
// Send a new join membership event into all joined rooms
let mut joined_rooms = Vec::new();
for room_id in all_joined_rooms {
let Ok(content) = services
.rooms
.state_accessor
.room_state_get_content(room_id, &StateEventType::RoomMember, user_id.as_str())
.await
else {
continue;
};
let pdu = PduBuilder::state(
user_id.to_string(),
&RoomMemberEventContent {
displayname: displayname.clone(),
join_authorized_via_users_server: None,
..content
},
);
joined_rooms.push((pdu, room_id));
}
update_all_rooms(services, joined_rooms, user_id).await;
Ok(())
}
pub async fn update_avatar_url(
services: &Services, user_id: &UserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>,
all_joined_rooms: &[OwnedRoomId],
) -> Result<()> {
let current_avatar_url = services.users.avatar_url(user_id).await.ok();
let current_blurhash = services.users.blurhash(user_id).await.ok();
if current_avatar_url == avatar_url && current_blurhash == blurhash {
return Ok(());
}
services.users.set_avatar_url(user_id, avatar_url.clone());
services.users.set_blurhash(user_id, blurhash.clone());
// Send a new join membership event into all joined rooms
let avatar_url = &avatar_url;
let blurhash = &blurhash;
let avatar_url = &current_avatar_url;
let blurhash = &current_blurhash;
let displayname = &displayname;
let all_joined_rooms: Vec<_> = all_joined_rooms
.iter()
.try_stream()
.and_then(|room_id: &OwnedRoomId| async move {
let content = services
.rooms
.state_accessor
.room_state_get_content(room_id, &StateEventType::RoomMember, user_id.as_str())
.await?;
let pdu = PduBuilder::state(
user_id.to_string(),
&RoomMemberEventContent {
avatar_url: avatar_url.clone(),
blurhash: blurhash.clone(),
join_authorized_via_users_server: None,
..content
},
);
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
displayname: displayname.clone(),
membership: MembershipState::Join,
avatar_url: avatar_url.clone(),
blurhash: blurhash.clone(),
join_authorized_via_users_server: None,
reason: None,
is_direct: None,
third_party_invite: None,
});
Ok((pdu, room_id))
})
@@ -371,12 +351,65 @@ pub async fn update_avatar_url(
.await;
update_all_rooms(services, all_joined_rooms, user_id).await;
}
Ok(())
pub async fn update_avatar_url(
services: &Services,
user_id: &UserId,
avatar_url: Option<OwnedMxcUri>,
blurhash: Option<String>,
all_joined_rooms: &[OwnedRoomId],
) {
let (current_avatar_url, current_blurhash, current_displayname) = join3(
services.users.avatar_url(user_id),
services.users.blurhash(user_id),
services.users.displayname(user_id),
)
.await;
let current_avatar_url = current_avatar_url.ok();
let current_blurhash = current_blurhash.ok();
let current_displayname = current_displayname.ok();
if current_avatar_url == avatar_url && current_blurhash == blurhash {
return;
}
services.users.set_avatar_url(user_id, avatar_url.clone());
services.users.set_blurhash(user_id, blurhash.clone());
// Send a new join membership event into all joined rooms
let avatar_url = &avatar_url;
let blurhash = &blurhash;
let displayname = &current_displayname;
let all_joined_rooms: Vec<_> = all_joined_rooms
.iter()
.try_stream()
.and_then(|room_id: &OwnedRoomId| async move {
let pdu = PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
avatar_url: avatar_url.clone(),
blurhash: blurhash.clone(),
membership: MembershipState::Join,
displayname: displayname.clone(),
join_authorized_via_users_server: None,
reason: None,
is_direct: None,
third_party_invite: None,
});
Ok((pdu, room_id))
})
.ignore_err()
.collect()
.await;
update_all_rooms(services, all_joined_rooms, user_id).await;
}
pub async fn update_all_rooms(
services: &Services, all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: &UserId,
services: &Services,
all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>,
user_id: &UserId,
) {
for (pdu_builder, room_id) in all_joined_rooms {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
@@ -386,7 +419,7 @@ pub async fn update_all_rooms(
.build_and_append_pdu(pdu_builder, user_id, room_id, &state_lock)
.await
{
warn!(%user_id, %room_id, %e, "Failed to update/send new profile join membership update in room");
warn!(%user_id, %room_id, "Failed to update/send new profile join membership update in room: {e}");
}
}
}
+141 -80
View File
@@ -1,18 +1,22 @@
use axum::extract::State;
use conduit::{err, Err};
use conduwuit::{err, Err};
use ruma::{
api::client::{
error::ErrorKind,
push::{
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions, get_pushrule_enabled, get_pushrules_all,
get_pushrules_global_scope, set_pusher, set_pushrule, set_pushrule_actions, set_pushrule_enabled,
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions,
get_pushrule_enabled, get_pushrules_all, get_pushrules_global_scope, set_pusher,
set_pushrule, set_pushrule_actions, set_pushrule_enabled,
},
},
events::{
push_rules::{PushRulesEvent, PushRulesEventContent},
GlobalAccountDataEventType,
},
push::{InsertPushRuleError, PredefinedContentRuleId, PredefinedOverrideRuleId, RemovePushRuleError, Ruleset},
push::{
InsertPushRuleError, PredefinedContentRuleId, PredefinedOverrideRuleId,
RemovePushRuleError, Ruleset,
},
CanonicalJsonObject, CanonicalJsonValue,
};
use service::Services;
@@ -23,9 +27,10 @@ use crate::{Error, Result, Ruma};
///
/// Retrieves the push rules event for this user.
pub(crate) async fn get_pushrules_all_route(
State(services): State<crate::State>, body: Ruma<get_pushrules_all::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_pushrules_all::v3::Request>,
) -> Result<get_pushrules_all::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.sender_user();
let Some(content_value) = services
.account_data
@@ -40,31 +45,56 @@ pub(crate) async fn get_pushrules_all_route(
return recreate_push_rules_and_return(&services, sender_user).await;
};
let account_data_content = serde_json::from_value::<PushRulesEventContent>(content_value.into())
.map_err(|e| err!(Database(warn!("Invalid push rules account data event in database: {e}"))))?;
let account_data_content =
serde_json::from_value::<PushRulesEventContent>(content_value.into()).map_err(|e| {
err!(Database(warn!("Invalid push rules account data event in database: {e}")))
})?;
let mut global_ruleset = account_data_content.global;
// remove old deprecated mentions push rules as per MSC4210
// and update the stored server default push rules
#[allow(deprecated)]
{
use ruma::push::RuleKind::*;
if global_ruleset
.get(Override, PredefinedOverrideRuleId::ContainsDisplayName.as_str())
.is_some()
|| global_ruleset
.get(Override, PredefinedOverrideRuleId::RoomNotif.as_str())
.is_some()
|| global_ruleset
.get(Content, PredefinedContentRuleId::ContainsUserName.as_str())
.is_some()
{
global_ruleset
.remove(Override, PredefinedOverrideRuleId::ContainsDisplayName)
.ok();
global_ruleset
.remove(Override, PredefinedOverrideRuleId::RoomNotif)
.ok();
global_ruleset
.remove(Content, PredefinedContentRuleId::ContainsUserName)
.ok();
global_ruleset
.remove(Override, PredefinedOverrideRuleId::ContainsDisplayName)
.ok();
global_ruleset
.remove(Override, PredefinedOverrideRuleId::RoomNotif)
.ok();
global_ruleset.update_with_server_default(Ruleset::server_default(sender_user));
global_ruleset
.remove(Content, PredefinedContentRuleId::ContainsUserName)
.ok();
services
.account_data
.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(PushRulesEvent {
content: PushRulesEventContent { global: global_ruleset.clone() },
})
.expect("to json always works"),
)
.await?;
}
};
Ok(get_pushrules_all::v3::Response {
global: global_ruleset,
})
Ok(get_pushrules_all::v3::Response { global: global_ruleset })
}
/// # `GET /_matrix/client/r0/pushrules/global/`
@@ -73,7 +103,8 @@ pub(crate) async fn get_pushrules_all_route(
///
/// This appears to be the exact same as `GET /_matrix/client/r0/pushrules/`.
pub(crate) async fn get_pushrules_global_route(
State(services): State<crate::State>, body: Ruma<get_pushrules_global_scope::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_pushrules_global_scope::v3::Request>,
) -> Result<get_pushrules_global_scope::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -107,38 +138,64 @@ pub(crate) async fn get_pushrules_global_route(
});
};
let account_data_content = serde_json::from_value::<PushRulesEventContent>(content_value.into())
.map_err(|e| err!(Database(warn!("Invalid push rules account data event in database: {e}"))))?;
let account_data_content =
serde_json::from_value::<PushRulesEventContent>(content_value.into()).map_err(|e| {
err!(Database(warn!("Invalid push rules account data event in database: {e}")))
})?;
let mut global_ruleset = account_data_content.global;
// remove old deprecated mentions push rules as per MSC4210
// and update the stored server default push rules
#[allow(deprecated)]
{
use ruma::push::RuleKind::*;
if global_ruleset
.get(Override, PredefinedOverrideRuleId::ContainsDisplayName.as_str())
.is_some()
|| global_ruleset
.get(Override, PredefinedOverrideRuleId::RoomNotif.as_str())
.is_some()
|| global_ruleset
.get(Content, PredefinedContentRuleId::ContainsUserName.as_str())
.is_some()
{
global_ruleset
.remove(Override, PredefinedOverrideRuleId::ContainsDisplayName)
.ok();
global_ruleset
.remove(Override, PredefinedOverrideRuleId::RoomNotif)
.ok();
global_ruleset
.remove(Content, PredefinedContentRuleId::ContainsUserName)
.ok();
global_ruleset
.remove(Override, PredefinedOverrideRuleId::ContainsDisplayName)
.ok();
global_ruleset
.remove(Override, PredefinedOverrideRuleId::RoomNotif)
.ok();
global_ruleset.update_with_server_default(Ruleset::server_default(sender_user));
global_ruleset
.remove(Content, PredefinedContentRuleId::ContainsUserName)
.ok();
services
.account_data
.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(PushRulesEvent {
content: PushRulesEventContent { global: global_ruleset.clone() },
})
.expect("to json always works"),
)
.await?;
}
};
Ok(get_pushrules_global_scope::v3::Response {
global: global_ruleset,
})
Ok(get_pushrules_global_scope::v3::Response { global: global_ruleset })
}
/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}`
///
/// Retrieves a single specified push rule for this user.
pub(crate) async fn get_pushrule_route(
State(services): State<crate::State>, body: Ruma<get_pushrule::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_pushrule::v3::Request>,
) -> Result<get_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -164,9 +221,7 @@ pub(crate) async fn get_pushrule_route(
.map(Into::into);
if let Some(rule) = rule {
Ok(get_pushrule::v3::Response {
rule,
})
Ok(get_pushrule::v3::Response { rule })
} else {
Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found."))
}
@@ -176,7 +231,8 @@ pub(crate) async fn get_pushrule_route(
///
/// Creates a single specified push rule for this user.
pub(crate) async fn set_pushrule_route(
State(services): State<crate::State>, body: Ruma<set_pushrule::v3::Request>,
State(services): State<crate::State>,
body: Ruma<set_pushrule::v3::Request>,
) -> Result<set_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let body = body.body;
@@ -187,32 +243,33 @@ pub(crate) async fn set_pushrule_route(
.await
.map_err(|_| err!(Request(NotFound("PushRules event not found."))))?;
if let Err(error) =
account_data
.content
.global
.insert(body.rule.clone(), body.after.as_deref(), body.before.as_deref())
{
if let Err(error) = account_data.content.global.insert(
body.rule.clone(),
body.after.as_deref(),
body.before.as_deref(),
) {
let err = match error {
InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest(
| InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest(
ErrorKind::InvalidParam,
"Rule IDs starting with a dot are reserved for server-default rules.",
),
InsertPushRuleError::InvalidRuleId => {
Error::BadRequest(ErrorKind::InvalidParam, "Rule ID containing invalid characters.")
},
InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest(
| InsertPushRuleError::InvalidRuleId => Error::BadRequest(
ErrorKind::InvalidParam,
"Rule ID containing invalid characters.",
),
| InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest(
ErrorKind::InvalidParam,
"Can't place a push rule relatively to a server-default rule.",
),
InsertPushRuleError::UnknownRuleId => {
Error::BadRequest(ErrorKind::NotFound, "The before or after rule could not be found.")
},
InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest(
| InsertPushRuleError::UnknownRuleId => Error::BadRequest(
ErrorKind::NotFound,
"The before or after rule could not be found.",
),
| InsertPushRuleError::BeforeHigherThanAfter => Error::BadRequest(
ErrorKind::InvalidParam,
"The before rule has a higher priority than the after rule.",
),
_ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
};
return Err(err);
@@ -235,7 +292,8 @@ pub(crate) async fn set_pushrule_route(
///
/// Gets the actions of a single specified push rule for this user.
pub(crate) async fn get_pushrule_actions_route(
State(services): State<crate::State>, body: Ruma<get_pushrule_actions::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_pushrule_actions::v3::Request>,
) -> Result<get_pushrule_actions::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -259,18 +317,17 @@ pub(crate) async fn get_pushrule_actions_route(
.global
.get(body.kind.clone(), &body.rule_id)
.map(|rule| rule.actions().to_owned())
.ok_or(err!(Request(NotFound("Push rule not found."))))?;
.ok_or_else(|| err!(Request(NotFound("Push rule not found."))))?;
Ok(get_pushrule_actions::v3::Response {
actions,
})
Ok(get_pushrule_actions::v3::Response { actions })
}
/// # `PUT /_matrix/client/r0/pushrules/global/{kind}/{ruleId}/actions`
///
/// Sets the actions of a single specified push rule for this user.
pub(crate) async fn set_pushrule_actions_route(
State(services): State<crate::State>, body: Ruma<set_pushrule_actions::v3::Request>,
State(services): State<crate::State>,
body: Ruma<set_pushrule_actions::v3::Request>,
) -> Result<set_pushrule_actions::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -306,7 +363,8 @@ pub(crate) async fn set_pushrule_actions_route(
///
/// Gets the enabled status of a single specified push rule for this user.
pub(crate) async fn get_pushrule_enabled_route(
State(services): State<crate::State>, body: Ruma<get_pushrule_enabled::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_pushrule_enabled::v3::Request>,
) -> Result<get_pushrule_enabled::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -316,9 +374,7 @@ pub(crate) async fn get_pushrule_enabled_route(
|| body.rule_id.as_str() == PredefinedOverrideRuleId::ContainsDisplayName.as_str()
|| body.rule_id.as_str() == PredefinedOverrideRuleId::RoomNotif.as_str()
{
return Ok(get_pushrule_enabled::v3::Response {
enabled: false,
});
return Ok(get_pushrule_enabled::v3::Response { enabled: false });
}
let event: PushRulesEvent = services
@@ -332,18 +388,17 @@ pub(crate) async fn get_pushrule_enabled_route(
.global
.get(body.kind.clone(), &body.rule_id)
.map(ruma::push::AnyPushRuleRef::enabled)
.ok_or(err!(Request(NotFound("Push rule not found."))))?;
.ok_or_else(|| err!(Request(NotFound("Push rule not found."))))?;
Ok(get_pushrule_enabled::v3::Response {
enabled,
})
Ok(get_pushrule_enabled::v3::Response { enabled })
}
/// # `PUT /_matrix/client/r0/pushrules/global/{kind}/{ruleId}/enabled`
///
/// Sets the enabled status of a single specified push rule for this user.
pub(crate) async fn set_pushrule_enabled_route(
State(services): State<crate::State>, body: Ruma<set_pushrule_enabled::v3::Request>,
State(services): State<crate::State>,
body: Ruma<set_pushrule_enabled::v3::Request>,
) -> Result<set_pushrule_enabled::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -379,7 +434,8 @@ pub(crate) async fn set_pushrule_enabled_route(
///
/// Deletes a single specified push rule for this user.
pub(crate) async fn delete_pushrule_route(
State(services): State<crate::State>, body: Ruma<delete_pushrule::v3::Request>,
State(services): State<crate::State>,
body: Ruma<delete_pushrule::v3::Request>,
) -> Result<delete_pushrule::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -395,11 +451,13 @@ pub(crate) async fn delete_pushrule_route(
.remove(body.kind.clone(), &body.rule_id)
{
let err = match error {
RemovePushRuleError::ServerDefault => {
Error::BadRequest(ErrorKind::InvalidParam, "Cannot delete a server-default pushrule.")
},
RemovePushRuleError::NotFound => Error::BadRequest(ErrorKind::NotFound, "Push rule not found."),
_ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
| RemovePushRuleError::ServerDefault => Error::BadRequest(
ErrorKind::InvalidParam,
"Cannot delete a server-default pushrule.",
),
| RemovePushRuleError::NotFound =>
Error::BadRequest(ErrorKind::NotFound, "Push rule not found."),
| _ => Error::BadRequest(ErrorKind::InvalidParam, "Invalid data."),
};
return Err(err);
@@ -422,7 +480,8 @@ pub(crate) async fn delete_pushrule_route(
///
/// Gets all currently active pushers for the sender user.
pub(crate) async fn get_pushers_route(
State(services): State<crate::State>, body: Ruma<get_pushers::v3::Request>,
State(services): State<crate::State>,
body: Ruma<get_pushers::v3::Request>,
) -> Result<get_pushers::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -437,7 +496,8 @@ pub(crate) async fn get_pushers_route(
///
/// - TODO: Handle `append`
pub(crate) async fn set_pushers_route(
State(services): State<crate::State>, body: Ruma<set_pusher::v3::Request>,
State(services): State<crate::State>,
body: Ruma<set_pusher::v3::Request>,
) -> Result<set_pusher::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -452,7 +512,8 @@ pub(crate) async fn set_pushers_route(
/// user somehow has bad push rules, these must always exist per spec.
/// so recreate it and return server default silently
async fn recreate_push_rules_and_return(
services: &Services, sender_user: &ruma::UserId,
services: &Services,
sender_user: &ruma::UserId,
) -> Result<get_pushrules_all::v3::Response> {
services
.account_data
+82 -71
View File
@@ -1,9 +1,9 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduit::PduCount;
use conduwuit::{err, Err, PduCount};
use ruma::{
api::client::{error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt},
api::client::{read_marker::set_read_marker, receipt::create_receipt},
events::{
receipt::{ReceiptThread, ReceiptType},
RoomAccountDataEventType,
@@ -11,7 +11,7 @@ use ruma::{
MilliSecondsSinceUnixEpoch,
};
use crate::{Error, Result, Ruma};
use crate::{Result, Ruma};
/// # `POST /_matrix/client/r0/rooms/{roomId}/read_markers`
///
@@ -21,16 +21,16 @@ use crate::{Error, Result, Ruma};
/// - If `read_receipt` is set: Update private marker and public read receipt
/// EDU
pub(crate) async fn set_read_marker_route(
State(services): State<crate::State>, body: Ruma<set_read_marker::v3::Request>,
State(services): State<crate::State>,
body: Ruma<set_read_marker::v3::Request>,
) -> Result<set_read_marker::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.sender_user();
if let Some(fully_read) = &body.fully_read {
if let Some(event) = &body.fully_read {
let fully_read_event = ruma::events::fully_read::FullyReadEvent {
content: ruma::events::fully_read::FullyReadEventContent {
event_id: fully_read.clone(),
},
content: ruma::events::fully_read::FullyReadEventContent { event_id: event.clone() },
};
services
.account_data
.update(
@@ -49,44 +49,25 @@ pub(crate) async fn set_read_marker_route(
.reset_notification_counts(sender_user, &body.room_id);
}
if let Some(event) = &body.private_read_receipt {
let count = services
.rooms
.timeline
.get_pdu_count(event)
.await
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event does not exist."))?;
let count = match count {
PduCount::Backfilled(_) => {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Read receipt is in backfilled timeline",
))
},
PduCount::Normal(c) => c,
};
// ping presence
if services.globals.allow_local_presence() {
services
.rooms
.read_receipt
.private_read_set(&body.room_id, sender_user, count);
.presence
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
.await?;
}
if let Some(event) = &body.read_receipt {
let mut user_receipts = BTreeMap::new();
user_receipts.insert(
sender_user.clone(),
ruma::events::receipt::Receipt {
ts: Some(MilliSecondsSinceUnixEpoch::now()),
thread: ReceiptThread::Unthreaded,
},
);
let mut receipts = BTreeMap::new();
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new();
receipt_content.insert(event.to_owned(), receipts);
let receipt_content = BTreeMap::from_iter([(
event.to_owned(),
BTreeMap::from_iter([(
ReceiptType::Read,
BTreeMap::from_iter([(sender_user.to_owned(), ruma::events::receipt::Receipt {
ts: Some(MilliSecondsSinceUnixEpoch::now()),
thread: ReceiptThread::Unthreaded,
})]),
)]),
)]);
services
.rooms
@@ -102,6 +83,26 @@ pub(crate) async fn set_read_marker_route(
.await;
}
if let Some(event) = &body.private_read_receipt {
let count = services
.rooms
.timeline
.get_pdu_count(event)
.await
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
let PduCount::Normal(count) = count else {
return Err!(Request(InvalidParam(
"Event is a backfilled PDU and cannot be marked as read."
)));
};
services
.rooms
.read_receipt
.private_read_set(&body.room_id, sender_user, count);
}
Ok(set_read_marker::v3::Response {})
}
@@ -109,9 +110,10 @@ pub(crate) async fn set_read_marker_route(
///
/// Sets private read marker and public read receipt EDU.
pub(crate) async fn create_receipt_route(
State(services): State<crate::State>, body: Ruma<create_receipt::v3::Request>,
State(services): State<crate::State>,
body: Ruma<create_receipt::v3::Request>,
) -> Result<create_receipt::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.sender_user();
if matches!(
&body.receipt_type,
@@ -123,8 +125,16 @@ pub(crate) async fn create_receipt_route(
.reset_notification_counts(sender_user, &body.room_id);
}
// ping presence
if services.globals.allow_local_presence() {
services
.presence
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
.await?;
}
match body.receipt_type {
create_receipt::v3::ReceiptType::FullyRead => {
| create_receipt::v3::ReceiptType::FullyRead => {
let fully_read_event = ruma::events::fully_read::FullyReadEvent {
content: ruma::events::fully_read::FullyReadEventContent {
event_id: body.event_id.clone(),
@@ -140,20 +150,20 @@ pub(crate) async fn create_receipt_route(
)
.await?;
},
create_receipt::v3::ReceiptType::Read => {
let mut user_receipts = BTreeMap::new();
user_receipts.insert(
sender_user.clone(),
ruma::events::receipt::Receipt {
ts: Some(MilliSecondsSinceUnixEpoch::now()),
thread: ReceiptThread::Unthreaded,
},
);
let mut receipts = BTreeMap::new();
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new();
receipt_content.insert(body.event_id.clone(), receipts);
| create_receipt::v3::ReceiptType::Read => {
let receipt_content = BTreeMap::from_iter([(
body.event_id.clone(),
BTreeMap::from_iter([(
ReceiptType::Read,
BTreeMap::from_iter([(
sender_user.to_owned(),
ruma::events::receipt::Receipt {
ts: Some(MilliSecondsSinceUnixEpoch::now()),
thread: ReceiptThread::Unthreaded,
},
)]),
)]),
)]);
services
.rooms
@@ -168,29 +178,30 @@ pub(crate) async fn create_receipt_route(
)
.await;
},
create_receipt::v3::ReceiptType::ReadPrivate => {
| create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services
.rooms
.timeline
.get_pdu_count(&body.event_id)
.await
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event does not exist."))?;
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
let count = match count {
PduCount::Backfilled(_) => {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Read receipt is in backfilled timeline",
))
},
PduCount::Normal(c) => c,
let PduCount::Normal(count) = count else {
return Err!(Request(InvalidParam(
"Event is a backfilled PDU and cannot be marked as read."
)));
};
services
.rooms
.read_receipt
.private_read_set(&body.room_id, sender_user, count);
},
_ => return Err(Error::bad_database("Unsupported receipt type")),
| _ =>
return Err!(Request(InvalidParam(warn!(
"Received unknown read receipt type: {}",
&body.receipt_type
)))),
}
Ok(create_receipt::v3::Response {})
+7 -6
View File
@@ -1,5 +1,7 @@
use axum::extract::State;
use ruma::{api::client::redact::redact_event, events::room::redaction::RoomRedactionEventContent};
use ruma::{
api::client::redact::redact_event, events::room::redaction::RoomRedactionEventContent,
};
use crate::{service::pdu::PduBuilder, Result, Ruma};
@@ -9,7 +11,8 @@ use crate::{service::pdu::PduBuilder, Result, Ruma};
///
/// - TODO: Handle txn id
pub(crate) async fn redact_event_route(
State(services): State<crate::State>, body: Ruma<redact_event::v3::Request>,
State(services): State<crate::State>,
body: Ruma<redact_event::v3::Request>,
) -> Result<redact_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let body = body.body;
@@ -21,7 +24,7 @@ pub(crate) async fn redact_event_route(
.timeline
.build_and_append_pdu(
PduBuilder {
redacts: Some(body.event_id.clone().into()),
redacts: Some(body.event_id.clone()),
..PduBuilder::timeline(&RoomRedactionEventContent {
redacts: Some(body.event_id.clone()),
reason: body.reason.clone(),
@@ -35,7 +38,5 @@ pub(crate) async fn redact_event_route(
drop(state_lock);
Ok(redact_event::v3::Response {
event_id: event_id.into(),
})
Ok(redact_event::v3::Response { event_id })
}
+30 -18
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduit::{
use conduwuit::{
at,
utils::{result::FlatOk, stream::WidebandExt, IterStream, ReadyExt},
PduCount, Result,
@@ -8,7 +8,8 @@ use futures::StreamExt;
use ruma::{
api::{
client::relations::{
get_relating_events, get_relating_events_with_rel_type, get_relating_events_with_rel_type_and_event_type,
get_relating_events, get_relating_events_with_rel_type,
get_relating_events_with_rel_type_and_event_type,
},
Direction,
},
@@ -21,7 +22,8 @@ use crate::Ruma;
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
State(services): State<crate::State>, body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>,
State(services): State<crate::State>,
body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>,
) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
paginate_relations_with_filter(
&services,
@@ -47,7 +49,8 @@ pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}`
pub(crate) async fn get_relating_events_with_rel_type_route(
State(services): State<crate::State>, body: Ruma<get_relating_events_with_rel_type::v1::Request>,
State(services): State<crate::State>,
body: Ruma<get_relating_events_with_rel_type::v1::Request>,
) -> Result<get_relating_events_with_rel_type::v1::Response> {
paginate_relations_with_filter(
&services,
@@ -73,7 +76,8 @@ pub(crate) async fn get_relating_events_with_rel_type_route(
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}`
pub(crate) async fn get_relating_events_route(
State(services): State<crate::State>, body: Ruma<get_relating_events::v1::Request>,
State(services): State<crate::State>,
body: Ruma<get_relating_events::v1::Request>,
) -> Result<get_relating_events::v1::Response> {
paginate_relations_with_filter(
&services,
@@ -93,16 +97,24 @@ pub(crate) async fn get_relating_events_route(
#[allow(clippy::too_many_arguments)]
async fn paginate_relations_with_filter(
services: &Services, sender_user: &UserId, room_id: &RoomId, target: &EventId,
filter_event_type: Option<TimelineEventType>, filter_rel_type: Option<RelationType>, from: Option<&str>,
to: Option<&str>, limit: Option<UInt>, recurse: bool, dir: Direction,
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
target: &EventId,
filter_event_type: Option<TimelineEventType>,
filter_rel_type: Option<RelationType>,
from: Option<&str>,
to: Option<&str>,
limit: Option<UInt>,
recurse: bool,
dir: Direction,
) -> Result<get_relating_events::v1::Response> {
let start: PduCount = from
.map(str::parse)
.transpose()?
.unwrap_or_else(|| match dir {
Direction::Forward => PduCount::min(),
Direction::Backward => PduCount::max(),
| Direction::Forward => PduCount::min(),
| Direction::Backward => PduCount::max(),
});
let to: Option<PduCount> = to.map(str::parse).flat_ok();
@@ -115,11 +127,7 @@ async fn paginate_relations_with_filter(
.min(100);
// Spec (v1.10) recommends depth of at least 3
let depth: u8 = if recurse {
3
} else {
1
};
let depth: u8 = if recurse { 3 } else { 1 };
let events: Vec<PdusIterItem> = services
.rooms
@@ -145,8 +153,8 @@ async fn paginate_relations_with_filter(
.await;
let next_batch = match dir {
Direction::Forward => events.last(),
Direction::Backward => events.first(),
| Direction::Forward => events.last(),
| Direction::Backward => events.first(),
}
.map(at!(0))
.as_ref()
@@ -164,7 +172,11 @@ async fn paginate_relations_with_filter(
})
}
async fn visibility_filter(services: &Services, sender_user: &UserId, item: PdusIterItem) -> Option<PdusIterItem> {
async fn visibility_filter(
services: &Services,
sender_user: &UserId,
item: PdusIterItem,
) -> Option<PdusIterItem> {
let (_, pdu) = &item;
services
+24 -10
View File
@@ -2,7 +2,7 @@ use std::time::Duration;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{info, utils::ReadyExt, Err};
use conduwuit::{info, utils::ReadyExt, Err};
use rand::Rng;
use ruma::{
api::client::{
@@ -25,7 +25,8 @@ use crate::{
/// Reports an abusive room to homeserver admins
#[tracing::instrument(skip_all, fields(%client), name = "report_room")]
pub(crate) async fn report_room_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<report_room::v3::Request>,
) -> Result<report_room::v3::Response> {
// user authentication
@@ -78,14 +79,16 @@ pub(crate) async fn report_room_route(
/// Reports an inappropriate event to homeserver admins
#[tracing::instrument(skip_all, fields(%client), name = "report_event")]
pub(crate) async fn report_event_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<report_content::v3::Request>,
) -> Result<report_content::v3::Response> {
// user authentication
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
info!(
"Received event report by user {sender_user} for room {} and event ID {}, with reason: \"{}\"",
"Received event report by user {sender_user} for room {} and event ID {}, with reason: \
\"{}\"",
body.room_id,
body.event_id,
body.reason.as_deref().unwrap_or("")
@@ -114,8 +117,8 @@ pub(crate) async fn report_event_route(
services
.admin
.send_message(message::RoomMessageEventContent::text_markdown(format!(
"@room Event report received from {} -\n\nEvent ID: {}\nRoom ID: {}\nSent By: {}\n\nReport Score: \
{}\nReport Reason: {}",
"@room Event report received from {} -\n\nEvent ID: {}\nRoom ID: {}\nSent By: \
{}\n\nReport Score: {}\nReport Reason: {}",
sender_user.to_owned(),
pdu.event_id,
pdu.room_id,
@@ -136,10 +139,18 @@ pub(crate) async fn report_event_route(
/// check if report reasoning is less than or equal to 750 characters
/// check if reporting user is in the reporting room
async fn is_event_report_valid(
services: &Services, event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: Option<&String>,
score: Option<ruma::Int>, pdu: &PduEvent,
services: &Services,
event_id: &EventId,
room_id: &RoomId,
sender_user: &UserId,
reason: Option<&String>,
score: Option<ruma::Int>,
pdu: &PduEvent,
) -> Result<()> {
debug_info!("Checking if report from user {sender_user} for event {event_id} in room {room_id} is valid");
debug_info!(
"Checking if report from user {sender_user} for event {event_id} in room {room_id} is \
valid"
);
if room_id != pdu.room_id {
return Err(Error::BadRequest(
@@ -183,6 +194,9 @@ async fn is_event_report_valid(
/// enumerating for potential events existing in our server.
async fn delay_response() {
let time_to_wait = rand::thread_rng().gen_range(2..5);
debug_info!("Got successful /report request, waiting {time_to_wait} seconds before sending successful response.");
debug_info!(
"Got successful /report request, waiting {time_to_wait} seconds before sending \
successful response."
);
sleep(Duration::from_secs(time_to_wait)).await;
}
+3 -2
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduit::{Error, Result};
use conduwuit::{Error, Result};
use futures::StreamExt;
use ruma::api::client::{error::ErrorKind, room::aliases};
@@ -12,7 +12,8 @@ use crate::Ruma;
/// - Only users joined to the room are allowed to call this, or if
/// `history_visibility` is world readable in the room
pub(crate) async fn get_room_aliases_route(
State(services): State<crate::State>, body: Ruma<aliases::v3::Request>,
State(services): State<crate::State>,
body: Ruma<aliases::v3::Request>,
) -> Result<aliases::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
+123 -97
View File
@@ -1,7 +1,9 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduit::{debug_info, debug_warn, error, info, pdu::PduBuilder, warn, Err, Error, Result};
use conduwuit::{
debug_info, debug_warn, err, error, info, pdu::PduBuilder, warn, Err, Error, Result,
};
use futures::FutureExt;
use ruma::{
api::client::{
@@ -24,7 +26,7 @@ use ruma::{
},
int,
serde::{JsonObject, Raw},
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId,
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
};
use serde_json::{json, value::to_raw_value};
use service::{appservice::RegistrationInfo, Services};
@@ -49,7 +51,8 @@ use crate::{client::invite_helper, Ruma};
/// - Send invite events
#[allow(clippy::large_stack_frames)]
pub(crate) async fn create_room_route(
State(services): State<crate::State>, body: Ruma<create_room::v3::Request>,
State(services): State<crate::State>,
body: Ruma<create_room::v3::Request>,
) -> Result<create_room::v3::Response> {
use create_room::v3::RoomPreset;
@@ -59,7 +62,10 @@ pub(crate) async fn create_room_route(
&& body.appservice_info.is_none()
&& !services.users.is_admin(sender_user).await
{
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room creation has been disabled."));
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Room creation has been disabled.",
));
}
let room_id: OwnedRoomId = if let Some(custom_room_id) = &body.room_id {
@@ -91,8 +97,8 @@ pub(crate) async fn create_room_route(
services
.admin
.send_text(&format!(
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
\"lockdown_public_room_directory\" is enabled",
"Non-admin user {sender_user} tried to publish {0} to the room directory \
while \"lockdown_public_room_directory\" is enabled",
&room_id
))
.await;
@@ -115,25 +121,20 @@ pub(crate) async fn create_room_route(
};
let room_version = match body.room_version.clone() {
Some(room_version) => {
if services
.globals
.supported_room_versions()
.contains(&room_version)
{
| Some(room_version) =>
if services.server.supported_room_version(&room_version) {
room_version
} else {
return Err(Error::BadRequest(
ErrorKind::UnsupportedRoomVersion,
"This server does not support that room version.",
));
}
},
None => services.globals.default_room_version(),
},
| None => services.server.config.default_room_version.clone(),
};
let create_content = match &body.creation_content {
Some(content) => {
| Some(content) => {
use RoomVersionId::*;
let mut content = content
@@ -143,7 +144,7 @@ pub(crate) async fn create_room_route(
Error::bad_database("Failed to deserialise content as canonical JSON.")
})?;
match room_version {
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
content.insert(
"creator".into(),
json!(&sender_user).try_into().map_err(|e| {
@@ -152,24 +153,25 @@ pub(crate) async fn create_room_route(
})?,
);
},
_ => {
| _ => {
// V11+ removed the "creator" key
},
}
content.insert(
"room_version".into(),
json!(room_version.as_str())
.try_into()
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))?,
json!(room_version.as_str()).try_into().map_err(|_| {
Error::BadRequest(ErrorKind::BadJson, "Invalid creation content")
})?,
);
content
},
None => {
| None => {
use RoomVersionId::*;
let content = match room_version {
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => RoomCreateEventContent::new_v1(sender_user.clone()),
_ => RoomCreateEventContent::new_v11(),
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
RoomCreateEventContent::new_v1(sender_user.clone()),
| _ => RoomCreateEventContent::new_v11(),
};
let mut content = serde_json::from_str::<CanonicalJsonObject>(
to_raw_value(&content)
@@ -194,7 +196,8 @@ pub(crate) async fn create_room_route(
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_content).expect("create event content serialization"),
content: to_raw_value(&create_content)
.expect("create event content serialization"),
state_key: Some(String::new()),
..Default::default()
},
@@ -210,16 +213,13 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
sender_user.to_string(),
&RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
is_direct: Some(body.is_direct),
..RoomMemberEventContent::new(MembershipState::Join)
},
),
PduBuilder::state(sender_user.to_string(), &RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
is_direct: Some(body.is_direct),
..RoomMemberEventContent::new(MembershipState::Join)
}),
sender_user,
&room_id,
&state_lock,
@@ -231,8 +231,8 @@ pub(crate) async fn create_room_route(
// Figure out preset. We need it for preset specific events
let preset = body.preset.clone().unwrap_or(match &body.visibility {
room::Visibility::Public => RoomPreset::PublicChat,
_ => RoomPreset::PrivateChat, // Room visibility should not be custom
| room::Visibility::Public => RoomPreset::PublicChat,
| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
});
let mut users = BTreeMap::from_iter([(sender_user.clone(), int!(100))]);
@@ -240,7 +240,9 @@ pub(crate) async fn create_room_route(
if preset == RoomPreset::TrustedPrivateChat {
for invite in &body.invite {
if services.users.user_is_ignored(sender_user, invite).await {
return Err!(Request(Forbidden("You cannot invite users you have ignored to rooms.")));
return Err!(Request(Forbidden(
"You cannot invite users you have ignored to rooms."
)));
} else if services.users.user_is_ignored(invite, sender_user).await {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
@@ -251,8 +253,11 @@ pub(crate) async fn create_room_route(
}
}
let power_levels_content =
default_power_levels_content(body.power_level_content_override.as_ref(), &body.visibility, users)?;
let power_levels_content = default_power_levels_content(
body.power_level_content_override.as_ref(),
&body.visibility,
users,
)?;
services
.rooms
@@ -260,7 +265,8 @@ pub(crate) async fn create_room_route(
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&power_levels_content).expect("serialized power_levels event content"),
content: to_raw_value(&power_levels_content)
.expect("serialized power_levels event content"),
state_key: Some(String::new()),
..Default::default()
},
@@ -277,13 +283,10 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
String::new(),
&RoomCanonicalAliasEventContent {
alias: Some(room_alias_id.to_owned()),
alt_aliases: vec![],
},
),
PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent {
alias: Some(room_alias_id.to_owned()),
alt_aliases: vec![],
}),
sender_user,
&room_id,
&state_lock,
@@ -302,9 +305,9 @@ pub(crate) async fn create_room_route(
PduBuilder::state(
String::new(),
&RoomJoinRulesEventContent::new(match preset {
RoomPreset::PublicChat => JoinRule::Public,
| RoomPreset::PublicChat => JoinRule::Public,
// according to spec "invite" is the default
_ => JoinRule::Invite,
| _ => JoinRule::Invite,
}),
),
sender_user,
@@ -338,8 +341,8 @@ pub(crate) async fn create_room_route(
PduBuilder::state(
String::new(),
&RoomGuestAccessEventContent::new(match preset {
RoomPreset::PublicChat => GuestAccess::Forbidden,
_ => GuestAccess::CanJoin,
| RoomPreset::PublicChat => GuestAccess::Forbidden,
| _ => GuestAccess::CanJoin,
}),
),
sender_user,
@@ -371,7 +374,9 @@ pub(crate) async fn create_room_route(
pdu_builder.state_key.get_or_insert_with(String::new);
// Silently skip encryption events if they are not allowed
if pdu_builder.event_type == TimelineEventType::RoomEncryption && !services.globals.allow_encryption() {
if pdu_builder.event_type == TimelineEventType::RoomEncryption
&& !services.globals.allow_encryption()
{
continue;
}
@@ -403,12 +408,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
String::new(),
&RoomTopicEventContent {
topic: topic.clone(),
},
),
PduBuilder::state(String::new(), &RoomTopicEventContent { topic: topic.clone() }),
sender_user,
&room_id,
&state_lock,
@@ -421,16 +421,19 @@ pub(crate) async fn create_room_route(
drop(state_lock);
for user_id in &body.invite {
if services.users.user_is_ignored(sender_user, user_id).await {
return Err!(Request(Forbidden("You cannot invite users you have ignored to rooms.")));
return Err!(Request(Forbidden(
"You cannot invite users you have ignored to rooms."
)));
} else if services.users.user_is_ignored(user_id, sender_user).await {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
continue;
}
if let Err(e) = invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct)
.boxed()
.await
if let Err(e) =
invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct)
.boxed()
.await
{
warn!(%e, "Failed to send invite");
}
@@ -450,7 +453,10 @@ pub(crate) async fn create_room_route(
if services.globals.config.admin_room_notices {
services
.admin
.send_text(&format!("{sender_user} made {} public to the room directory", &room_id))
.send_text(&format!(
"{sender_user} made {} public to the room directory",
&room_id
))
.await;
}
info!("{sender_user} made {0} public to the room directory", &room_id);
@@ -463,21 +469,24 @@ pub(crate) async fn create_room_route(
/// creates the power_levels_content for the PDU builder
fn default_power_levels_content(
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>, visibility: &room::Visibility,
power_level_content_override: Option<&Raw<RoomPowerLevelsEventContent>>,
visibility: &room::Visibility,
users: BTreeMap<OwnedUserId, Int>,
) -> Result<serde_json::Value> {
let mut power_levels_content = serde_json::to_value(RoomPowerLevelsEventContent {
users,
..Default::default()
})
.expect("event is valid, we just created it");
let mut power_levels_content =
serde_json::to_value(RoomPowerLevelsEventContent { users, ..Default::default() })
.expect("event is valid, we just created it");
// secure proper defaults of sensitive/dangerous permissions that moderators
// (power level 50) should not have easy access to
power_levels_content["events"]["m.room.power_levels"] = serde_json::to_value(100).expect("100 is valid Value");
power_levels_content["events"]["m.room.server_acl"] = serde_json::to_value(100).expect("100 is valid Value");
power_levels_content["events"]["m.room.tombstone"] = serde_json::to_value(100).expect("100 is valid Value");
power_levels_content["events"]["m.room.encryption"] = serde_json::to_value(100).expect("100 is valid Value");
power_levels_content["events"]["m.room.power_levels"] =
serde_json::to_value(100).expect("100 is valid Value");
power_levels_content["events"]["m.room.server_acl"] =
serde_json::to_value(100).expect("100 is valid Value");
power_levels_content["events"]["m.room.tombstone"] =
serde_json::to_value(100).expect("100 is valid Value");
power_levels_content["events"]["m.room.encryption"] =
serde_json::to_value(100).expect("100 is valid Value");
power_levels_content["events"]["m.room.history_visibility"] =
serde_json::to_value(100).expect("100 is valid Value");
@@ -485,14 +494,18 @@ fn default_power_levels_content(
// useful in read-only announcement rooms that post a public poll.
power_levels_content["events"]["org.matrix.msc3381.poll.response"] =
serde_json::to_value(0).expect("0 is valid Value");
power_levels_content["events"]["m.poll.response"] = serde_json::to_value(0).expect("0 is valid Value");
power_levels_content["events"]["m.poll.response"] =
serde_json::to_value(0).expect("0 is valid Value");
// synapse does this too. clients do not expose these permissions. it prevents
// default users from calling public rooms, for obvious reasons.
if *visibility == room::Visibility::Public {
power_levels_content["events"]["m.call.invite"] = serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["m.call"] = serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["m.call.member"] = serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["m.call.invite"] =
serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["m.call"] =
serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["m.call.member"] =
serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["org.matrix.msc3401.call"] =
serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["org.matrix.msc3401.call.member"] =
@@ -501,7 +514,9 @@ fn default_power_levels_content(
if let Some(power_level_content_override) = power_level_content_override {
let json: JsonObject = serde_json::from_str(power_level_content_override.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override."))?;
.map_err(|_| {
Error::BadRequest(ErrorKind::BadJson, "Invalid power_level_content_override.")
})?;
for (key, value) in json {
power_levels_content[key] = value;
@@ -513,14 +528,16 @@ fn default_power_levels_content(
/// if a room is being created with a room alias, run our checks
async fn room_alias_check(
services: &Services, room_alias_name: &str, appservice_info: Option<&RegistrationInfo>,
services: &Services,
room_alias_name: &str,
appservice_info: Option<&RegistrationInfo>,
) -> Result<OwnedRoomAliasId> {
// Basic checks on the room alias validity
if room_alias_name.contains(':') {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Room alias contained `:` which is not allowed. Please note that this expects a localpart, not the full \
room alias.",
"Room alias contained `:` which is not allowed. Please note that this expects a \
localpart, not the full room alias.",
));
} else if room_alias_name.contains(char::is_whitespace) {
return Err(Error::BadRequest(
@@ -538,11 +555,15 @@ async fn room_alias_check(
return Err(Error::BadRequest(ErrorKind::Unknown, "Room alias name is forbidden."));
}
let full_room_alias = RoomAliasId::parse(format!("#{}:{}", room_alias_name, services.globals.config.server_name))
let server_name = services.globals.server_name();
let full_room_alias = OwnedRoomAliasId::parse(format!("#{room_alias_name}:{server_name}"))
.map_err(|e| {
info!("Failed to parse room alias {room_alias_name}: {e}");
Error::BadRequest(ErrorKind::InvalidParam, "Invalid room alias specified.")
})?;
err!(Request(InvalidParam(debug_error!(
?e,
?room_alias_name,
"Failed to parse room alias.",
))))
})?;
if services
.rooms
@@ -556,14 +577,20 @@ async fn room_alias_check(
if let Some(info) = appservice_info {
if !info.aliases.is_match(full_room_alias.as_str()) {
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace."));
return Err(Error::BadRequest(
ErrorKind::Exclusive,
"Room alias is not in namespace.",
));
}
} else if services
.appservice
.is_exclusive_alias(&full_room_alias)
.await
{
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias reserved by appservice."));
return Err(Error::BadRequest(
ErrorKind::Exclusive,
"Room alias reserved by appservice.",
));
}
debug_info!("Full room alias: {full_room_alias}");
@@ -585,8 +612,8 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
if custom_room_id.contains(':') {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Custom room ID contained `:` which is not allowed. Please note that this expects a localpart, not the \
full room ID.",
"Custom room ID contained `:` which is not allowed. Please note that this expects a \
localpart, not the full room ID.",
));
} else if custom_room_id.contains(char::is_whitespace) {
return Err(Error::BadRequest(
@@ -595,12 +622,11 @@ fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<Own
));
}
let full_room_id = format!("!{}:{}", custom_room_id, services.globals.config.server_name);
let server_name = services.globals.server_name();
let full_room_id = format!("!{custom_room_id}:{server_name}");
debug_info!("Full custom room ID: {full_room_id}");
RoomId::parse(full_room_id).map_err(|e| {
info!("User attempted to create room with custom room ID {custom_room_id} but failed parsing: {e}");
Error::BadRequest(ErrorKind::InvalidParam, "Custom room ID could not be parsed")
})
OwnedRoomId::parse(full_room_id)
.map_err(Into::into)
.inspect(|full_room_id| debug_info!(?full_room_id, "Full custom room ID"))
.inspect_err(|e| warn!(?e, ?custom_room_id, "Failed to create room with custom room ID",))
}
+4 -5
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduit::{err, Err, Event, Result};
use conduwuit::{err, Err, Event, Result};
use futures::{try_join, FutureExt, TryFutureExt};
use ruma::api::client::room::get_room_event;
@@ -9,7 +9,8 @@ use crate::{client::ignored_filter, Ruma};
///
/// Gets a single event.
pub(crate) async fn get_room_event_route(
State(services): State<crate::State>, ref body: Ruma<get_room_event::v3::Request>,
State(services): State<crate::State>,
ref body: Ruma<get_room_event::v3::Request>,
) -> Result<get_room_event::v3::Response> {
let event = services
.rooms
@@ -47,7 +48,5 @@ pub(crate) async fn get_room_event_route(
let event = event.to_room_event();
Ok(get_room_event::v3::Response {
event,
})
Ok(get_room_event::v3::Response { event })
}
+3 -2
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduit::{at, utils::BoolExt, Err, Result};
use conduwuit::{at, utils::BoolExt, Err, Result};
use futures::StreamExt;
use ruma::api::client::room::initial_sync::v3::{PaginationChunk, Request, Response};
@@ -8,7 +8,8 @@ use crate::Ruma;
const LIMIT_MAX: usize = 100;
pub(crate) async fn room_initial_sync_route(
State(services): State<crate::State>, body: Ruma<Request>,
State(services): State<crate::State>,
body: Ruma<Request>,
) -> Result<Response> {
let room_id = &body.room_id;
+31 -35
View File
@@ -1,7 +1,7 @@
use std::cmp::max;
use axum::extract::State;
use conduit::{err, info, pdu::PduBuilder, Error, Result};
use conduwuit::{err, info, pdu::PduBuilder, Error, Result};
use futures::StreamExt;
use ruma::{
api::client::{error::ErrorKind, room::upgrade_room},
@@ -21,15 +21,15 @@ use crate::Ruma;
/// Recommended transferable state events list from the spec
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
StateEventType::RoomServerAcl,
StateEventType::RoomEncryption,
StateEventType::RoomName,
StateEventType::RoomAvatar,
StateEventType::RoomTopic,
StateEventType::RoomEncryption,
StateEventType::RoomGuestAccess,
StateEventType::RoomHistoryVisibility,
StateEventType::RoomJoinRules,
StateEventType::RoomName,
StateEventType::RoomPowerLevels,
StateEventType::RoomServerAcl,
StateEventType::RoomTopic,
];
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
@@ -43,15 +43,16 @@ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
/// - Moves local aliases
/// - Modifies old room power levels to prevent users from speaking
pub(crate) async fn upgrade_room_route(
State(services): State<crate::State>, body: Ruma<upgrade_room::v3::Request>,
State(services): State<crate::State>,
body: Ruma<upgrade_room::v3::Request>,
) -> Result<upgrade_room::v3::Response> {
debug_assert!(
TRANSFERABLE_STATE_EVENTS.is_sorted(),
"TRANSFERABLE_STATE_EVENTS is not sorted"
);
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services
.globals
.supported_room_versions()
.contains(&body.new_version)
{
if !services.server.supported_room_version(&body.new_version) {
return Err(Error::BadRequest(
ErrorKind::UnsupportedRoomVersion,
"This server does not support that room version.",
@@ -76,13 +77,10 @@ pub(crate) async fn upgrade_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
String::new(),
&RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.clone(),
},
),
PduBuilder::state(String::new(), &RoomTombstoneEventContent {
body: "This room has been replaced".to_owned(),
replacement_room: replacement_room.clone(),
}),
sender_user,
&body.room_id,
&state_lock,
@@ -112,7 +110,7 @@ pub(crate) async fn upgrade_room_route(
{
use RoomVersionId::*;
match body.new_version {
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
create_event_content.insert(
"creator".into(),
json!(&sender_user).try_into().map_err(|e| {
@@ -121,7 +119,7 @@ pub(crate) async fn upgrade_room_route(
})?,
);
},
_ => {
| _ => {
// "creator" key no longer exists in V11+ rooms
create_event_content.remove("creator");
},
@@ -158,7 +156,8 @@ pub(crate) async fn upgrade_room_route(
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_event_content).expect("event is valid, we just created it"),
content: to_raw_value(&create_event_content)
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(String::new()),
redacts: None,
@@ -207,8 +206,8 @@ pub(crate) async fn upgrade_room_route(
.room_state_get(&body.room_id, event_type, "")
.await
{
Ok(v) => v.content.clone(),
Err(_) => continue, // Skipping missing events.
| Ok(v) => v.content.clone(),
| Err(_) => continue, // Skipping missing events.
};
services
@@ -262,7 +261,9 @@ pub(crate) async fn upgrade_room_route(
power_levels_event_content
.users_default
.checked_add(int!(1))
.ok_or_else(|| err!(Request(BadJson("users_default power levels event content is not valid"))))?,
.ok_or_else(|| {
err!(Request(BadJson("users_default power levels event content is not valid")))
})?,
);
// Modify the power levels in the old room to prevent sending of events and
@@ -271,14 +272,11 @@ pub(crate) async fn upgrade_room_route(
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(
String::new(),
&RoomPowerLevelsEventContent {
events_default: new_level,
invite: new_level,
..power_levels_event_content
},
),
PduBuilder::state(String::new(), &RoomPowerLevelsEventContent {
events_default: new_level,
invite: new_level,
..power_levels_event_content
}),
sender_user,
&body.room_id,
&state_lock,
@@ -288,7 +286,5 @@ pub(crate) async fn upgrade_room_route(
drop(state_lock);
// Return the replacement room id
Ok(upgrade_room::v3::Response {
replacement_room,
})
Ok(upgrade_room::v3::Response { replacement_room })
}
+17 -5
View File
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduit::{
use conduwuit::{
at, is_true,
result::FlatOk,
utils::{stream::ReadyExt, IterStream},
@@ -35,7 +35,10 @@ const BATCH_MAX: usize = 20;
///
/// - Only works if the user is currently joined to the room (TODO: Respect
/// history visibility)
pub(crate) async fn search_events_route(State(services): State<crate::State>, body: Ruma<Request>) -> Result<Response> {
pub(crate) async fn search_events_route(
State(services): State<crate::State>,
body: Ruma<Request>,
) -> Result<Response> {
let sender_user = body.sender_user();
let next_batch = body.next_batch.as_deref();
let room_events_result: OptionFuture<_> = body
@@ -56,7 +59,10 @@ pub(crate) async fn search_events_route(State(services): State<crate::State>, bo
#[allow(clippy::map_unwrap_or)]
async fn category_room_events(
services: &Services, sender_user: &UserId, next_batch: Option<&str>, criteria: &Criteria,
services: &Services,
sender_user: &UserId,
next_batch: Option<&str>,
criteria: &Criteria,
) -> Result<ResultRoomEvents> {
let filter = &criteria.filter;
@@ -186,11 +192,17 @@ async fn procure_room_state(services: &Services, room_id: &RoomId) -> Result<Roo
Ok(state_events)
}
async fn check_room_visible(services: &Services, user_id: &UserId, room_id: &RoomId, search: &Criteria) -> Result {
async fn check_room_visible(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
search: &Criteria,
) -> Result {
let check_visible = search.filter.rooms.is_some();
let check_state = check_visible && search.include_state.is_some_and(is_true!());
let is_joined = !check_visible || services.rooms.state_cache.is_joined(user_id, room_id).await;
let is_joined =
!check_visible || services.rooms.state_cache.is_joined(user_id, room_id).await;
let state_visible = !check_state
|| services
+15 -11
View File
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduit::{err, Err};
use conduwuit::{err, Err};
use ruma::{api::client::message::send_message_event, events::MessageLikeEventType};
use serde_json::from_str;
@@ -17,14 +17,17 @@ use crate::{service::pdu::PduBuilder, utils, Result, Ruma};
/// - Tries to send the event into the room, auth rules will determine if it is
/// allowed
pub(crate) async fn send_message_event_route(
State(services): State<crate::State>, body: Ruma<send_message_event::v3::Request>,
State(services): State<crate::State>,
body: Ruma<send_message_event::v3::Request>,
) -> Result<send_message_event::v3::Response> {
let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref();
let appservice_info = body.appservice_info.as_ref();
// Forbid m.room.encrypted if encryption is disabled
if MessageLikeEventType::RoomEncrypted == body.event_type && !services.globals.allow_encryption() {
if MessageLikeEventType::RoomEncrypted == body.event_type
&& !services.globals.allow_encryption()
{
return Err!(Request(Forbidden("Encryption has been disabled")));
}
@@ -60,8 +63,8 @@ pub(crate) async fn send_message_event_route(
let mut unsigned = BTreeMap::new();
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
let content =
from_str(body.body.body.json().get()).map_err(|e| err!(Request(BadJson("Invalid JSON body: {e}"))))?;
let content = from_str(body.body.body.json().get())
.map_err(|e| err!(Request(BadJson("Invalid JSON body: {e}"))))?;
let event_id = services
.rooms
@@ -80,13 +83,14 @@ pub(crate) async fn send_message_event_route(
)
.await?;
services
.transaction_ids
.add_txnid(sender_user, sender_device, &body.txn_id, event_id.as_bytes());
services.transaction_ids.add_txnid(
sender_user,
sender_device,
&body.txn_id,
event_id.as_bytes(),
);
drop(state_lock);
Ok(send_message_event::v3::Response {
event_id: event_id.into(),
})
Ok(send_message_event::v3::Response { event_id })
}
+55 -33
View File
@@ -1,6 +1,6 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{debug, err, info, utils::ReadyExt, warn, Err};
use conduwuit::{debug, err, info, utils::ReadyExt, warn, Err};
use futures::StreamExt;
use ruma::{
api::client::{
@@ -18,7 +18,7 @@ use ruma::{
},
uiaa::UserIdentifier,
},
UserId,
OwnedUserId, UserId,
};
use serde::Deserialize;
@@ -37,7 +37,8 @@ struct Claims {
/// the `type` field when logging in.
#[tracing::instrument(skip_all, fields(%client), name = "login")]
pub(crate) async fn get_login_types_route(
InsecureClientIp(client): InsecureClientIp, _body: Ruma<get_login_types::v3::Request>,
InsecureClientIp(client): InsecureClientIp,
_body: Ruma<get_login_types::v3::Request>,
) -> Result<get_login_types::v3::Response> {
Ok(get_login_types::v3::Response::new(vec![
get_login_types::v3::LoginType::Password(PasswordLoginType::default()),
@@ -61,13 +62,15 @@ pub(crate) async fn get_login_types_route(
/// supported login types.
#[tracing::instrument(skip_all, fields(%client), name = "login")]
pub(crate) async fn login_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp, body: Ruma<login::v3::Request>,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<login::v3::Request>,
) -> Result<login::v3::Response> {
// Validate login method
// TODO: Other login methods
let user_id = match &body.login_info {
#[allow(deprecated)]
login::v3::LoginInfo::Password(login::v3::Password {
| login::v3::LoginInfo::Password(login::v3::Password {
identifier,
password,
user,
@@ -75,9 +78,12 @@ pub(crate) async fn login_route(
}) => {
debug!("Got password login type");
let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
UserId::parse_with_server_name(user_id.to_lowercase(), services.globals.server_name())
UserId::parse_with_server_name(
user_id.to_lowercase(),
services.globals.server_name(),
)
} else if let Some(user) = user {
UserId::parse(user)
OwnedUserId::parse(user)
} else {
warn!("Bad login type: {:?}", &body.login_info);
return Err!(Request(Forbidden("Bad login type.")));
@@ -100,22 +106,29 @@ pub(crate) async fn login_route(
user_id
},
login::v3::LoginInfo::Token(login::v3::Token {
token,
}) => {
| login::v3::LoginInfo::Token(login::v3::Token { token }) => {
debug!("Got token login type");
if let Some(jwt_decoding_key) = services.globals.jwt_decoding_key() {
let token =
jsonwebtoken::decode::<Claims>(token, jwt_decoding_key, &jsonwebtoken::Validation::default())
.map_err(|e| {
warn!("Failed to parse JWT token from user logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Token is invalid.")
})?;
let token = jsonwebtoken::decode::<Claims>(
token,
jwt_decoding_key,
&jsonwebtoken::Validation::default(),
)
.map_err(|e| {
warn!("Failed to parse JWT token from user logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Token is invalid.")
})?;
let username = token.claims.sub.to_lowercase();
UserId::parse_with_server_name(username, services.globals.server_name())
.map_err(|e| err!(Request(InvalidUsername(debug_error!(?e, "Failed to parse login username")))))?
UserId::parse_with_server_name(username, services.globals.server_name()).map_err(
|e| {
err!(Request(InvalidUsername(debug_error!(
?e,
"Failed to parse login username"
))))
},
)?
} else {
return Err!(Request(Unknown(
"Token login is not supported (server has no jwt decoding key)."
@@ -123,15 +136,18 @@ pub(crate) async fn login_route(
}
},
#[allow(deprecated)]
login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService {
| login::v3::LoginInfo::ApplicationService(login::v3::ApplicationService {
identifier,
user,
}) => {
debug!("Got appservice login type");
let user_id = if let Some(UserIdentifier::UserIdOrLocalpart(user_id)) = identifier {
UserId::parse_with_server_name(user_id.to_lowercase(), services.globals.server_name())
UserId::parse_with_server_name(
user_id.to_lowercase(),
services.globals.server_name(),
)
} else if let Some(user) = user {
UserId::parse(user)
OwnedUserId::parse(user)
} else {
warn!("Bad login type: {:?}", &body.login_info);
return Err(Error::BadRequest(ErrorKind::forbidden(), "Bad login type."));
@@ -143,18 +159,27 @@ pub(crate) async fn login_route(
if let Some(ref info) = body.appservice_info {
if !info.is_user_match(&user_id) {
return Err(Error::BadRequest(ErrorKind::Exclusive, "User is not in namespace."));
return Err(Error::BadRequest(
ErrorKind::Exclusive,
"User is not in namespace.",
));
}
} else {
return Err(Error::BadRequest(ErrorKind::MissingToken, "Missing appservice token."));
return Err(Error::BadRequest(
ErrorKind::MissingToken,
"Missing appservice token.",
));
}
user_id
},
_ => {
| _ => {
warn!("Unsupported or unknown login type: {:?}", &body.login_info);
debug!("JSON body: {:?}", &body.json_body);
return Err(Error::BadRequest(ErrorKind::Unknown, "Unsupported or unknown login type."));
return Err(Error::BadRequest(
ErrorKind::Unknown,
"Unsupported or unknown login type.",
));
},
};
@@ -233,7 +258,9 @@ pub(crate) async fn login_route(
/// - Triggers device list updates
#[tracing::instrument(skip_all, fields(%client), name = "logout")]
pub(crate) async fn logout_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp, body: Ruma<logout::v3::Request>,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<logout::v3::Request>,
) -> Result<logout::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");
@@ -243,9 +270,6 @@ pub(crate) async fn logout_route(
.remove_device(sender_user, sender_device)
.await;
// send device list update for user after logout
services.users.mark_device_key_update(sender_user).await;
Ok(logout::v3::Response::new())
}
@@ -264,7 +288,8 @@ pub(crate) async fn logout_route(
/// user.
#[tracing::instrument(skip_all, fields(%client), name = "logout")]
pub(crate) async fn logout_all_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<logout_all::v3::Request>,
) -> Result<logout_all::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@@ -275,8 +300,5 @@ pub(crate) async fn logout_all_route(
.for_each(|device_id| services.users.remove_device(sender_user, device_id))
.await;
// send device list update for user after logout
services.users.mark_device_key_update(sender_user).await;
Ok(logout_all::v3::Response::new())
}

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