Compare commits

..
532 Commits
Author SHA1 Message Date
Jason Volk 4fea0abac4 Bump 1.4.7
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-03 16:40:08 +00:00
Jason Volk cab0b3fd9f Reduce join-server selection randomness with separated sets.
Keep alias server biased at the front unless configured deprioritized.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-03 09:23:33 +00:00
Jason Volk f311332bad Add configuration for make_join and send_join attempt counts.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-03 09:23:33 +00:00
Jason Volk fb0c2a2832 Allow clippy::useless-let-if-seq.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-03 09:23:29 +00:00
yefim 3403943880 Fix LDAP unauthorized 2025-12-02 18:34:18 +02:00
Jason Volk 7e69e9b051 Bump jemalloc.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:39:44 +00:00
Jason Volk 27ed9b88f1 Bump Complement.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:21:53 +00:00
Jason Volk 10edc3bd5e Disconnect jemalloc stats and prof until after default feature-shifting.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:21:53 +00:00
Jason Volk 6553ba829f Fix room-leave conditions for rooms without joined users or remote servers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:01:52 +00:00
Jason Volk 1ce3d2b01f Add ReadyBoolExt special case for ReadyEqExt.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 08:01:52 +00:00
Jason Volk 50bfb0fe5e Provide default for empty PathAndQuery (Sentry ID c09f58adf5...)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 05:20:26 +00:00
Jason Volk 74f8cd3708 Stop panic propagation on malloc_stats_print() callback.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-12-01 05:20:26 +00:00
Jason Volk bcc2be7661 docs: Use a !tip for the generic deployment sub-header.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-30 00:59:06 +00:00
Jason Volk 4f9b1d6dbd Add config options to coarsely disable log/tracing without initialization.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-30 00:52:55 +00:00
Jason Volk c9362b8605 Optimize formatted event serializations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-29 12:26:49 +00:00
Jason Volk e84d6666c0 Optimize separate constraint for each stream argument.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 11:13:27 +00:00
Jason Volk 7666bb63d8 Optimize syncv3 joined room path with additional conditional queries.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 07:22:55 +00:00
Jason Volk 3125b7e291 Actually re-establish and assert Syncness of Error. (98affbdeaf)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 07:22:55 +00:00
Jason Volk aebe2d72de Add utils for asserting common marker traits.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 07:22:55 +00:00
Jason Volk 8f54d9dc09 Replace unnecessary exchange util.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 07:22:55 +00:00
Jason Volk 52b156e034 Implement MSC4289/MSC4291 (room version 12) upgrade support. (closes #141)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 00:22:45 +00:00
Jason Volk d2d6a98180 Improve backfill server selection post-v12 power and creator changes.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-27 00:22:45 +00:00
Jason Volk 0bbc228f7a Bump Ruma for context events_before serialization.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 23:25:18 +00:00
Jason Volk 0d782095ad Refactor room upgrade endpoint; rollback on failure.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 08:16:01 +00:00
Jason Volk f6b95ff1c4 Cleanup and optimize several user_can conditions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 08:16:01 +00:00
Jason Volk 347298d7d6 Additional json/jsint Error cases for ErrorKind and StatusCode.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 06:49:57 +00:00
Jason Volk 442a5aafeb Derive or impl fmt::Debug on several structs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 06:49:57 +00:00
Jason Volk 8bb0d02619 Replace calls through sender request interface.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 06:49:57 +00:00
Jason Volk 71f3ccf140 Unbox and pin database streams.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 06:49:57 +00:00
Jason Volk 98affbdeaf Re-establish Syncness of Error.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 03:02:26 +00:00
Jason Volk e5073165f0 Cleanup TryIgnore/TryExpect stream extensions related.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-26 03:02:26 +00:00
Jason Volk 6705efc760 Fix lib search path for liburing.a on redhat/centos.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 21:50:14 +00:00
Jason Volk 61085f4707 Bump RocksDB to 10.7.5-tuwunel.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 03:36:17 +00:00
Jason Volk deb5c65885 docker: Add conditions to set assert_linkage for Dockerfile.install
ci: Check packages when using test branch.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 03:36:17 +00:00
Jason Volk 11c4cbf54e docker: Build RocksDB with NUMA policy support.
docker: Build RocksDB with LTO.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 03:30:19 +00:00
Jason Volk a748edd621 Reduce high volume of strings from FedDest and ActualDest in resolver.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-23 03:30:19 +00:00
Jason Volk 9e539d0a22 Activate recursive relations. Maximum fan-out.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-21 03:16:01 +00:00
Jason Volk 5260912c3b Add admin configurable Access-Control-Allow-Origin.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-20 08:34:31 +00:00
Jason Volk b924412efb Reduce tracing level of request handler return value.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-20 08:34:31 +00:00
Jason Volk 120ab1d068 Add config circuit-breaker for heroes calculations during sync.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-20 08:34:31 +00:00
Jason Volk e60e86e9ed Optimize heroes calculation using member state instead of timeline scan.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-19 02:14:35 +00:00
Jason Volk 5147b541b5 Fix relations pagination compliance.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-19 01:43:28 +00:00
Jason Volk f4eeaaf167 Remove unknown multilingual directive from book.toml.
Fix and improve mdbook related.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:46:25 +00:00
Jason Volk 54fe4bdf56 docker: Move complement logs into tests directory.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:29:21 +00:00
Jason Volk fe12daead9 Fix short-circuit condition; propagate 403 in unlikely branch anyway. (fixes #219)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:29:21 +00:00
Jason Volk 6f29a34ffb Fix existing event reinsertions during backfill handling.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:29:21 +00:00
Jason Volk dafbe59d00 Return whether event already existed from event_handler.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 19:29:21 +00:00
Jason Volk 0746f4b1ad Eliminate clone of Pdu to queue during relations recursions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 10:47:59 +00:00
Jason Volk 90228e4865 Combine pdu_metadata Data into Service unit.
Move recursive component of get_relations from service to api crate.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 10:47:59 +00:00
Jason Volk 53b5eb4ba6 ci: Re-enable lychee.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 10:47:59 +00:00
dasha_uwuandJason Volk 20f080fc49 Fix typo. 2025-11-18 10:36:02 +00:00
Jason Volk 424ed3d7ad Bump dependencies.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-18 03:56:37 +00:00
Tails32 728085bd1b Remove allow_check_for_updates from configs [ci skip]
to suppress a warning at startup due to removal of the update module in edb245a2ba
2025-11-16 01:05:08 +01:00
Jason Volk 64a029ee09 Bump 1.4.6
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-06 19:34:51 +00:00
Jason Volk a3f6971579 docker: Add rust-sdk memcheck target.
docker: Separate and tweak cargo cache paths.

ci: Improve memcheck job targets.

cargo: Add missing debug syms to bench profile.

docker: Optimize layer copying.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-06 19:34:51 +00:00
Jason Volk 51681aec1b docker: Bump complement.
ci: Run complement with optimizations.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-06 17:14:45 +00:00
Jason Volk 39c84fabb4 Optimize filter fetch in syncv3 prologue.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-06 17:14:45 +00:00
Jason Volk f77bd41837 Add empty config default for rtc_transports to not generate false.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-06 17:14:45 +00:00
Jason Volk 6f34b8e9ca Fix missing pushrules evaluation on federation invite.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-06 17:14:45 +00:00
Jason Volk 5051da493a Fix pushrule evals relying on non-option RoomPowerLevels.
Add option to bypass pushrules to send everything to pushers.

Improve robustness of pushrule eval loops.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-06 17:14:45 +00:00
Jason Volk ff0e007c45 Fix readability of admin query account_data outputs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-06 17:14:45 +00:00
Alexandru Scvorțov b85fb5ea6f Fix tests in nix and re-enable 2025-11-06 16:38:13 +00:00
dasha_uwuandJason Volk e905538269 Shuffle additional join servers. 2025-11-04 06:40:51 +00:00
Jason Volk 6f672b7304 Handle missing media dir as individual item errors w/o aborting check. (closes #213)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-04 06:40:51 +00:00
Jason Volk 4363ed6ec3 Adapt MSC4143 rtc_foci support to Ruma types. (e1f89b69ea)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-04 06:40:51 +00:00
Jason Volk dd50a4cb0b Fix ignored column descriptions; enhance preservation behavior. (7320d0a40b)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-03 23:02:42 +00:00
tototomate123andJason Volk e0a997c227 docs(reverse-proxy): restructure and add nginx guide
- Moved Caddy and Nginx configuration examples to separate files
- Added documentation for Nginx setup
- Updated generic reverse proxy section
2025-11-03 12:49:04 +00:00
tototomate123andJason Volk e1f89b69ea feat: Add Element Call / MatrixRTC support 2025-11-03 11:36:19 +00:00
dasha_uwuandJason Volk 888f72d8d0 Generalize common_prefix for AsStr inputs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-03 11:36:19 +00:00
Jason Volk 06618eadab Re-establish federating with several Conduit endpoints.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-03 11:36:19 +00:00
Benedikt Vollmerhaus 05390d6097 Fix shared TURN secret from turn_secret_file not being used 2025-11-03 12:30:00 +01:00
Jason Volk 1f803fe3a9 Fix verification loss; workaround Nheko-Reborn/nheko#1908 (closes #146)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-03 04:45:31 +00:00
Jason Volk 1492d68e25 Additional tracing of request body.
Optimize router request parse types.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-03 04:45:31 +00:00
Jason Volk c1aa94fb91 Describe roomsynctoken_shortstatehash for deletion.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-03 02:05:35 +00:00
Jason Volk 7320d0a40b Improve config options for missing and dropping db columns.
Implement actual drop functionality.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-03 02:05:35 +00:00
Jason Volk abded2d442 Allow guests even when token required for normal registration. (closes #189)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-02 18:19:11 +00:00
Jason Volk 4afd6f347b Implement GET /_matrix/client/v3/events live room previews.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-02 08:06:32 +00:00
Jason Volk 6b8d6956a3 Fix timeline events order in /initialSync.
Implement acccount_data for response.

Ensure consistency of revealed events.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-02 08:06:32 +00:00
Jason Volk f59d62c01c Bump Ruma for CanonicalJson property name optimizations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-02 03:57:11 +00:00
Jason Volk a14556da97 Implement encryption_enabled_by_default_for_room_type. (closes #29)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-02 00:02:49 +00:00
Jason Volk 8b1de3d8db Add update for device last_seen_ts. (closes #40)
Piggyback on presence ping for controlled device seen updates.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-02 00:02:49 +00:00
Jason Volk 240b498489 Add unwrap suite to future::OptionExt extensions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-02 00:02:49 +00:00
Jason Volk d680a6ba53 Bump Ruma for device type optimizations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-02 00:02:49 +00:00
Jason Volk aa3f14cd57 Optimize federation user devices response.
Modernize some error branches.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-01 23:15:37 +00:00
Jason Volk 15627bc8d0 Simplify device metadata update interface.
Add broadcast argument; reorder notify to after write.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-01 23:15:37 +00:00
Jason Volk 084facf474 Move user profile related functions to profile unit.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-11-01 23:15:37 +00:00
Jason Volk d24986edf1 sliding-sync: Stop propagation from room handlers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-31 03:38:35 +00:00
Jason Volk ce1ac277a6 Refactor sync v3 leave handler.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-31 03:38:35 +00:00
Jason Volk 7aeed0a95a Fix next_batch pagination tokens in relations response.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-31 03:38:35 +00:00
Jason Volk 9265748a57 Remove legacy ruma return type on sync endpoint.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 16:28:27 +00:00
Jason Volk e85cfdf48a Remove errant inline. (48923b3657)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 14:01:57 +00:00
Jason Volk 48923b3657 Implement notifications retrieval. (closes #201)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 03:48:16 +00:00
Jason Volk aedaf3f0c1 Fix pushing useless notifications.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 03:48:16 +00:00
Jason Volk b24b59dc38 Add admin query raw command to clear table.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 03:48:16 +00:00
Jason Volk 738b5e3fa5 Add attempted newtype struct name to deserialization error message.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 03:48:16 +00:00
Jason Volk 1f0cfec5ca Use Result for last_notification_read as zero-default not appropriate.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 03:48:16 +00:00
Jason Volk 9c5caa3a5f Move appservice request unit from sender to appservice.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 03:48:16 +00:00
Jason Volk 1cf4a26ae9 docker: Use prefix match for nightly rust_toolchain settings.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-30 03:48:16 +00:00
Jason Volk 3694ffbab3 Bump Ruma w/ PushRule type optimizations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-28 23:23:27 +00:00
Jason Volk af0e01e016 Add util to assist with Display/to_string for SmallString.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-28 23:23:27 +00:00
Jason Volk 5e89f0acae Split pusher service send/request into unit.
Refactor sender's push destination handler.

Combine remnants of service::rooms::user with pusher service.

Further split and reorg pusher service units.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-28 23:23:19 +00:00
Jason Volk 563873af77 Use signed PduCount for better bumpstamp on create and backfill.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 15:24:50 +00:00
Jason Volk d2072080c9 Add admin query for remote alias resolve.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 15:24:17 +00:00
Jason Volk e191730950 Tracing span adjustments for ret verbosity.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 15:15:12 +00:00
Jason Volk f660e00bb5 Implement Dehydrated Devices MSC3814 (closes #200)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 12:06:43 +00:00
Jason Volk f613d0c2ad Add admin command to delete a user's device.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 10:55:12 +00:00
Jason Volk 7596ad2019 Add rountripping test for database cbor serialization.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 10:41:49 +00:00
Jason Volk 384add9784 ci: Extract Nix artifacts.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:39:57 +00:00
Jason Volk a234f019b1 Advertise MSC2659 support; reorder features by MSC ascending.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk e8a87bdfa3 Remove DynamicUser from systemd files (closes #207).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk 6a4aff424f Add count value to the to_device iter item.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk 8959d9e2c1 Add bulk one_time_keys adder to interface.
Add device_exists to interface.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk a30c043386 Support creating devices without access_tokens.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk a39ef994d2 Add is_err_or to Result utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk b714f24029 sliding-sync: Filter redacted events from bump calculation.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk 2263f2e874 docker: Add dirty-runs option passed to complement.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk 9abe9becd6 Split pusher/push-rule from timeline append.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk 4b74c01895 Split appservice dissemination from timeline::append().
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk 1223763e2b Split sliding-sync into additional units.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk 9b64c1f105 Bump Nix flake. 2025-10-27 09:06:02 +00:00
Jason Volk 212a8434a8 Bump Rust 1.89
We can't upgrade to 1.90 because NixOS breaks. 🙂‍↔️

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-27 09:06:02 +00:00
Jason Volk bfaac8b5a2 Bump dependencies.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 17:46:25 +00:00
Jason Volk 9af15ecbba Bump 1.4.5
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 13:33:32 +00:00
Jason Volk f66a83763e Persist sliding-sync state; mitigate initial-sync.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 13:33:32 +00:00
Jason Volk 718c3adcb2 ci: Expand the rust-sdk integration matrix.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 08:44:16 +00:00
Jason Volk af80482c04 Add branch in room handler for left rooms.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 08:34:49 +00:00
Jason Volk a20ddcd586 Recency bump on own membership event.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 08:08:26 +00:00
Jason Volk b483306367 Fix sliding-sync invite regression.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-25 07:52:40 +00:00
Jason Volk f5e98467be Improve sliding-sync robustness to deeper replays.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-24 18:31:48 +00:00
Jason Volk d44db45f83 Adjust the order of operations during read_marker handling.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-24 18:31:48 +00:00
Jason Volk 0397bb8237 Implement specified non-blocking semantic for sliding-sync.
Simplify sliding-sync watch loop and bounds.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-23 11:00:55 +00:00
Jason Volk 4010fc62bc Use take_while rather than filter for scanning last_receipt_count.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-23 03:37:52 +00:00
Jason Volk 0d823a2822 Revert "Involve m.fully_read for resetting notification counts."
This reverts commit 0df5e5e7ac.
2025-10-23 03:37:52 +00:00
Jason Volk 9cd175b125 Additional logging/tracing tweaks around sliding-sync.
Additional spans around receipt service interface.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-23 03:37:52 +00:00
Jason Volk 050a1a350a Add dbg profile to Cargo; default to faster builds accomodating new users. 2025-10-23 03:37:52 +00:00
Jason Volk ec0f872f8f Add tracing around db watcher notify including cf name.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 20:23:30 +00:00
Jason Volk 024e8eae62 Implement num_live for sliding-sync room response.
Avoid using origin_server_ts for recency stamp.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 09:14:41 +00:00
Jason Volk 4fd60b2605 Improve sliding-sync selector and windowing logic.
Bump Ruma; eliminate premature release compat.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 08:15:25 +00:00
Jason Volk e53968d9eb Improve tracing spans for notif counts and sliding-sync exts.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 07:05:03 +00:00
Jason Volk 7cbc2ee385 Fix the accidentally dual-keyspaced cf from conduit.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 07:03:10 +00:00
Jason Volk 0df5e5e7ac Involve m.fully_read for resetting notification counts.
EXA is not reliably removing its unread marker any other way. Clients
wishing to move the fully_read marker in the other direction should
file an issue to make themselves known.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-22 06:52:30 +00:00
Jason Volk 312eb69450 Add tool to get sibling connections for a device.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 17:48:08 +00:00
Jason Volk 5b620a2c37 Add .and_is() / .and_if() to the BoolExt smalltalk.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 17:41:42 +00:00
Jason Volk 3b4fbb8c1a Fix inverted debug assert message.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 15:48:05 +00:00
Jason Volk 158d44e1a9 docs: Remove line about other reverse proxies. (#197)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 15:02:36 +00:00
Jason Volk d2aab468cf Fixes for sliding-sync request replays.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 06:46:02 +00:00
Jason Volk aa4486dfdf Simplify types, mitigate expansion; eliminate unnecessary move.
Further simplify future::BoolExt toward type expansion mitigation.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-21 05:25:11 +00:00
Jason Volk 2a662445b6 Make account_data.last_count() upper bound argument optional.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-20 23:58:38 +00:00
Jason Volk 5b3f0fde23 Add invite rooms to sync v3 watch rooms list.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-20 23:57:52 +00:00
Jason Volk 9640afebff Join the async prologue operations in sync v3.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-20 23:55:42 +00:00
Jason Volk dd5c5c7a4a Notify watchers on delete operations in addition to inserts. 2025-10-20 20:45:38 +00:00
Jason Volk fc0f04defa Optimize watcher range collection on notify.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-20 20:44:45 +00:00
Jason Volk 7d8f7cbe5d Bump 1.4.4
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-16 01:55:43 +00:00
Jason Volk 4e0249cd2f Fix sliding-sync again by resetting after interrupted requests. (fixes #190)
This solution is not elegant but appears to be correct for fixing the issue. A
a more efficient solution might be possible without resetting the connection
which should be further investigated.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-16 01:05:16 +00:00
Jason Volk d5b39aa995 Bump 1.4.3
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-11 02:12:43 +00:00
Jason Volk 46c940b863 Refactor sliding window selector. (fixes #170)
Refactor list filtering.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 15:56:08 +00:00
Jason Volk ab8536d5c3 Add query to get latest account data count.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 12:44:23 +00:00
Jason Volk 4918868632 Add query to get last read receipt for room.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 12:44:23 +00:00
Jason Volk 9e00f70197 Allow nursery clippy::tuple_array_conversions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 08:30:08 +00:00
Jason Volk d3aaf9e4a9 Additional FutureBoolExt; attempt to reduce type-length expansion.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 96dc56ad07 Relax clippy::rest-pat-in-fully-bound-structs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk e12b0262da Fix unnecessary async for future and/or utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk e5bf005eaf Simplify state_cache.user_memberships() masking for callers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 02ccb1dceb Cleanup legacy config path component.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk a450eb96eb ci: Remove default-features builds from publication to registries.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 155af0fda3 Add iteration of space children; simplify space child event fetcher.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 5c61b4d4a3 Add room state accessor iterating state_keys joined by respective event_id.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-10 07:37:40 +00:00
Jason Volk 97e709492c Add is_direct() alternative query for m.room.member.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 51fc2342a4 Add is_direct() query for account_data.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 4ca68deef8 Add better interface for getting and setting room tag account data.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 4d8d64f5c7 Add missing wildcard matching for sliding-sync required state.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk dff30e5924 Additional state accessor iterations for pdus of a specific type.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 7fee459b1a Add admin diagnostic query suite for sync state.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk a6127fcd1a Provide sync watch rooms as argument rather than assuming all joined rooms.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:12 +00:00
Jason Volk 6c724bbc2f Cleanup/optimize sliding sync types and arguments.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-08 03:33:08 +00:00
Jason Volk b1ea7b101d Split sliding-sync extensions into units.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 15:56:49 +00:00
Jason Volk 4baa25f66f Add presence ping to sliding-sync.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 15:56:49 +00:00
Jason Volk 227b77e58e Refactor sliding-sync e2ee extension.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 15:56:49 +00:00
Jason Volk 54057da84e ci: Fix artifact extraction argument for package.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 15:56:49 +00:00
Jason Volk 732825a390 ci: Publish verbose logging container builds to registries. (closes #181)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 04:11:57 +00:00
Jason Volk 6b74425f76 ci: Add integration memcheck to test matrix.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:22:02 +00:00
Jason Volk 74a576caf7 Add option to toggle compact log format.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 2b7cf7d5d5 Add optimized iteration of all room memberships for user.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 443248965d Add more optimized iterations for user memberships room_id's.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk ee777bc287 Rename user rooms iterations which have state vectors as values.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk aef38b1178 Rename userroomid/roomuserid table handles to reflect counter value.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 4743a8d968 Move args and runtime back to main from core (1313eb0b64).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 53ab6742c8 Fix shutdown signalling on startup.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 46193de7e8 Create appservice user on registration.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 9253f46c80 ci: Dedup the nix package job with the other distro package jobs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 5c127b5abd Add is_false() to BoolExt.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 17f6f1a5a6 Device list updates for left users was previously deduplicated.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 75509d50ca Add shorteventid to pdu_id query; reorg related id query interface.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jason Volk 1d14426018 Fix missing Send on generic stream and result items.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-06 01:19:36 +00:00
Jimmy Brush 2b9563be67 Fix off-by-one in sync v5
Simplified sliding sync specifies ranges to be inclusive while rust ranges are
exclusive.
2025-10-05 18:28:05 -04:00
Jason Volk 68c4f60bb3 Implement stateless sync; erase all the sync tokens.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 15:07:06 +00:00
Jason Volk d95c3f126f Fix typos
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 15:07:06 +00:00
Jason Volk f1c2548807 Add PduCount value to userroomid/roomuserid_joined; move PduCount to argument for update_membership.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk eda45e445c Implement better fmt::Debug for pdu::Builder.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 049defe977 nix: Disable doChecks due to missing /etc/resolve.conf.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 3c073110b8 Update nix flake.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 8d6bfde5a0 Use insta and criterion for main integration test and benches respectively.
docker/ci: Separate integration and unit tests and benches jobs.

Add directives to remove db before/after integration tests are performed.

Split start/run/stop phases; add more granular smoketests.

Split main integration tests into units for isolation.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
dasha_uwuandJason Volk 43f0882d83 clear destination and overrides cache on request failure. (#186) 2025-10-02 05:29:48 +00:00
Jason Volk fed52d24e4 Implement additional Matches for RoomFilter and Filter.
Apply filter for rooms/not_rooms; sender filter for presence.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk e6c85c97c6 Fix names and types misrepresenting PduCount as ShortEventId.
Add get_shorteventid_from_pdu_id() conversion.

Fix prev/next nearest-state interface (dev branch 642086ecfcfa).

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 368ead20a6 Add then_none() to BoolExt; move unsorted then_ utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk a803b84b27 Add missing reverse query for shortroomid => room_id.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 1058fbe9a7 Use specific key,val buffer sizes for append_to_state (no change to correctness).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk ae4aad3641 Add set difference for sorted streams util.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 95435ffe98 Fix unconnected inspect_log traits in Result utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 63e2cfa21b Rename core integration test directory for state_res.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk 9383922d09 Use trace log level for multi-line table factory options on rocksdb startup.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:48 +00:00
Jason Volk ae52676e33 Add back the default database path.
Allow default server_name when testing.

Default to smoke test vector when testing.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:45 +00:00
Jason Volk 292b601755 Fix possibly dependent conditions for server shutdown in config worker.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:29:00 +00:00
Jason Volk 1313eb0b64 Reorganize main crate for testability.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:28:58 +00:00
Jason Volk ba12773a5a Optimize force_state query pattern.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 83afe81f60 Move shortstatehash queries from state_accessor to state service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk f2740822e2 De-indent rooms state service definitions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 2417764771 Fix redundant prefix in admin query raw command suite.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 41ff81f843 Add handler_prefix to admin_command_dispatch for method dedup purposes.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk fbcf4ba4f3 ci: Support job masking directives in commit message. [ci only it]
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason VolkandHashbrown777 af3cdf9263 Add script to docs for available optimization levels.
Co-authored-by: Hashbrown777 <hashbrown777@gmail.com>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 9d9ace1452 Add timeline interface to get next or prev pdu nearest to a pduid.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 83d64e0879 Implement Deserialize for RawPduId for database convenience.
Add room equality convenience on RawPduId.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 4cae17e83d Fix missing Send bound for Future.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 1c6992ccd4 Add option to bypass dropped column erasure.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk 89be6dc097 Switch to Criterion for benchtests.
ci: Add benches to workflow.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk a47f8f8a82 Rename complement test results file.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-10-02 05:27:48 +00:00
Jason Volk aac5006bf5 ci: Add nix build w/ smoketest. (closes #111)
docker: Add target for nix build. (#111)

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-25 14:38:23 +00:00
Jason Volk 6d3ed09a2b ci: Add matrix-rust-sdk integration testing.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-25 14:38:23 +00:00
Jason Volk 377b7166f0 Bump Ruma.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-25 12:25:08 +00:00
Jason Volk 85b3de055d Broadcast send_join concurrent to other operations; pre-gather state concurrently.
Broadcast send_knock concurrently.

Concurrent access check for fed event.

Concurrent gather for state responses.

Populate room_version for format_pdu to elide repeated queries.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-25 02:32:45 +00:00
Jason Volk b9c790326a docker: Consolidate distro package fetches in base builder image.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-24 08:20:54 +00:00
Jason Volk 4e5910471b Require stronger ordering to sample final counter values on shutdown.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-24 08:20:54 +00:00
Jason Volk c3bc8c14f7 Make hickory system_conf error more useful. (resolves #179)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 21:02:37 +00:00
Jason Volk ac3ceb1b95 Modernize federation profile query route.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 19:48:07 +00:00
Jason Volk aa37e32471 Optimize Pdu prev_events and auth_events containers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 18:16:24 +00:00
Jason Volk 887a22dabd Fix docs regarding optimized builds; replace haswell with v1/v2/v3 etc. (fixes #175)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 18:16:24 +00:00
Jason Volk 6dca02860c Allow clippy::needless-update.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 11:53:39 +00:00
Jason Volkanddasha_uwu 86103183b3 Remove unused utils.
Co-authored-by: dasha_uwu <dasha@linuxping.win>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 09:02:35 +00:00
Jason Volk 8f4cc87051 Query rooms in server_sees_user() concurrently.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 08:49:09 +00:00
Jason Volk 14721c90c9 Fetch presence avatar_url and displayname concurrently.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 08:49:09 +00:00
Jason Volk 6d3b2d864f Simplify keys_changed iteration using room_keys_changed() upper-bound.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 08:49:09 +00:00
Jason Volk 7cf246eb73 Support dot paths for -O/--option command-line args. (fixes #162) (fixes #167)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:16:29 +00:00
Jason Volk bffb7f89c8 Provide better generated config defaults for ldap section.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:16:29 +00:00
Jason Volk 5467c9e486 Add option to redirect logging to stderr. (resolves #154)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 89a67af607 pull stuff out of globals 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 6bb101ac51 add local alias check 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 1c0b4e94ac fix spec violation and slight alias resolution refactor 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 9c4d376bec pass args by ref 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk d6e95c51c0 current_exe changes
remove unsafe

remove incorrect doc
2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk d5ce4b316f remove unnecessary as_ref 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk 8f07a6c60f remove unused macros 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk b882e7efdb rename db to engine 2025-09-23 05:16:29 +00:00
dasha_uwuandJason Volk e0169e3dca presence refactor 2025-09-23 05:16:29 +00:00
Jason Volk fe46755418 Remove declaration for Room V1 until support is confirmed complete. (#12)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk bbac80d2e6 ci: Fix pages deployment. (#165)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk a31c9b0c62 Fix documented examples of delegation. (fixes #165)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk cf4e65c607 Fix alternative resolver minimum TTL configuration. (fixes #176)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk f32b6ae17d Abstract and simplify serde_json round-trip to Ruma Raw.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk 27222f23d2 Macroize several legacy errors.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk ed324d5972 Update device_signing/upload UIAA (fixes #169).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk 69075b166f Add config option to toggle dns case randomization.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk e3a711482a Update assertion for serde crate reorg to serde_core.
Show the deserialize_any type which fails the assertion.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-23 05:15:10 +00:00
Jason Volk 6759187b37 Bump dependencies.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-22 20:44:25 +00:00
Guido GüntherandJason Volk 83ed29eb65 example config: Document support items
Signed-off-by: Guido Günther <agx@sigxcpu.org>
2025-09-22 20:44:25 +00:00
Guido Günther 6a685b7ee9 packaging: Move unit alias to install section
Otherwise there will be

  systemd[1]: /usr/lib/systemd/system/tuwunel.service:5: Unknown key 'Alias' in section [Unit], ignoring.

on startup. See `systemd.unit(5)`.

Signed-off-by: Guido Günther <agx@sigxcpu.org>
2025-09-15 19:53:05 +02:00
Jason Volk fb4dfafe2d Bump 1.4.2
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-12 04:13:34 +00:00
Jason Volk 8dbea51968 Slightly decrease the default OTK limit.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-12 04:13:34 +00:00
Jason Volk 7b5905bbca Remove inapposite readme sections on versioning.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-12 04:13:34 +00:00
Jason Volk 42d143c013 Allow matching for passthru resolver on all resolution paths. (#158)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-12 04:13:27 +00:00
Jason Volk 10fb1cd192 Purge room synctokens during deletion.
Purge last notification read counts.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-11 08:56:38 +00:00
Jason Volk 1322ba1b00 Send unmaintained hardened_malloc-rs to valhalla.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-11 08:56:38 +00:00
Jason Volk d73b4332bf Fix deletion of local leave state to allow sync for clients.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-11 06:20:41 +00:00
Jason Volk 11eeca7e01 Prune one time keys based on reasonable configured limit.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-11 04:27:33 +00:00
Jason Volk 19dc5fafd4 Fix missing device_one_time_keys_count in empty timeout responses.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-10 21:29:04 +00:00
Jason Volk 54b347b855 Abstract and dedup the general UIAA pattern into api::router.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-10 10:35:49 +00:00
Jason Volk 8ed61aecb0 Split api::router::auth server/appservice units.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-10 05:41:44 +00:00
Jason Volk bf8aa57d03 Add config option for dns passthru for appservices. (#158)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-10 01:17:40 +00:00
Jason Volk a9f1926654 Use dns_resolver2 per reqwest client builder deprecation notice.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-10 00:59:50 +00:00
Jason Volk 248d778290 Add alternative resolver path with passthru cache-characteristics. (resolves #158)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-10 00:01:26 +00:00
Jason Volk 2e13e87e43 docs: Update the Helm Chart link for Kubernetes.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-09 22:47:33 +00:00
Jason Volk ba19d407d0 Add config to control m.federate in room create events. (fixes #151)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-09 04:46:19 +00:00
Jason Volk de7c5dcbc8 Make iss and aud args optional to debug create-jwt command.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-09 03:03:53 +00:00
dasha_uwuandJason Volk adadafa88f Fix alias resolution logic; element-web v1.11.111 compat
element-hq/element-web#30641
element-hq/element-web#30611

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-09 02:28:19 +00:00
Jason Volk deda746222 Support JWT authentication on deactivate route.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-09 02:28:19 +00:00
Jason Volk 29c2c8a333 Branch based on config to note sync for push suppression.
Minor if-let condition and scope reduction; formatting.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-08 22:25:00 +00:00
tototomate123andJason Volk 296018f0cc mark notification suppression as experimental 2025-09-08 22:25:00 +00:00
tototomate123andJason Volk 6295ca135a feature marked as experimental and executed rustfmt 2025-09-08 22:25:00 +00:00
tototomate123andJason Volk b5a9884194 feat(presence,push) optionally suppress push notifications for active users 2025-09-08 22:25:00 +00:00
Jason Volk 1be7fd9247 Fix cfg condition for debug logging (996a5488be)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-08 22:25:00 +00:00
Jason Volk be1264965a Refactor sliding-sync.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-08 00:07:59 +00:00
Jason Volk f6d2ce2f22 Greenlight hydra backports.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-08 00:07:59 +00:00
Jason Volk 3cbe1e25cd Cleanup Resolver construction/configuration.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-08 00:07:59 +00:00
Jason Volk 897322964c Fix dns_min_ttl config doc.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-08 00:07:59 +00:00
Jason Volk 996a5488be ci: Add build for release-logging, optimized build with debug logging.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-08 00:07:59 +00:00
Jason Volk 0ed4bd1e07 Only ping presence for public read receipts.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-07 21:39:20 +00:00
Jason Volk da79de5381 Add cfg to preclude log elision without requiring debug_assertions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-07 21:39:20 +00:00
Jason Volk 1bcf3ae19a Soften the Docker troubleshooting note.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-07 20:50:14 +00:00
Jason Volk 91e500efe3 Further tweak troubleshooting formatting.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 22:11:42 +00:00
Jason Volk 35b1ebaf3b Bump dependencies.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 07:45:04 +00:00
Jason Volk ea5deb7e85 Generate CoC HTML inside book directory. (closes #155)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 07:45:04 +00:00
Jason Volk 542cc51047 ci: Add docs test step.
docker: Add docs test target.

docker: Add mising color args to rustfmt.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 06:49:45 +00:00
Jason Volk 3225db0ddd Add external repair tool step to db repair troubleshooting.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk a164da8b58 Fix and extend the apply! util macro.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 7221d466ce Ignore canonical_alias with removed alias for directory results.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk f6c88e3a16 Add query for alias existing; tweak tracing spans.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 3deebeab78 Fix sliding-sync room_avatar conditions.
Eliminate jsOption from non-sliding-sync avatar related codepaths.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 1d2818de58 Round upward for events_after in /context limit.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 86781522b6 Allow state visibility for once-joined users when history_visibility=shared.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 4b49aaad53 Fix spec compliance limiting /joined_rooms to current members only.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 3a78ba2b16 Support v1/v2 conditions for join/leave, creation and other operations. (#12)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 685946faed Cleanup validate/verify suite room version related; optimize event_id insert. (#12)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 13c9385ef7 Add v1/v2 support to gen_event_id() scheme. (#12)
Add v1/v2 and improve reference and content hashing suite. (#12)

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk 6a583a359e Support v1/v2 prev_events/auth_events outgoing federation format. (#12)
Support v1/v2 prev_events/auth_events when handling outlier pdu. (#12)

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Jason Volk eec6e45358 Improve error messages for missing auth event and invalid join validations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-05 05:45:41 +00:00
Alexandru ScvorțovandJason Volk 804257eb52 Fix broken doctest in src/core/utils/debug.rs 2025-09-04 22:21:14 +00:00
Jason Volk a1677f05d3 Bump 1.4.1
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-02 09:43:50 +00:00
Jason Volk 1f77200299 Mark MSC4297 compliance as flake.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-02 06:29:40 +00:00
Jason Volk 888b93df91 Fix /joined_members results. (regression a3666fe672) (fixes #147)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-02 05:34:37 +00:00
Jason Volk a127724eaa Bump 1.4.0
Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-01 11:34:08 +00:00
Jason Volk 00102c4276 ci: Skip unnecessary amd64 -v2 and -v3 builds in test-profile mode.
docker: Use env override for git_checkout.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-09-01 02:33:00 +00:00
dasha_uwu b59af0530c Deprioritize joins through configured servers 2025-08-31 12:30:27 +05:00
dasha_uwu 44af849ed0 Dedup base join servers 2025-08-31 11:30:10 +05:00
Jason Volk 26e7a8e962 Bump 1.4.0 Preview
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 20:27:39 +00:00
Jason Volk aa413ae601 Implement room purge after last local user leaves. (resolves #83)
Consume a state_lock for room delete call.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 11:45:32 +00:00
Jason Volk 7c19d1e1ff Fix unnecessary backfill requests after reaching m.room.create.
Cleanup condition.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 11:45:32 +00:00
Jason Volk 616f20f273 Fix unused lifetims.
Fix unused pub(crate).

Allow implicit hasher.

Fix unused self.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 11:13:14 +00:00
Jason Volk 91efcf8a8e Bump and unpatch tracing-subscriber.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:51:55 +00:00
Jason Volk 8708e98478 ci: Tweak docker cache sizes.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:16:43 +00:00
Jason Volk 4bdc260015 Update the troubleshooting for easier rocksdb recovery/repair.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:16:43 +00:00
Jason Volk 7bc47e58d0 Add state_cache iteration for local pending invitees to room.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:04:24 +00:00
Jason Volk 6172b9c8ec Replace legacy federation proto-event formatter.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-30 10:04:24 +00:00
k0tlin 9dc16eeab7 Adjust generic docs
Match readme instruction with doc
2025-08-30 00:45:41 +03:00
k0tlin 402f7a2157 Update example caddy config 2025-08-30 00:35:38 +03:00
Jason Volk 9c6e0e3350 Stabilize room version 12. (#126)
Bless complement.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 07:49:36 +00:00
Jason Volk 12bdfe3b66 Optimize backfill_if_required conditions.
Optimize backfill_pdu.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
dasha_uwuandJason Volk 54ce221e88 Add active request assert on tls listener shutdown 2025-08-29 06:49:04 +00:00
dasha_uwuandJason Volk 33a16f4831 Make /sync respond early on server shutdown 2025-08-29 06:49:04 +00:00
Jason Volk d8e6d95f4b Fix device verification crutch (regression 628597c318); cleanup. (fixes #134)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 9c290bc513 Fix latest key backup determination.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk a4520424bd Adjust several log levels; cleanup unformatted log call.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 280d7733fc Update capability for MSC4133 profile fields.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 79e3c898ac Fix /send_join response event format. Optimize.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 757d38e891 Optimize expected auth types checking.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jason Volk 4b66911886 Eliminate redundant duplicate auth and matching room check.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 06:49:04 +00:00
Jade EllisandJason Volk 9db3e35f41 Fix incorrect command descriptions. [ci skip] 2025-08-29 02:17:46 +00:00
Jason Volk 7977512d5d Fix additional cases for room_id.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 02:17:46 +00:00
Jason Volk e354be6830 Simplify Pdu from_id_val constructions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 02:17:46 +00:00
Jason Volk f58081838e Log error for send_join auth_check failure.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-29 02:17:46 +00:00
dasha_uwuandJason Volk 526a138021 Force promotion (closes #136) 2025-08-29 02:17:46 +00:00
dasha_uwuandJason Volk 7fa6894334 Remove excessive doc on admin command 2025-08-29 02:17:46 +00:00
dasha_uwuandJason Volk 33d0ad1be2 Remove dead link in readme 2025-08-29 02:17:46 +00:00
Jason Volkanddasha_uwu fbc3892904 ci: Disable lychee for now due to excessive build time issue during install.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:28:20 +05:00
Jason Volkanddasha_uwu ce6212b161 Add room_version argument and reorg convert_outgoing_federation_event.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:28:20 +05:00
Jason Volkanddasha_uwu 0aea56f02a Optimize OnceServices; simplify init.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:28:20 +05:00
dasha_uwu d05d3f710f Room deletion (fixes #43) 2025-08-28 23:28:20 +05:00
Jason Volkanddasha_uwu 7a3496869b Remove associated pushers on device delete. (fixes #120)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:28:20 +05:00
dasha_uwu 146b72265d Skip serialization for appservice id 2025-08-28 23:28:20 +05:00
dasha_uwu 120a43cceb Fix joining restricted rooms with invite 2025-08-28 23:28:20 +05:00
dasha_uwu c40671bfb0 Remove api crate dependency from admin crate 2025-08-28 23:28:20 +05:00
dasha_uwu 8e9c6661b2 Membership refactor
Introduce deactivate, membership services

Move membership and user deactivation functions from api crate into those services
2025-08-28 23:28:20 +05:00
dasha_uwu 6810604629 banned_room_check: pull out common part 2025-08-28 23:28:20 +05:00
dasha_uwu 3dd984eff9 clippy: add must_use attribute 2025-08-28 23:28:20 +05:00
dasha_uwu bcd4f80149 Move server user presence handling to presence service 2025-08-28 23:28:20 +05:00
dasha_uwu f51a7b12b5 Make Service.interrupt async 2025-08-28 23:28:20 +05:00
dasha_uwu 3a1649bcaf Add config option for creating admin room non-federated (closes #118) 2025-08-28 23:28:20 +05:00
dasha_uwu d4421422b2 Remove explicit unstable thirdparty/protocols route
Already handled by the other ruma route
2025-08-28 23:28:20 +05:00
dasha_uwu 4884c55be7 Split disable_room, ban_room functions 2025-08-28 23:28:13 +05:00
Jason Volkanddasha_uwu a4f589f475 Remove unnecessary recursion_limit.
Reduce type_length_limits.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-28 23:20:44 +05:00
dasha_uwu b5890b9664 Services refactor
Replace structs of Dep<Service> with OnceServices, so each service has a Services reference

Remove service name => Service map

Flatten Services.rooms

Make reqwest Clients lazy initialized (client service)
2025-08-28 23:15:59 +05:00
Jason Volk 26b3a84b88 Fix updated serde type expected by database deserialize_any.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-27 00:41:13 +00:00
Jason Volk 3430d4ac86 Improved solution to current_shortstatehash > next_batch. (59b62b1)
`current_shortstatehash` is now calculated from timeline which is already
upper-bound by the `next_batch` snapshot. Previously `current_shortstatehash`
was truly current and had to be disallowed from exceeding `next_batch` by
ignoring the room during sync until a future pass when these values finally
met that condition.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-27 00:41:12 +00:00
Jason Volk 3e2f2fbffa Bump Ruma post-hydra.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-27 00:41:12 +00:00
Jason Volk 74deb14ca0 Bump Complement post-hydra.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-27 00:41:12 +00:00
Jason Volk acd930b9ca Replace clippy::string_to_string with clippy::implicit_clone
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-26 08:49:26 +00:00
Jason Volk e2db2029c5 Add note directing users to docker deployment docs.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-26 02:40:25 +00:00
Jason Volk 4bb83a1972 Fix version 12 outgoing federation format.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-26 02:40:25 +00:00
Jason Volk 751aa27859 Bump dependencies for yanked slab.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-14 02:25:54 +00:00
Jason Volk 628597c318 State-reset and security mitigations.
Upgrade Ruma to present.

The following are intentionally benign for activation in a later commit:

- Hydra backports not default.
- Room version 12 not default.
- Room version 12 not listed as stable.

Do not enable them manually or you can brick your database.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-11 18:45:28 +00:00
Jason Volk 2c6dd78502 Reduce log level on router::args returned error.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-11 11:32:53 +00:00
Jason Volk cdb0e31b21 Allow clippy::default_trait_access.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-11 11:32:53 +00:00
dasha_uwuandJason Volk 6a6f77a100 Simplify feature condition.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-11 10:24:40 +00:00
Jason Volk a3666fe672 Use member profile rather than global profile for joined_members (fixes #121)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-10 23:52:03 +00:00
Jason Volk 0c8df52d18 Bump dependencies.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-07 04:52:46 +00:00
Jason Volk d7e5c0ccf3 Optimize spaces pageload; pipeline outer loop.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-07 03:16:32 +00:00
Jason Volk e106e50ed0 Update the since token to skip empty range from prior iteration.
Log and discard presence ping errors without preventing sync.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-06 08:06:39 +00:00
Jason Volk 00f11a9e8f Additional instrumentation around global counter events. 2025-08-06 06:45:43 +00:00
Jason Volk bf48c7b1b4 Move active and finished request counters out of span field.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-06 00:11:26 +00:00
Jason Volk 8a8a5d598e Fix sync loop watcher ABA.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-05 10:51:56 +00:00
Jason Volk fa84a7e6c2 Bump 1.3.0
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-05 07:02:31 +00:00
Jason Volk 2bdeca0ed3 ci: Fix missing condition for Push Branch. (#103)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-05 07:02:31 +00:00
Jason Volk c337ea1864 Fixes for sync v3 protocol compliance.
Add sync v3 filter support.

Fix events duplicated between state and timeline; cleanup some lets.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-04 18:55:37 +00:00
Jason Volk b00361d274 Add specific shorteventid to shortstatehash query function.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-04 18:55:37 +00:00
Jason Volk fb17aa19bf Add configs for creating the admin room and granting first user.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-04 18:55:37 +00:00
Jason Volk fc28e8e1dd Update flakes list; unflake several packages as fail.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-04 18:55:37 +00:00
Jason Volk cea25c6580 docker: Make the complement defaults more accessible.
commit newly uncovered tests.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-04 05:13:06 +00:00
Jason Volk a0dc37e024 Fetch prev_events concurrently.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-04 05:13:06 +00:00
Jason Volk 003257693b Fetch outlier and auth chains concurrently.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-04 00:48:30 +00:00
Jason Volk bc898efcce Uncomment generated-config's global section header, comment all others. (#116)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-03 21:55:36 +00:00
Jason Volk fa8ad1d629 Update the router log truncator for new axum syntax.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-03 00:53:53 +00:00
Jason Volk 44b60050b3 Reorder auth match cases; add todo for AppserviceTokenOptional.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 22:02:26 +00:00
Jason Volk 4b84e7c31a Flatten and dedup append_incoming branch in upgrade_outlier.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 18:00:20 +00:00
Jason Volk 4237f21903 Renames for several event_handler service files, fn's and args.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 18:00:20 +00:00
Jason Volk 4ac61fd87b Remove federation_handletime for now.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 18:00:20 +00:00
Jason Volk d217927000 Split fetch_outlier; abstract backoff stanzas into fn.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 18:00:20 +00:00
Jason Volk 4b9b85f671 ABI encapsulations; generate leaf code in pub fn's rather than inling them.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 18:00:20 +00:00
Jason Volk fa3b72947a Eliminate rooms outlier service, duplicate subset of timeline service.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 18:00:20 +00:00
Jason Volk 5d963abda6 Combine rooms timeline service data unit into mod; eliminate wrappers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 18:00:20 +00:00
ObiomaAtWanaguandJason Volk a91380ac17 Change from the error 'No more rooms.' to the output 'No rooms are published.' if there are no rooms.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-02 18:00:20 +00:00
Jason Volk 1664a2c225 Implement refresh-tokens. (resolves #50)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-01 09:14:58 +00:00
Jason Volk 0d7eeb7766 Extend the append count guards over subsequent operations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-01 09:14:58 +00:00
Jason Volk 16447f7383 Handle defaulting trailing elements in truncated database records.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-01 05:30:40 +00:00
Jason Volk 3612b04890 Add timepoint_from_epoch to time utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-08-01 03:40:27 +00:00
Jason Volk e6f1b98d6a Improve tracing of request error responses.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-31 14:31:28 +00:00
Jason Volk 6742e1d206 Sort misc cargo list entries, database maps.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-31 07:02:59 +00:00
Jason Volk dc0976a2e5 Respond with 403 instead of 404 for non-existing user.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-31 07:02:59 +00:00
Jason Volk f3f23ee7ee Fix missing counter retirements (51931de94c).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 23:34:21 +00:00
Jason Volk 0b15ab2006 Ensure unwind safety in dispatch sequence.
Ensure await safety/efficiency in retirement sequence.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 23:34:21 +00:00
Jason Volk bd0c3e33e2 Loop syncv3 for robust response without re-request overhead.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 23:34:21 +00:00
Jason Volk 1bb16c8b73 Additional span logging of counter state; trace logging of contents.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 23:34:21 +00:00
Jason Volk 59b62b1453 Fix partial v3 syncs on post-timeout pass; fix partial state on room join.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 23:34:21 +00:00
Jason Volk 2e0b156de0 Fix missing user account data sync watcher.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 19:28:16 +00:00
Jason Volk 3593b8ebf7 Implement Debug for RawPduId.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 19:09:46 +00:00
Jason Volk 80015a6a30 Fix missed rustfmt let due to comment in chain.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 19:09:46 +00:00
Jason Volk 95e49d3cb8 Fix map_or calling function.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 19:09:46 +00:00
Jason Volk 25164e030d Hide own user from user directory search results.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-30 19:09:46 +00:00
Jason Volk f04924d6be docker: Extract complement logs on failure.
docker: Sort config options used during complement.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-29 14:34:42 +00:00
Jason Volk df7cd58618 Support matching room_id as publicrooms search term.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-29 04:18:21 +00:00
Jason Volk bf12e05dae Add meta iteration for public rooms.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-29 04:18:21 +00:00
Jason Volk e53366a492 Fix missing sync watcher not added for knocking feature.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-29 04:18:21 +00:00
Jason Volk ce25d79e66 Flatten authorised-join check queries.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-29 04:18:21 +00:00
Jason Volk 71ef093edc Unbox and pin various either-or futures.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-29 04:18:21 +00:00
Jason Volk 066097a83d Optimize user and appservice token queries.
Split/cleanup api::router auth delegator.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-29 04:18:20 +00:00
Jason Volk 1c24ed09fa Fix misuse of try_select in logical-or future util.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-29 04:18:20 +00:00
Jason Volk d3dbe09380 Add SystemTimeError to core error enum.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-27 13:09:30 +00:00
Jason Volk e9d0baa32e Add granular timeout configs for sync longpolling.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-27 11:34:15 +00:00
Jason Volk 7dd10ebc6c docs: Add note to configuration doc.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-27 11:03:12 +00:00
Jason Volk 29ac62ec27 Fix zero since-token value not used as initial sync condition.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-27 11:03:12 +00:00
Jason Volk 8c8985e8f2 Add user_id to v3 sync span; add span for sync token association.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-27 11:03:12 +00:00
Jason Volk 730920b571 Collect v5 typing events concurrent with other extensions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-27 07:04:10 +00:00
Jason Volk 66334bdd53 Move syncv3 typings event for user.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-27 07:04:10 +00:00
Jason Volk 6b7bf80de5 Disable legacy media request fallbacks by default.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-27 07:04:09 +00:00
Jason Volk 4c35bfbda4 Fix M_UNKNOWN error which should be M_BAD_ALIAS.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-26 07:29:30 +00:00
Jason Volk ba51748ad3 Add missing upper-bounded calls; improve snake-sync windowing.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-26 05:52:30 +00:00
Jason Volk 63dfe8f7e3 Add upper-bound for presence_since().
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-26 05:36:19 +00:00
Jason Volk c6836e51b2 Add upper-bound to readreceipts_since() and callsites.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-26 04:44:09 +00:00
Jason Volk 33a9fce828 dedup room directory banned server check.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-26 02:26:09 +00:00
Jason Volk 267fd4de5b Enable additional complement parallel tests.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 22:52:29 +00:00
Jason Volk ce30f83052 Optimize sync watchers and key serializations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 21:55:37 +00:00
Jason Volk e09a2c0e0f Window sync ranges below any partially completed writes.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 21:55:37 +00:00
Jason Volk 992abfab8f Separate cached body from request body in snake-sync; cleanup.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 21:55:37 +00:00
Jason Volk 51931de94c Add release-sequence for counter retirements.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 21:55:37 +00:00
Jason Volk 85a84f93c7 Fix misinterpretation of filter.room.include_leave in sync v3.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 21:55:37 +00:00
Jason Volk e58ef326d4 Refactor legacy database watcher system.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 20:07:21 +00:00
Jason Volk 0d439188e8 Fix sequence windowing for sync v3 invite/leave/knock rooms.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 20:07:21 +00:00
Jason Volk 22ef704ac6 Fix bug from roomuserid_knockedcount vs. roomid_knockedcount.
The use of "count" instead of "sequence" leads to confusion with
i.e. counting the elements of a set e.g. Iterator::count().

Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 20:07:21 +00:00
Jason Volk 7bdd4065c8 Split database::Map put/del from insert.rs and remove.rs
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 20:07:18 +00:00
Jason Volk 0fcb072239 Refactor counter increment sites for TwoPhaseCounter.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 10:32:52 +00:00
Jason Volk 05bb1f4ac7 Add TwoPhaseCounter to core utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 10:32:52 +00:00
Jason Volk 89e3a8c182 Add common editor temps to gitignore.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-25 10:32:52 +00:00
Jason Volk f0576f8d7d Remove unused deprecated sliding sync service code.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 23:13:01 +00:00
Jason Volk 11f77defc7 Handle AuthData::FallbackAcknowledgement; mute error for now.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 23:13:01 +00:00
Jason Volk 23c75855ba Split api/client/register from api/client/account suite.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 23:13:01 +00:00
Jason Volk 5253a8d36a Inline and apply must_use to remaining services globals wrappers.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 22:31:11 +00:00
Jason Volk c9adee86f5 Unify calculate_state_changes in syncv3
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 22:17:30 +00:00
Jason Volk 3c47516c85 Pipeline private read receipt fetch; cleanup tuples syncv3.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 22:17:30 +00:00
Jason Volk 299d3230a1 Optimize typing event collection.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 22:17:30 +00:00
9ea8fbc482 Add typing indicators to sync v5.
Co-authored-by: Jade Ellis <jade@ellis.link>
2025-07-24 22:17:30 +00:00
Jason Volk f7877d122b List #tuwunel:matrix.org; simplify readme.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:28 +00:00
Jason Volk 17119670c0 Add admin debug command to manually resync with primary. (#35)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:28 +00:00
Jason Volk 535fcab4b5 ci: Create multi-arch manifest for each release tag (fixes #103).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:28 +00:00
Jason Volk cbe34affa3 Add unique id counter to request tracing span. (fixes #112)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:28 +00:00
Jason Volk ed0b3c764e Parallelize state_at_incoming sstatehash/prev_event fetches.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:28 +00:00
Jason Volk 190269f616 Simplify Pdu Hashes struct.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:28 +00:00
Jason Volk 3ea8c82ef0 Follow-up to admin channel change (7d2d42542c).
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:28 +00:00
Jason Volk 2a627baa96 Fix excessive nesting.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk 50de6277b8 Split/cleanup backfill stream stanza.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk 4429323e11 Flatten threads_until().
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk 18f8d6c65c Rebox future segmentations.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk aac49b09c7 Improve mutable state in sender closure captures.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk c8d35cca57 Refactor to async closures.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk cca0f20148 Replace several expects with error coercions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk 23d188f612 Spacing/formatting adjustments.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk ae707ab465 Simplify default Result generics.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk 8244d78cb2 Make Event trait Send+Sync.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk b0315da3d7 Fix branches sharing code.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk eca01cfb3f Fix useless if let seq.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk cf13f5afc1 Allow clippy::map_unwrap_or.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk c944f1124a Additional BoolExt utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk 70fc95e800 Fix same generic used for separate closures in BoolExt.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
Jason Volk 507bd1edc7 Update complement; mask new tests.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-24 04:07:27 +00:00
coolGiandJason Volk ee0e594aef Fixed nix cached shasum for rust-toolchain.toml 2025-07-21 21:05:36 -07:00
ObiomaAtWanaguandJason Volk fd080914c2 Enhanced docu for configuration
Include that more than one config file can be named.
Describe the relevance of ordering for setting the value.
2025-07-09 20:38:27 +00:00
dasha_uwuandJason Volk 9dd2b28605 Add base64 output for admin query command.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-08 23:49:18 +00:00
Jade EllisandJason Volk 34abe1cce4 fix: Allow joining via invite for knock_restricted rooms 2025-07-08 23:38:18 +00:00
Tyler MayoffandJason Volk a340e6786d use room names first and only fallback to heroes if not available 2025-07-07 07:36:51 +00:00
June StrawberryandJason Volk c37ee97351 allow nightly unstable_name_collisions lint due to BoolExt conflict
Signed-off-by: June Strawberry <june@basednetworking.com>
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-07 07:36:51 +00:00
Jason Volk 7d2d42542c Simplify admin channel type.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-07 07:09:49 +00:00
Jason Volk fd0082fd2c Add ready_try_skip_while() to stream utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-07 07:09:49 +00:00
Jason Volk ca8f3ae057 Add gitignores for -Zself-profile output.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-07 07:09:49 +00:00
Jason Volk 8a748e02df Fix into_recv_seek lifetime flow to match into_recv_get.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-07 07:09:49 +00:00
Jason Volk 79ee7838e7 Rename elided_named_lifetimes to mismatched_lifetime_syntaxes.
Reapply is_multiple_of().

Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-07 07:09:49 +00:00
Jason Volk e9a441fb6e Bump tokio to 1.46
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-07 02:44:18 +00:00
Jason Volk 93f5557232 Bump rust to 1.88.0
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-07 02:23:25 +00:00
Jason Volk 3d88767977 Bump 1.2.0
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-04 02:00:07 +00:00
Jason Volk b68cff3b2f Fix clippy::manual-is-multiple-of.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-04 01:49:57 +00:00
Jason Volk 36c3845ff6 Comment section headers in generated example config. (fixes #96)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-04 00:31:09 +00:00
Jason Volk 7f1c7b964e Add missing display/default attributes to appservice example config.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-04 00:13:30 +00:00
Jason Volk 1cbcd3fc88 Add Result traits map_ref()/and_then_ref().
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-03 22:18:44 +00:00
Jason Volk ad9ddd91cd Add broad_find_map() to stream utils.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-03 22:18:44 +00:00
Jason Volk a2b4c07cf7 Additional internal pdu getters.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-07-03 22:18:44 +00:00
orhtej2andJason Volk 2ead282bec Dedup entries returned by LDAP 2025-06-29 04:02:41 +00:00
Jason Volk 9496d05cd2 ci: Pass package build condition as workflow arg.
ci: Add defaults for init matrix; branch conditions for pulls.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 04:02:41 +00:00
Jason Volk c8ab1713c3 Fix errant ldap3 dependency in src/core.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-29 04:02:41 +00:00
orhtej2 84acdccb93 Other typo 2025-06-29 01:20:02 +02:00
orhtej2 6164f62395 Fix optional admin config 2025-06-29 01:16:51 +02:00
Jason Volk b36ad9cfef docker: use OCI media types for registry images. (fixes #79)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-26 01:01:56 +00:00
Jason Volk d557f0f456 README: soften warning to tip. [ci skip]
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-26 01:01:56 +00:00
Jason Volk d858c5c1f4 docker: Add Instruction Set Architectures to build matrix.
ci: Publish multi-arch images for both branch and tag refs.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-25 17:21:55 +00:00
Jason Volk 972201da60 Add warning when listening=false. (fixes #81)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-25 01:04:18 +00:00
Jason Volk 56d9c9a8b5 Handle empty std::env::args() for FreeBSD. (fixes #75)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-25 01:04:18 +00:00
Jason Volk 84ce3e0d0e Change default emoji due to inconsistent support. (closes #80)
Move config item to more prominent location.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-25 01:04:18 +00:00
Jason Volk 578cf9d2d7 Fix missing lz4 and bz2; tweak feature projection. (closes #78)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-25 01:04:18 +00:00
Jason Volk ca69af97c1 docker: Re-relax cargo cache expectations. (#53)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-24 23:45:08 +00:00
Jason Volk ac26193ffe Check appservice registrations for unique as_token. (#72)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-20 18:08:19 +00:00
Jason Volk a84e559640 Implement declarative appservices. (closes #67)
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-20 18:08:19 +00:00
Jason Volk 2e559a0d3e docker: Use zstd for all compressions.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-20 13:51:35 +00:00
Jason Volk 2634fed2d4 Tweak readme wording.
Fix actual typo.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-20 13:04:02 +00:00
Jason Volk 31a3676cd8 ci: Publish multi-arch manifest. (fixes #74)
ci: Toggle package install checks via vars.

docker: Disable provenance attestations which auto-generate manifests.

Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-20 10:52:35 +00:00
Jason Volk aa1609d611 Fix RELEASE notes formatting for GH.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-20 01:21:07 +00:00
Jason Volk 8e13f07d2a ci: Fix distro package generation for tags.
Signed-off-by: Jason Volk <jason@zemos.net>
2025-06-19 13:05:49 +00:00
499 changed files with 37752 additions and 20933 deletions
+2
View File
@@ -0,0 +1,2 @@
[env]
RUMA_UNSTABLE_EXHAUSTIVE_TYPES = "true"
+15 -15
View File
@@ -43,7 +43,7 @@ jobs:
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Format
uses: ./.github/workflows/bake.yml
@@ -54,9 +54,9 @@ jobs:
rust_toolchains: '["nightly"]'
rust_targets: ${{inputs.rust_targets}}
sys_names: ${{inputs.sys_names}}
sys_targets: ${{inputs.sys_targets}}
sys_versions: ${{inputs.sys_versions}}
machines: '["X64"]'
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
@@ -66,7 +66,7 @@ jobs:
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Typos
uses: ./.github/workflows/bake.yml
@@ -78,8 +78,8 @@ jobs:
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: ${{inputs.sys_targets}}
machines: '["X64"]'
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
@@ -89,7 +89,7 @@ jobs:
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Audit
uses: ./.github/workflows/bake.yml
@@ -101,8 +101,8 @@ jobs:
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: ${{inputs.sys_targets}}
machines: '["X64"]'
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
@@ -112,7 +112,7 @@ jobs:
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Lychee
uses: ./.github/workflows/bake.yml
@@ -124,8 +124,8 @@ jobs:
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: ${{inputs.sys_targets}}
machines: '["X64"]'
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
@@ -135,7 +135,7 @@ jobs:
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Check
uses: ./.github/workflows/bake.yml
@@ -147,8 +147,8 @@ jobs:
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: ${{inputs.sys_targets}}
machines: '["X64"]'
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
+122 -38
View File
@@ -20,13 +20,13 @@ jobs:
name: Init
runs-on: ${{matrix.runner}}
env:
default_cargo_profiles: '["test", "release"]'
default_cargo_profiles: '["test", "bench", "release"]'
default_feat_sets: '["none", "default", "all"]'
default_rust_toolchains: '["nightly", "stable"]'
default_sys_names: '["debian"]'
default_sys_versions: '["testing-slim"]'
default_rust_targets: '["x86_64-unknown-linux-gnu"]'
default_sys_targets: '["x86_64-linux-gnu"]'
default_sys_targets: '["x86_64-v1-linux-gnu", "x86_64-v3-linux-gnu"]'
default_machines: '["X64"]'
outputs:
@@ -38,21 +38,32 @@ jobs:
sys_targets: ${{vars.SYS_TARGETS || env.default_sys_targets}}
sys_versions: ${{vars.SYS_VERSIONS || env.default_sys_versions}}
machines: ${{vars.MACHINES || env.default_machines}}
package: ${{vars.PACKAGE != 'false'}}
publish: ${{vars.PUBLISH != 'false'}}
build_nix: ${{vars.BUILD_NIX != 'false'}}
build_pkgs: ${{vars.BUILD_PKGS || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || contains(github.ref, 'tags/v')}}
check_pkgs: ${{vars.CHECK_PKGS || github.ref == 'refs/heads/test'}}
complement: ${{vars.COMPLEMENT != 'false'}}
complement_runner: 'het'
docker_repo: ${{vars.DOCKER_REPO}}
release_url: ${{steps.release.outputs.upload_url}}
pages_url: 'https://matrix-construct.github.io/tuwunel/'
rust_sdk_integ: ${{vars.RUST_SDK_INTEGRATION != 'false'}}
head_msg: ${{github.event.head_commit.message || github.event.workflow_run.head_commit.message}}
strategy:
fail-fast: true
matrix:
runner: ${{fromJSON(vars.RUNNERS)}}
machine: ${{fromJSON(vars.MACHINES)}}
exclude: ${{fromJSON(vars.RUNNERS_EXCLUDES)}}
runner: ${{fromJSON(vars.RUNNERS || '["het"]')}}
machine: ${{fromJSON(vars.MACHINES || '["X64"]')}}
exclude: ${{fromJSON(vars.RUNNERS_EXCLUDES || '[]')}}
steps:
- name: Initialize Builder
env:
runner: ${{matrix.runner}}
reserved_space: '{"het": "256GB", "aws": "48GB", "gcp": "160GB"}'
max_used_space: '{"het": "448GB", "aws": "64GB", "gcp": "192GB"}'
reserved_space: '{"het": "192GB", "aws": "48GB", "gcp": "160GB"}'
max_used_space: '{"het": "384GB", "aws": "64GB", "gcp": "192GB"}'
run: |
set +e
docker buildx inspect "${GITHUB_ACTOR}"
@@ -107,6 +118,8 @@ jobs:
if: >
!failure() && !cancelled()
&& !contains(github.ref, 'refs/tags/v')
&& !contains(needs.init.outputs.head_msg, '[ci no lint]')
&& !contains(needs.init.outputs.head_msg, '[ci only it]')
name: Lint
needs: [init] #needs: [init, deps]
@@ -122,34 +135,50 @@ jobs:
machines: ${{needs.init.outputs.machines}}
excludes: >
[
{"cargo_profile": "test", "feat_set": "logging"},
{"cargo_profile": "test", "rust_toolchain": "stable", "feat_set": "none"},
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "bench"},
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "none"},
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "default"},
{"cargo_profile": "release", "rust_toolchain": "nightly", "feat_set": "logging"},
{"cargo_profile": "release-debuginfo"},
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
{"cargo_profile": "release-native", "feat_set": "none"},
{"cargo_profile": "release-native", "feat_set": "default"},
{"cargo_profile": "release-native", "feat_set": "logging"},
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
{"rust_target": "aarch64-unknown-linux-gnu", "rust_toolchain": "nightly"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu"},
{"sys_target": "x86_64-v3-linux-gnu"},
{"sys_target": "x86_64-v4-linux-gnu"},
]
test:
if: >
!failure() && !cancelled()
&& !contains(github.ref, 'refs/tags/v')
&& !contains(needs.init.outputs.head_msg, '[ci no test]')
name: Test
needs: [init, lint]
uses: ./.github/workflows/test.yml
with:
complement: ${{fromJSON(vars.COMPLEMENT || 'true')}}
head_msg: ${{needs.init.outputs.head_msg}}
build_nix: ${{fromJSON(needs.init.outputs.build_nix)}}
rust_sdk_integ: ${{fromJSON(needs.init.outputs.rust_sdk_integ)}}
complement: ${{fromJSON(needs.init.outputs.complement)}}
complement_runner: ${{needs.init.outputs.complement_runner}}
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
feat_sets: ${{needs.init.outputs.feat_sets}}
rust_toolchains: ${{needs.init.outputs.rust_toolchains}}
@@ -158,34 +187,54 @@ jobs:
sys_targets: ${{needs.init.outputs.sys_targets}}
sys_versions: ${{needs.init.outputs.sys_versions}}
machines: ${{needs.init.outputs.machines}}
complement_runner: 'het'
excludes: >
[
{"cargo_profile": "test", "rust_toolchain": "stable", "feat_set": "none"},
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "release-debuginfo"},
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
{"cargo_profile": "release-native", "feat_set": "default"},
{"cargo_profile": "release-native", "feat_set": "none"},
{"cargo_profile": "release-native", "feat_set": "default"},
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
{"rust_target": "aarch64-unknown-linux-gnu", "rust_toolchain": "nightly"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "none"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "none"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "none"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
]
package:
if: >
!failure() && !cancelled()
&& needs.init.outputs.package
&& !contains(github.ref, 'refs/pull')
&& !contains(needs.init.outputs.head_msg, '[ci only it]')
&& !contains(needs.init.outputs.head_msg, '[ci no build]')
&& !contains(needs.init.outputs.head_msg, '[ci no package]')
name: Package
needs: [init, lint]
needs: [init, test]
uses: ./.github/workflows/package.yml
with:
release_url: ${{needs.init.outputs.release_url}}
check_pkgs: ${{needs.init.outputs.check_pkgs}}
build_pkgs: ${{needs.init.outputs.build_pkgs}}
build_nix: ${{fromJSON(needs.init.outputs.build_nix)}}
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
feat_sets: ${{needs.init.outputs.feat_sets}}
rust_toolchains: ${{needs.init.outputs.rust_toolchains}}
@@ -198,34 +247,58 @@ jobs:
[
{"feat_set": "none"},
{"cargo_profile": "test"},
{"cargo_profile": "bench"},
{"cargo_profile": "release-native"},
{"cargo_profile": "release-debuginfo", "feat_set": "default"},
{"cargo_profile": "release-debuginfo", "feat_set": "logging"},
{"cargo_profile": "release-debuginfo", "rust_toolchain": "nightly"},
{"cargo_profile": "release-debuginfo", "bake_target": "oci"},
{"cargo_profile": "release-debuginfo", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "release-native", "feat_set": "default"},
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
{"cargo_profile": "release-native", "feat_set": "default"},
{"cargo_profile": "release-native", "bake_target": "docker"},
{"cargo_profile": "release-native", "bake_target": "oci"},
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "release-debuginfo", "sys_target": "x86_64-v2-linux-gnu"},
{"cargo_profile": "release-debuginfo", "sys_target": "x86_64-v3-linux-gnu"},
{"cargo_profile": "release-debuginfo", "sys_target": "x86_64-v4-linux-gnu"},
{"cargo_profile": "release", "rust_toolchain": "nightly"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
{"rust_target": "aarch64-unknown-linux-gnu", "bake_target": "nix"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v2-linux-gnu", "bake_target": "nix"},
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v3-linux-gnu", "bake_target": "nix"},
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v4-linux-gnu", "bake_target": "nix"},
]
publish:
if: >
!failure() && !cancelled()
&& needs.init.outputs.publish
&& !contains(github.ref, 'refs/pull')
&& !contains(needs.init.outputs.head_msg, '[ci only it]')
&& !contains(needs.init.outputs.head_msg, '[ci no build]')
&& !contains(needs.init.outputs.head_msg, '[ci no package]')
&& !contains(needs.init.outputs.head_msg, '[ci no publish]')
name: Publish
needs: [init, test, package]
uses: ./.github/workflows/publish.yml
with:
docker_repo: ${{vars.DOCKER_REPO}}
docker_repo: ${{needs.init.outputs.docker_repo}}
release_url: ${{needs.init.outputs.release_url}}
pages_url: ${{needs.init.outputs.pages_url}}
cargo_profiles: ${{needs.init.outputs.cargo_profiles}}
feat_sets: ${{needs.init.outputs.feat_sets}}
rust_toolchains: ${{needs.init.outputs.rust_toolchains}}
@@ -237,15 +310,26 @@ jobs:
excludes: >
[
{"feat_set": "none"},
{"feat_set": "default"},
{"cargo_profile": "test"},
{"cargo_profile": "bench"},
{"cargo_profile": "release-debuginfo"},
{"cargo_profile": "release-native"},
{"cargo_profile": "release", "rust_toolchain": "nightly"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
{"rust_toolchain": "nightly"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "logging"},
]
secrets:
+21 -6
View File
@@ -38,6 +38,15 @@ on:
release_url:
type: string
description: For release assets
build_pkgs:
type: string
default: 'true'
check_pkgs:
type: string
default: 'false'
build_nix:
type: string
default: 'true'
jobs:
book:
@@ -46,7 +55,7 @@ jobs:
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["default"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Book
uses: ./.github/workflows/bake.yml
@@ -57,9 +66,9 @@ jobs:
rust_toolchains: '["stable"]'
rust_targets: ${{inputs.rust_targets}}
sys_names: ${{inputs.sys_names}}
sys_targets: ${{inputs.sys_targets}}
sys_versions: ${{inputs.sys_versions}}
machines: '["X64"]'
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
runner: 'het'
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
@@ -144,12 +153,12 @@ jobs:
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
&& github.ref == 'refs/heads/main'
&& fromJSON(inputs.build_pkgs)
name: Distro Packages
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["deb", "rpm"]'
bake_targets: '["deb", "rpm", "nix"]'
cargo_profiles: '["release"]'
feat_sets: '["all"]'
rust_toolchains: '["stable"]'
@@ -170,6 +179,12 @@ jobs:
"rpm": {
"dst": "tuwunel.rpm",
"mime": "application/x-rpm"
},
"nix": {
"dst": "tuwunel.nix.tar.zst",
"src": "/opt/tuwunel.nix.tar",
"mime": "application/zstd",
"zstd": 11
}
}
@@ -179,7 +194,7 @@ jobs:
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
&& github.ref == 'refs/heads/main'
&& fromJSON(inputs.check_pkgs)
name: Check Packages
needs: [pkgs]
+208 -7
View File
@@ -44,20 +44,27 @@ on:
release_url:
type: string
description: For release assets
pages_url:
type: string
description: For pages deployment
jobs:
documents:
if: >
!failure() && !cancelled()
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
&& !contains(github.ref, '-draft')
name: Documents
runs-on: ['X64', 'het']
permissions:
pages: write
contents: read
id-token: write
pages: write
environment:
name: github-pages
url: ${{inputs.pages_url}}
steps:
- id: book
@@ -70,16 +77,14 @@ jobs:
&& inputs.machines
&& inputs.docker_repo != ''
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
&& !contains(github.ref, '-draft')
name: Containers
runs-on: ${{matrix.machine}}
permissions: write-all
continue-on-error: false
strategy:
fail-fast: true
matrix:
bake_target: ${{fromJSON('["github", "dockerhub"]')}}
bake_target: ${{fromJSON('["docker_io", "ghcr_io"]')}}
cargo_profile: ${{fromJSON(inputs.cargo_profiles)}}
rust_toolchain: ${{fromJSON(inputs.rust_toolchains)}}
feat_set: ${{fromJSON(inputs.feat_sets)}}
@@ -92,6 +97,10 @@ jobs:
include: ${{fromJSON(inputs.includes)}}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: GitHub Login
uses: docker/login-action@v3
with:
@@ -120,8 +129,200 @@ jobs:
acct: ${{github.actor}}
repo: ${{github.repository}}
docker_repo: ${{inputs.docker_repo}}
docker_tag_preview: ${{ matrix.cargo_profile == 'release' && matrix.feat_set == 'all' && matrix.sys_target == 'x86_64-linux-gnu' && contains(github.ref, 'refs/tags/v') }}
docker_tag_latest: ${{ matrix.cargo_profile == 'release' && matrix.feat_set == 'all' && matrix.sys_target == 'x86_64-linux-gnu' && contains(github.ref, 'refs/tags/v') && !contains(github.ref, '-rc') }}
run: |
docker/bake.sh "${{matrix.bake_target}}"
bundles:
if: >
!failure() && !cancelled()
&& inputs.machines
&& inputs.docker_repo != ''
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
name: Bundles
needs: [containers]
runs-on: 'het'
permissions: write-all
strategy:
fail-fast: true
matrix:
bake_target: ${{fromJSON('["docker_io", "ghcr_io"]')}}
cargo_profile: ${{fromJSON('["release"]')}}
feat_set: ${{fromJSON('["all"]')}}
rust_toolchain: ${{fromJSON('["stable"]')}}
sys_name: ${{fromJSON(inputs.sys_names)}}
sys_version: ${{fromJSON(inputs.sys_versions)}}
rust_target: ${{fromJSON(inputs.rust_targets)}}
sys_target: ${{fromJSON(inputs.sys_targets)}}
machine: ${{fromJSON(inputs.machines)}}
exclude: ${{fromJSON(inputs.excludes)}}
include: ${{fromJSON(inputs.includes)}}
steps:
- name: GitHub Login
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.ghcr_token }}
- name: DockerHub Login
uses: docker/login-action@v3
with:
registry: docker.io
username: ${{ github.actor }}
password: ${{ secrets.dockerhub_token }}
- name: Release Branch
if: >
!failure() && !cancelled()
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
env:
acct: ${{github.actor}}
ghcr_repo: ${{github.repository}}
docker_repo: ${{inputs.docker_repo}}
registry: ${{matrix.bake_target}}
cargo_profile: ${{matrix.cargo_profile}}
feat_set: ${{matrix.feat_set}}
sys_target: ${{matrix.sys_target}}
ref_name: ${{github.ref_name}}
run: |
registry="$(echo -n $registry | sed s/_/./)"
if test "$registry" = "ghcr.io"; then
repo="$ghcr_repo"
else
repo="$docker_repo"
fi
docker manifest create "${registry}/${repo}:${ref_name}" \
--amend "${registry}/${repo}:${ref_name}-${cargo_profile}-${feat_set}-${sys_target}"
- name: Release Preview
if: >
!failure() && !cancelled()
&& contains(github.ref, 'refs/tags/v')
&& !contains(github.ref, '-draft')
env:
acct: ${{github.actor}}
ghcr_repo: ${{github.repository}}
docker_repo: ${{inputs.docker_repo}}
registry: ${{matrix.bake_target}}
cargo_profile: ${{matrix.cargo_profile}}
feat_set: ${{matrix.feat_set}}
sys_target: ${{matrix.sys_target}}
ref_name: ${{github.ref_name}}
run: |
registry="$(echo -n $registry | sed s/_/./)"
if test "$registry" = "ghcr.io"; then
repo="$ghcr_repo"
else
repo="$docker_repo"
fi
docker manifest create "${registry}/${repo}:preview" \
--amend "${registry}/${repo}:${ref_name}-${cargo_profile}-${feat_set}-${sys_target}"
- name: Release Latest
if: >
!failure() && !cancelled()
&& contains(github.ref, 'refs/tags/v')
&& !contains(github.ref, '-draft')
&& !contains(github.ref, '-rc')
env:
acct: ${{github.actor}}
ghcr_repo: ${{github.repository}}
docker_repo: ${{inputs.docker_repo}}
registry: ${{matrix.bake_target}}
cargo_profile: ${{matrix.cargo_profile}}
feat_set: ${{matrix.feat_set}}
sys_target: ${{matrix.sys_target}}
ref_name: ${{github.ref_name}}
run: |
registry="$(echo -n $registry | sed s/_/./)"
if test "$registry" = "ghcr.io"; then
repo="$ghcr_repo"
else
repo="$docker_repo"
fi
docker manifest create "${registry}/${repo}:latest" \
--amend "${registry}/${repo}:${ref_name}-${cargo_profile}-${feat_set}-${sys_target}"
delivery:
if: >
!failure() && !cancelled()
&& inputs.machines
&& inputs.docker_repo != ''
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
name: Delivery
needs: [bundles, documents]
runs-on: 'het'
permissions: write-all
steps:
- name: GitHub Login
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.ghcr_token }}
- name: DockerHub Login
uses: docker/login-action@v3
with:
registry: docker.io
username: ${{ github.actor }}
password: ${{ secrets.dockerhub_token }}
- name: Push Branch
if: >
!failure() && !cancelled()
&& (github.ref == 'refs/heads/main' || contains(github.ref, 'refs/tags/v'))
env:
ref_name: ${{github.ref_name}}
ghcr_repo: ${{github.repository}}
docker_repo: ${{inputs.docker_repo}}
run: |
docker manifest push -p "ghcr.io/${ghcr_repo}:${ref_name}"
docker manifest push -p "docker.io/${docker_repo}:${ref_name}"
- name: Push Preview
if: >
!failure() && !cancelled()
&& contains(github.ref, 'refs/tags/v')
&& !contains(github.ref, '-draft')
env:
ghcr_repo: ${{github.repository}}
docker_repo: ${{inputs.docker_repo}}
run: |
docker manifest push -p "ghcr.io/${ghcr_repo}:preview"
docker manifest push -p "docker.io/${docker_repo}:preview"
- name: Push Latest
if: >
!failure() && !cancelled()
&& contains(github.ref, 'refs/tags/v')
&& !contains(github.ref, '-draft')
&& !contains(github.ref, '-rc')
env:
ghcr_repo: ${{github.repository}}
docker_repo: ${{inputs.docker_repo}}
run: |
docker manifest push -p "ghcr.io/${ghcr_repo}:latest"
docker manifest push -p "docker.io/${docker_repo}:latest"
+231 -35
View File
@@ -38,38 +38,116 @@ on:
complement:
type: boolean
default: true
complement_feat_sets:
type: string
default: '["all"]'
complement_runner:
type: string
rust_sdk_integ:
type: boolean
default: true
build_nix:
type: boolean
default: true
head_msg:
type: string
jobs:
unit:
docs:
if: >
contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
!contains(inputs.head_msg, '[ci only it]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Unit
name: Docs
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["unit"]'
bake_targets: '["docs"]'
cargo_profiles: '["test"]'
feat_sets: '["all"]'
rust_toolchains: '["nightly"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
unit:
if: >
!contains(inputs.head_msg, '[ci only it]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
name: Module
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["unit", "integ"]'
cargo_profiles: '["test"]'
feat_sets: '["all"]'
rust_toolchains: ${{inputs.rust_toolchains}}
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: ${{inputs.sys_targets}}
machines: '["X64"]'
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
bench:
if: >
!contains(inputs.head_msg, '[ci only it]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["bench"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v3-linux-gnu"]')[0])
name: Bench
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["unit", "integ"]'
cargo_profiles: '["bench"]'
feat_sets: '["all"]'
rust_toolchains: '["nightly"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: '["x86_64-v3-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
memcheck:
if: >
!contains(inputs.head_msg, '[ci only it]')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["bench"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v3-linux-gnu"]')[0])
name: Memcheck
uses: ./.github/workflows/bake.yml
with:
#bake_targets: '["unit-valgrind", "integ-valgrind"]'
bake_targets: '["integ-valgrind"]'
cargo_profiles: '["bench"]' # use bench not release for debug syms
feat_sets: '["all"]'
rust_toolchains: '["nightly"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: '["x86_64-v3-linux-gnu"]'
machines: ${{inputs.machines}}
includes: ${{inputs.includes}}
excludes: ${{inputs.excludes}}
smoke:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci only it]')
&& !contains(inputs.head_msg, '[ci no build]')
&& inputs.cargo_profiles
&& inputs.machines
@@ -89,12 +167,18 @@ jobs:
excludes: >
[
{"cargo_profile": "test", "feat_set": "default"},
{"cargo_profile": "test", "feat_set": "logging"},
{"cargo_profile": "test", "feat_set": "none", "bake_target": "smoke-valgrind"},
{"cargo_profile": "test", "rust_toolchain": "stable"},
{"cargo_profile": "test", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "test", "sys_target": "x86_64-v2-linux-gnu"},
{"cargo_profile": "test", "sys_target": "x86_64-v3-linux-gnu"},
{"cargo_profile": "test", "bake_target": "smoke-valgrind"},
{"cargo_profile": "bench"},
{"cargo_profile": "release", "rust_toolchain": "nightly"},
{"cargo_profile": "release", "rust_toolchain": "stable", "feat_set": "none"},
{"cargo_profile": "release", "bake_target": "smoke-valgrind"},
{"cargo_profile": "release-debuginfo", "feat_set": "logging"},
{"cargo_profile": "release-debuginfo", "rust_toolchain": "nightly"},
{"cargo_profile": "release-debuginfo", "rust_toolchain": "stable", "feat_set": "none"},
{"cargo_profile": "release-debuginfo", "bake_target": "smoke"},
@@ -102,37 +186,138 @@ jobs:
{"cargo_profile": "release-native", "rust_toolchain": "stable"},
{"cargo_profile": "release-native", "feat_set": "none"},
{"cargo_profile": "release-native", "feat_set": "default"},
{"cargo_profile": "release-native", "feat_set": "logging"},
{"cargo_profile": "release-native", "bake_target": "smoke-valgrind"},
{"cargo_profile": "release-native", "rust_target": "aarch64-unknown-linux-gnu"},
{"cargo_profile": "release-native", "sys_target": "x86_64-v2-linux-gnu"},
{"cargo_profile": "release-native", "sys_target": "x86_64-v3-linux-gnu"},
{"cargo_profile": "release-native", "sys_target": "x86_64-v4-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "none"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "default"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-linux-gnu"},
{"sys_target": "x86_64-linux-gnu", "machine": "ARM64"},
{"sys_target": "aarch64-linux-gnu", "machine": "X64"},
{"rust_target": "aarch64-unknown-linux-gnu", "feat_set": "logging"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v1-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v3-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v4-linux-gnu"},
{"rust_target": "x86_64-unknown-linux-gnu", "sys_target": "aarch64-v8-linux-gnu"},
{"sys_target": "aarch64-v8-linux-gnu", "machine": "X64"},
{"sys_target": "x86_64-v1-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "none"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v2-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v3-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "none"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v3-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v3-linux-gnu", "bake_target": "smoke-valgrind"},
{"sys_target": "x86_64-v4-linux-gnu", "machine": "ARM64"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "none"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "default"},
{"sys_target": "x86_64-v4-linux-gnu", "feat_set": "logging"},
{"sys_target": "x86_64-v4-linux-gnu", "bake_target": "smoke-valgrind"},
]
nix:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci only it]')
&& !contains(inputs.head_msg, '[ci no build]')
&& !contains(github.ref, 'refs/pull')
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["release"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["stable"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[0])
&& fromJSON(inputs.build_nix)
name: Smoke NixOS
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["smoke-nix"]'
cargo_profiles: '["release"]'
feat_sets: '["all"]'
rust_toolchains: '["stable"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: '["x86_64-v1-linux-gnu"]'
machines: ${{inputs.machines}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
rust-sdk-integ:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci no build]')
&& inputs.rust_sdk_integ
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.rust_targets), fromJSON('["x86_64-unknown-linux-gnu"]')[0])
name: Matrix SDK Integration
needs: [smoke]
uses: ./.github/workflows/bake.yml
with:
#bake_targets: '["rust-sdk-integ", "rust-sdk-valgrind"]'
bake_targets: '["rust-sdk-integ"]'
cargo_profiles: ${{inputs.cargo_profiles}}
feat_sets: '["all"]'
rust_toolchains: '["nightly"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: '["x86_64-unknown-linux-gnu"]'
sys_targets: ${{inputs.sys_targets}}
machines: '["X64"]'
runner: ${{inputs.complement_runner}}
includes: ${{inputs.includes}}
artifact: >
{
"rust-sdk-integ": {
"src": "/var/log/tuwunel.log",
"dst": "rust-sdk-integ.tuwunel.log",
},
"rust-sdk-valgrind": {
"src": "/var/log/tuwunel.log",
"dst": "rust-sdk-valgrind.tuwunel.log",
}
}
excludes: >
[
{"bake_target": "rust-sdk-valgrind", "cargo_profile": "test"},
{"feat_set": "none"},
{"feat_set": "logging"},
{"cargo_profile": "release"},
{"cargo_profile": "release-debuginfo"},
{"cargo_profile": "release-native"},
{"cargo_profile": "test", "sys_target": "x86_64-v2-linux-gnu"},
{"cargo_profile": "test", "sys_target": "x86_64-v3-linux-gnu"},
{"cargo_profile": "bench", "sys_target": "x86_64-v1-linux-gnu"},
{"cargo_profile": "bench", "sys_target": "x86_64-v2-linux-gnu"},
{"rust_target": "aarch64-unknown-linux-gnu"},
{"sys_target": "aarch64-v8-linux-gnu"},
]
complement:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci no build]')
&& inputs.complement
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.complement_feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["bench"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v3-linux-gnu"]')[0])
name: Complement
uses: ./.github/workflows/bake.yml
with:
bake_targets: '["complement-tester", "complement-testee"]'
cargo_profiles: '["test"]'
feat_sets: ${{inputs.complement_feat_sets}}
cargo_profiles: '["bench"]'
feat_sets: '["logging"]'
rust_toolchains: '["nightly"]'
sys_names: ${{inputs.sys_names}}
sys_versions: ${{inputs.sys_versions}}
rust_targets: ${{inputs.rust_targets}}
sys_targets: ${{inputs.sys_targets}}
machines: '["X64"]'
sys_targets: '["x86_64-v3-linux-gnu"]'
machines: ${{inputs.machines}}
runner: ${{inputs.complement_runner}}
excludes: ${{inputs.excludes}}
includes: ${{inputs.includes}}
@@ -140,15 +325,15 @@ jobs:
compliance:
if: >
!failure() && !cancelled()
&& !contains(inputs.head_msg, '[ci no build]')
&& inputs.complement
&& inputs.machines
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["test"]')[0])
&& contains(fromJSON(inputs.feat_sets), fromJSON('["all"]')[0])
&& contains(fromJSON(inputs.cargo_profiles), fromJSON('["bench"]')[0])
&& contains(fromJSON(inputs.rust_toolchains), fromJSON('["nightly"]')[0])
&& contains(fromJSON(inputs.machines), fromJSON('["X64"]')[0])
&& contains(fromJSON(inputs.sys_targets), fromJSON('["x86_64-v3-linux-gnu"]')[0])
name: Matrix Compliance
needs: [complement]
needs: [complement, smoke]
runs-on: ["${{matrix.machine}}", "${{inputs.complement_runner}}"]
concurrency:
group: complement-cant-walk-and-chew-bubblegum
@@ -157,14 +342,14 @@ jobs:
strategy:
fail-fast: false
matrix:
cargo_profile: ${{fromJSON('["test"]')}}
feat_set: ${{fromJSON(inputs.complement_feat_sets)}}
cargo_profile: ${{fromJSON('["bench"]')}}
feat_set: ${{fromJSON('["logging"]')}}
rust_toolchain: ${{fromJSON('["nightly"]')}}
sys_name: ${{fromJSON(inputs.sys_names)}}
sys_version: ${{fromJSON(inputs.sys_versions)}}
rust_target: ${{fromJSON(inputs.rust_targets)}}
sys_target: ${{fromJSON(inputs.sys_targets)}}
machine: ${{fromJSON('["X64"]')}}
sys_target: ${{fromJSON('["x86_64-v3-linux-gnu"]')}}
machine: ${{fromJSON(inputs.machines)}}
exclude: ${{fromJSON(inputs.excludes)}}
include: ${{fromJSON(inputs.includes)}}
@@ -185,7 +370,8 @@ jobs:
run: |
docker/complement.sh
- name: Extract
- if: success() || failure() && steps.execute.outcome == 'failure'
name: Extract
id: extract
env:
name: complement_tester__${{matrix.sys_name}}__${{matrix.sys_version}}__${{matrix.sys_target}}
@@ -193,14 +379,24 @@ jobs:
run: |
cid=$(cat "$name")
docker cp "$cid:/usr/src/complement/new_results.jsonl" tests/test_results/complement/test_results.jsonl
docker cp "$cid:/usr/src/complement/new_results.jsonl" tests/complement/results.jsonl
- name: Upload
id: upload
- if: success() || failure() && steps.execute.outcome == 'failure'
name: Upload New Results
id: upload-result
uses: actions/upload-artifact@v4
with:
name: test_results-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
path: ./tests/test_results/complement/test_results.jsonl
name: complement_results-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
path: ./tests/complement/results.jsonl
- if: failure() && steps.execute.outcome == 'failure'
name: Upload Failure Output
id: upload-output
uses: actions/upload-artifact@v4
with:
name: complement_output-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
path: ./tests/complement/logs.jsonl
if-no-files-found: ignore
- name: Accept
id: accept
+8 -1
View File
@@ -94,4 +94,11 @@ public/
rustc-ice-*
# complement test logs are huge
tests/test_results/complement/test_logs.jsonl
tests/complement/logs.jsonl
# cargo profiles from -Z self-profile
*.mm_profdata
# Editor crashfiles
*.save
*.swp
Generated
+1332 -1105
View File
File diff suppressed because it is too large Load Diff
+148 -149
View File
@@ -28,8 +28,8 @@ keywords = [
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/matrix-construct/tuwunel"
rust-version = "1.86.0"
version = "1.1.0"
rust-version = "1.89.0"
version = "1.4.7"
[workspace.metadata.crane]
name = "tuwunel"
@@ -39,7 +39,7 @@ name = "tuwunel"
#
[workspace.dependencies.argon2]
version = "0.5.3"
version = "0.5"
default-features = false
features = [
"alloc",
@@ -47,17 +47,17 @@ features = [
]
[workspace.dependencies.arrayvec]
version = "0.7.6"
version = "0.7"
features = ["serde"]
[workspace.dependencies.async-channel]
version = "2.3.1"
[workspace.dependencies.async-trait]
version = "0.1.88"
version = "0.1"
[workspace.dependencies.axum]
version = "0.8.4"
version = "0.8"
default-features = false
features = [
"form",
@@ -73,7 +73,7 @@ features = [
version = "0.7"
[workspace.dependencies.axum-extra]
version = "0.10.1"
version = "0.10"
default-features = false
features = [
"typed-header",
@@ -81,7 +81,7 @@ features = [
]
[workspace.dependencies.axum-server]
version = "0.7.2"
version = "0.7"
default-features = false
[workspace.dependencies.axum-server-dual-protocol]
@@ -89,11 +89,11 @@ version = "0.7"
default-features = false
[workspace.dependencies.base64]
version = "0.22.1"
version = "0.22"
default-features = false
[workspace.dependencies.blurhash]
version = "0.2.3"
version = "0.2"
default-features = false
features = [
"fast-linear-to-srgb",
@@ -101,13 +101,13 @@ features = [
]
[workspace.dependencies.bytes]
version = "1.10.1"
version = "1.10"
[workspace.dependencies.bytesize]
version = "2.0.1"
version = "2.1"
[workspace.dependencies.cargo_toml]
version = "0.22.1"
version = "0.22"
default-features = false
features = ["features"]
@@ -115,12 +115,12 @@ features = ["features"]
version = "0.1"
[workspace.dependencies.chrono]
version = "0.4.41"
version = "0.4"
features = ["alloc", "std"]
default-features = false
[workspace.dependencies.clap]
version = "4.5.39"
version = "4.5"
default-features = false
features = [
"derive",
@@ -140,40 +140,40 @@ features = [
version = "0.8.3"
[workspace.dependencies.const-str]
version = "0.6.2"
version = "0.7"
[workspace.dependencies.criterion]
version = "0.7"
default-features = false
features = [
"cargo_bench_support",
"async_futures",
"async_tokio",
]
[workspace.dependencies.ctor]
version = "0.4.2"
version = "0.5"
[workspace.dependencies.cyborgtime]
version = "2.1.1"
version = "2.1"
[workspace.dependencies.either]
version = "1.15.0"
version = "1.15"
default-features = false
features = ["serde"]
[workspace.dependencies.figment]
version = "0.10.19"
version = "0.10"
default-features = false
features = ["env", "toml"]
[workspace.dependencies.futures]
version = "0.3.31"
version = "0.3"
default-features = false
features = ["std", "async-await"]
[workspace.dependencies.hardened_malloc-rs]
version = "0.1.2"
default-features = false
features = [
"static",
"gcc",
"light",
]
[workspace.dependencies.hickory-resolver]
version = "0.25.2"
version = "0.25"
default-features = false
features = [
"serde",
@@ -182,17 +182,17 @@ features = [
]
[workspace.dependencies.hmac]
version = "0.12.1"
version = "0.12"
default-features = false
[workspace.dependencies.http]
version = "1.3.1"
version = "1.3"
[workspace.dependencies.http-body-util]
version = "0.1.3"
version = "0.1"
[workspace.dependencies.hyper]
version = "1.6.0"
version = "1.7"
default-features = false
features = [
"server",
@@ -210,7 +210,7 @@ features = [
]
[workspace.dependencies.image]
version = "0.25.6"
version = "0.25"
default-features = false
features = [
"jpeg",
@@ -219,14 +219,24 @@ features = [
"webp",
]
[workspace.dependencies.insta]
version = "1.43"
features = [
"json",
]
[workspace.dependencies.ipaddress]
version = "0.1.3"
version = "0.1"
[workspace.dependencies.itertools]
version = "0.14.0"
version = "0.14"
[workspace.dependencies.jevmalloc]
git = "https://github.com/matrix-construct/jevmalloc"
rev = "29fde02533ee2c6a5e9b87095324e6f7a5d43738"
[workspace.dependencies.jsonwebtoken]
version = "9.3.1"
version = "9.3"
default-features = false
features = ["use_pem"]
@@ -237,42 +247,45 @@ default-features = false
features = ["sync", "tls-rustls"]
[workspace.dependencies.libc]
version = "0.2.172"
version = "0.2"
[workspace.dependencies.libloading]
version = "0.8.8"
version = "0.8"
[workspace.dependencies.log]
version = "0.4.27"
version = "0.4"
default-features = false
[workspace.dependencies.loole]
version = "0.4.1"
version = "0.4"
[workspace.dependencies.lru-cache]
version = "0.1.2"
version = "0.1"
[workspace.dependencies.maplit]
version = "1.0.2"
version = "1.0"
[workspace.dependencies.minicbor]
version = "1.0.0"
version = "2.1"
features = ["std"]
[workspace.dependencies.minicbor-serde]
version = "0.5.0"
version = "0.6"
features = ["std"]
[workspace.dependencies.nix]
version = "0.30.1"
version = "0.30"
default-features = false
features = ["resource"]
features = [
"resource",
"user",
]
[workspace.dependencies.num-traits]
version = "0.2.19"
version = "0.2"
[workspace.dependencies.opentelemetry]
version = "0.30.0"
version = "0.31"
# Disabled until they move to opentelemetry 0.30
#[workspace.dependencies.opentelemetry-jaeger]
@@ -280,23 +293,23 @@ version = "0.30.0"
#features = ["rt-tokio"]
[workspace.dependencies.opentelemetry_sdk]
version = "0.30.0"
version = "0.31"
features = ["rt-tokio"]
[workspace.dependencies.proc-macro2]
version = "1.0.95"
version = "1.0"
[workspace.dependencies.quote]
version = "1.0.40"
version = "1.0"
[workspace.dependencies.rand]
version = "0.8.5"
version = "0.8"
[workspace.dependencies.regex]
version = "1.11.1"
version = "1.12"
[workspace.dependencies.reqwest]
version = "0.12.19"
version = "0.12"
default-features = false
features = [
"rustls-tls-native-roots",
@@ -306,50 +319,50 @@ features = [
]
[workspace.dependencies.ring]
version = "0.17.14"
version = "0.17"
default-features = false
[workspace.dependencies.ruma]
git = "https://github.com/matrix-construct/ruma"
rev = "0155c2b33233bec9dece79d5134a9574b347f4c1"
rev = "f8aa292ce48f28ee6c82b29a811d26c6ccb5aad2"
features = [
"compat",
"rand",
"__compat",
"appservice-api-c",
"client-api",
"client-api-s",
"compat-upload-signatures",
"federation-api",
"identifiers-validation",
"markdown",
"push-gateway-api-c",
"unstable-exhaustive-types",
"rand",
"ring-compat",
"compat-upload-signatures",
"identifiers-validation",
"unstable-unspecified",
"unstable-msc2448",
"unstable-msc2666",
"unstable-msc2867",
"unstable-msc2870",
"unstable-msc3026",
"unstable-msc3061",
"unstable-msc3814",
"unstable-msc3245",
"unstable-msc3266",
"unstable-msc3381", # polls
"unstable-msc3489", # beacon / live location
"unstable-msc3575",
"unstable-msc3930", # polls push rules
"unstable-msc4075",
"unstable-msc4095",
"unstable-msc4121",
"unstable-msc4125",
"unstable-msc4133",
"unstable-msc4143",
"unstable-msc4186",
"unstable-msc4203", # sending to-device events to appservices
"unstable-msc4210", # remove legacy mentions
"unstable-msc4310",
"unstable-msc4311",
"unstable-extensible-events",
"unstable-pdu",
]
[workspace.dependencies.rustls]
version = "0.23.27"
version = "0.23"
default-features = false
features = ["aws_lc_rs"]
@@ -359,25 +372,27 @@ default-features = false
[workspace.dependencies.rust-rocksdb]
git = "https://github.com/matrix-construct/rust-rocksdb"
rev = "225a42519276e502205bdc845cebdb22d70ee245"
rev = "31e6f8dd4a531b379c132e82773efc9315b0489a"
default-features = false
features = [
"multi-threaded-cf",
"mt_static",
"lz4",
"zstd",
"bzip2",
"lto",
"lz4",
"multi-threaded-cf",
"serde1",
"zstd",
"zstd-static-linking-only",
]
[workspace.dependencies.sanitize-filename]
version = "0.6.0"
version = "0.6"
[workspace.dependencies.sd-notify]
version = "0.4.5"
version = "0.4"
default-features = false
[workspace.dependencies.sentry]
version = "0.38.1"
version = "0.45"
default-features = false
features = [
"backtrace",
@@ -393,48 +408,52 @@ features = [
]
[workspace.dependencies.sentry-tower]
version = "0.38.1"
version = "0.45"
[workspace.dependencies.sentry-tracing]
version = "0.38.1"
version = "0.45"
[workspace.dependencies.serde]
version = "1.0.219"
version = "1.0"
default-features = false
features = ["rc"]
[workspace.dependencies.serde_html_form]
version = "0.2.6"
version = "0.2"
[workspace.dependencies.serde_json]
version = "1.0.140"
version = "1.0"
default-features = false
features = ["raw_value"]
[workspace.dependencies.serde_regex]
version = "1.1.0"
version = "1.1"
[workspace.dependencies.serde_yaml]
version = "0.9.34"
version = "0.9"
[workspace.dependencies.sha1]
version = "0.10.6"
version = "0.10"
default-features = false
[workspace.dependencies.sha2]
version = "0.10.9"
version = "0.10"
default-features = false
[workspace.dependencies.similar]
version = "2.7"
[workspace.dependencies.smallstr]
version = "0.3"
features = [
"ffi",
"serde",
"std",
"union",
]
[workspace.dependencies.smallvec]
version = "1.15.1"
version = "1.15"
features = [
"const_generics",
"const_new",
@@ -444,7 +463,7 @@ features = [
]
[workspace.dependencies.syn]
version = "2.0.101"
version = "2.0"
default-features = false
features = [
"extra-traits",
@@ -455,39 +474,15 @@ features = [
]
[workspace.dependencies.termimad]
version = "0.33.0"
version = "0.34"
default-features = false
[workspace.dependencies.thiserror]
version = "2.0.12"
version = "2.0"
default-features = false
[workspace.dependencies.tikv-jemallocator]
git = "https://github.com/matrix-construct/jemallocator"
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
default-features = false
features = [
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
]
[workspace.dependencies.tikv-jemalloc-ctl]
git = "https://github.com/matrix-construct/jemallocator"
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
default-features = false
features = ["use_std"]
[workspace.dependencies.tikv-jemalloc-sys]
git = "https://github.com/matrix-construct/jemallocator"
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
default-features = false
features = [
"background_threads_runtime_support",
"unprefixed_malloc_on_supported_platforms",
]
[workspace.dependencies.tokio]
version = "1.45.1"
version = "1.48"
default-features = false
features = [
"fs",
@@ -502,20 +497,20 @@ features = [
]
[workspace.dependencies.tokio-metrics]
version = "0.4.2"
version = "0.4"
[workspace.dependencies.toml]
version = "0.8.23"
version = "0.9"
default-features = false
features = ["parse"]
[workspace.dependencies.tower]
version = "0.5.2"
version = "0.5"
default-features = false
features = ["util"]
[workspace.dependencies.tower-http]
version = "0.6.6"
version = "0.6"
default-features = false
features = [
"add-extension",
@@ -529,21 +524,21 @@ features = [
]
[workspace.dependencies.tracing]
version = "0.1.41"
version = "0.1"
default-features = false
[workspace.dependencies.tracing-core]
version = "0.1.33"
version = "0.1"
default-features = false
[workspace.dependencies.tracing-flame]
version = "0.2.0"
version = "0.2"
[workspace.dependencies.tracing-opentelemetry]
version = "0.31.0"
version = "0.32"
[workspace.dependencies.tracing-subscriber]
version = "0.3.19"
version = "0.3"
default-features = false
features = [
"ansi",
@@ -555,35 +550,18 @@ features = [
]
[workspace.dependencies.url]
version = "2.5.4"
version = "2.5"
default-features = false
features = ["serde"]
[workspace.dependencies.webpage]
version = "2.0.1"
version = "2.0"
default-features = false
#
# Patches
#
# backport of [https://github.com/tokio-rs/tracing/pull/2956] to the 0.1.x
# branch of tracing. we can switch back to upstream if #2956 is merged and
# backported in the upstream repo.
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
[patch.crates-io.tracing-subscriber]
git = "https://github.com/matrix-construct/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing]
git = "https://github.com/matrix-construct/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing-core]
git = "https://github.com/matrix-construct/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
[patch.crates-io.tracing-log]
git = "https://github.com/matrix-construct/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
# adds a tab completion callback:
# https://github.com/girlbossceo/rustyline-async/commit/de26100b0db03e419a3d8e1dd26895d170d1fe50
# adds event for CTRL+\:
@@ -709,7 +687,7 @@ inherits = "release-native.build-override"
[profile.bench]
debug = "limited"
strip = false
strip = "none"
#rustflags = [
# "-Cremark=all",
# '-Ztime-passes',
@@ -729,7 +707,7 @@ strip = false
# and can be raised if build times are tolerable.
[profile.dev]
debug = "full"
debug = 0
#rustflags = [
# '--cfg', 'tuwunel_mods',
# '-Ztime-passes',
@@ -789,7 +767,7 @@ inherits = "dev"
[profile.dev.package.'*']
inherits = "dev"
debug = "limited"
debug = 0
incremental = false
codegen-units = 1
opt-level = 'z'
@@ -806,6 +784,11 @@ opt-level = 'z'
# '-Clink-arg=-Wl,-z,nodelete',
#]
# same as dev but slower.
[profile.dbg]
inherits = "dev"
debug = "full"
# primarily used for CI
[profile.test]
debug = "limited"
@@ -856,6 +839,7 @@ unused-qualifications = "warn"
## some sadness
elided_named_lifetimes = "allow" # TODO!
mismatched_lifetime_syntaxes = "allow" # TODO!
let_underscore_drop = "allow"
missing_docs = "allow"
# cfgs cannot be limited to expected cfgs or their de facto non-transitive/opt-in use-case e.g.
@@ -871,6 +855,15 @@ variant_size_differences = "allow"
# we check nightly clippy lints
unknown_lints = "allow"
# rust claims elided_named_lifetimes has been replaced by
# mismatched_lifetime_syntaxes but it actually has not.
renamed_and_removed_lints = "allow"
# nightly has a new lint that conflicts with our BoolExt trait at
# src/core/utils/bool.rs:68:48
# <https://github.com/rust-lang/rust/issues/142748>
unstable_name_collisions = "allow" # TODO
#######################################
#
# Clippy lints
@@ -887,6 +880,9 @@ multiple_crate_versions = { level = "allow", priority = 1 }
###################
complexity = { level = "warn", priority = -1 }
# promotes forward-compat for literal ..default() construction
needless_update = { level = "allow", priority = 1 }
###################
correctness = { level = "warn", priority = -1 }
@@ -899,18 +895,22 @@ option_if_let_else = { level = "allow", priority = 1 } # TODO
redundant_pub_crate = { level = "allow", priority = 1 } # TODO
significant_drop_in_scrutinee = { level = "allow", priority = 1 } # TODO
significant_drop_tightening = { level = "allow", priority = 1 } # TODO
tuple_array_conversions = { level = "allow", priority = 1 }
###################
pedantic = { level = "warn", priority = -1 }
## some sadness
too_long_first_doc_paragraph = { level = "allow", priority = 1 }
default_trait_access = { level = "allow", priority = 1 }
doc_markdown = { level = "allow", priority = 1 }
enum_glob_use = { level = "allow", priority = 1 }
if_not_else = { level = "allow", priority = 1 }
if_then_some_else_none = { level = "allow", priority = 1 }
inline_always = { level = "allow", priority = 1 }
map_unwrap_or = { level = "allow", priority = 1 }
match_bool = { level = "allow", priority = 1 }
match_same_arms = { level = "allow", priority = 1 }
missing_docs_in_private_items = { level = "allow", priority = 1 }
missing_errors_doc = { level = "allow", priority = 1 }
missing_panics_doc = { level = "allow", priority = 1 }
@@ -956,12 +956,11 @@ pub_without_shorthand = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
redundant_type_annotations = "warn"
rest_pat_in_fully_bound_structs = "warn"
semicolon_outside_block = "warn"
str_to_string = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
implicit_clone = "warn"
suspicious_xor_used_as_pow = "warn"
tests_outside_test_module = "warn"
try_err = "warn"
+16 -32
View File
@@ -1,4 +1,4 @@
# Tuwunel <sup>🎔</sup>
# Tuwunel<sup>💕</sup>
![GitHub License](https://img.shields.io/github/license/matrix-construct/tuwunel?style=flat&color=%238A2BE2)
![GitHub Created At](https://img.shields.io/github/created-at/matrix-construct/tuwunel?style=flat&color=%238A2BE2)
@@ -21,7 +21,7 @@ ## High Performance Matrix Homeserver in Rust!
[bot](https://matrix.org/ecosystem/integrations/). It is written entirely in Rust to be a scalable,
lightweight, low-cost, community-driven alternative covering all but the most niche uses.
This project is the official successor to [conduwuit](https://github.com/girlbossceo/conduwuit), which
This project is the official successor to conduwuit, which
was a featureful and high-performance fork of [Conduit](https://gitlab.com/famedly/conduit), all
community-lead homeservers implementing the compatible
[Matrix Specification](https://spec.matrix.org/latest/).
@@ -47,7 +47,7 @@ ### Getting Started
This is just a summary for the impatient. See the full
[documentation](https://matrix-construct.github.io/tuwunel/).
> [!WARNING]
> [!TIP]
> Avoid using a sub-domain for your `server_name`. You can always delegate later with a [`.well-known`](https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/docs/configuring-well-known.md)
> file, but you can never change your `server_name`.
@@ -57,12 +57,12 @@ ### Getting Started
`/etc/caddy/Caddyfile` configuration with [Element](https://github.com/element-hq/element-web/releases)
unzipped to `/var/www/element`:
```
https://tuwunel.me:8448 {
reverse_proxy http://127.0.0.1:8008
tuwunel.me, tuwunel.me:8448 {
reverse_proxy localhost:8008
}
https://tuwunel.me:443 {
root * /var/www/element/
file_server
web.tuwunel.me {
root * /var/www/element/
file_server
}
```
`caddy reload --config /etc/caddy/Caddyfile`
@@ -75,7 +75,7 @@ ### Getting Started
🤗 Did you find this and other documentation helpful? We would love to hear feedback about setting
up Tuwunel.
### Migrating to Tuwunel
@@ -110,22 +110,11 @@ ### Upgrading & Downgrading Tuwunel
safe but often prevented by a guard. An error will indicate the downgrade is not possible and a
newer version which does not error must be sought.
#### Versioning
Tuwunel uses a semantic version tag in the format of `v<major>.<minor>.<patch>`. The `patch` value will
always correspond to the number of commits from the last `minor` change with significance to developers.
The `minor` version is changed for fixes or features significant to users. The `major` value corresponds
to significant feature evolutions, and does not indicate any "breaking change" nor connote stability.
> [!IMPORTANT]
> **Users should attempt to keep up to date with the latest minor version.**
#### Branches
The main branch is always _reasonably safe_ to run. We understand the propensity for users to simply clone
the main branch to get up and running, and we're obliged to ensure it's always viable. Nevertheless, only
tagged releases are true releases. If you don't care to update often, find the latest `minor` version
change rather than `patch`. We don't recommend simply following `major` version changes at this time.
tagged releases are true releases.
#### Container Tracking
@@ -140,31 +129,26 @@ #### Container Tracking
### Getting Help & Support
The official community will be found at [#tuwunel:tuwunel.chat](https://matrix.to/#/#tuwunel:tuwunel.chat).
If this is currently inaccessible please be patient as it's still coming online at the time of
the first release; we will have updates to this section. This is a fully moderated space to protect
the wellbeing of our users and create a non-toxic work environment for staff. If you require assistance
with anything that is not remedied by the documentation, don't hesitate to open an issue on github.
If you are opposed to using github, or if private discussion is required such as for security
disclosures, or for any other reason, I would be happy to receive your DM at
[@jason:tuwunel.me](https://matrix.to/#/@jason:tuwunel.me). This will not be bothering me as it would
be my pleasure to help you anytime. As an emergency contact you can send an email to jasonzemos@gmail.com.
be my pleasure to help you when possible. As an emergency contact you can send an email to
jasonzemos@gmail.com.
##### Tuwunel Fanclub
We also have an unofficial community-run chat which is publicly accessible at
[#tuwunel:grin.hu](https://matrix.to/#/#tuwunel:grin.hu). The members, content, or moderation
We have an unofficial community-run chat which is publicly accessible at
[#tuwunel:matrix.org](https://matrix.to/#/#tuwunel:matrix.org). The members, content, or moderation
decisions of this room are not in any way related or endorsed by this project or its sponsors,
and not all project staff will be present there. There will be at least some presence by staff to
offer assistance so long as the room remains in minimally good standing.
## Tuwunel <sup>🎔</sup>
## Tuwunel<sup>💕</sup>
Tuwunel's theme is **empathy** in communication defined by the works of
[Edith Stein](https://plato.stanford.edu/entries/stein/). Empathy is the basis for how we approach
every message, and a reminder for how we should all conduct ourselves in every conversation.
every message and our responsibility to the other in every conversation.
<!-- ANCHOR_END: body -->
+29 -27
View File
@@ -1,37 +1,39 @@
# Tuwunel 1.1.0
# Tuwunel 1.4.7
June 19, 2025
December 3, 2025
All dependencies have been fully upgraded for the first time since the
conduwuit transition. RocksDB is now synchronized to 10.2.1-tuwunel for all
builders. The Nix build itself has now been fully migrated from conduwuit;
special thanks to @wkordalski for making this happen. Thanks to @Askhalion
for opening a NixOS package request which you can [vote for here](https://github.com/NixOS/nixpkgs/issues/415469).
An [Arch package](https://aur.archlinux.org/packages/tuwunel) has also been
created in the AUR courtesy of @drrossum in addition to the transitional
package setup by @Kimiblock which we failed to acknowledge during the first
release. The RPM package now has systemd and proper installation added thanks
to a report by @alythemonk.
ARMv8 builds are now supported and bundled with this release. Thanks to
@zaninime and @clement-escolano for reminding us.
JSON Web Token logins are now supported. This feature was commissioned and
made public by an enterprise sponsor. The type `org.matrix.login.jwt` is now
recognized.
Tuwunel is now deployed at scale serving the citizens of Switzerland in production. Some optimizations were requested to reduce operating costs from projected growth over product lifespan: this release delivers with markedly reduced CPU usage and improves responsiveness. However, complications during routine dependency upgrades consumed valuable time planned for features and issues which could not be completed for this release.
### New Features
- JWT login support.
- Upgrade Support for Room Version 12 is now available. Though this room version has been supported for the entire 1.4 series, all Tuwunel servers have been protected by Hydra Backports on all room versions. As such, other work was able to be prioritized for the preceding releases.
### Follow-up Features
### Enhancements
- aarch64 build and packages.
- NixOS build support. (thanks @wkordalski and @coolGi69)
- Dependency upgrades, including Axum 0.8. (thanks @dasha_uwu)
- RPM package systemd and proper installation scripts.
- Recursive relations have been enabled. This is an optimization which allows the server to gather more data using fewer client requests, for example, of a thread with many reactions and replies. The implementation is now optimal and utilizes the full capabilities of Tuwunel's asynchronous database.
- Several miscellaneous but significant optimizations took place at the direction of memory profiling. This reduced load on the allocator for database queries and JSON serialization of complex objects. Heroes calculations and the joined room hot-path on sync v3 were further optimized to reduce the database query load itself.
- Jemalloc has been repackaged with platform-specific optimizations enhancing the build. The upgrade to the dev branch of libjemalloc itself was considered as too much variability for the same release, it is planned for an upcoming release.
- Thanks to element-hq/synapse#18970 by @dasha-uwu, we have very slightly turned down the amount of randomness when selecting join-servers, More retries also occur within a single request if necessary. Thanks to @gogo199432 and @lifeofguenter for reporting problems in (#128) and (#205) respectively. More opportunities are still available to make large room joins robust.
### Bug Fixes
- Changing passwords for pre-migration users was precluded by an error.
Special thanks to @teidesu for making a superb report about this.
- Special thanks to @yefimg for fixing LDAP logout in (#231) from a report kindly made by @orhtej2 in (#97); thank you for your patience waiting for domain expertise to assist here.
- Thanks to @Radiant-Xyz the example configurations have been updated to remove `allow_check_for_updates`. This fixes any warnings for the item no longer existing. (#221)
- Thanks again to @Radiant-Xyz reporting in (#219) the `/whoami` endpoint is now returns spec-compliant errors for Mautrix bridges (fe12daead9). Thanks also to @bobobo1618 for confirming the fix is working.
- Relations responses were sometimes incorrect in the forwards direction. This was fixed by (5147b541) bringing those responses into full compliance. Note the prior release had also fixed compliance issues but in the backwards direction.
- Server selection for backfill struggled sometimes for version 12 rooms. These rooms might fail to load history after join. Additional servers are now found using `creators` and `additional_creators` instead.
- Room leave compliance has been fixed for an edge-case where a room becomes empty except for a locally invited user which does not have its leave event sent down `/sync`.
- Thanks to @grinapo for a report which lead to the discovery of events acquired over backfill not being checked for whether they already exist.
### Upcoming
- As stated in the summary, several planned items could not be cut into this release. These include SSO/OIDC support (#7), Element Call setup assistance and documentation (#217)(#215), User-level Admin Room and Media deletion (#192), and any other assigned issue. These items are on the short-list for the next cycle and mean a lot to us; to all participants: your issues are not being ignored and we hear you.
+1 -1
View File
@@ -4,7 +4,6 @@ Wants=network-online.target
After=network-online.target
Documentation=https://tuwunel.chat/
RequiresMountsFor=/var/lib/private/tuwunel
Alias=matrix-tuwunel.service
[Service]
DynamicUser=yes
@@ -77,3 +76,4 @@ StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Alias=matrix-tuwunel.service
+3 -2
View File
@@ -5,7 +5,6 @@ authors = [
]
description = "Tuwunel, a high performance successor to Conduit and Conduwuit"
language = "en"
multilingual = false
src = "docs"
title = "Tuwunel One"
text-direction = "ltr"
@@ -15,7 +14,9 @@ build-dir = "public"
create-missing = true
extra-watch-dirs = [
"debian",
"docker",
"docs",
"rpm",
]
[rust]
@@ -24,7 +25,7 @@ edition = "2024"
[output.html]
git-repository-url = "https://github.com/matrix-construct/tuwunel"
edit-url-template = "https://github.com/matrix-construct/tuwunel/edit/main/{path}"
git-repository-icon = "fa-github-square"
git-repository-icon = "fab-github"
[output.html.search]
limit-results = 15
+6 -6
View File
@@ -1,11 +1,11 @@
stack-size-threshold = 393216
future-size-threshold = 24576
array-size-threshold = 4096
cognitive-complexity-threshold = 100 # 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 # TODO reduce me ALARA
too-many-lines-threshold = 780 # TODO reduce me to <= 100
type-complexity-threshold = 250 # reduce me to ~200
large-error-threshold = 256 # TODO reduce me ALARA
too-many-lines-threshold = 780 # TODO reduce me to <= 100
excessive-nesting-threshold = 8
type-complexity-threshold = 250 # reduce me to ~200
cognitive-complexity-threshold = 100 # TODO reduce me ALARA
#disallowed-macros = [
# { path = "log::error", reason = "use tuwunel_core::error" },
+1 -2
View File
@@ -2,11 +2,9 @@
Description=Tuwunel Matrix homeserver
Wants=network-online.target
After=network-online.target
Alias=matrix-tuwunel.service
Documentation=https://tuwunel.chat/
[Service]
DynamicUser=yes
User=tuwunel
Group=tuwunel
Type=notify
@@ -64,3 +62,4 @@ StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Alias=matrix-tuwunel.service
+19 -13
View File
@@ -1,7 +1,7 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS cargo
ARG sys_target
ARG sys_triple
ARG rust_target
ARG rust_toolchain
ARG RUST_HOME
@@ -12,10 +12,12 @@ ARG CARGO_TARGET_DIR
ARG cargo_target_profile
ARG cargo_target_artifact
ARG cargo_target_share
ARG cargo_share
ARG CARGO_TERM_VERBOSE=0
ARG RUST_BACKTRACE
ARG JEMALLOC_OVERRIDE
ARG ROCKSDB_LIB_DIR
ARG VALGRINDFLAGS=""
ARG CARGO_BUILD_RUSTFLAGS
ARG CARGO_PROFILE_TEST_DEBUG
ARG CARGO_PROFILE_TEST_INCREMENTAL
@@ -32,21 +34,25 @@ ARG color_args="--color always"
ARG recipe_args=""
ARG cargo_args=""
ARG git_checkout
ARG targ_dir="${CARGO_TARGET_DIR}/${cargo_target_profile}"
ARG targ_targ_dir="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}"
WORKDIR /
COPY --link --from=input . .
COPY --link --from=rust ${RUST_HOME} ${RUST_HOME}
COPY --link --from=deps /usr/src/tuwunel /usr/src/tuwunel
WORKDIR /usr/lib/${sys_target}
WORKDIR /usr/lib/${sys_triple}
COPY --link --from=rocksdb . .
WORKDIR /usr/src/tuwunel
SHELL ["/bin/bash", "-c"]
ENV PATH="${CARGO_HOME}/bin:$PATH"
ENV CARGO_TERM_VERBOSE="${CARGO_TERM_VERBOSE}"
ENV RUST_BACKTRACE="${RUST_BACKTRACE}"
ENV JEMALLOC_OVERRIDE="${JEMALLOC_OVERRIDE}"
ENV ROCKSDB_LIB_DIR="${ROCKSDB_LIB_DIR}"
ENV VALGRINDFLAGS="${VALGRINDFLAGS}"
ENV CARGO_PROFILE_TEST_DEBUG="${CARGO_PROFILE_TEST_DEBUG}"
ENV CARGO_PROFILE_TEST_INCREMENTAL="${CARGO_PROFILE_TEST_INCREMENTAL}"
ENV CARGO_PROFILE_BENCH_DEBUG="${CARGO_PROFILE_BENCH_DEBUG}"
@@ -56,17 +62,15 @@ ENV CARGO_PROFILE_RELEASE_DEBUGINFO_DEBUG="${CARGO_PROFILE_RELEASE_DEBUGINFO_DEB
ENV CARGO_PROFILE_RELEASE_DEBUGINFO_LTO="${CARGO_PROFILE_RELEASE_DEBUGINFO_LTO}"
ENV CARGO_BUILD_RUSTFLAGS="${CARGO_BUILD_RUSTFLAGS}"
ENV CARGO_TARGET_DIR="${CARGO_TARGET_DIR}"
ENV targ_dir="${CARGO_TARGET_DIR}/${cargo_target_profile}"
ENV targ_targ_dir="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}"
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
--mount=type=cache,dst=${targ_dir}/deps,id=${cargo_target_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_dir}/build,id=${cargo_target_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_dir}/examples,id=${cargo_target_share}/examples,sharing=locked \
--mount=type=cache,dst=${targ_dir}/incremental,id=${cargo_target_share}/incremental,sharing=locked \
--mount=type=cache,dst=${targ_dir}/.fingerprint,id=${cargo_target_share}/fingerprint,sharing=locked \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
--mount=type=cache,dst=${targ_dir}/deps,id=${cargo_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_dir}/build,id=${cargo_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_dir}/examples,id=${cargo_share}/examples,sharing=locked \
--mount=type=cache,dst=${targ_dir}/incremental,id=${cargo_share}/incremental,sharing=locked \
--mount=type=cache,dst=${targ_dir}/.fingerprint,id=${cargo_share}/fingerprint,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/deps,id=${cargo_target_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/build,id=${cargo_target_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/examples,id=${cargo_target_share}/examples,sharing=locked \
@@ -74,7 +78,9 @@ RUN \
--mount=type=cache,dst=${targ_targ_dir}/.fingerprint,id=${cargo_target_share}/fingerprint,sharing=locked \
<<EOF
set -eux
rustup run ${rust_toolchain} \
ulimit -n 65535
rustup run "${rust_toolchain}" \
cargo ${cargo_cmd} \
--verbose \
--locked \
+3 -4
View File
@@ -1,7 +1,6 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS audit
ARG sys_target
ARG rust_toolchain="nightly"
ARG RUSTUP_HOME
ARG CARGO_HOME
@@ -12,9 +11,9 @@ COPY --link --from=input . .
WORKDIR /usr/src/tuwunel
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
<<EOF
set -eux
rustup run ${rust_toolchain} \
+8 -8
View File
@@ -1,7 +1,6 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS build-deb
ARG sys_target
ARG rust_target
ARG rust_toolchain
ARG RUSTUP_HOME
@@ -11,9 +10,12 @@ ARG CARGO_TARGET_DIR
ARG cargo_target_profile
ARG cargo_target_artifact
ARG cargo_target_share
ARG cargo_share
ARG cargo_profile
ARG cargo_features
ARG cargo_spec_features
ARG targ_dir="${CARGO_TARGET_DIR}/${cargo_target_profile}"
ARG targ_targ_dir="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}"
ARG pkg_dir
ARG deb_args=""
@@ -21,17 +23,15 @@ WORKDIR /
COPY --link --from=input . .
WORKDIR /usr/src/tuwunel
ENV targ_dir="${CARGO_TARGET_DIR}/${cargo_target_profile}"
ENV targ_targ_dir="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}"
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
--mount=type=cache,dst=${targ_dir}/deps,id=${cargo_target_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_dir}/build,id=${cargo_target_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_dir}/examples,id=${cargo_target_share}/examples,sharing=locked \
--mount=type=cache,dst=${targ_dir}/incremental,id=${cargo_target_share}/incremental,sharing=locked \
--mount=type=cache,dst=${targ_dir}/.fingerprint,id=${cargo_target_share}/fingerprint,sharing=locked \
--mount=type=cache,dst=${targ_dir}/deps,id=${cargo_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_dir}/build,id=${cargo_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_dir}/examples,id=${cargo_share}/examples,sharing=locked \
--mount=type=cache,dst=${targ_dir}/incremental,id=${cargo_share}/incremental,sharing=locked \
--mount=type=cache,dst=${targ_dir}/.fingerprint,id=${cargo_share}/fingerprint,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/deps,id=${cargo_target_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/build,id=${cargo_target_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/examples,id=${cargo_target_share}/examples,sharing=locked \
+3 -4
View File
@@ -1,7 +1,6 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS fmt
ARG sys_target
ARG rust_toolchain="nightly"
ARG RUSTUP_HOME
ARG CARGO_HOME
@@ -12,9 +11,9 @@ COPY --link --from=input . .
WORKDIR /usr/src/tuwunel
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
<<EOF
set -eux
rustup run ${rust_toolchain} \
+3 -4
View File
@@ -1,7 +1,6 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS lychee
ARG sys_target
ARG rust_toolchain="nightly"
ARG RUSTUP_HOME
ARG CARGO_HOME
@@ -16,9 +15,9 @@ COPY --link --from=input . .
WORKDIR /usr/src/
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
--mount=type=cache,dst=lychee,sharing=locked \
<<EOF
set -eux
+9 -6
View File
@@ -1,7 +1,7 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS build-rpm
ARG sys_target
ARG rust_target
ARG rust_toolchain
ARG RUSTUP_HOME
ARG CARGO_HOME
@@ -10,9 +10,12 @@ ARG CARGO_TARGET_DIR
ARG cargo_target_profile
ARG cargo_target_artifact
ARG cargo_target_share
ARG cargo_share
ARG cargo_profile
ARG cargo_features
ARG cargo_spec_features
ARG targ_dir="${CARGO_TARGET_DIR}/${cargo_target_profile}"
ARG targ_targ_dir="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}"
ARG pkg_dir
ARG gen_rpm_args=""
@@ -24,11 +27,11 @@ RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
--mount=type=cache,dst=${targ_dir}/deps,id=${cargo_target_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_dir}/build,id=${cargo_target_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_dir}/examples,id=${cargo_target_share}/examples,sharing=locked \
--mount=type=cache,dst=${targ_dir}/incremental,id=${cargo_target_share}/incremental,sharing=locked \
--mount=type=cache,dst=${targ_dir}/.fingerprint,id=${cargo_target_share}/fingerprint,sharing=locked \
--mount=type=cache,dst=${targ_dir}/deps,id=${cargo_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_dir}/build,id=${cargo_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_dir}/examples,id=${cargo_share}/examples,sharing=locked \
--mount=type=cache,dst=${targ_dir}/incremental,id=${cargo_share}/incremental,sharing=locked \
--mount=type=cache,dst=${targ_dir}/.fingerprint,id=${cargo_share}/fingerprint,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/deps,id=${cargo_target_share}/deps,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/build,id=${cargo_target_share}/build,sharing=locked \
--mount=type=cache,dst=${targ_targ_dir}/examples,id=${cargo_target_share}/examples,sharing=locked \
+3 -3
View File
@@ -11,9 +11,9 @@ COPY --link --from=input . .
WORKDIR /usr/src/tuwunel
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared,ro \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared,ro \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
<<EOF
set -eux
rustup run ${rust_toolchain} \
+52 -82
View File
@@ -1,22 +1,6 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS key-gen-base
ARG var_cache
ARG var_lib_apt
WORKDIR /
COPY --link --from=input . .
RUN \
--mount=type=cache,dst=/var/cache,id=${var_cache},sharing=locked \
--mount=type=cache,dst=/var/lib/apt,id=${var_lib_apt},sharing=locked \
<<EOF
set -eux
apt-get -y -U install --no-install-recommends openssl gawk
EOF
FROM key-gen-base AS key-gen
FROM input AS key-gen
WORKDIR /complement
COPY <<EOF v3.ext
@@ -62,53 +46,53 @@ RUN [ -f certificate.crt ] && [ -f private_key.pem ]
FROM scratch AS complement-config
WORKDIR /complement
COPY --from=key-gen /complement/* .
COPY --from=source /usr/src/tuwunel/tests/test_results/complement/test_results.jsonl old_results.jsonl
COPY --from=source /usr/src/tuwunel/tests/complement/results.jsonl old_results.jsonl
COPY <<EOF complement.toml
[global]
address = "0.0.0.0"
admin_room_notices = false
allow_device_name_federation = true
allow_guest_registration = true
allow_invalid_tls_certificates = true
allow_legacy_media = true
allow_public_room_directory_over_federation = true
allow_public_room_directory_without_auth = true
allow_registration = true
create_admin_room = false
database_path = "/database"
log = "debug,tuwunel=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
dns_attempts = 20
dns_timeout = 60
federation_idle_timeout = 300
intentionally_unknown_config_option_for_testing = true
ip_range_denylist = []
url_preview_domain_contains_allowlist = ["*"]
url_preview_domain_explicit_denylist = ["*"]
log = "debug,tuwunel=trace,h2=warn,hyper=warn"
log_colors = false
log_guest_registrations = false
log_span_events = "NONE"
log_thread_ids = true
media_compat_file_link = false
media_startup_check = true
port = [8008, 8448]
prune_missing_media = true
log_colors = false
admin_room_notices = false
allow_check_for_updates = false
intentionally_unknown_config_option_for_testing = true
rocksdb_log_level = "debug"
rocksdb_max_log_files = 1
rocksdb_recovery_mode = 0
rocksdb_paranoid_file_checks = true
log_guest_registrations = false
allow_legacy_media = true
startup_netburst = true
startup_netburst_keep = -1
# valgrind makes things so slow
dns_timeout = 60
dns_attempts = 20
query_trusted_key_servers_first = false
query_trusted_key_servers_first_on_join = false
request_conn_timeout = 60
request_timeout = 120
well_known_conn_timeout = 60
well_known_timeout = 60
federation_idle_timeout = 300
sender_timeout = 300
rocksdb_log_level = "debug"
rocksdb_max_log_files = 1
rocksdb_paranoid_file_checks = true
rocksdb_recovery_mode = 0
sender_idle_timeout = 300
sender_retry_backoff_limit = 300
allow_invalid_tls_certificates = true
sender_timeout = 300
startup_netburst = true
startup_netburst_keep = -1
trusted_servers = []
url_preview_domain_contains_allowlist = ["*"]
url_preview_domain_explicit_denylist = ["*"]
well_known_conn_timeout = 60
well_known_timeout = 60
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
[global.tls]
certs = "/complement/certificate.crt"
@@ -119,9 +103,6 @@ EOF
FROM input AS complement-testee
WORKDIR /
COPY --link --from=input . .
EXPOSE 8008 8448
RUN mkdir /database
COPY --from=complement-config * /complement/
@@ -131,9 +112,6 @@ ENTRYPOINT tuwunel -Oserver_name=\""$SERVER_NAME\""
FROM input AS complement-testee-valgrind
WORKDIR /
COPY --link --from=input . .
EXPOSE 8008 8448
RUN mkdir /database
COPY --from=complement-config * /complement/
@@ -150,22 +128,13 @@ ENTRYPOINT valgrind \
FROM input AS complement-base
ARG var_cache
ARG var_lib_apt
ARG complement_tags="tuwunel_blacklist"
ARG complement_ref="4d3130f06d0dc3f794b5d48fbdba0b466792b52b"
ARG complement_tags="conduwuit_blacklist"
ARG complement_tests="./tests/..."
ARG complement_run=".*"
WORKDIR /
RUN \
--mount=type=cache,dst=/var/cache,id=${var_cache},sharing=locked \
--mount=type=cache,dst=/var/lib/apt,id=${var_lib_apt},sharing=locked \
--mount=type=cache,dst=/go/pkg/mod/cache,sharing=locked \
<<EOF
set -eux
apt-get -y -U install --no-install-recommends golang-go jq
EOF
WORKDIR /usr/src
ADD https://github.com/matrix-construct/complement.git complement
ADD https://github.com/matrix-construct/complement.git#${complement_ref} complement
WORKDIR /usr/src/complement
ENV COMPLEMENT_BASE_IMAGE="complement-testee"
@@ -181,8 +150,9 @@ EOF
FROM input AS complement-tester
ARG complement_verbose=0
ARG complement_debug=0
ARG complement_dirty=0
ARG complement_count=1
ARG complement_parallel=16
ARG complement_parallel=1
ARG complement_shuffle=1337
ARG complement_timeout="1h"
ARG complement_run=".*"
@@ -191,9 +161,6 @@ ARG complement_tags="conduwuit_blacklist"
ARG complement_tests="./tests/..."
ARG complement_base_image
WORKDIR /
COPY --link --from=input . .
WORKDIR /usr/src/complement
ENV COMPLEMENT_DEBUG=$complement_debug
ENV complement_parallel="$complement_parallel"
@@ -205,12 +172,14 @@ ENV complement_tests="$complement_tests"
ENV complement_skip="$complement_skip"
ENV complement_run="$complement_run"
ENV complement_tests="$complement_tests"
ENV COMPLEMENT_ENABLE_DIRTY_RUNS="$complement_dirty"
ENV COMPLEMENT_ALWAYS_PRINT_SERVER_LOGS="$complement_verbose"
ENV COMPLEMENT_HOSTNAME_RUNNING_COMPLEMENT="host.docker.internal"
ENV COMPLEMENT_HOST_MOUNTS="/var/run/docker.sock:/var/run/docker.sock"
ENV jq_res='{Action: .Action, Test: .Test}'
ENV jq_sel='select((.Action == \"pass\" or .Action == \"fail\" or .Action == \"skip\") and .Test != null)'
ENV jq_tab='[.Action, .Test] | @tsv'
ENV jq_out='select(.Test != null) | {Test: .Test, Output: .Output}'
COPY --from=complement-config /complement/old_results.jsonl .
COPY <<EOF uwu.sh
env;
@@ -219,22 +188,23 @@ COPY <<EOF uwu.sh
COMPLEMENT_BASE_IMAGE="\${1:-$complement_base_image}"
go test
-json
-shuffle="${complement_shuffle}"
-parallel="${complement_parallel}"
-timeout="${complement_timeout}"
-count="${complement_count}"
-tags="${complement_tags}"
-skip="${complement_skip}"
-run="${complement_run}"
"${complement_tests}"
| jq --unbuffered -c "${jq_sel} | ${jq_res}"
"-shuffle=\$complement_shuffle"
"-parallel=\$complement_parallel"
"-timeout=\$complement_timeout"
"-count=\$complement_count"
"-tags=\$complement_tags"
"-skip=\$complement_skip"
"-run=\$complement_run"
"\$complement_tests"
| jq --unbuffered -c "."
| tee output.jsonl
| jq --unbuffered -c "$jq_sel | $jq_res"
| tee results.jsonl
| jq --unbuffered -r "${jq_tab}"
| jq --unbuffered -r "$jq_tab"
;
jq -s -c "sort_by(.Test)[]" < results.jsonl | uniq > new_results.jsonl;
wc -l old_results.jsonl new_results.jsonl;
diff -w -y -t --width=275 --suppress-common-lines old_results.jsonl new_results.jsonl;
jq -s -c "sort_by(.Test, .Timestamp)[] | $jq_out" < output.jsonl > full_output.jsonl;
EOF
RUN echo $(tr -d '\n' < uwu.sh) > uwu.sh
RUN echo $(tr -d '\n' < uwu.sh) > uwu.sh && chmod a+x uwu.sh
ENTRYPOINT ["/bin/bash", "/usr/src/complement/uwu.sh"]
+6 -4
View File
@@ -15,15 +15,17 @@ ENV src_path="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}/tuwunel
ENV dst_path="${install_prefix}/bin/tuwunel"
COPY --from=bins $src_path $dst_path
RUN <<EOF
ret=$(ldd "${dst_path}")
ldd -v "${dst_path}"
ret=$?
if [ "$ret" = "0" ] && [ "$assert_linkage" = "static" ]; then
echo "($ret) expected a static binary"
exit 1
elif [ "$ret" != "0" ] && [ "$assert_linkage" = "dynamic" ]; then
echo "($ret) expected a dynamic binary"
exit 1
fi
set -eux
ldd -v ${dst_path} || true
du -h ${dst_path}
sha1sum ${dst_path}
du -h "${dst_path}"
sha1sum "${dst_path}"
EOF
+100
View File
@@ -0,0 +1,100 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS rust-sdk-integration
ARG sys_name
ARG sys_version
ARG feat_set
ARG rust_target
ARG rust_toolchain
ARG cargo_profile
ARG RUSTUP_HOME
ARG CARGO_HOME
ARG CARGO_TARGET
ARG MRSDK_TARGET_DIR="/usr/src/matrix-rust-sdk/target"
ARG mrsdk_target_share
#ARG mrsdk_ref="integration"
ARG mrsdk_ref="tuwunel-changes"
ARG mrsdk_test_args=""
ARG mrsdk_test_opts=""
ARG mrsdk_skip_list=""
ARG mrsdk_parallel=2
ARG mrsdk_startup_delay="10s"
ARG mrsdk_testee="/usr/bin/tuwunel"
WORKDIR /usr/src
ADD --link https://github.com/matrix-construct/matrix-rust-sdk.git#${mrsdk_ref} matrix-rust-sdk
WORKDIR /etc
COPY <<EOF tuwunel.toml
[global]
admin_room_notices = false
allow_device_name_federation = true
allow_guest_registration = true
allow_legacy_media = true
allow_public_room_directory_over_federation = true
allow_public_room_directory_without_auth = true
allow_registration = true
create_admin_room = false
ip_range_denylist = []
log = "debug,tuwunel=trace,h2=warn,hyper=warn"
log_colors = false
log_guest_registrations = false
log_span_events = "NONE"
log_thread_ids = true
media_compat_file_link = false
media_startup_check = true
query_trusted_key_servers_first = false
query_trusted_key_servers_first_on_join = false
rocksdb_log_level = "debug"
rocksdb_max_log_files = 1
rocksdb_paranoid_file_checks = true
rocksdb_recovery_mode = 0
trusted_servers = []
url_preview_domain_contains_allowlist = ["*"]
url_preview_domain_explicit_denylist = ["*"]
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
EOF
WORKDIR /usr/lib
COPY --link --from=install /usr/lib .
WORKDIR /usr/bin
COPY --link --from=install /usr/bin/tuwunel .
WORKDIR /usr/src/matrix-rust-sdk
SHELL ["/bin/bash", "-c"]
ENV RUST_BACKTRACE="full"
ENV TUWUNEL_CONFIG="/etc/tuwunel.toml"
ENV TUWUNEL_DATABASE_PATH="/var/db/tuwunel"
ENV TUWUNEL_SERVER_NAME="localhost"
ENV TUWUNEL_PORT="[8448]"
ENV HOMESERVER_URL="http://localhost:8448"
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/registry,sharing=shared \
--mount=type=cache,dst=${CARGO_HOME}/git,sharing=shared \
--mount=type=cache,dst=${MRSDK_TARGET_DIR},id=${mrsdk_target_share},sharing=locked \
<<EOF
set -eux
nohup ${mrsdk_testee[@]} 1> /var/log/tuwunel.log &
PID=$!; trap "sleep 10s; set +e; kill -QUIT ${PID}; wait ${PID}" EXIT
sleep "${mrsdk_startup_delay}"
rustup run ${rust_toolchain} \
cargo test \
--locked \
--release \
"--color=always" \
"--features=default" \
"--target=${rust_target}" \
"--target-dir=${MRSDK_TARGET_DIR}" \
"--package=matrix-sdk-integration-testing" \
${mrsdk_test_args[@]} \
-- \
"--color=always" \
"--test-threads=${mrsdk_parallel}" \
${mrsdk_skip_list[@]} \
${mrsdk_test_opts[@]} \
;
EOF
+91
View File
@@ -0,0 +1,91 @@
# syntax = docker/dockerfile:1.11-labs
FROM input AS nix-base
WORKDIR /
COPY --link --from=input . .
RUN \
--mount=type=cache,dst=/nix,sharing=shared \
--mount=type=cache,dst=/root/.cache/nix,sharing=shared \
--mount=type=cache,dst=/root/.local/state/nix,sharing=shared \
<<EOF
set -eux
curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install > nix-install
sh ./nix-install --daemon
rm nix-install
EOF
FROM nix-base AS build-nix
WORKDIR /usr/src/tuwunel
COPY --link --from=source /usr/src/tuwunel .
RUN \
--mount=type=cache,dst=/nix,sharing=shared \
--mount=type=cache,dst=/root/.cache/nix,sharing=shared \
--mount=type=cache,dst=/root/.local/state/nix,sharing=shared \
<<EOF
set -eux
nix-build \
--verbose \
--cores 0 \
--max-jobs $(nproc) \
--log-format raw \
.
cp -afRL --copy-contents result /opt/tuwunel
EOF
FROM input AS smoke-nix
WORKDIR /
COPY --link --from=nix-base . .
WORKDIR /usr/src/tuwunel
COPY --link --from=source /usr/src/tuwunel .
ENV TUWUNEL_DATABASE_PATH="/tmp/tuwunel/smoketest.db"
ENV TUWUNEL_LOG="info"
RUN \
--mount=type=cache,dst=/nix,sharing=shared \
--mount=type=cache,dst=/root/.cache/nix,sharing=shared \
--mount=type=cache,dst=/root/.local/state/nix,sharing=shared \
<<EOF
set -eux
alias nix="nix --extra-experimental-features nix-command --extra-experimental-features flakes"
nix run \
--verbose \
--cores 0 \
--max-jobs $(nproc) \
--log-format raw \
.#all-features \
-- \
-Otest='["smoke", "fresh"]' \
-Oserver_name=\"localhost\" \
EOF
FROM input AS nix-pkg
WORKDIR /
COPY --link --from=nix-base . .
WORKDIR /usr/src/tuwunel
COPY --link --from=source /usr/src/tuwunel .
RUN \
--mount=type=cache,dst=/nix,sharing=shared \
--mount=type=cache,dst=/root/.cache/nix,sharing=shared \
--mount=type=cache,dst=/root/.local/state/nix,sharing=shared \
<<EOF
set -eux
alias nix="nix --extra-experimental-features nix-command --extra-experimental-features flakes"
ID=$(nix-store --realise $(nix path-info --derivation))
mkdir -p tuwunel
nix-store --export $ID > tuwunel/tuwunel.drv
tar -cvf /opt/tuwunel.nix.tar tuwunel
EOF
+6 -3
View File
@@ -26,12 +26,14 @@ EOF
FROM input AS rocksdb-build
ARG rocksdb_shared=0
ARG rocksdb_portable=1
ARG rocksdb_portable="1"
ARG rocksdb_opt_level="3"
ARG rocksdb_lto="-flto -ffat-lto-objects"
ARG rocksdb_build_type="Release"
ARG rocksdb_cxx_flags="-ftls-model=initial-exec -mno-outline-atomics"
ARG rocksdb_cxx_flags="-ftls-model=initial-exec"
ARG rocksdb_make_verbose="ON"
ARG rocksdb_make_rule_messages="OFF"
ARG rocksdb_numa=0
ARG rocksdb_jemalloc=1
ARG rocksdb_iouring=1
ARG rocksdb_zstd=1
@@ -59,7 +61,7 @@ RUN <<EOF
"-DBUILD_SHARED_LIBS=${rocksdb_shared}" \
"-DROCKSDB_BUILD_SHARED=${rocksdb_shared}" \
"-DCMAKE_CXX_FLAGS:STRING=${rocksdb_cxx_flags}" \
"-DCMAKE_CXX_FLAGS_RELEASE:STRING=-g0 -O${rocksdb_opt_level} -DNDEBUG" \
"-DCMAKE_CXX_FLAGS_RELEASE:STRING=-g0 -O${rocksdb_opt_level} -DNDEBUG ${rocksdb_lto}" \
"-DPORTABLE=${rocksdb_portable}" \
"-DFAIL_ON_WARNINGS=0" \
"-DUSE_RTTI=0" \
@@ -73,6 +75,7 @@ RUN <<EOF
"-DWITH_TOOLS=0" \
"-DWITH_TESTS=0" \
"-DWITH_GFLAGS=0" \
"-DWITH_NUMA=${rocksdb_numa}" \
"-DWITH_LIBURING=${rocksdb_iouring}" \
"-DWITH_JEMALLOC=${rocksdb_jemalloc}" \
"-DWITH_ZSTD=${rocksdb_zstd}" \
+2 -8
View File
@@ -6,9 +6,6 @@ ARG rust_target
ARG rustup_version="1.28.2"
ARG rustup_profile="minimal"
WORKDIR /
COPY --link --from=input . .
WORKDIR ${RUST_HOME}
RUN <<EOF
set -eux
@@ -16,7 +13,7 @@ RUN <<EOF
var="${rustup_version}/${rust_target}"
url="https://static.rust-lang.org/rustup/archive/$var/rustup-init"
curl -S -O -s "$url"
chmod o+x rustup-init
chmod a+rx rustup-init
EOF
FROM input AS rust
@@ -32,9 +29,6 @@ ARG CARGO_TERM_VERBOSE
ARG rustup_components
ARG cargo_installs
WORKDIR /
COPY --link --from=input . .
WORKDIR ${RUST_HOME}
ENV CARGO_TARGET="${rust_target}"
ENV RUSTUP_HOME="${RUSTUP_HOME}"
@@ -54,7 +48,7 @@ RUN \
--no-update-default-toolchain \
--no-modify-path
chmod -R go+rw $CARGO_HOME $RUSTUP_HOME
chmod -R a+rw $CARGO_HOME $RUSTUP_HOME
rm rustup-init
rustup component add \
-8
View File
@@ -3,9 +3,6 @@
FROM input AS source
ARG git_checkout
WORKDIR /
COPY --link --from=input . .
ADD --link --keep-git-dir . /usr/src/tuwunel
WORKDIR /usr/src/tuwunel
RUN <<EOF
@@ -18,7 +15,6 @@ EOF
FROM input AS ingredients
ARG sys_target
ARG rust_target
ARG rust_toolchain
ARG RUST_HOME
@@ -31,7 +27,6 @@ ARG JEMALLOC_OVERRIDE
ARG ROCKSDB_LIB_DIR
WORKDIR /
COPY --link --from=input . .
COPY --link --from=rust ${RUST_HOME} ${RUST_HOME}
COPY --link --from=source /usr/src/tuwunel /usr/src/tuwunel
@@ -68,9 +63,6 @@ ARG RUSTUP_HOME
ARG CARGO_HOME
ARG CARGO_TARGET
WORKDIR /
COPY --link --from=input . .
WORKDIR /usr/src/tuwunel
RUN \
--mount=type=cache,dst=${RUSTUP_HOME}/downloads,sharing=locked \
-3
View File
@@ -12,9 +12,6 @@ ARG var_lib_apt
ARG packages
ARG DEBIAN_FRONTEND
WORKDIR /
COPY --link --from=input . .
ENV DEBIAN_FRONTEND="${DEBIAN_FRONTEND}"
RUN \
--mount=type=cache,dst=/var/cache,id=${var_cache},sharing=locked \
+5
View File
@@ -1,5 +1,10 @@
# Docker Builder
> [!NOTE]
> This directory is for building docker images. For [deploying](https://github.com/matrix-construct/tuwunel/tree/main/docs/deploying)
> [docker](https://github.com/matrix-construct/tuwunel/blob/main/docs/deploying/docker.md) visit
> the appropriate documentation instead.
All Docker images for the project are built here. All images are
[Docker Bake](https://docs.docker.com/build/bake/) targets. All targets are leaves and
branches of a unified tree leading to a single root. It is a combinatorial matrix
+402 -81
View File
@@ -1,4 +1,4 @@
variable "CI" {}
variable "GITHUB_ACTOR" {}
variable "GITHUB_REPOSITORY" {}
variable "GITHUB_REF" {}
@@ -16,12 +16,6 @@ variable "repo" {
variable "docker_repo" {
default = "${repo}"
}
variable "docker_tag_preview" {
default = false
}
variable "docker_tag_latest" {
default = false
}
variable "git_ref" {
default = "${GITHUB_REF}"
@@ -35,8 +29,12 @@ variable "git_ref_name" {
cargo_feat_sets = {
none = ""
# Default features
default = "brotli_compression,element_hacks,gzip_compression,io_uring,jemalloc,jemalloc_conf,media_thumbnail,release_max_log_level,systemd,url_preview,zstd_compression"
all = "blurhashing,brotli_compression,tuwunel_mods,console,default,direct_tls,element_hacks,gzip_compression,hardened_malloc,io_uring,jemalloc,jemalloc_conf,jemalloc_prof,jemalloc_stats,ldap,media_thumbnail,perf_measurements,release_max_log_level,sentry_telemetry,systemd,tokio_console,url_preview,zstd_compression"
# All features sans release_max_log_level
logging = "blurhashing,brotli_compression,bzip2_compression,console,direct_tls,element_hacks,gzip_compression,io_uring,jemalloc,jemalloc_conf,jemalloc_prof,jemalloc_stats,ldap,lz4_compression,media_thumbnail,perf_measurements,sentry_telemetry,systemd,tokio_console,tuwunel_mods,url_preview,zstd_compression"
# All features
all = "blurhashing,brotli_compression,bzip2_compression,console,direct_tls,element_hacks,gzip_compression,io_uring,jemalloc,jemalloc_conf,jemalloc_prof,jemalloc_stats,ldap,lz4_compression,media_thumbnail,perf_measurements,release_max_log_level,sentry_telemetry,systemd,tokio_console,tuwunel_mods,url_preview,zstd_compression"
}
variable "cargo_features_always" {
default = "direct_tls"
@@ -63,19 +61,19 @@ variable "rust_targets" {
default = "[\"x86_64-unknown-linux-gnu\"]"
}
variable "sys_targets" {
default = "[\"x86_64-linux-gnu\"]"
variable "sys_names" {
default = "[\"debian\"]"
}
variable "sys_versions" {
default = "[\"testing-slim\"]"
}
variable "sys_names" {
default = "[\"debian\"]"
variable "sys_targets" {
default = "[\"x86_64-v1-linux-gnu\"]"
}
# RocksDB options
variable "rocksdb_portable" {
default = 1
default = "1"
}
variable "rocksdb_opt_level" {
default = "3"
@@ -86,6 +84,9 @@ variable "rocksdb_build_type" {
variable "rocksdb_make_verbose" {
default = "ON"
}
variable "rocksdb_numa" {
default = "0"
}
# Complement options
variable "complement_count" {
@@ -186,7 +187,6 @@ dynamic_libs = [
nightly_rustflags = [
"--cfg tokio_unstable",
"--cfg tuwunel_bench",
"--allow=unstable-features",
"-Z crate-attr=feature(test)",
"-Z enforce-type-length-limit",
@@ -239,9 +239,18 @@ group "lints" {
group "tests" {
targets = [
"docs",
"unit",
"smoke",
"integration",
"matrix-compliance",
]
}
group "matrix-compliance" {
targets = [
"complement",
"rust-sdk-integ",
]
}
@@ -279,28 +288,24 @@ group "publish" {
]
}
target "github" {
target "ghcr_io" {
name = elem("github", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
"ghcr.io/${repo}:${git_ref_name}-${cargo_profile}-${feat_set}-${sys_target}",
docker_tag_preview? "ghcr.io/${repo}:preview": "",
docker_tag_latest? "ghcr.io/${repo}:latest": "",
]
output = ["type=registry,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
output = ["type=registry,oci-mediatypes=true,compression=zstd,compression-level=${zstd_image_compress_level},force-compression=true,mode=min"]
matrix = cargo_rust_feat_sys
inherits = [
elem("docker", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
}
target "dockerhub" {
target "docker_io" {
name = elem("dockerhub", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
"${docker_repo}:${git_ref_name}-${cargo_profile}-${feat_set}-${sys_target}",
docker_tag_preview? "${docker_repo}:preview": "",
docker_tag_latest? "${docker_repo}:latest": "",
"docker.io/${docker_repo}:${git_ref_name}-${cargo_profile}-${feat_set}-${sys_target}",
]
output = ["type=registry,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
output = ["type=registry,oci-mediatypes=true,compression=zstd,compression-level=${zstd_image_compress_level},force-compression=true,mode=min"]
matrix = cargo_rust_feat_sys
inherits = [
elem("docker", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
@@ -413,7 +418,7 @@ target "complement-base" {
elem("complement-config", [sys_name, sys_version, sys_target])
]
contexts = {
input = elem("target:base", [sys_name, sys_version, sys_target])
input = elem("target:builder", [sys_name, sys_version, sys_target])
}
args = complement_args
}
@@ -434,6 +439,116 @@ target "complement-config" {
}
}
#
# Integration tests
#
group "integration" {
targets = [
"integ",
"rust-sdk-integ",
]
}
variable "valgrind_flags" {
default = "--error-exitcode=1 --exit-on-first-error=yes --undef-value-errors=no --leak-check=no"
}
target "rust-sdk-valgrind" {
name = elem("rust-sdk-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("rust-sdk-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
matrix = cargo_rust_feat_sys
inherits = [
elem("rust-sdk-integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
]
contexts = {
input = elem("target:rust", [rust_toolchain, rust_target, sys_name, sys_version, sys_target])
install = elem("target:install", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
VALGRINDFLAGS = "${valgrind_flags}"
mrsdk_testee = "valgrind ${valgrind_flags} /usr/bin/tuwunel"
mrsdk_test_args = ""
mrsdk_startup_delay = "30s"
mrsdk_skip_list =<<EOF
--skip test_delayed_invite_response_and_sent_message_decryption
--skip test_history_share_on_invite_pin_violation
EOF
}
}
target "rust-sdk-integ" {
name = elem("rust-sdk-integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("rust-sdk-integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
output = ["type=docker,compression=zstd,mode=max,compression-level=${zstd_image_compress_level}"]
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
target = "rust-sdk-integration"
dockerfile = "${docker_dir}/Dockerfile.matrix-rust-sdk"
matrix = cargo_rust_feat_sys
inherits = [
elem("rust", [rust_toolchain, rust_target, sys_name, sys_version, sys_target]),
elem("integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:rust", [rust_toolchain, rust_target, sys_name, sys_version, sys_target])
install = elem("target:install", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
mrsdk_target_share = "/usr/src/matrix-rust-sdk/target/${sys_name}/${sys_version}/${rust_target}/${rust_toolchain}/_shared_cache"
mrsdk_testee = "/usr/bin/tuwunel"
mrsdk_test_args = "--no-fail-fast"
mrsdk_skip_list =<<EOF
--skip test_delayed_invite_response_and_sent_message_decryption
EOF
}
}
target "integ-valgrind" {
name = elem("integ-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("integ-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
matrix = cargo_rust_feat_sys
inherits = [
elem("integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
VALGRINDFLAGS = "${valgrind_flags}"
cargo_cmd = "valgrind test"
cargo_args = "--test=*"
}
}
target "integ" {
name = elem("integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
matrix = cargo_rust_feat_sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
TUWUNEL_DATABASE_PATH = "/tmp/integration.test.db"
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
cargo_args = (cargo_profile == "bench"?
"--no-fail-fast --bench=*": "--no-fail-fast --test=*"
)
}
}
#
# Smoke tests
#
@@ -442,11 +557,26 @@ group "smoke" {
targets = [
"smoke-version",
"smoke-startup",
#"smoke-nix",
#"smoke-valgrind",
#"smoke-perf",
]
}
target "smoke-nix" {
name = elem("smoke-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("smoke-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
output = ["type=cacheonly,compression=zstd,mode=min,compression-level=${cache_compress_level}"]
dockerfile = "${docker_dir}/Dockerfile.nix"
target = "smoke-nix"
matrix = cargo_rust_feat_sys
inherits = [
elem("build-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
}
target "smoke-valgrind" {
name = elem("smoke-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
@@ -519,6 +649,70 @@ target "tests-smoke" {
}
}
#
# Unit tests
#
target "unit-valgrind" {
name = elem("unit-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("unit-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
matrix = cargo_rust_feat_sys
inherits = [
elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
VALGRINDFLAGS = "${valgrind_flags}"
cargo_cmd = "valgrind test"
cargo_args = "--lib --bins"
}
}
target "unit" {
name = elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
matrix = cargo_rust_feat_sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
cargo_args = (cargo_profile == "bench"?
"--no-fail-fast --lib": "--no-fail-fast --lib --bins"
)
}
}
target "docs" {
name = elem("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
matrix = cargo_rust_feat_sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
cargo_cmd = "test"
cargo_args = "--doc --no-fail-fast"
}
}
#
# Installation
#
@@ -534,7 +728,7 @@ group "installs" {
install_labels = {
"org.opencontainers.image.authors" = "${package_authors}"
"org.opencontainers.image.created" ="${package_last_modified}"
"org.opencontainers.image.created" = "${package_last_modified}"
"org.opencontainers.image.description" = "Matrix Chat Server in Rust"
"org.opencontainers.image.documentation" = "https://github.com/matrix-construct/tuwunel/tree/main/docs/"
"org.opencontainers.image.licenses" = "Apache-2.0"
@@ -546,12 +740,26 @@ install_labels = {
"org.opencontainers.image.version" = "${package_version}"
}
install_annotations = [
"org.opencontainers.image.authors=${package_authors}",
"org.opencontainers.image.created=${package_last_modified}",
"org.opencontainers.image.description=Matrix Chat Server in Rust",
"org.opencontainers.image.documentation=https://github.com/matrix-construct/tuwunel/tree/main/docs/",
"org.opencontainers.image.licenses=Apache-2.0",
"org.opencontainers.image.revision=${package_revision}",
"org.opencontainers.image.source=https://github.com/matrix-construct/tuwunel",
"org.opencontainers.image.title=${package_name}",
"org.opencontainers.image.url=https://github.com/matrix-construct/tuwunel",
"org.opencontainers.image.vendor=matrix-construct",
"org.opencontainers.image.version=${package_version}",
]
target "oci" {
name = elem("oci", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("oci", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
output = ["type=oci,dest=tuwunel-oci.tar.zst,mode=min,compression=zstd,compression-level=${zstd_image_compress_level}"]
output = ["type=oci,dest=tuwunel-oci.tar.zst,compression=zstd,compression-level=${zstd_image_compress_level},force-compression=true,mode=min"]
matrix = cargo_rust_feat_sys
inherits = [
elem("docker", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
@@ -563,7 +771,7 @@ target "docker" {
tags = [
elem_tag("docker", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
output = ["type=docker,compression=gzip,mode=min,compression-level=${gz_image_compress_level}"]
output = ["type=docker,compression=zstd,compression-level=${zstd_image_compress_level},force-compression=true,mode=min"]
matrix = cargo_rust_feat_sys
inherits = [
elem("static", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
@@ -643,7 +851,8 @@ target "install" {
elem_tag("install", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
labels = install_labels
output = ["type=docker,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
annotations = install_annotations
output = ["type=docker,compression=zstd,mode=max,compression-level=${zstd_image_compress_level}"]
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
dockerfile = "${docker_dir}/Dockerfile.install"
target = "install"
@@ -659,6 +868,12 @@ target "install" {
}
args = {
install_prefix = install_prefix
assert_linkage = (
substr(cargo_profile, 0, 5) == "bench"? "static":
substr(cargo_profile, 0, 7) == "release"? "static":
substr(rust_toolchain, 0, 6) == "stable"? "static":
""
)
}
}
@@ -668,6 +883,7 @@ target "install" {
group "pkg" {
targets = [
"nix",
"deb",
"rpm",
"deb-install",
@@ -779,28 +995,36 @@ target "build-deb" {
}
}
#
# Unit tests
#
target "unit" {
name = elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
target "nix" {
name = elem("nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
elem_tag("nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
target = "cargo"
output = ["type=docker,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
target = "nix-pkg"
matrix = cargo_rust_feat_sys
inherits = [
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
elem("build-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
]
}
target "build-nix" {
name = elem("build-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("build-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
output = ["type=cacheonly,compression=zstd,mode=min,compression-level=${cache_compress_level}"]
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
dockerfile = "${docker_dir}/Dockerfile.nix"
target = "build-nix"
matrix = cargo_rust_feat_sys
inherits = [
elem("builder", [sys_name, sys_version, sys_target]),
elem("source", [sys_name, sys_version, sys_target]),
]
contexts = {
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
cargo_args = (rust_toolchain == "nightly"?
"--no-fail-fast --all-targets": "--no-fail-fast --bins --tests"
)
input = elem("target:builder", [sys_name, sys_version, sys_target]),
source = elem("target:source", [sys_name, sys_version, sys_target]),
}
}
@@ -825,15 +1049,14 @@ target "book" {
}
dockerfile-inline =<<EOF
FROM input AS book
COPY --link --from=input . .
RUN ["mdbook", "build", "-d", "/book", "/usr/src/tuwunel"]
EOF
}
target "docs" {
name = elem("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
target "build-docs" {
name = elem("build-docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
tags = [
elem_tag("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
elem_tag("build-docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
]
matrix = cargo_rust_feat_sys
inherits = [
@@ -893,7 +1116,7 @@ target "build-tests" {
}
args = {
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
cargo_args = "--no-run"
cargo_args = (cargo_profile == "bench"? "--no-run --benches": "--no-run --tests")
}
}
@@ -1030,7 +1253,7 @@ target "fmt" {
input = elem("target:ingredients", [rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
}
args = {
fmt_args = "-- --color always"
fmt_args = "-- --color=always"
}
}
@@ -1171,6 +1394,8 @@ target "deps-base" {
# cache key for unique artifact area
cargo_target_artifact = "${cargo_tgt_dir_base}/${sys_name}/${sys_version}/${rust_target}/${rust_toolchain}/${cargo_profile}/${feat_set}/${git_ref_sha}"
# cache key for hashed subdirs
cargo_share = "${cargo_tgt_dir_base}/${sys_name}/${sys_version}/${rust_toolchain}/${cargo_profile}/_shared_cache"
# cache key for hashed subdirs
cargo_target_share = "${cargo_tgt_dir_base}/${sys_name}/${sys_version}/${rust_target}/${rust_toolchain}/${cargo_profile}/_shared_cache"
# cased name of profile subdir within target complex
cargo_target_profile = (
@@ -1181,8 +1406,8 @@ target "deps-base" {
CARGO_PROFILE_TEST_DEBUG = "false"
CARGO_PROFILE_TEST_INCREMENTAL = "false"
CARGO_PROFILE_BENCH_DEBUG = "limited"
CARGO_PROFILE_BENCH_LTO = "false"
CARGO_PROFILE_BENCH_DEBUG = "false"
CARGO_PROFILE_BENCH_LTO = "thin"
CARGO_PROFILE_RELEASE_LTO = "thin"
CARGO_PROFILE_RELEASE_DEBUGINFO_DEBUG = "limited"
CARGO_PROFILE_RELEASE_DEBUGINFO_LTO = "off"
@@ -1195,65 +1420,93 @@ target "deps-base" {
join(" ", static_rustflags),
join(" ", static_nightly_rustflags),
join(" ", native_rustflags),
"-C link-arg=-L/usr/lib/gcc/${sys_target}/14", #FIXME
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-l:libbz2.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
"-C link-arg=-l:liblz4.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
"-C link-arg=-l:libzstd.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
"-C link-arg=-l:liburing.a": "",
join(" ", static_libs),
sys_target == "aarch64-linux-gnu"?
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
"-C link-arg=-l:libgcc.a": "",
]):
cargo_profile == "release" && rust_toolchain == "nightly"?
(cargo_profile == "release" || cargo_profile == "bench") && substr(rust_toolchain, 0, 7) == "nightly"?
join(" ", [
join(" ", rustflags),
join(" ", nightly_rustflags),
join(" ", static_rustflags),
join(" ", static_nightly_rustflags),
"-C link-arg=-L/usr/lib/gcc/${sys_target}/14", #FIXME
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
"-C target-cpu=${sys_target_isa(sys_target)}": "",
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-l:libbz2.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
"-C link-arg=-l:liblz4.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
"-C link-arg=-l:libzstd.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
"-C link-arg=-l:liburing.a": "",
join(" ", static_libs),
sys_target == "aarch64-linux-gnu"?
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
"-C link-arg=-l:libgcc.a": "",
]):
cargo_profile == "release" || cargo_profile == "release-debuginfo"?
cargo_profile == "release" || cargo_profile == "release-debuginfo" || cargo_profile == "bench"?
join(" ", [
join(" ", rustflags),
join(" ", static_rustflags),
"-C link-arg=-L/usr/lib/gcc/${sys_target}/14", #FIXME
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
"-C target-cpu=${sys_target_isa(sys_target)}": "",
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-l:libbz2.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
"-C link-arg=-l:liblz4.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
"-C link-arg=-l:libzstd.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
"-C link-arg=-l:liburing.a": "",
join(" ", static_libs),
sys_target == "aarch64-linux-gnu"?
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
"-C link-arg=-l:libgcc.a": "",
]):
rust_toolchain == "stable"?
substr(rust_toolchain, 0, 6) == "stable"?
join(" ", [
join(" ", rustflags),
join(" ", static_rustflags),
"-C link-arg=-L/usr/lib/gcc/${sys_target}/14", #FIXME
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
"-C target-cpu=${sys_target_isa(sys_target)}": "",
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-l:libbz2.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
"-C link-arg=-l:liblz4.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
"-C link-arg=-l:libzstd.a": "",
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
"-C link-arg=-l:liburing.a": "",
join(" ", static_libs),
sys_target == "aarch64-linux-gnu"?
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
"-C link-arg=-l:libgcc.a": "",
]):
rust_toolchain == "nightly"?
substr(rust_toolchain, 0, 7) == "nightly"?
join(" ", [
join(" ", rustflags),
join(" ", nightly_rustflags),
join(" ", dynamic_rustflags),
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
"-C target-cpu=${sys_target_isa(sys_target)}": "",
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
"-C link-arg=-lbz2": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
"-C link-arg=-llz4": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")?
"-C link-arg=-lzstd": "",
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")?
@@ -1303,13 +1556,33 @@ target "rocksdb-build" {
input = elem("target:kitchen", [feat_set, sys_name, sys_version, sys_target])
}
args = {
rocksdb_bz2 = contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")? 1: 0
rocksdb_lz4 = contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")? 1: 0
rocksdb_zstd = contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? 1: 0
rocksdb_jemalloc = contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? 1: 0
rocksdb_iouring = contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? 1: 0
rocksdb_portable = cargo_profile == "release_max_perf"? 0: rocksdb_portable
rocksdb_build_type = rocksdb_build_type
rocksdb_opt_level = rocksdb_opt_level
rocksdb_numa = rocksdb_numa
rocksdb_shared = 0
rocksdb_opt_level = rocksdb_opt_level
rocksdb_build_type = rocksdb_build_type
rocksdb_cxx_flags = (
cargo_profile == "release-native" && sys_target_triple(sys_target) == "aarch64-linux-gnu"?
"-ftls-model=local-exec -mno-outline-atomics":
cargo_profile == "release-native"?
"-ftls-model=local-exec":
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
"-ftls-model=initial-exec -mno-outline-atomics":
sys_target_triple(sys_target) == "x86_64-linux-gnu" && sys_target != "x86_64-v1-linux-gnu"?
"-ftls-model=initial-exec -mpclmul":
"-ftls-model=initial-exec"
)
rocksdb_portable = (
cargo_profile == "release-native"?
"0":
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
"${sys_target_isa(sys_target)}":
rocksdb_portable
)
}
}
@@ -1405,8 +1678,8 @@ target "ingredients" {
"--all-features": "--no-default-features"
)
RUST_BACKTRACE = "full"
ROCKSDB_LIB_DIR="/usr/lib/${sys_target}"
JEMALLOC_OVERRIDE="/usr/lib/${sys_target}/libjemalloc.a"
ROCKSDB_LIB_DIR="/usr/lib/${sys_target_triple(sys_target)}"
JEMALLOC_OVERRIDE="/usr/lib/${sys_target_triple(sys_target)}/libjemalloc.a"
ZSTD_SYS_USE_PKG_CONFIG = (
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? 1: 0
)
@@ -1447,11 +1720,12 @@ rustup_components = [
]
cargo_installs = [
"cargo-chef",
"cargo-audit",
"cargo-deb",
#"cargo-arch",
"cargo-chef",
"cargo-deb",
"cargo-generate-rpm",
"cargo-valgrind",
"lychee",
"mdbook",
"typos-cli",
@@ -1487,8 +1761,8 @@ target "rust" {
cargo_installs = join(" ", cargo_installs)
CARGO_TERM_VERBOSE = CARGO_TERM_VERBOSE
RUSTUP_HOME = "/opt/rust/rustup/${sys_name}/${sys_target}"
CARGO_HOME = "/opt/rust/cargo/${sys_name}/${sys_target}"
RUSTUP_HOME = "/opt/rust/rustup/${sys_name}/${sys_target_triple(sys_target)}"
CARGO_HOME = "/opt/rust/cargo/${sys_name}/${sys_target_triple(sys_target)}"
}
}
@@ -1535,12 +1809,22 @@ kitchen_packages = [
"clang",
"cmake",
"curl",
"gawk",
"git",
"golang-go",
"gzip",
"jq",
"libc6-dev",
"libclang-dev",
"libnuma-dev",
"libssl-dev",
"libsqlite3-dev",
"make",
"nix-bin",
"openssl",
"pkg-config",
"pkgconf",
"valgrind",
"xz-utils",
]
@@ -1559,10 +1843,11 @@ target "kitchen" {
}
args = {
packages = join(" ", [
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")? "libbz2-dev": "",
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? "liburing-dev": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? "libzstd-dev": "",
contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? "libjemalloc-dev": "",
contains(split(",", cargo_feat_sets[feat_set]), "hardened_malloc")? "g++": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")? "liblz4-dev": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? "libzstd-dev": "",
])
}
}
@@ -1662,18 +1947,18 @@ target "runtime" {
}
args = {
packages = join(" ", [
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")? "bzip2": "",
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? "liburing2": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? "libzstd1": "",
contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? "libjemalloc2": "",
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")? "liblz4-1": "",
contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? "libzstd1": "",
])
}
}
base_pkgs = [
"adduser",
"bzip2",
"ca-certificates",
"gzip",
]
target "base" {
@@ -1692,8 +1977,8 @@ target "base" {
}
args = {
DEBIAN_FRONTEND="noninteractive"
var_lib_apt = "/var/lib/apt/${sys_name}/${sys_version}/${sys_target}"
var_cache = "/var/cache/${sys_name}/${sys_version}/${sys_target}"
var_lib_apt = "/var/lib/apt/${sys_name}/${sys_version}/${sys_target_triple(sys_target)}"
var_cache = "/var/cache/${sys_name}/${sys_version}/${sys_target_triple(sys_target)}"
packages = join(" ", base_pkgs)
}
}
@@ -1713,12 +1998,21 @@ target "system" {
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
cache_from = ["type=local"]
dockerfile = "${docker_dir}/Dockerfile.system"
matrix = sys
context = "."
matrix = sys
platforms = (
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
["linux/amd64/${sys_target_ver(sys_target)}"]:
sys_target_triple(sys_target) == "aarch64-linux-gnu"?
["linux/arm64"]:
["local"]
)
args = {
sys_name = sys_name
sys_version = sys_version
sys_target = sys_target
sys_triple = sys_target_triple(sys_target)
sys_isa = sys_target_isa(sys_target)
}
}
@@ -1727,6 +2021,33 @@ target "system" {
# Utils
#
function "sys_target_isa" {
params = [sys_target]
result = (
sys_target_ver(sys_target) != "v1"?
join("-",
[
replace(split("-", sys_target)[0], "_", "-"),
sys_target_ver(sys_target)
]):
replace(split("-", sys_target)[0], "_", "-")
)
}
function "sys_target_triple" {
params = [sys_target]
result = join("-", [
split("-", sys_target)[0],
split("-", sys_target)[2],
split("-", sys_target)[3],
])
}
function "sys_target_ver" {
params = [sys_target]
result = split("-", sys_target)[1]
}
function "elem_tag" {
params = [prefix, matrix, tag]
result = join(":", [elem(prefix, matrix), tag])
+3 -12
View File
@@ -14,8 +14,8 @@ default_feat_sets='["all"]'
default_rust_toolchains='["nightly"]'
default_rust_targets='["x86_64-unknown-linux-gnu"]'
default_sys_names='["debian"]'
default_sys_targets='["x86_64-linux-gnu"]'
default_sys_versions='["testing-slim"]'
default_sys_targets='["x86_64-v1-linux-gnu"]'
if test ! -z "$cargo_profile"; then
env_cargo_profiles="[\"${cargo_profile}\"]"
@@ -61,18 +61,8 @@ toolchain_toml="$docker_dir/../rust-toolchain.toml"
rust_msrv=$(grep "channel = " "$toolchain_toml" | cut -d'=' -f2 | sed 's/\s"\|"$//g')
rocksdb_opt_level=3
rocksdb_portable=1
git_checkout="HEAD"
git_checkout="${git_checkout:-HEAD}"
use_chef="true"
complement_count=1
complement_skip="TestPartialStateJoin.*"
complement_skip="${complement_skip}|TestRoomDeleteAlias/Pa.*/Can_delete_canonical_alias"
complement_skip="${complement_skip}|TestUnbanViaInvite.*"
complement_skip="${complement_skip}|TestRoomDeleteAlias/Pa.*/Regular_users_can_add_and_delete_aliases_when.*"
complement_skip="${complement_skip}|TestToDeviceMessagesOverFederation/stopped_server"
complement_skip="${complement_skip}|TestLogin/parallel/POST_/login_as_non-existing_user_is_rejected"
complement_skip="${complement_skip}|TestRoomState/Parallel/GET_/publicRooms_lists_newly-created_room"
complement_skip="${complement_skip}|TestThreadReceiptsInSyncMSC4102"
complement_run=".*"
set +a
###############################################################################
@@ -83,6 +73,7 @@ if test "$CI" = "true"; then
fi
args=""
args="$args --provenance=false"
args="$args --builder ${builder_name}"
#args="$args --set *.platform=${sys_platform}"
+51 -11
View File
@@ -12,9 +12,28 @@ default_feat_set="all"
default_rust_toolchain="nightly"
default_rust_target="x86_64-unknown-linux-gnu"
default_sys_name="debian"
default_sys_target="x86_64-linux-gnu"
default_sys_target="x86_64-v1-linux-gnu"
default_sys_version="testing-slim"
default_complement_verbose=0
default_complement_dirty=0
default_complement_count=1
default_complement_parallel=1
default_complement_shuffle=0
default_complement_timeout="1h"
default_complement_run=".*"
run="${1:-$default_complement_run}"
skip=""
skip="${skip}TestToDeviceMessagesOverFederation/stopped_server"
skip="${skip}|TestToDeviceMessagesOverFederation/interrupted_connectivity"
skip="${skip}|TestRoomCreate/Parallel/POST_/createRoom_makes_a_room_with_a_topic_and_writes_rich_topic_representation"
skip="${skip}|TestRoomCreate/Parallel/POST_/createRoom_makes_a_room_with_a_topic_via_initial_state_overwritten_by_topic"
skip="${skip}|TestLogin/parallel/POST_/"
skip="${skip}|TestUnbanViaInvite"
skip="${skip}|TestRoomState/Parallel/GET_/publicRooms_lists_newly-created_room"
skip="${skip}|TestThreadReceiptsInSyncMSC4102"
set -a
cargo_profile="${cargo_profile:-$default_cargo_profile}"
feat_set="${feat_set:-$default_feat_set}"
@@ -30,12 +49,22 @@ set +a
###############################################################################
envs=""
envs="$envs -e complement_verbose=${complement_verbose:-$default_complement_verbose}"
envs="$envs -e complement_count=${complement_count:-$default_complement_count}"
envs="$envs -e complement_dirty=${complement_dirty:-$default_complement_dirty}"
envs="$envs -e complement_parallel=${complement_parallel:-$default_complement_parallel}"
envs="$envs -e complement_shuffle=${complement_shuffle:-$default_complement_shuffle}"
envs="$envs -e complement_timeout=${complement_timeout:-$default_complement_timeout}"
envs="$envs -e complement_skip=${complement_skip:-$skip}"
envs="$envs -e complement_run=${1:-$default_complement_run}"
set -x
tester_image="complement-tester--${sys_name}--${sys_version}--${sys_target}"
testee_image="complement-testee--${cargo_profile}--${rust_toolchain}--${rust_target}--${feat_set}--${sys_name}--${sys_version}--${sys_target}"
name="complement_tester__${sys_name}__${sys_version}__${sys_target}"
sock="/var/run/docker.sock"
arg="--name $name -v $sock:$sock --network=host $tester_image ${testee_image}"
arg="--name $name -v $sock:$sock --network=host $envs $tester_image ${testee_image}"
set +x
if test "$CI_VERBOSE_ENV" = "true"; then
@@ -45,21 +74,32 @@ fi
docker rm -f "$name" 2>/dev/null
trap 'set +x; date; echo -e "\033[1;41;37mERROR\033[0m"' ERR
arg="-d $arg"
cid=$(docker run $arg)
if test "$CI" = "true"; then
arg="-d $arg"
cid=$(docker run $arg)
else
arg="--rm -a stdout -a stderr $arg"
docker run $arg
exit $?
echo -n "$cid" > "$name"
fi
echo -n "$cid" > "$name"
output_src="$cid:/usr/src/complement/full_output.jsonl"
output_dst="tests/complement/logs.jsonl"
extract_output() {
docker cp "$output_src" "$output_dst"
}
trap 'docker container stop $cid; set +x; date; echo -e "\033[1;41;37mERROR\033[0m"' INT
result_src="$cid:/usr/src/complement/new_results.jsonl"
result_dst="tests/complement/results.jsonl"
extract_results() {
docker cp "$result_src" "$result_dst"
}
trap 'extract_output; set +x; date; echo -e "\033[1;41;37mERROR\033[0m"' ERR
trap 'docker container stop $cid; extract_output' INT
docker logs -f "$cid"
docker wait "$cid" 2>/dev/null
extract_results
git diff -U0 --color --shortstat "$result_dst" | (grep "$run" || true)
git diff --quiet --exit-code "$result_dst"
echo -e "\033[1;42;30mACCEPT\033[0m"
+1
View File
@@ -0,0 +1 @@
../CODE_OF_CONDUCT.md
+3 -1
View File
@@ -5,6 +5,8 @@ # Summary
- [Examples](configuration/examples.md)
- [Deploying](deploying.md)
- [Generic](deploying/generic.md)
- [Reverse Proxy - Caddy](deploying/reverse-proxy-caddy.md)
- [Reverse Proxy - Nginx](deploying/reverse-proxy-nginx.md)
- [NixOS](deploying/nixos.md)
- [Docker](deploying/docker.md)
- [Kubernetes](deploying/kubernetes.md)
@@ -20,4 +22,4 @@ # Summary
- [Contributing](contributing.md)
- [Testing](development/testing.md)
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
- [Tuwunel Community Code of Conduct](../CODE_OF_CONDUCT.md)
- [Tuwunel Community Code of Conduct](CODE_OF_CONDUCT.md)
+26 -6
View File
@@ -7,14 +7,20 @@ ## Basics
Tuwunel uses a config file for the majority of the settings, but also supports
setting individual config options via commandline.
Please refer to the [example config
file](./configuration/examples.md#example-configuration) for all of those
settings.
Please refer to the [example config file](./configuration/examples.md#example-configuration) for all of those settings.
The config file to use can be specified on the commandline when running
Tuwunel by specifying the `-c`, `--config` flag. Alternatively, you can use
the environment variable `TUWUNEL_CONFIG` to specify the config file to used.
Conduit's environment variables are supported for backwards compatibility.
Tuwunel by specifying the `-c`, `--config` flag. It is also possible to specify
more than one config file.
Alternatively, you can use the environment variable `TUWUNEL_CONFIG` to specify
the config file to used. Conduit's environment variables are supported for
backwards compatibility.
> [!IMPORTANT]
> It is bad practice to uncomment default options without changing them. Many
> defaults are updated by developers as features evolve and can be essential
> to expected server function.
## Option commandline flag
@@ -31,6 +37,20 @@ ## Option commandline flag
- `--option log=\"debug\"` works ✅
- `--option server_name='"example.com'"` works ✅
## Relevance of configuration settings
There is a specific sequence for reading and overwriting the settings. The
latest setting takes precedence and defines the configuration.
1. Set in `CONDUIT_CONFIG`.
2. Set in `CONDUWUIT_CONFIG`.
3. Set in `TUWUNEL_CONFIG`.
4. Set in the first config file on the command line (e.g. `-c config_file_1.toml`).
5. Set in the second config file on the command line (e.g. `-c config_file_2.toml`).
6. Set in any additional config file on the command line (e.g. `-c config_file_n.toml`).
7. Set within the options (again, the latest option in the list overrides).
## Execute commandline flag
Tuwunel supports running admin commands on startup using the commandline
@@ -20,7 +20,6 @@ services:
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
TUWUNEL_ALLOW_FEDERATION: 'true'
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
#TUWUNEL_LOG: warn,state_res=warn
TUWUNEL_ADDRESS: 0.0.0.0
@@ -36,7 +36,6 @@ services:
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
TUWUNEL_ALLOW_FEDERATION: 'true'
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
#TUWUNEL_LOG: warn,state_res=warn
TUWUNEL_ADDRESS: 0.0.0.0
@@ -26,7 +26,6 @@ services:
# TUWUNEL_LOG: info # default is: "warn,state_res=warn"
# TUWUNEL_ALLOW_ENCRYPTION: 'true'
# TUWUNEL_ALLOW_FEDERATION: 'true'
# TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
# TUWUNEL_ALLOW_INCOMING_PRESENCE: true
# TUWUNEL_ALLOW_OUTGOING_PRESENCE: true
# TUWUNEL_ALLOW_LOCAL_PRESENCE: true
-1
View File
@@ -20,7 +20,6 @@ services:
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
TUWUNEL_ALLOW_FEDERATION: 'true'
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
#TUWUNEL_LOG: warn,state_res=warn
TUWUNEL_ADDRESS: 0.0.0.0
+42 -64
View File
@@ -1,9 +1,9 @@
# Generic deployment documentation
> ### Getting help
> [!TIP]
>
> If you run into any problems while setting up Tuwunel [open an issue on
> GitHub](https://github.com/matrix-construct/tuwunel/issues/new).
> Getting help: If you run into any problems while setting up Tuwunel
> [open an issue on GitHub](https://github.com/matrix-construct/tuwunel/issues/new).
## Installing Tuwunel
@@ -12,24 +12,23 @@ ### 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
Prebuilt fully static binaries can be downloaded from the latest tagged
release [here](https://github.com/matrix-construct/tuwunel/releases/latest) or
`main` CI branch workflow artifact output. These also include Debian/Ubuntu
packages.
`main` CI branch workflow artifact output. These also include `.deb` packages
for Debian or Ubuntu and `.rpm` packages for Red Hat or Fedora.
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.
For the **best** performance; if using an `x86_64` CPU made in the last ~10 years,
we recommend using the `-v3-` optimised packages. See below for a command to check
what your system supports. If the server refuses to start or exits with an "Illegal
Instruction" error you will need `-v2-` or `-v1-` packages instead. The database
backend, RocksDB, benefits from `-v2-` or greater as it features performance
critical hardware accelerated CRC32 hashing/checksumming.
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.
Linux users can run this script to display which optimization levels they may
choose:
```
cat /proc/cpuinfo | grep -Po '(avx|sse)[235]' | sort -u | sed 's/avx5/v4/;s/avx2/v3/;s/sse3/v2/;s/sse2/v1/' | sort
```
### Compiling
@@ -142,69 +141,48 @@ ## Setting the correct file permissions
## Setting up the Reverse Proxy
We recommend Caddy as a reverse proxy, as it is trivial to use, handling TLS certificates, reverse proxy headers, etc transparently with proper defaults.
For other software, please refer to their respective documentation or online guides.
We recommend Caddy as a reverse proxy, as it is trivial to use, handling TLS certificates, reverse proxy headers, etc. transparently with proper defaults. However, Nginx is also well-supported and widely used.
### Caddy
**Choose your reverse proxy:**
After installing Caddy via your preferred method, create `/etc/caddy/conf.d/tuwunel_caddyfile`
and enter this (substitute for your server name).
- **[Caddy Setup Guide](reverse-proxy-caddy.md)** - Recommended for ease of use and automatic TLS
- **[Nginx Setup Guide](reverse-proxy-nginx.md)** - Popular choice with extensive documentation
```caddyfile
your.server.name, your.server.name:8448 {
# TCP reverse_proxy
reverse_proxy 127.0.0.1:6167
# UNIX socket
#reverse_proxy unix//run/tuwunel/tuwunel.sock
}
```
### Quick Overview
That's it! Just start and enable the service and you're set.
Regardless of which reverse proxy you choose, you will need to:
```bash
sudo systemctl enable --now caddy
```
1. **Reverse proxy the following routes:**
- `/_matrix/` - core Matrix C-S and S-S APIs
- `/_tuwunel/` - ad-hoc Tuwunel routes such as `/local_user_count` and `/server_version`
### Other Reverse Proxies
2. **Optionally reverse proxy (recommended):**
- `/.well-known/matrix/client` and `/.well-known/matrix/server` if using Tuwunel to perform delegation (see the `[global.well_known]` config section)
- `/.well-known/matrix/support` if using Tuwunel to send the homeserver admin contact and support page (formerly known as MSC1929)
- `/` if you would like to see `hewwo from tuwunel woof!` at the root
As we would prefer our users to use Caddy, we will not provide configuration files for other proxys.
3. **Handle ports:**
- Port 443 (HTTPS) for client-server API
- Port 8448 for federation (if federating with other homeservers)
You will need to reverse proxy everything under following routes:
- `/_matrix/` - core Matrix C-S and S-S APIs
- `/_tuwunel/` - ad-hoc Tuwunel routes such as `/local_user_count` and
`/server_version`
You can optionally reverse proxy the following individual routes:
- `/.well-known/matrix/client` and `/.well-known/matrix/server` if using
Tuwunel to perform delegation (see the `[global.well_known]` config section)
- `/.well-known/matrix/support` if using Tuwunel to send the homeserver admin
contact and support page (formerly known as MSC1929)
- `/` if you would like to see `hewwo from tuwunel woof!` at the root
See the following spec pages for more details on these files:
See the following spec pages for more details on well-known files:
- [`/.well-known/matrix/server`](https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixserver)
- [`/.well-known/matrix/client`](https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient)
- [`/.well-known/matrix/support`](https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixsupport)
Examples of delegation:
- <https://puppygock.gay/.well-known/matrix/server>
- <https://puppygock.gay/.well-known/matrix/client>
- <https://matrix.org/.well-known/matrix/server>
- <https://matrix.org/.well-known/matrix/client>
For Apache and Nginx there are many examples available online.
### Other Reverse Proxies
Lighttpd is not supported as it seems to mess with the `X-Matrix` Authorization
header, making federation non-functional. If a workaround is found, feel free to share to get it added to the documentation here.
_Specific contributions for other proxies are welcome!_
If using Apache, you need to use `nocanon` in your `ProxyPass` directive to prevent httpd from messing with the `X-Matrix` header (note that Apache isn't very good as a general reverse proxy and we discourage the usage of it if you can).
**Not Recommended:**
- **Apache**: While possible, Apache requires special configuration (`nocanon` in `ProxyPass`) to prevent corruption of the `X-Matrix` header.
- **Lighttpd**: Its proxy module alters the `X-Matrix` authorization header, breaking federation functionality.
If using Nginx, you need to give Tuwunel the request URI using `$request_uri`, or like so:
- `proxy_pass http://127.0.0.1:6167$request_uri;`
- `proxy_pass http://127.0.0.1:6167;`
Nginx users need to increase `client_max_body_size` (default is 1M) to match
`max_request_size` defined in tuwunel.toml.
## You're done
## You are done
Now you can start Tuwunel with:
+2 -1
View File
@@ -2,7 +2,8 @@ # Tuwunel for Kubernetes
Tuwunel doesn't support horizontal scalability or distributed loading
natively, however a community maintained Helm Chart is available here to run
Tuwunel on Kubernetes: <https://gitlab.cronce.io/charts/conduwuit>
Tuwunel on Kubernetes: <https://github.com/AreYouLoco/tuwunel-helm> and the
legacy conduwuit version: <https://gitlab.cronce.io/charts/conduwuit>.
Should changes need to be made, please reach out to the maintainer in our
Matrix room as this is not maintained/controlled by the Tuwunel maintainers.
+48
View File
@@ -0,0 +1,48 @@
# Reverse Proxy Setup - Caddy
[<= Back to Generic Deployment Guide](generic.md#setting-up-the-reverse-proxy)
We recommend Caddy as a reverse proxy, as it is trivial to use, handling TLS certificates, reverse proxy headers, etc. transparently with proper defaults.
## Installation
Install Caddy via your preferred method. Refer to the [official Caddy installation guide](https://caddyserver.com/docs/install) for your distribution.
## Configuration
After installing Caddy, create `/etc/caddy/conf.d/tuwunel_caddyfile` and enter this (substitute `your.server.name` with your actual server name):
```caddyfile
your.server.name, your.server.name:8448 {
# TCP reverse_proxy
reverse_proxy localhost:8008
# UNIX socket (alternative - comment out the line above and uncomment this)
#reverse_proxy unix//run/tuwunel/tuwunel.sock
}
```
### What this does
- Handles both port 443 (HTTPS) and port 8448 (Matrix federation) automatically
- Automatically provisions and renews TLS certificates via Let's Encrypt
- Sets all necessary reverse proxy headers correctly
- Routes all traffic to Tuwunel listening on `localhost:8008`
That's it! Just start and enable the service and you're set.
```bash
sudo systemctl enable --now caddy
```
## Verification
After starting Caddy, verify it's working by checking:
```bash
curl https://your.server.name/_tuwunel/server_version
curl https://your.server.name:8448/_tuwunel/server_version
```
---
[=> Continue with "You're Done"](generic.md#you-are-done)
+164
View File
@@ -0,0 +1,164 @@
# Reverse Proxy Setup - Nginx
[<= Back to Generic Deployment Guide](generic.md#setting-up-the-reverse-proxy)
This guide shows you how to configure Nginx as a reverse proxy for Tuwunel with TLS support.
## Installation
Install Nginx via your preferred method. Most distributions include Nginx in their package repositories:
```bash
# Debian/Ubuntu
sudo apt install nginx
# Red Hat/Fedora
sudo dnf install nginx
# Arch Linux
sudo pacman -S nginx
```
## Configuration
Create a new configuration file at `/etc/nginx/sites-available/tuwunel` (or `/etc/nginx/conf.d/tuwunel.conf` on some distributions):
```nginx
# Client-Server API over HTTPS (port 443)
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name matrix.example.com;
# Nginx standard body size is 1MB, which is quite small for media uploads
# Increase this to match the max_request_size in your tuwunel.toml
client_max_body_size 100M;
# Forward requests to Tuwunel (listening on 127.0.0.1:8008)
location / {
proxy_pass http://127.0.0.1:8008;
# Preserve host and scheme - critical for proper Matrix operation
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
# TLS configuration (Let's Encrypt example using certbot)
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
}
# Matrix Federation over HTTPS (port 8448)
# Only needed if you want to federate with other homeservers
# Don't forget to open port 8448 in your firewall!
server {
listen 8448 ssl http2;
listen [::]:8448 ssl http2;
server_name matrix.example.com;
# Same body size increase for larger files
client_max_body_size 100M;
# Forward to the same local port as client-server API
location / {
proxy_pass http://127.0.0.1:8008;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
# TLS configuration (same certificates as above)
ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;
}
```
### Important Notes
- **Replace `matrix.example.com`** with your actual server name
- **`client_max_body_size`**: Must match or exceed `max_request_size` in your `tuwunel.toml`
- **Do NOT use `$request_uri`** in `proxy_pass` - while some guides suggest this, it's not necessary for Tuwunel and can cause issues
- **IPv6**: The `listen [::]:443` and `listen [::]:8448` lines enable IPv6 support. Remove them if you don't need IPv6
### TLS Certificates
The example above uses Let's Encrypt certificates via certbot. To obtain certificates:
```bash
sudo certbot certonly --nginx -d matrix.example.com
```
Certbot will automatically handle renewal. Make sure to reload Nginx after certificate renewal:
```bash
sudo systemctl reload nginx
```
### Optional: Timeout Configuration
The default Nginx timeouts are usually sufficient for Matrix operations. Element's long-polling `/sync` requests typically run for 30 seconds, which is within Nginx's default timeouts.
However, if you experience federation retries or dropped long-poll connections, you can extend the timeouts by adding these lines inside your `location /` blocks:
```nginx
location / {
proxy_pass http://127.0.0.1:8008;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
# Optional: Extend timeouts if experiencing issues
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
```
## Enable the Configuration
If using sites-available/sites-enabled structure:
```bash
sudo ln -s /etc/nginx/sites-available/tuwunel /etc/nginx/sites-enabled/
```
Test the configuration:
```bash
sudo nginx -t
```
If the test passes, reload Nginx:
```bash
sudo systemctl reload nginx
```
Enable Nginx to start on boot:
```bash
sudo systemctl enable nginx
```
## Verification
After configuring Nginx, verify it's working by checking:
```bash
curl https://matrix.example.com/_tuwunel/server_version
curl https://matrix.example.com:8448/_tuwunel/server_version
```
## Troubleshooting
### Apache Compatibility Note
If you're considering Apache instead of Nginx: Apache is not well-suited as a reverse proxy for Matrix homeservers. If you must use Apache, you need to use `nocanon` in your `ProxyPass` directive to prevent httpd from corrupting the `X-Matrix` authorization header, which will break federation.
### Lighttpd is Not Supported
Lighttpd has known issues with the `X-Matrix` authorization header, making federation non-functional. We do not recommend using Lighttpd with Tuwunel.
---
[=> Continue with "You're Done"](generic.md#you-are-done)
+1 -1
View File
@@ -1,5 +1,5 @@
[Container]
Environment=TUWUNEL_SERVER_NAME=your.domain.here TUWUNEL_DATABASE_PATH=/var/lib/tuwunel TUWUNEL_PORT=6167 TUWUNEL_MAX_REQUEST_SIZE=20000000 TUWUNEL_ALLOW_REGISTRATION=true TUWUNEL_REGISTRATION_TOKEN=YOUR_TOKEN TUWUNEL_ALLOW_FEDERATION=true TUWUNEL_ALLOW_CHECK_FOR_UPDATES=true TUWUNEL_TRUSTED_SERVERS=["matrix.org"] TUWUNEL_ADDRESS=0.0.0.0 # Add TUWUNEL_CONFIG: '/etc/tuwunel.toml' if the config is mapped
Environment=TUWUNEL_SERVER_NAME=your.domain.here TUWUNEL_DATABASE_PATH=/var/lib/tuwunel TUWUNEL_PORT=6167 TUWUNEL_MAX_REQUEST_SIZE=20000000 TUWUNEL_ALLOW_REGISTRATION=true TUWUNEL_REGISTRATION_TOKEN=YOUR_TOKEN TUWUNEL_ALLOW_FEDERATION=true TUWUNEL_TRUSTED_SERVERS=["matrix.org"] TUWUNEL_ADDRESS=0.0.0.0 # Add TUWUNEL_CONFIG: '/etc/tuwunel.toml' if the config is mapped
Image=docker.io/jevolk/tuwunel:latest
PublishPort=8448:6167
Volume=/path/to/db:/var/lib/tuwunel
+100 -46
View File
@@ -1,17 +1,11 @@
# Troubleshooting Tuwunel
> ## Docker users ⚠️
>
> Docker is extremely UX unfriendly. Because of this, a ton of issues or support
> is actually Docker support, not tuwunel support. We also cannot document the
> ever-growing list of Docker issues here.
>
> [!IMPORTANT]
> If you intend on asking for support and you are using Docker, **PLEASE**
> triple validate your issues are **NOT** because you have a misconfiguration in
> your Docker setup.
>
> If there are things like Compose file issues or Dockerhub image issues, those
> can still be mentioned as long as they're something we can fix.
> your Docker setup. We must remain focused on supporting Tuwunel issues and
> cannot budget our time for generic Docker support. Compose file issues or
> Dockerhub image issues are okay if they are something we can fix.
## Tuwunel and Matrix issues
@@ -93,48 +87,107 @@ ## RocksDB / database issues
#### Database corruption
If your database is corrupted *and* is failing to start (e.g. checksum
mismatch), it may be recoverable but careful steps must be taken, and there is
no guarantee it may be recoverable.
There are many causes and varieties of database corruption. There are several
methods for mitigation, each with outcomes ranging from a recovered state down
to a savage state. This guide has been simplified into a set of universal steps
which everyone can follow from the top until they have recovered or reach the
end. The details and implications will be explained within each step.
The first thing that can be done is launching Tuwunel with the
`rocksdb_repair` config option set to true. This will tell RocksDB to attempt to
repair itself at launch. If this does not work, disable the option and continue
reading.
> [!TIP]
> All command-line `-O` options can be expressed as environment variables or in
> the config file based on your deployment's requirements. Note that
> `--maintenance` is equivalent to configuring `startup_netburst = false` and
> `listening = false`.
RocksDB has the following recovery modes:
> [!IMPORTANT]
> Always create a backup of the database before running any operation. This is
> critical for steps 3 and above.
- `TolerateCorruptedTailRecords`
- `AbsoluteConsistency`
- `PointInTime`
- `SkipAnyCorruptedRecord`
**0. Start the server with the following options:**
By default, Tuwunel uses `TolerateCorruptedTailRecords` as generally these may
be due to bad federation and we can re-fetch the correct data over federation.
The RocksDB default is `PointInTime` which will attempt to restore a "snapshot"
of the data when it was last known to be good. This data can be either a few
seconds old, or multiple minutes prior. `PointInTime` may not be suitable for
default usage due to clients and servers possibly not being able to handle
sudden "backwards time travels", and `AbsoluteConsistency` may be too strict.
`tuwunel --maintenance -O rocksdb_recovery_mode=0`
`AbsoluteConsistency` will fail to start the database if any sign of corruption
is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless
it forbids the database from opening (e.g. too severe). Usage of
`SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or
leave your database in a permanently inconsistent state, but it may do something
if `PointInTime` does not work as a last ditch effort.
This is actually a "control" and not a method of recovery. If the server starts
you either do not have corruption or have deep corruption indicated by very
specific errors from rocksdb citing corruption during runtime. If you are
certain there is deep corruption skip to step 4, otherwise you are finished
without any modifications.
With this in mind:
**1. Start the server in Tolerate-Corrupted-Tail-Records mode:**
- First start Tuwunel with the `PointInTime` recovery method. See the [example
config](configuration/examples.md) for how to do this using
`rocksdb_recovery_mode`
- If your database successfully opens, clients are recommended to clear their
client cache to account for the rollback
- Leave your Tuwunel running in `PointInTime` for at least 30-60 minutes so as
much possible corruption is restored
- If all goes will, you should be able to restore back to using
`TolerateCorruptedTailRecords` and you have successfully recovered your database
`tuwunel --maintenance -O rocksdb_recovery_mode=1`
The most common corruption scenario is from a loss of power to the hardware
(not an application crash, though it is still possible). This is remediated
by dropping the most recently written record. It is highly unlikely there will
be any impact on the application from this loss. In the best-case the same data
is often re-requested over the federation or replaced by a client. In the
worst-case clients may need to clear-cache & reload to guarantee correctness.
If the server starts you are finished.
**2. Start the server in Point-In-Time mode:**
`tuwunel --maintenance -O rocksdb_recovery_mode=2`
Similar to the corruption scenario above but for more severe cases. The most
recent records are discarded back to the point where there is no corruption.
It is highly unlikely there will be any impact on the application from this
loss, but it is more likely than above that clients may need to clear-cache
& reload to correctly resynchronize with the server.
**3. Start the server in Skip-Any-Corrupted-Record mode:**
> [!WARNING]
> Salvage mode potentially impacting the application's ability to function.
> We cannot provide support for users who have entered this mode.
`tuwunel --maintenance -O rocksdb_recovery_mode=3`
Similar to the prior corruption scenarios but for the most severe cases.
The database will be inconsistent. It is theoretically possible for the
server to continue functioning without notable issue in the best case, but
it is completely uncertain what the effect of this operation will be. If
the server starts you should immediately export your messages, encryption
keys, etc, in a salvage effort and prepare to reinstall.
**4. Start the server in repair mode.**
> [!WARNING]
> Salvage mode potentially impacting the application's ability to function.
> We cannot provide support for users who have entered this mode.
> [!CAUTION]
> Always create a backup of the database before entering this mode. The repair
> is not configurable and not interactive. It may automatically remove more
> data than anticipated, preventing further salvage efforts.
`tuwunel --maintenance -O rocksdb_repair=true`
For corruption affecting the bulk database tables not covered by any journal.
This will leave the database in an inconsistent and unpredictable state. It
is theoretically possible to continue operating the server depending on which
records were dropped, such as some historical records which are no longer
essential. Nevertheless the impact of this operation is impossible to assess
and a successful recovery should be used to salvage data prior to reinstall.
Once finished, restart the server without `rocksdb_repair`. If no errors
persist, restart the server again without maintenance mode.
**5. Utilize an external repair tool.**
> [!WARNING]
> Salvage mode potentially impacting the application's ability to function.
> We cannot provide support for users who have entered this mode.
```
git clone https://github.com/facebook/rocksdb
cd rocksdb
make -j$(nproc) ldb
./ldb repair --db=/var/lib/tuwunel/ 2>./repair-log.txt
```
For situations when the repair mode in step 4 failed or produced unexpected
results.
## Debugging
@@ -147,7 +200,8 @@ #### Debug/Trace log level
Tuwunel builds without debug or trace log levels at compile time by default
for substantial performance gains in CPU usage and improved compile times. If
you need to access debug/trace log levels, you will need to build without the
`release_max_log_level` feature or use our provided static debug binaries.
`release_max_log_level` feature or use our provided release-logging binaries
and images.
#### Changing log level dynamically
Generated
+100 -140
View File
@@ -10,11 +10,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1748532342,
"narHash": "sha256-CvaKOUq8G10sghKpZhEB2UYjJoWhEkrDFggDgi7piUI=",
"lastModified": 1758711588,
"narHash": "sha256-0nZlCCDC5PfndsQJXXtcyrtrfW49I3KadGMDlutzaGU=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "ce9373715fe3fac7a174a65a7e6d6baeba8cb4f9",
"rev": "12cbeca141f46e1ade76728bce8adc447f2166c6",
"type": "github"
},
"original": {
@@ -29,14 +29,14 @@
"devenv": "devenv",
"flake-compat": "flake-compat_2",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_4"
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1748883665,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
"lastModified": 1763236786,
"narHash": "sha256-JB19RGXDr6loKSdqwvA15jhRHwf6+9Crq2glqqVar84=",
"owner": "cachix",
"repo": "cachix",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
"rev": "938a275857047c300596092beaabaee6d892e243",
"type": "github"
},
"original": {
@@ -58,16 +58,21 @@
],
"git-hooks": [
"cachix",
"devenv"
"devenv",
"git-hooks"
],
"nixpkgs": "nixpkgs_2"
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1744206633,
"narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=",
"lastModified": 1752264895,
"narHash": "sha256-1zBPE/PNAkPNUsOWFET4J0cjlvziH8DOekesDmjND+w=",
"owner": "cachix",
"repo": "cachix",
"rev": "8a60090640b96f9df95d1ab99e5763a586be1404",
"rev": "47053aef762f452e816e44eb9a23fbc3827b241a",
"type": "github"
},
"original": {
@@ -80,11 +85,11 @@
"complement": {
"flake": false,
"locked": {
"lastModified": 1741891349,
"narHash": "sha256-YvrzOWcX7DH1drp5SGa+E/fc7wN3hqFtPbqPjZpOu1Q=",
"lastModified": 1761739261,
"narHash": "sha256-XdzSBbJIYG6thrHbo44/qBiMu5R4bayfy/dlWo9AXBA=",
"owner": "matrix-construct",
"repo": "complement",
"rev": "e587b3df569cba411aeac7c20b6366d03c143745",
"rev": "350d7666cab14cb0051ef53da7a1b0b3216d7269",
"type": "github"
},
"original": {
@@ -95,18 +100,12 @@
}
},
"crane": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1722960479,
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
"lastModified": 1751562746,
"narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
"rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd",
"type": "github"
},
"original": {
@@ -117,11 +116,11 @@
},
"crane_2": {
"locked": {
"lastModified": 1748970125,
"narHash": "sha256-UDyigbDGv8fvs9aS95yzFfOKkEjx1LO3PL3DsKopohA=",
"lastModified": 1763511871,
"narHash": "sha256-KKZWi+ij7oT0Ag8yC6MQkzfHGcytyjMJDD+47ZV1YNU=",
"owner": "ipetkov",
"repo": "crane",
"rev": "323b5746d89e04b22554b061522dfce9e4c49b18",
"rev": "099f9014bc8d0cd6e445470ea1df0fd691d5a548",
"type": "github"
},
"original": {
@@ -138,6 +137,7 @@
"cachix",
"flake-compat"
],
"flake-parts": "flake-parts_2",
"git-hooks": [
"cachix",
"git-hooks"
@@ -149,11 +149,11 @@
]
},
"locked": {
"lastModified": 1748273445,
"narHash": "sha256-5V0dzpNgQM0CHDsMzh+ludYeu1S+Y+IMjbaskSSdFh0=",
"lastModified": 1760560333,
"narHash": "sha256-goJQdVl9oDgCxF9CggPUw1DvB4gsot1jzMmz9px8Du8=",
"owner": "cachix",
"repo": "devenv",
"rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb",
"rev": "0a4043938f540027e562c5a0feebbe6be872c3ea",
"type": "github"
},
"original": {
@@ -170,11 +170,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1749883145,
"narHash": "sha256-RlcGw3vAnbI3cfZn8aFaovNUd7312VZh+/FDWkqdA7E=",
"lastModified": 1763707297,
"narHash": "sha256-Bd9VGavwFBLpyU4pjiWfv73gUibNj8dc3xmOW8ff3bI=",
"owner": "nix-community",
"repo": "fenix",
"rev": "a804172f150bcf81262655324e583bb0cd0f28dd",
"rev": "7c2d3a165a4a080fdcb6c191d8f9768281c99f75",
"type": "github"
},
"original": {
@@ -187,11 +187,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
@@ -219,11 +219,11 @@
"flake-compat_3": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
@@ -241,11 +241,11 @@
]
},
"locked": {
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"lastModified": 1751413152,
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
"type": "github"
},
"original": {
@@ -259,16 +259,15 @@
"nixpkgs-lib": [
"cachix",
"devenv",
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"lastModified": 1756770412,
"narHash": "sha256-+uWLQZccFHwqpGqr2Yt5VsW/PbeJVTn9Dk6SHWhNRPw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"rev": "4524271976b625a4a605beefd893f270620fd751",
"type": "github"
},
"original": {
@@ -309,11 +308,11 @@
]
},
"locked": {
"lastModified": 1747372754,
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
"lastModified": 1760392170,
"narHash": "sha256-WftxJgr2MeDDFK47fQKywzC72L2jRc/PWcyGdjaDzkw=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"rev": "46d55f0aeb1d567a78223e69729734f3dca25a85",
"type": "github"
},
"original": {
@@ -344,30 +343,14 @@
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"liburing": {
"flake": false,
"locked": {
"lastModified": 1749816983,
"narHash": "sha256-p5hXfDe53Y4MVwL2+wKZYpy4OPGvqFFnOEvkMsFAO6c=",
"lastModified": 1763758538,
"narHash": "sha256-cDsxLOqeC7imBcArolTHvejSnWoadgpvDy1DJ2/3MOw=",
"owner": "axboe",
"repo": "liburing",
"rev": "ad83d3ab64894c16eaf21ef869656a5bddb93ca4",
"rev": "e1ef1e680ee38ed9116989155fca47921698c25f",
"type": "github"
},
"original": {
@@ -381,11 +364,24 @@
"inputs": {
"flake-compat": [
"cachix",
"devenv"
"devenv",
"flake-compat"
],
"flake-parts": [
"cachix",
"devenv",
"flake-parts"
],
"git-hooks-nix": [
"cachix",
"devenv",
"git-hooks"
],
"nixpkgs": [
"cachix",
"devenv",
"nixpkgs"
],
"flake-parts": "flake-parts_2",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_3",
"nixpkgs-23-11": [
"cachix",
"devenv"
@@ -393,34 +389,30 @@
"nixpkgs-regression": [
"cachix",
"devenv"
],
"pre-commit-hooks": [
"cachix",
"devenv"
]
},
"locked": {
"lastModified": 1745930071,
"narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
"owner": "domenkozar",
"lastModified": 1758763079,
"narHash": "sha256-Bx1A+lShhOWwMuy3uDzZQvYiBKBFcKwy6G6NEohhv6A=",
"owner": "cachix",
"repo": "nix",
"rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
"rev": "6f0140527c2b0346df4afad7497baa08decb929f",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"owner": "cachix",
"ref": "devenv-2.30.5",
"repo": "nix",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"lastModified": 1757882181,
"narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
"type": "github"
},
"original": {
@@ -438,11 +430,11 @@
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"lastModified": 1737420293,
"narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9",
"type": "github"
},
"original": {
@@ -453,11 +445,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1726042813,
"narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=",
"lastModified": 1751949589,
"narHash": "sha256-mgFxAPLWw0Kq+C8P3dRrZrOYEQXOtKuYVlo9xvPntt8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "159be5db480d1df880a0135ca0bfed84c2f88353",
"rev": "9b008d60392981ad674e04016d25619281550a9d",
"type": "github"
},
"original": {
@@ -469,27 +461,27 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1724316499,
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
"lastModified": 1751741127,
"narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841",
"rev": "29e290002bfff26af1db6f64d070698019460302",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"lastModified": 1760524057,
"narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5",
"type": "github"
},
"original": {
@@ -501,43 +493,11 @@
},
"nixpkgs_3": {
"locked": {
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"lastModified": 1763618868,
"narHash": "sha256-v5afmLjn/uyD9EQuPBn7nZuaZVV9r+JerayK/4wvdWA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1748190013,
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1749871736,
"narHash": "sha256-K9yBph93OLTNw02Q6e9CYFGrUhvEXnh45vrZqIRWfvQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6afe187897bef7933475e6af374c893f4c84a293",
"rev": "a8d610af3f1a5fb71e23e08434d8d61a466fc942",
"type": "github"
},
"original": {
@@ -550,11 +510,11 @@
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1749358049,
"narHash": "sha256-ZSjvAZBfZkJrBIpw8ANZMbJVb8AeuogvuAipGVE4Qe4=",
"lastModified": 1763593074,
"narHash": "sha256-aOV/jJjRjNJ3hrRqhCsXlIz05NvEhDF/j5Q5UOQuvp8=",
"owner": "matrix-construct",
"repo": "rocksdb",
"rev": "cf7f65d0b377af019661c240f9165b3ef60640c3",
"rev": "9a3a213b55df0b11408102c899a940675c0d90e4",
"type": "github"
},
"original": {
@@ -575,18 +535,18 @@
"flake-utils": "flake-utils",
"liburing": "liburing",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_5",
"nixpkgs": "nixpkgs_3",
"rocksdb": "rocksdb"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1749829309,
"narHash": "sha256-t6x6/PKg8Shnkd3htrxf3WMgycfRLRWvN9JHAmGWf+s=",
"lastModified": 1763648203,
"narHash": "sha256-/WJdebbRD+m5vr2xy/bJdCpqd7YHSMapjuXAM/0lvtA=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "a497f4114ccf24978accb56190e60d1e1659e0c7",
"rev": "eaaa2da9fbbfd7a79ff501e0563351cb2004574a",
"type": "github"
},
"original": {
+1 -55
View File
@@ -26,7 +26,7 @@
file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml`
sha256 = "sha256-X/4ZBHO3iW0fOenQ3foEvscgAPJYl2abspaBThDOukI=";
sha256 = "sha256-+9FmLhAOezBZCOziO0Qct1NOrfpjNsXxc/8I0c7BdKE=";
};
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
@@ -204,8 +204,6 @@
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
@@ -218,8 +216,6 @@
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
};
@@ -231,8 +227,6 @@
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
};
@@ -246,8 +240,6 @@
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
};
@@ -260,12 +252,9 @@
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
};
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
oci-image = scopeHost.oci-image;
oci-image-all-features = scopeHost.oci-image.override {
@@ -278,8 +267,6 @@
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
@@ -294,18 +281,11 @@
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
};
};
oci-image-hmalloc = scopeHost.oci-image.override {
main = scopeHost.main.override {
features = ["hardened_malloc"];
};
};
book = scopeHost.book;
@@ -359,8 +339,6 @@
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
@@ -379,8 +357,6 @@
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
@@ -400,8 +376,6 @@
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
@@ -420,22 +394,12 @@
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
};
}
# An output for a statically-linked binary with hardened_malloc
{
name = "${binaryName}-hmalloc";
value = scopeCrossStatic.main.override {
features = ["hardened_malloc"];
};
}
# An output for an OCI image based on that binary
{
name = "oci-image-${crossSystem}";
@@ -478,8 +442,6 @@
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
@@ -501,8 +463,6 @@
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
@@ -523,8 +483,6 @@
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
@@ -532,16 +490,6 @@
};
}
# An output for an OCI image based on that binary with hardened_malloc
{
name = "oci-image-${crossSystem}-hmalloc";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
features = ["hardened_malloc"];
};
};
}
# An output for a complement OCI image for the specified platform
{
name = "complement-${crossSystem}";
@@ -571,8 +519,6 @@
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
];
-1
View File
@@ -21,7 +21,6 @@ media_startup_check = true
prune_missing_media = true
log_colors = true
admin_room_notices = false
allow_check_for_updates = false
intentionally_unknown_config_option_for_testing = true
rocksdb_log_level = "info"
rocksdb_max_log_files = 1
+20 -2
View File
@@ -23,8 +23,6 @@
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# tuwunel_mods is a development-only hot reload feature
"tuwunel_mods"
]
@@ -131,6 +129,7 @@ buildDepsOnlyEnv =
buildPackageEnv = {
TUWUNEL_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
TUWUNEL_DATABASE_PATH = "/var/tmp/tuwunel.db";
} // buildDepsOnlyEnv // {
# Only needed in static stdenv because these are transitive dependencies of rocksdb
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
@@ -203,8 +202,27 @@ craneLib.buildPackage ( commonAttrs // {
env = buildDepsOnlyEnv;
});
nativeCheckInputs = [
pkgsBuildHost.libredirect.hook
];
preCheck =
let
fakeResolvConf = pkgsBuildHost.writeTextFile {
name = "resolv.conf";
text = ''
nameserver 0.0.0.0
'';
};
in
''
export NIX_REDIRECTS="/etc/resolv.conf=${fakeResolvConf}"
export TUWUNEL_DATABASE_PATH="$(mktemp -d)/smoketest.db"
'';
doCheck = true;
doBenchmark = false;
cargoExtraArgs = "--no-default-features --locked "
+ lib.optionalString
(features'' != [])
+1 -2
View File
@@ -2,11 +2,9 @@
Description=Tuwunel Matrix homeserver
Wants=network-online.target
After=network-online.target
Alias=matrix-tuwunel.service
Documentation=https://tuwunel.chat/
[Service]
DynamicUser=yes
User=tuwunel
Group=tuwunel
Type=notify
@@ -63,3 +61,4 @@ StartLimitBurst=5
[Install]
WantedBy=multi-user.target
Alias=matrix-tuwunel.service
+1 -1
View File
@@ -9,7 +9,7 @@
# If you're having trouble making the relevant changes, bug a maintainer.
[toolchain]
channel = "1.86.0"
channel = "1.89.0"
profile = "minimal"
components = [
# For rust-analyzer
+10 -10
View File
@@ -11,6 +11,7 @@ version.workspace = true
[lib]
path = "mod.rs"
bench = false
crate-type = [
"rlib",
# "dylib",
@@ -18,46 +19,46 @@ crate-type = [
[features]
brotli_compression = [
"tuwunel-api/brotli_compression",
"tuwunel-core/brotli_compression",
"tuwunel-service/brotli_compression",
]
bzip2_compression = [
"tuwunel-database/bzip2_compression",
"tuwunel-service/bzip2_compression",
]
gzip_compression = [
"tuwunel-api/gzip_compression",
"tuwunel-core/gzip_compression",
"tuwunel-service/gzip_compression",
]
io_uring = [
"tuwunel-api/io_uring",
"tuwunel-database/io_uring",
"tuwunel-service/io_uring",
]
jemalloc = [
"tuwunel-api/jemalloc",
"tuwunel-core/jemalloc",
"tuwunel-database/jemalloc",
"tuwunel-service/jemalloc",
]
jemalloc_conf = [
"tuwunel-api/jemalloc_conf",
"tuwunel-core/jemalloc_conf",
"tuwunel-database/jemalloc_conf",
"tuwunel-service/jemalloc_conf",
]
jemalloc_prof = [
"tuwunel-api/jemalloc_prof",
"tuwunel-core/jemalloc_prof",
"tuwunel-database/jemalloc_prof",
"tuwunel-service/jemalloc_prof",
]
jemalloc_stats = [
"tuwunel-api/jemalloc_stats",
"tuwunel-core/jemalloc_stats",
"tuwunel-database/jemalloc_stats",
"tuwunel-service/jemalloc_stats",
]
lz4_compression = [
"tuwunel-database/lz4_compression",
"tuwunel-service/lz4_compression",
]
release_max_log_level = [
"tuwunel-api/release_max_log_level",
"tuwunel-core/release_max_log_level",
"tuwunel-database/release_max_log_level",
"tuwunel-service/release_max_log_level",
@@ -67,15 +68,14 @@ release_max_log_level = [
"log/release_max_level_info",
]
zstd_compression = [
"tuwunel-api/zstd_compression",
"tuwunel-core/zstd_compression",
"tuwunel-database/zstd_compression",
"tuwunel-service/zstd_compression",
]
[dependencies]
base64.workspace = true
clap.workspace = true
tuwunel-api.workspace = true
tuwunel-core.workspace = true
tuwunel-database.workspace = true
tuwunel-macros.workspace = true
+57 -79
View File
@@ -28,10 +28,7 @@
},
warn,
};
use tuwunel_service::rooms::{
short::{ShortEventId, ShortRoomId},
state_compressor::HashSetCompressStateEvent,
};
use tuwunel_service::rooms::{short::ShortRoomId, state_compressor::HashSetCompressStateEvent};
use crate::admin_command;
@@ -45,7 +42,6 @@ pub(super) async fn echo(&self, message: Vec<String>) -> Result {
pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
let Ok(event) = self
.services
.rooms
.timeline
.get_pdu_json(&event_id)
.await
@@ -64,7 +60,6 @@ pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
let start = Instant::now();
let count = self
.services
.rooms
.auth_chain
.event_ids_iter(room_id, once(event_id.as_ref()))
.ready_filter_map(Result::ok)
@@ -87,9 +82,12 @@ pub(super) async fn parse_pdu(&self) -> Result {
}
let string = self.body[1..self.body.len().saturating_sub(1)].join("\n");
let rules = RoomVersionId::V6
.rules()
.expect("rules for V6 rooms");
match serde_json::from_str(&string) {
| Err(e) => return Err!("Invalid json in command body: {e}"),
| Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
| Ok(value) => match ruma::signatures::reference_hash(&value, &rules) {
| Err(e) => return Err!("Could not parse PDU JSON: {e:?}"),
| Ok(hash) => {
let event_id = OwnedEventId::parse(format!("${hash}"));
@@ -108,7 +106,6 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
let mut outlier = false;
let mut pdu_json = self
.services
.rooms
.timeline
.get_non_outlier_pdu_json(&event_id)
.await;
@@ -117,7 +114,6 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
outlier = true;
pdu_json = self
.services
.rooms
.timeline
.get_pdu_json(&event_id)
.await;
@@ -139,20 +135,11 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
}
#[admin_command]
pub(super) async fn get_short_pdu(
&self,
shortroomid: ShortRoomId,
shorteventid: ShortEventId,
) -> Result {
let pdu_id: RawPduId = PduId {
shortroomid,
shorteventid: shorteventid.into(),
}
.into();
pub(super) async fn get_short_pdu(&self, shortroomid: ShortRoomId, count: i64) -> Result {
let pdu_id: RawPduId = PduId { shortroomid, count: count.into() }.into();
let pdu_json = self
.services
.rooms
.timeline
.get_pdu_json_from_id(&pdu_id)
.await;
@@ -249,10 +236,9 @@ pub(super) async fn get_remote_pdu(
match self
.services
.sending
.send_federation_request(&server, ruma::api::federation::event::get_event::v1::Request {
.federation
.execute(&server, ruma::api::federation::event::get_event::v1::Request {
event_id: event_id.clone(),
include_unredacted_content: None,
})
.await
{
@@ -273,32 +259,28 @@ pub(super) async fn get_remote_pdu(
})?;
trace!("Attempting to parse PDU: {:?}", &response.pdu);
let _parsed_pdu = {
let (room_id, ..) = {
let parsed_result = self
.services
.rooms
.event_handler
.parse_incoming_pdu(&response.pdu)
.boxed()
.await;
let (event_id, value, room_id) = match parsed_result {
match parsed_result {
| Ok(t) => t,
| Err(e) => {
warn!("Failed to parse PDU: {e}");
info!("Full PDU: {:?}", &response.pdu);
return Err!("Failed to parse PDU remote server {server} sent us: {e}");
},
};
vec![(event_id, value, room_id)]
}
};
info!("Attempting to handle event ID {event_id} as backfilled PDU");
self.services
.rooms
.timeline
.backfill_pdu(&server, response.pdu)
.backfill_pdu(&room_id, &server, response.pdu)
.await?;
let text = serde_json::to_string_pretty(&json)?;
@@ -311,10 +293,9 @@ pub(super) async fn get_remote_pdu(
#[admin_command]
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.rooms.alias.resolve(&room).await?;
let room_id = self.services.alias.maybe_resolve(&room).await?;
let room_state: Vec<Raw<AnyStateEvent>> = self
.services
.rooms
.state_accessor
.room_state_full_pdus(&room_id)
.map_ok(Event::into_format)
@@ -346,11 +327,8 @@ pub(super) async fn ping(&self, server: OwnedServerName) -> Result {
match self
.services
.sending
.send_federation_request(
&server,
ruma::api::federation::discovery::get_server_version::v1::Request {},
)
.federation
.execute(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
.await
{
| Err(e) => {
@@ -492,7 +470,6 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
let mut event = self
.services
.rooms
.timeline
.get_pdu_json(&event_id)
.await?;
@@ -517,7 +494,6 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
if !self
.services
.rooms
.state_cache
.server_in_room(&self.services.server.name, &room_id)
.await
@@ -527,7 +503,6 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
let first_pdu = self
.services
.rooms
.timeline
.first_pdu_in_room(&room_id)
.await
@@ -542,7 +517,6 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
if !self
.services
.rooms
.state_cache
.server_in_room(&self.services.server.name, &room_id)
.await
@@ -552,7 +526,6 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
let latest_pdu = self
.services
.rooms
.timeline
.latest_pdu_in_room(&room_id)
.await
@@ -571,7 +544,6 @@ pub(super) async fn force_set_room_state_from_server(
) -> Result {
if !self
.services
.rooms
.state_cache
.server_in_room(&self.services.server.name, &room_id)
.await
@@ -581,7 +553,6 @@ pub(super) async fn force_set_room_state_from_server(
let first_pdu = self
.services
.rooms
.timeline
.latest_pdu_in_room(&room_id)
.await
@@ -589,7 +560,6 @@ pub(super) async fn force_set_room_state_from_server(
let room_version = self
.services
.rooms
.state
.get_room_version(&room_id)
.await?;
@@ -598,8 +568,8 @@ pub(super) async fn force_set_room_state_from_server(
let remote_state_response = self
.services
.sending
.send_federation_request(&server_name, get_room_state::v1::Request {
.federation
.execute(&server_name, get_room_state::v1::Request {
room_id: room_id.clone(),
event_id: first_pdu.event_id().to_owned(),
})
@@ -608,7 +578,6 @@ pub(super) async fn force_set_room_state_from_server(
for pdu in remote_state_response.pdus.clone() {
match self
.services
.rooms
.event_handler
.parse_incoming_pdu(&pdu)
.await
@@ -627,24 +596,33 @@ pub(super) async fn force_set_room_state_from_server(
.server_keys
.validate_and_add_event_id(pdu, &room_version)
}) {
let Ok((event_id, value)) = result.await else {
let Ok((event_id, mut value)) = result.await else {
continue;
};
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
let invalid_pdu_err = |e| {
debug_error!("Invalid PDU in fetching remote room state PDUs response: {value:#?}");
err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}")))
})?;
};
let pdu = if value["type"] == "m.room.create" {
PduEvent::from_rid_val(&room_id, &event_id, value.clone()).map_err(invalid_pdu_err)?
} else {
PduEvent::from_id_val(&event_id, value.clone()).map_err(invalid_pdu_err)?
};
if !value.contains_key("room_id") {
let room_id = CanonicalJsonValue::String(room_id.as_str().into());
value.insert("room_id".into(), room_id);
}
self.services
.rooms
.outlier
.timeline
.add_pdu_outlier(&event_id, &value);
if let Some(state_key) = &pdu.state_key {
let shortstatekey = self
.services
.rooms
.short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
.await;
@@ -667,14 +645,12 @@ pub(super) async fn force_set_room_state_from_server(
};
self.services
.rooms
.outlier
.timeline
.add_pdu_outlier(&event_id, &value);
}
let new_room_state = self
.services
.rooms
.event_handler
.resolve_state(&room_id, &room_version, state)
.await?;
@@ -686,21 +662,13 @@ pub(super) async fn force_set_room_state_from_server(
removed,
} = self
.services
.rooms
.state_compressor
.save_state(room_id.clone().as_ref(), new_room_state)
.await?;
let state_lock = self
.services
.rooms
.state
.mutex
.lock(&*room_id)
.await;
let state_lock = self.services.state.mutex.lock(&*room_id).await;
self.services
.rooms
.state
.force_state(room_id.clone().as_ref(), short_state_hash, added, removed, &state_lock)
.await?;
@@ -710,7 +678,6 @@ pub(super) async fn force_set_room_state_from_server(
the room's m.room.member state"
);
self.services
.rooms
.state_cache
.update_joined_count(&room_id)
.await;
@@ -937,7 +904,7 @@ pub(super) async fn database_files(&self, map: Option<String>, level: Option<i32
let mut files: Vec<_> = self
.services
.db
.db
.engine
.file_list()
.collect::<Result<_>>()?;
@@ -993,10 +960,10 @@ pub(super) async fn create_jwt(
#[derive(Serialize)]
struct Claim {
sub: String,
iss: String,
aud: String,
exp: usize,
nbf: usize,
iss: Option<String>,
aud: Option<String>,
exp: Option<usize>,
nbf: Option<usize>,
}
let config = &self.services.config.jwt;
@@ -1013,21 +980,19 @@ struct Claim {
let claim = Claim {
sub: user,
iss: issuer.unwrap_or_default(),
iss: issuer,
aud: audience.unwrap_or_default(),
aud: audience,
exp: exp_from_now
.and_then(|val| now_secs().checked_add(val))
.map(TryInto::try_into)
.and_then(Result::ok)
.unwrap_or(usize::MAX),
.and_then(Result::ok),
nbf: nbf_from_now
.and_then(|val| now_secs().checked_add(val))
.map(TryInto::try_into)
.and_then(Result::ok)
.unwrap_or(0),
.and_then(Result::ok),
};
encode(&header, &claim, &key)
@@ -1035,3 +1000,16 @@ struct Claim {
.map(async |token| self.write_str(&token).await)?
.await
}
#[admin_command]
pub(super) async fn resync_database(&self) -> Result {
if !self.services.db.is_secondary() {
return Err!("Not a secondary instance.");
}
self.services
.db
.engine
.update()
.map_err(|e| err!("Failed to update from primary: {e:?}"))
}
+8 -14
View File
@@ -4,7 +4,7 @@
use clap::Subcommand;
use ruma::{OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName};
use tuwunel_core::Result;
use tuwunel_service::rooms::short::{ShortEventId, ShortRoomId};
use tuwunel_service::rooms::short::ShortRoomId;
use self::tester::TesterCommand;
use crate::admin_command_dispatch;
@@ -43,8 +43,8 @@ pub(super) enum DebugCommand {
/// Shortroomid integer
shortroomid: ShortRoomId,
/// Shorteventid integer
shorteventid: ShortEventId,
/// PduCount integer
count: i64,
},
/// - Attempts to retrieve a PDU from a remote server. Inserts it into our
@@ -72,15 +72,6 @@ pub(super) enum DebugCommand {
},
/// - Gets all the room state events for the specified room.
///
/// This is functionally equivalent to `GET
/// /_matrix/client/v3/rooms/{roomid}/state`, except the admin command does
/// *not* check if the sender user is allowed to see state events. This is
/// done because it's implied that server admins here have database access
/// and can see/get room info themselves anyways if they were malicious
/// admins.
///
/// Of course the check is still done on the actual client API.
GetRoomState {
/// Room ID
room_id: OwnedRoomOrAliasId,
@@ -125,13 +116,13 @@ pub(super) enum DebugCommand {
reset: bool,
},
/// - Verify json signatures
/// - Sign JSON blob
///
/// This command needs a JSON blob provided in a Markdown code block below
/// the command.
SignJson,
/// - Verify json signatures
/// - Verify JSON signatures
///
/// This command needs a JSON blob provided in a Markdown code block below
/// the command.
@@ -256,6 +247,9 @@ pub(super) enum DebugCommand {
audience: Option<String>,
},
/// - Synchronize database with primary (secondary only)
ResyncDatabase,
/// - Developer test stubs
#[command(subcommand)]
#[allow(non_snake_case)]
+3 -30
View File
@@ -1,5 +1,3 @@
use std::fmt::Write;
use futures::StreamExt;
use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
use tuwunel_core::{Err, Result};
@@ -8,43 +6,19 @@
#[admin_command]
pub(super) async fn disable_room(&self, room_id: OwnedRoomId) -> Result {
self.services
.rooms
.metadata
.disable_room(&room_id, true);
self.services.metadata.disable_room(&room_id);
self.write_str("Room disabled.").await
}
#[admin_command]
pub(super) async fn enable_room(&self, room_id: OwnedRoomId) -> Result {
self.services
.rooms
.metadata
.disable_room(&room_id, false);
self.services.metadata.enable_room(&room_id);
self.write_str("Room enabled.").await
}
#[admin_command]
pub(super) async fn incoming_federation(&self) -> Result {
let msg = {
let map = self
.services
.rooms
.event_handler
.federation_handletime
.read()
.expect("locked");
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
for (r, (e, i)) in map.iter() {
let elapsed = i.elapsed();
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
}
msg
};
self.write_str(&msg).await
Err!("This command is temporarily disabled")
}
#[admin_command]
@@ -102,7 +76,6 @@ pub(super) async fn remote_user_in_rooms(&self, user_id: OwnedUserId) -> Result
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.then(|room_id| get_room_info(self.services, room_id))
-1
View File
@@ -37,7 +37,6 @@ pub(super) async fn delete(
// parsing the PDU for any MXC URLs begins here
match self
.services
.rooms
.timeline
.get_pdu_json(&event_id)
.await
-1
View File
@@ -1,4 +1,3 @@
#![recursion_limit = "192"]
#![allow(clippy::wildcard_imports)]
#![allow(clippy::enum_glob_use)]
#![allow(clippy::too_many_arguments)]
+5 -3
View File
@@ -2,6 +2,7 @@
use futures::StreamExt;
use ruma::{OwnedRoomId, OwnedUserId};
use tuwunel_core::Result;
use tuwunel_database::Deserialized;
use crate::{admin_command, admin_command_dispatch};
@@ -46,7 +47,7 @@ async fn changes_since(
.await;
let query_time = timer.elapsed();
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"))
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{results:?}\n```"))
.await
}
@@ -58,11 +59,12 @@ async fn account_data_get(
room_id: Option<OwnedRoomId>,
) -> Result {
let timer = tokio::time::Instant::now();
let results = self
let results: serde_json::Value = self
.services
.account_data
.get_raw(room_id.as_deref(), &user_id, &kind)
.await;
.await
.deserialized()?;
let query_time = timer.elapsed();
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"))
+1 -1
View File
@@ -32,7 +32,7 @@ pub(super) async fn process(subcommand: GlobalsCommand, context: &Context<'_>) -
},
| GlobalsCommand::CurrentCount => {
let timer = tokio::time::Instant::now();
let results = services.globals.db.current_count();
let results = services.globals.current_count();
let query_time = timer.elapsed();
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
+6 -1
View File
@@ -10,6 +10,7 @@
mod room_timeline;
mod sending;
mod short;
mod sync;
mod users;
use clap::Subcommand;
@@ -20,7 +21,7 @@
presence::PresenceCommand, pusher::PusherCommand, raw::RawCommand, resolver::ResolverCommand,
room_alias::RoomAliasCommand, room_state_cache::RoomStateCacheCommand,
room_timeline::RoomTimelineCommand, sending::SendingCommand, short::ShortCommand,
users::UsersCommand,
sync::SyncCommand, users::UsersCommand,
};
use crate::admin_command_dispatch;
@@ -76,6 +77,10 @@ pub(super) enum QueryCommand {
#[command(subcommand)]
Short(ShortCommand),
/// - sync service
#[command(subcommand)]
Sync(SyncCommand),
/// - raw service
#[command(subcommand)]
Raw(RawCommand),
+5 -2
View File
@@ -19,6 +19,9 @@ pub(crate) enum PresenceCommand {
PresenceSince {
/// UNIX timestamp since (u64)
since: u64,
/// Upper-bound of since
to: Option<u64>,
},
}
@@ -34,11 +37,11 @@ pub(super) async fn process(subcommand: PresenceCommand, context: &Context<'_>)
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| PresenceCommand::PresenceSince { since } => {
| PresenceCommand::PresenceSince { since, to } => {
let timer = tokio::time::Instant::now();
let results: Vec<(_, _, _)> = services
.presence
.presence_since(since)
.presence_since(since, to)
.map(|(user_id, count, bytes)| (user_id.to_owned(), count, bytes.to_vec()))
.collect()
.await;
+67 -24
View File
@@ -1,5 +1,6 @@
use std::{borrow::Cow, collections::BTreeMap, ops::Deref, sync::Arc};
use base64::prelude::*;
use clap::Subcommand;
use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
use tokio::time::Instant;
@@ -15,34 +16,29 @@
use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch]
#[admin_command_dispatch(handler_prefix = "raw")]
#[derive(Debug, Subcommand)]
#[allow(clippy::enum_variant_names)]
/// Query tables from database
pub(crate) enum RawCommand {
/// - List database maps
RawMaps,
Maps,
/// - Raw database query
RawGet {
Get {
/// Map name
map: String,
/// Key
key: String,
},
/// - Raw database delete (for string keys)
RawDel {
/// Map name
map: String,
/// Key
key: String,
/// Encode as base64
#[arg(long, short)]
base64: bool,
},
/// - Raw database keys iteration
RawKeys {
Keys {
/// Map name
map: String,
@@ -51,7 +47,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database key size breakdown
RawKeysSizes {
KeysSizes {
/// Map name
map: Option<String>,
@@ -60,7 +56,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database keys total bytes
RawKeysTotal {
KeysTotal {
/// Map name
map: Option<String>,
@@ -69,7 +65,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database values size breakdown
RawValsSizes {
ValsSizes {
/// Map name
map: Option<String>,
@@ -78,7 +74,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database values total bytes
RawValsTotal {
ValsTotal {
/// Map name
map: Option<String>,
@@ -87,7 +83,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database items iteration
RawIter {
Iter {
/// Map name
map: String,
@@ -96,7 +92,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database keys iteration
RawKeysFrom {
KeysFrom {
/// Map name
map: String,
@@ -109,7 +105,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database items iteration
RawIterFrom {
IterFrom {
/// Map name
map: String,
@@ -122,7 +118,7 @@ pub(crate) enum RawCommand {
},
/// - Raw database record count
RawCount {
Count {
/// Map name
map: Option<String>,
@@ -130,7 +126,26 @@ pub(crate) enum RawCommand {
prefix: Option<String>,
},
/// - Compact database
/// - Raw database delete (for string keys) DANGER!!!
Del {
/// Map name
map: String,
/// Key
key: String,
},
/// - Clear database table DANGER!!!
Clear {
/// Map name
map: String,
/// Confirm
#[arg(long)]
confirm: bool,
},
/// - Compact database DANGER!!!
Compact {
#[arg(short, long, alias("column"))]
map: Option<Vec<String>>,
@@ -160,7 +175,7 @@ pub(crate) enum RawCommand {
}
#[admin_command]
pub(super) async fn compact(
pub(super) async fn raw_compact(
&self,
map: Option<Vec<String>>,
start: Option<String>,
@@ -421,13 +436,41 @@ pub(super) async fn raw_del(&self, map: String, key: String) -> Result {
}
#[admin_command]
pub(super) async fn raw_get(&self, map: String, key: String) -> Result {
pub(super) async fn raw_clear(&self, map: String, confirm: bool) -> Result {
let map = self.services.db.get(&map)?;
if !confirm {
return Err!("Are you really sure you want to clear all data? Add the --confirm option.");
}
let timer = Instant::now();
let cork = self.services.db.cork();
map.raw_keys()
.ignore_err()
.ready_for_each(|key| map.remove(&key))
.boxed()
.await;
drop(cork);
let query_time = timer.elapsed();
self.write_str(&format!("Operation completed in {query_time:?}"))
.await
}
#[admin_command]
pub(super) async fn raw_get(&self, map: String, key: String, base64: bool) -> Result {
let map = self.services.db.get(&map)?;
let timer = Instant::now();
let handle = map.get(&key).await?;
let query_time = timer.elapsed();
let result = String::from_utf8_lossy(&handle);
let result = if base64 {
BASE64_STANDARD.encode(&handle)
} else {
String::from_utf8_lossy(&handle).to_string()
};
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{result:?}\n```"))
.await
}
+15 -7
View File
@@ -8,6 +8,13 @@
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
pub(crate) enum RoomAliasCommand {
/// - Resolve any local or remote alias.
ResolveAlias {
/// Full room alias
alias: OwnedRoomAliasId,
},
/// - Resolve an alias on this server.
ResolveLocalAlias {
/// Full room alias
alias: OwnedRoomAliasId,
@@ -28,13 +35,16 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
let services = context.services;
match subcommand {
| RoomAliasCommand::ResolveAlias { alias } => {
let timer = tokio::time::Instant::now();
let results = services.alias.resolve_alias(&alias).await;
let query_time = timer.elapsed();
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| RoomAliasCommand::ResolveLocalAlias { alias } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.alias
.resolve_local_alias(&alias)
.await;
let results = services.alias.resolve_local_alias(&alias).await;
let query_time = timer.elapsed();
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
@@ -42,7 +52,6 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
| RoomAliasCommand::LocalAliasesForRoom { room_id } => {
let timer = tokio::time::Instant::now();
let aliases: Vec<_> = services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(ToOwned::to_owned)
@@ -55,7 +64,6 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Context<'_>)
| RoomAliasCommand::AllLocalAliases => {
let timer = tokio::time::Instant::now();
let aliases = services
.rooms
.alias
.all_local_aliases()
.map(|(room_id, alias)| (room_id.to_owned(), alias.to_owned()))
+22 -18
View File
@@ -74,6 +74,10 @@ pub(crate) enum RoomStateCacheCommand {
user_id: OwnedUserId,
room_id: OwnedRoomId,
},
UserMemberships {
user_id: OwnedUserId,
},
}
pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context<'_>) -> Result {
@@ -83,7 +87,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::ServerInRoom { server, room_id } => {
let timer = tokio::time::Instant::now();
let result = services
.rooms
.state_cache
.server_in_room(&server, &room_id)
.await;
@@ -98,7 +101,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomServers { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.room_servers(&room_id)
.map(ToOwned::to_owned)
@@ -115,7 +117,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::ServerRooms { server } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.server_rooms(&server)
.map(ToOwned::to_owned)
@@ -132,7 +133,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomMembers { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
@@ -149,7 +149,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::LocalUsersInRoom { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.local_users_in_room(&room_id)
.map(ToOwned::to_owned)
@@ -166,7 +165,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::ActiveLocalUsersInRoom { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.active_local_users_in_room(&room_id)
.map(ToOwned::to_owned)
@@ -183,7 +181,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomJoinedCount { room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.room_joined_count(&room_id)
.await;
@@ -198,7 +195,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomInvitedCount { room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.room_invited_count(&room_id)
.await;
@@ -213,7 +209,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomUserOnceJoined { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.room_useroncejoined(&room_id)
.map(ToOwned::to_owned)
@@ -230,7 +225,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomMembersInvited { room_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.room_members_invited(&room_id)
.map(ToOwned::to_owned)
@@ -247,7 +241,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::GetInviteCount { room_id, user_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.get_invite_count(&room_id, &user_id)
.await;
@@ -262,7 +255,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::GetLeftCount { room_id, user_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.get_left_count(&room_id, &user_id)
.await;
@@ -277,7 +269,6 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomsJoined { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.rooms_joined(&user_id)
.map(ToOwned::to_owned)
@@ -294,9 +285,8 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomsInvited { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.rooms_invited(&user_id)
.rooms_invited_state(&user_id)
.collect()
.await;
let query_time = timer.elapsed();
@@ -310,9 +300,8 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::RoomsLeft { user_id } => {
let timer = tokio::time::Instant::now();
let results: Vec<_> = services
.rooms
.state_cache
.rooms_left(&user_id)
.rooms_left_state(&user_id)
.collect()
.await;
let query_time = timer.elapsed();
@@ -326,12 +315,27 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
| RoomStateCacheCommand::InviteState { user_id, room_id } => {
let timer = tokio::time::Instant::now();
let results = services
.rooms
.state_cache
.invite_state(&user_id, &room_id)
.await;
let query_time = timer.elapsed();
context
.write_str(&format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))
.await
},
| RoomStateCacheCommand::UserMemberships { user_id } => {
let timer = tokio::time::Instant::now();
let results = services
.state_cache
.all_user_memberships(&user_id)
.map(|(membership, room_id)| (membership, room_id.to_owned()))
.collect::<Vec<_>>()
.await;
let query_time = timer.elapsed();
context
.write_str(&format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
+3 -7
View File
@@ -27,16 +27,14 @@ pub(crate) enum RoomTimelineCommand {
pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result {
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.maybe_resolve(&room_id)
.await?;
let result = self
.services
.rooms
.timeline
.last_timeline_count(None, &room_id)
.last_timeline_count(None, &room_id, None)
.await?;
self.write_str(&format!("{result:#?}")).await
@@ -51,16 +49,14 @@ pub(super) async fn pdus(
) -> Result {
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.maybe_resolve(&room_id)
.await?;
let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?;
let result: Vec<_> = self
.services
.rooms
.timeline
.pdus_rev(None, &room_id, from)
.try_take(limit.unwrap_or(3))
+1 -4
View File
@@ -21,7 +21,6 @@ pub(crate) enum ShortCommand {
pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
let shortid = self
.services
.rooms
.short
.get_shorteventid(&event_id)
.await?;
@@ -33,14 +32,12 @@ pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
pub(super) async fn short_room_id(&self, room_id: OwnedRoomOrAliasId) -> Result {
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.maybe_resolve(&room_id)
.await?;
let shortid = self
.services
.rooms
.short
.get_shortroomid(&room_id)
.await?;
+82
View File
@@ -0,0 +1,82 @@
use clap::Subcommand;
use ruma::{OwnedDeviceId, OwnedUserId};
use tuwunel_core::Result;
use tuwunel_service::sync::into_connection_key;
use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
/// Query sync service state
pub(crate) enum SyncCommand {
/// List sliding-sync connections.
ListConnections,
/// Show details of sliding sync connection by ID.
ShowConnection {
user_id: OwnedUserId,
device_id: OwnedDeviceId,
conn_id: Option<String>,
},
/// Drop connections for a user, device, or all.
DropConnections {
user_id: Option<OwnedUserId>,
device_id: Option<OwnedDeviceId>,
conn_id: Option<String>,
},
}
#[admin_command]
pub(super) async fn list_connections(&self) -> Result {
let connections = self.services.sync.list_loaded_connections().await;
for connection_key in connections {
self.write_str(&format!("{connection_key:?}\n"))
.await?;
}
Ok(())
}
#[admin_command]
pub(super) async fn show_connection(
&self,
user_id: OwnedUserId,
device_id: OwnedDeviceId,
conn_id: Option<String>,
) -> Result {
let key = into_connection_key(user_id, device_id, conn_id);
let cache = self
.services
.sync
.get_loaded_connection(&key)
.await?;
let out;
{
let cached = cache.lock().await;
out = format!("{cached:#?}");
};
self.write_str(out.as_str()).await
}
#[admin_command]
pub(super) async fn drop_connections(
&self,
user_id: Option<OwnedUserId>,
device_id: Option<OwnedDeviceId>,
conn_id: Option<String>,
) -> Result {
self.services
.sync
.clear_connections(
user_id.as_deref(),
device_id.as_deref(),
conn_id.map(Into::into).as_ref(),
)
.await;
Ok(())
}
-1
View File
@@ -134,7 +134,6 @@ async fn get_shared_rooms(&self, user_a: OwnedUserId, user_b: OwnedUserId) -> Re
let timer = tokio::time::Instant::now();
let result: Vec<_> = self
.services
.rooms
.state_cache
.get_shared_rooms(&user_a, &user_b)
.map(ToOwned::to_owned)
+8 -16
View File
@@ -63,17 +63,15 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
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,
) {
match services
.alias
.set_alias(&room_alias, &room_id, server_user)
{
| Err(err) => Err!("Failed to remove alias: {err}"),
| Ok(()) =>
context
@@ -88,11 +86,10 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
overwrite"
),
| (_, Err(_)) => {
match services.rooms.alias.set_alias(
&room_alias,
&room_id,
server_user,
) {
match services
.alias
.set_alias(&room_alias, &room_id, server_user)
{
| Err(err) => Err!("Failed to remove alias: {err}"),
| Ok(()) => context.write_str("Successfully set alias").await,
}
@@ -101,14 +98,12 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
},
| RoomAliasCommand::Remove { .. } => {
match services
.rooms
.alias
.resolve_local_alias(&room_alias)
.await
{
| Err(_) => Err!("Alias isn't in use."),
| Ok(id) => match services
.rooms
.alias
.remove_alias(&room_alias, server_user)
.await
@@ -123,7 +118,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
},
| RoomAliasCommand::Which { .. } => {
match services
.rooms
.alias
.resolve_local_alias(&room_alias)
.await
@@ -141,7 +135,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
| RoomAliasCommand::List { room_id } =>
if let Some(room_id) = room_id {
let aliases: Vec<OwnedRoomAliasId> = services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(Into::into)
@@ -160,7 +153,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Context<'_>) ->
context.write_str(&plain).await
} else {
let aliases = services
.rooms
.alias
.all_local_aliases()
.map(|(room_id, localpart)| (room_id.into(), localpart.into()))
+24 -23
View File
@@ -16,27 +16,14 @@ pub(super) async fn list_rooms(
let page = page.unwrap_or(1);
let mut rooms = self
.services
.rooms
.metadata
.iter_ids()
.filter_map(|room_id| async move {
(!exclude_disabled
|| !self
.services
.rooms
.metadata
.is_disabled(room_id)
.await)
.filter_map(async |room_id| {
(!exclude_disabled || !self.services.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)
.filter_map(async |room_id| {
(!exclude_banned || !self.services.metadata.is_banned(room_id).await)
.then_some(room_id)
})
.then(|room_id| get_room_info(self.services, room_id))
@@ -74,12 +61,26 @@ pub(super) async fn list_rooms(
#[admin_command]
pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result {
let result = self
.services
.rooms
.metadata
.exists(&room_id)
.await;
let result = self.services.metadata.exists(&room_id).await;
self.write_str(&format!("{result}")).await
}
#[admin_command]
pub(super) async fn delete_room(&self, room_id: OwnedRoomId, force: bool) -> Result {
if self.services.admin.is_admin_room(&room_id).await {
return Err!("Cannot delete admin room");
}
let state_lock = self.services.state.mutex.lock(&room_id).await;
self.services
.delete
.delete_room(&room_id, force, state_lock)
.await?;
self.write_str("Successfully deleted the room from our database.")
.await?;
Ok(())
}
+8 -5
View File
@@ -1,7 +1,7 @@
use clap::Subcommand;
use futures::StreamExt;
use ruma::OwnedRoomId;
use tuwunel_core::{Err, Result};
use tuwunel_core::Result;
use crate::{Context, PAGE_SIZE, get_room_info};
@@ -29,18 +29,17 @@ pub(super) async fn process(command: RoomDirectoryCommand, context: &Context<'_>
let services = context.services;
match command {
| RoomDirectoryCommand::Publish { room_id } => {
services.rooms.directory.set_public(&room_id);
services.directory.set_public(&room_id);
context.write_str("Room published").await
},
| RoomDirectoryCommand::Unpublish { room_id } => {
services.rooms.directory.set_not_public(&room_id);
services.directory.set_not_public(&room_id);
context.write_str("Room unpublished").await
},
| 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
.rooms
.directory
.public_rooms()
.then(|room_id| get_room_info(services, room_id))
@@ -57,7 +56,11 @@ pub(super) async fn process(command: RoomDirectoryCommand, context: &Context<'_>
.collect();
if rooms.is_empty() {
return Err!("No more rooms.");
context
.write_str("No rooms are published.")
.await?;
return Ok(());
}
let body = rooms
+1 -4
View File
@@ -30,7 +30,6 @@ pub(crate) enum RoomInfoCommand {
async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> Result {
let room_name = self
.services
.rooms
.state_accessor
.get_name(&room_id)
.await
@@ -38,7 +37,6 @@ async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> R
let member_info: Vec<_> = self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_filter(|user_id| {
@@ -47,7 +45,7 @@ async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> R
.unwrap_or(true)
})
.map(ToOwned::to_owned)
.filter_map(|user_id| async move {
.filter_map(async |user_id| {
Some((
self.services
.users
@@ -75,7 +73,6 @@ async fn list_joined_members(&self, room_id: OwnedRoomId, local_only: bool) -> R
async fn view_room_topic(&self, room_id: OwnedRoomId) -> Result {
let Ok(room_topic) = self
.services
.rooms
.state_accessor
.get_room_topic(&room_id)
.await
+8
View File
@@ -56,4 +56,12 @@ pub(super) enum RoomCommand {
Exists {
room_id: OwnedRoomId,
},
/// - Delete room
DeleteRoom {
room_id: OwnedRoomId,
#[arg(short, long)]
force: bool,
},
}
+33 -65
View File
@@ -1,7 +1,6 @@
use clap::Subcommand;
use futures::{FutureExt, StreamExt};
use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId};
use tuwunel_api::client::leave_room;
use tuwunel_core::{
Err, Result, debug,
utils::{IterStream, ReadyExt},
@@ -49,7 +48,7 @@ pub(crate) enum RoomModerationCommand {
async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
debug!("Got room alias or ID: {}", room);
let admin_room_alias = &self.services.globals.admin_alias;
let admin_room_alias = &self.services.admin.admin_alias;
if let Ok(admin_room_id) = self.services.admin.get_admin_room().await {
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
@@ -70,10 +69,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
};
debug!("Room specified is a room ID, banning room ID");
self.services
.rooms
.metadata
.ban_room(room_id, true);
self.services.metadata.ban_room(room_id);
room_id.to_owned()
} else if room.is_room_alias_id() {
@@ -95,7 +91,6 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = match self
.services
.rooms
.alias
.resolve_local_alias(room_alias)
.await
@@ -109,9 +104,8 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.resolve_alias(room_alias)
.await
{
| Ok((room_id, servers)) => {
@@ -131,10 +125,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
},
};
self.services
.rooms
.metadata
.ban_room(&room_id, true);
self.services.metadata.ban_room(&room_id);
room_id
} else {
@@ -148,7 +139,6 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
debug!("Making all users leave the room {room_id} and forgetting it");
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
@@ -161,27 +151,31 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
evicting admins too)",
);
if let Err(e) = leave_room(self.services, user_id, &room_id, None)
let state_lock = self.services.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.membership
.leave(user_id, &room_id, None, false, &state_lock)
.boxed()
.await
{
warn!("Failed to leave room: {e}");
}
drop(state_lock);
self.services
.rooms
.state_cache
.forget(&room_id, user_id);
}
self.services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(ToOwned::to_owned)
.for_each(|local_alias| async move {
.for_each(async |local_alias| {
self.services
.rooms
.alias
.remove_alias(&local_alias, &self.services.globals.server_user)
.await
@@ -190,15 +184,9 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
.await;
// unpublish from room directory
self.services
.rooms
.directory
.set_not_public(&room_id);
self.services.directory.set_not_public(&room_id);
self.services
.rooms
.metadata
.disable_room(&room_id, true);
self.services.metadata.disable_room(&room_id);
self.write_str(
"Room banned, removed all our local users, and disabled incoming federation with room.",
@@ -221,7 +209,7 @@ async fn ban_list_of_rooms(&self) -> Result {
.drain(1..self.body.len().saturating_sub(1))
.collect::<Vec<_>>();
let admin_room_alias = &self.services.globals.admin_alias;
let admin_room_alias = &self.services.admin.admin_alias;
let mut room_ban_count: usize = 0;
let mut room_ids: Vec<OwnedRoomId> = Vec::new();
@@ -258,7 +246,6 @@ async fn ban_list_of_rooms(&self) -> Result {
| Ok(room_alias) => {
let room_id = match self
.services
.rooms
.alias
.resolve_local_alias(room_alias)
.await
@@ -272,9 +259,8 @@ async fn ban_list_of_rooms(&self) -> Result {
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.resolve_alias(room_alias)
.await
{
| Ok((room_id, servers)) => {
@@ -320,10 +306,7 @@ async fn ban_list_of_rooms(&self) -> Result {
}
for room_id in room_ids {
self.services
.rooms
.metadata
.ban_room(&room_id, true);
self.services.metadata.ban_room(&room_id);
debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1);
@@ -331,7 +314,6 @@ async fn ban_list_of_rooms(&self) -> Result {
debug!("Making all users leave the room {room_id} and forgetting it");
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
@@ -344,28 +326,32 @@ async fn ban_list_of_rooms(&self) -> Result {
evicting admins too)",
);
if let Err(e) = leave_room(self.services, user_id, &room_id, None)
let state_lock = self.services.state.mutex.lock(&room_id).await;
if let Err(e) = self
.services
.membership
.leave(user_id, &room_id, None, false, &state_lock)
.boxed()
.await
{
warn!("Failed to leave room: {e}");
}
drop(state_lock);
self.services
.rooms
.state_cache
.forget(&room_id, user_id);
}
// remove any local aliases, ignore errors
self.services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(ToOwned::to_owned)
.for_each(|local_alias| async move {
.for_each(async |local_alias| {
self.services
.rooms
.alias
.remove_alias(&local_alias, &self.services.globals.server_user)
.await
@@ -374,15 +360,9 @@ async fn ban_list_of_rooms(&self) -> Result {
.await;
// unpublish from room directory, ignore errors
self.services
.rooms
.directory
.set_not_public(&room_id);
self.services.directory.set_not_public(&room_id);
self.services
.rooms
.metadata
.disable_room(&room_id, true);
self.services.metadata.disable_room(&room_id);
}
self.write_str(&format!(
@@ -407,10 +387,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
};
debug!("Room specified is a room ID, unbanning room ID");
self.services
.rooms
.metadata
.ban_room(room_id, false);
self.services.metadata.unban_room(room_id);
room_id.to_owned()
} else if room.is_room_alias_id() {
@@ -432,7 +409,6 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = match self
.services
.rooms
.alias
.resolve_local_alias(room_alias)
.await
@@ -446,9 +422,8 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.resolve_alias(room_alias)
.await
{
| Ok((room_id, servers)) => {
@@ -466,10 +441,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
},
};
self.services
.rooms
.metadata
.ban_room(&room_id, false);
self.services.metadata.unban_room(&room_id);
room_id
} else {
@@ -480,10 +452,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
);
};
self.services
.rooms
.metadata
.disable_room(&room_id, false);
self.services.metadata.enable_room(&room_id);
self.write_str("Room unbanned and federation re-enabled.")
.await
}
@@ -492,7 +461,6 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
async fn list_banned_rooms(&self, no_details: bool) -> Result {
let room_ids: Vec<OwnedRoomId> = self
.services
.rooms
.metadata
.list_banned_rooms()
.map(Into::into)
+4 -4
View File
@@ -67,7 +67,7 @@ pub(super) async fn list_features(&self, available: bool, enabled: bool, comma:
#[admin_command]
pub(super) async fn memory_usage(&self) -> Result {
let services_usage = self.services.memory_usage().await?;
let database_usage = self.services.db.db.memory_usage()?;
let database_usage = self.services.db.engine.memory_usage()?;
let allocator_usage = tuwunel_core::alloc::memory_usage()
.map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
@@ -88,7 +88,7 @@ pub(super) async fn clear_caches(&self) -> Result {
pub(super) async fn list_backups(&self) -> Result {
self.services
.db
.db
.engine
.backup_list()?
.try_stream()
.try_for_each(|result| write!(self, "{result}"))
@@ -102,13 +102,13 @@ pub(super) async fn backup_database(&self) -> Result {
.services
.server
.runtime()
.spawn_blocking(move || match db.db.backup() {
.spawn_blocking(move || match db.engine.backup() {
| Ok(()) => "Done".to_owned(),
| Err(e) => format!("Failed: {e}"),
})
.await?;
let count = self.services.db.db.backup_count()?;
let count = self.services.db.engine.backup_count()?;
self.write_str(&format!("{result}. Currently have {count} backups."))
.await
}
+220 -141
View File
@@ -2,30 +2,27 @@
use futures::{FutureExt, StreamExt};
use ruma::{
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
Int, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
events::{
RoomAccountDataEventType, StateEventType,
room::{
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent, UserPowerLevel},
redaction::RoomRedactionEventContent,
},
tag::{TagEvent, TagEventContent, TagInfo},
},
};
use tuwunel_api::client::{
full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url,
update_displayname,
};
use tuwunel_core::{
Err, Result, debug, debug_warn, error, info, is_equal_to,
matrix::{Event, pdu::PduBuilder},
utils::{self, ReadyExt},
warn,
};
use tuwunel_service::Services;
use crate::{
admin_command, get_room_info,
utils::{parse_active_local_user_id, parse_local_user_id},
utils::{parse_active_local_user_id, parse_local_user_id, parse_user_id},
};
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
@@ -122,7 +119,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
.is_empty()
{
for room in &self.services.server.config.auto_join_rooms {
let Ok(room_id) = self.services.rooms.alias.resolve(room).await else {
let Ok(room_id) = self.services.alias.maybe_resolve(room).await else {
error!(
%user_id,
"Failed to resolve room alias to room ID when attempting to auto join {room}, skipping"
@@ -132,7 +129,6 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
if !self
.services
.rooms
.state_cache
.server_in_room(self.services.globals.server_name(), &room_id)
.await
@@ -143,20 +139,24 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
continue;
}
let state_lock = self.services.state.mutex.lock(&room_id).await;
if let Some(room_server_name) = room.server_name() {
match join_room_by_id_helper(
self.services,
&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(),
],
None,
&None,
)
.await
match self
.services
.membership
.join(
&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(),
],
&None,
&state_lock,
)
.await
{
| Ok(_response) => {
info!("Automatically joined room {room} for user {user_id}");
@@ -175,6 +175,8 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
.await;
},
}
drop(state_lock);
}
}
}
@@ -186,7 +188,6 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
if let Ok(admin_room) = self.services.admin.get_admin_room().await {
if self
.services
.rooms
.state_cache
.room_joined_count(&admin_room)
.await
@@ -195,6 +196,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
self.services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
warn!("Granting {user_id} admin privileges as the first user");
}
@@ -216,36 +218,31 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
return Err!("Not allowed to deactivate the server service account.",);
}
self.services
.users
.deactivate_account(&user_id)
.await?;
if !no_leave_rooms {
self.services
.admin
.send_text(&format!("Making {user_id} leave all rooms after deactivation..."))
.await;
let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.map(Into::into)
.collect()
.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;
leave_all_rooms(self.services, &user_id).await;
}
deactivate_user(self.services, &user_id, no_leave_rooms).await?;
self.write_str(&format!("User {user_id} has been deactivated"))
.await
}
#[admin_command]
pub(super) async fn delete_device(
&self,
user_id: OwnedUserId,
device_id: OwnedDeviceId,
) -> Result {
if !self.services.globals.user_is_local(&user_id) {
return Err!("Cannot delete device of remote user");
}
self.services
.users
.remove_device(&user_id, &device_id)
.await;
self.write_str(&format!("User {user_id}'s device {device_id} removed."))
.await
}
#[admin_command]
pub(super) async fn reset_password(&self, username: String, password: Option<String>) -> Result {
let user_id = parse_local_user_id(self.services, &username)?;
@@ -333,38 +330,16 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
let mut deactivation_count: usize = 0;
for user_id in user_ids {
match self
.services
.users
.deactivate_account(&user_id)
.await
{
match deactivate_user(self.services, &user_id, no_leave_rooms).await {
| Ok(()) => {
deactivation_count = deactivation_count.saturating_add(1);
},
| Err(e) => {
self.services
.admin
.send_text(&format!("Failed deactivating user: {e}"))
.await;
},
| 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");
let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.map(Into::into)
.collect()
.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;
leave_all_rooms(self.services, &user_id).await;
}
},
}
}
@@ -381,6 +356,20 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
.await
}
async fn deactivate_user(services: &Services, user_id: &UserId, no_leave_rooms: bool) -> Result {
if !no_leave_rooms {
services
.deactivate
.full_deactivate(user_id)
.boxed()
.await?;
} else {
services.users.deactivate_account(user_id).await?;
}
Ok(())
}
#[admin_command]
pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result {
// Validate user id
@@ -388,7 +377,6 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result {
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
.services
.rooms
.state_cache
.rooms_joined(&user_id)
.then(|room_id| get_room_info(self.services, room_id))
@@ -438,14 +426,12 @@ pub(super) async fn force_join_list_of_local_users(
let (room_id, servers) = self
.services
.rooms
.alias
.resolve_with_servers(&room_id, None)
.maybe_resolve_with_servers(&room_id, None)
.await?;
if !self
.services
.rooms
.state_cache
.server_in_room(self.services.globals.server_name(), &room_id)
.await
@@ -455,7 +441,6 @@ pub(super) async fn force_join_list_of_local_users(
let server_admins: Vec<_> = self
.services
.rooms
.state_cache
.active_local_users_in_room(&admin_room)
.map(ToOwned::to_owned)
@@ -464,7 +449,6 @@ pub(super) async fn force_join_list_of_local_users(
if !self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_any(|user_id| server_admins.contains(&user_id.to_owned()))
@@ -512,17 +496,21 @@ pub(super) async fn force_join_list_of_local_users(
let mut failed_joins: usize = 0;
let mut successful_joins: usize = 0;
let state_lock = self.services.state.mutex.lock(&room_id).await;
for user_id in user_ids {
match join_room_by_id_helper(
self.services,
&user_id,
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
None,
&None,
)
.await
match self
.services
.membership
.join(
&user_id,
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
&None,
&state_lock,
)
.await
{
| Ok(_res) => {
successful_joins = successful_joins.saturating_add(1);
@@ -534,6 +522,8 @@ pub(super) async fn force_join_list_of_local_users(
}
}
drop(state_lock);
self.write_str(&format!(
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
failed.",
@@ -560,14 +550,12 @@ pub(super) async fn force_join_all_local_users(
let (room_id, servers) = self
.services
.rooms
.alias
.resolve_with_servers(&room_id, None)
.maybe_resolve_with_servers(&room_id, None)
.await?;
if !self
.services
.rooms
.state_cache
.server_in_room(self.services.globals.server_name(), &room_id)
.await
@@ -577,7 +565,6 @@ pub(super) async fn force_join_all_local_users(
let server_admins: Vec<_> = self
.services
.rooms
.state_cache
.active_local_users_in_room(&admin_room)
.map(ToOwned::to_owned)
@@ -586,7 +573,6 @@ pub(super) async fn force_join_all_local_users(
if !self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_any(|user_id| server_admins.contains(&user_id.to_owned()))
@@ -598,6 +584,8 @@ pub(super) async fn force_join_all_local_users(
let mut failed_joins: usize = 0;
let mut successful_joins: usize = 0;
let state_lock = self.services.state.mutex.lock(&room_id).await;
for user_id in &self
.services
.users
@@ -606,16 +594,18 @@ pub(super) async fn force_join_all_local_users(
.collect::<Vec<_>>()
.await
{
match join_room_by_id_helper(
self.services,
user_id,
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
None,
&None,
)
.await
match self
.services
.membership
.join(
user_id,
&room_id,
Some(String::from(BULK_JOIN_REASON)),
&servers,
&None,
&state_lock,
)
.await
{
| Ok(_res) => {
successful_joins = successful_joins.saturating_add(1);
@@ -627,6 +617,8 @@ pub(super) async fn force_join_all_local_users(
}
}
drop(state_lock);
self.write_str(&format!(
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
failed.",
@@ -643,18 +635,24 @@ pub(super) async fn force_join_room(
let user_id = parse_local_user_id(self.services, &user_id)?;
let (room_id, servers) = self
.services
.rooms
.alias
.resolve_with_servers(&room_id, None)
.maybe_resolve_with_servers(&room_id, None)
.await?;
assert!(
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)
let state_lock = self.services.state.mutex.lock(&room_id).await;
self.services
.membership
.join(&user_id, &room_id, None, &servers, &None, &state_lock)
.await?;
drop(state_lock);
self.write_str(&format!("{user_id} has been joined to {room_id}.",))
.await
}
@@ -668,9 +666,8 @@ pub(super) async fn force_leave_room(
let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.maybe_resolve(&room_id)
.await?;
assert!(
@@ -680,7 +677,6 @@ pub(super) async fn force_leave_room(
if !self
.services
.rooms
.state_cache
.is_joined(&user_id, &room_id)
.await
@@ -688,10 +684,16 @@ pub(super) async fn force_leave_room(
return Err!("{user_id} is not joined in the room");
}
leave_room(self.services, &user_id, &room_id, None)
let state_lock = self.services.state.mutex.lock(&room_id).await;
self.services
.membership
.leave(&user_id, &room_id, None, false, &state_lock)
.boxed()
.await?;
drop(state_lock);
self.write_str(&format!("{user_id} has left {room_id}.",))
.await
}
@@ -701,9 +703,8 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = self
.services
.rooms
.alias
.resolve(&room_id)
.maybe_resolve(&room_id)
.await?;
assert!(
@@ -711,45 +712,42 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
"Parsed user_id must be a local user"
);
let state_lock = self
.services
.rooms
.state
.mutex
.lock(&room_id)
.await;
let state_lock = self.services.state.mutex.lock(&room_id).await;
let room_power_levels: Option<RoomPowerLevelsEventContent> = self
let room_power_levels: Option<RoomPowerLevels> = self
.services
.rooms
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomPowerLevels, "")
.get_power_levels(&room_id)
.await
.ok();
let user_can_demote_self = room_power_levels
let user_can_change_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)
}) || self
.services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender() == user_id);
.is_some_and(|power_levels| {
power_levels.user_can_change_user_power_level(&user_id, &user_id)
});
let user_can_demote_self = user_can_change_self
|| self
.services
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender() == user_id);
if !user_can_demote_self {
return Err!("User is not allowed to modify their own power levels in the room.",);
return Err!("User is not allowed to modify their own power levels in the room.");
}
let mut power_levels_content = room_power_levels.unwrap_or_default();
let mut power_levels_content: RoomPowerLevelsEventContent = room_power_levels
.map(TryInto::try_into)
.transpose()?
.unwrap_or_default();
power_levels_content.users.remove(&user_id);
let event_id = self
.services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
@@ -766,6 +764,89 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
.await
}
#[admin_command]
pub(super) async fn force_promote(
&self,
target_id: String,
room_id: OwnedRoomOrAliasId,
) -> Result {
let target_id = parse_user_id(self.services, &target_id)?;
let room_id = self
.services
.alias
.maybe_resolve(&room_id)
.await?;
let state_lock = self.services.state.mutex.lock(&room_id).await;
let room_power_levels = self
.services
.state_accessor
.get_power_levels(&room_id)
.await?;
let privileged_member = self
.services
.state_cache
.room_members(&room_id)
.ready_filter(|member_id| {
self.services.globals.user_is_local(member_id)
&& room_power_levels.user_can_change_user_power_level(member_id, &target_id)
})
.map(ToOwned::to_owned)
.ready_fold_default(|selected_user, member_id| match selected_user {
| None => Some(member_id),
| Some(selected_user) => Some(
if room_power_levels.for_user(&selected_user)
> room_power_levels.for_user(&member_id)
{
selected_user
} else {
member_id
},
),
})
.await;
let Some(privileged_member) = privileged_member else {
return Err!("No privileged user exists in room, cannot promote.");
};
info!("Selected privileged member {privileged_member}");
let power_level: Int = match room_power_levels.for_user(&privileged_member) {
| UserPowerLevel::Infinite => Int::MAX,
| UserPowerLevel::Int(x) => x,
};
let mut power_levels_content: RoomPowerLevelsEventContent = room_power_levels.try_into()?;
power_levels_content
.users
.insert(target_id.clone(), power_level);
let event_id = self
.services
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
&privileged_member,
&room_id,
&state_lock,
)
.await?;
drop(state_lock);
self.write_str(&format!(
"User {privileged_member} promoted {target_id} to {power_level} power level in \
{room_id} - {event_id}"
))
.await?;
Ok(())
}
#[admin_command]
pub(super) async fn make_user_admin(&self, user_id: String) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
@@ -777,6 +858,7 @@ pub(super) async fn make_user_admin(&self, user_id: String) -> Result {
self.services
.admin
.make_user_admin(&user_id)
.boxed()
.await?;
self.write_str(&format!("{user_id} has been granted admin privileges.",))
@@ -883,7 +965,6 @@ pub(super) async fn get_room_tags(&self, user_id: String, room_id: OwnedRoomId)
pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
let Ok(event) = self
.services
.rooms
.timeline
.get_non_outlier_pdu(&event_id)
.await
@@ -911,14 +992,12 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
let redaction_event_id = {
let state_lock = self
.services
.rooms
.state
.mutex
.lock(event.room_id())
.await;
self.services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
+13 -1
View File
@@ -1,7 +1,7 @@
mod commands;
use clap::Subcommand;
use ruma::{OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId};
use ruma::{OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId};
use tuwunel_core::Result;
use crate::admin_command_dispatch;
@@ -59,6 +59,12 @@ pub(super) enum UserCommand {
force: bool,
},
/// - Deletes a user's device.
DeleteDevice {
user_id: OwnedUserId,
device_id: OwnedDeviceId,
},
/// - List local users in the database
#[clap(alias = "list")]
ListUsers,
@@ -88,6 +94,12 @@ pub(super) enum UserCommand {
room_id: OwnedRoomOrAliasId,
},
/// - Force promote
ForcePromote {
user_id: String,
room_id: OwnedRoomOrAliasId,
},
/// - Grant server-admin privileges to a user.
MakeUserAdmin {
user_id: String,
-2
View File
@@ -17,13 +17,11 @@ pub(crate) async fn get_room_info(
(
room_id.into(),
services
.rooms
.state_cache
.room_joined_count(room_id)
.await
.unwrap_or(0),
services
.rooms
.state_accessor
.get_name(room_id)
.await
+10 -3
View File
@@ -11,6 +11,7 @@ version.workspace = true
[lib]
path = "mod.rs"
bench = false
crate-type = [
"rlib",
# "dylib",
@@ -22,6 +23,9 @@ brotli_compression = [
"tuwunel-service/brotli_compression",
"reqwest/brotli",
]
bzip2_compression = [
"tuwunel-service/bzip2_compression",
]
element_hacks = [
"tuwunel-service/element_hacks",
]
@@ -52,6 +56,9 @@ jemalloc_stats = [
ldap = [
"tuwunel-service/ldap",
]
lz4_compression = [
"tuwunel-service/lz4_compression",
]
release_max_log_level = [
"tuwunel-core/release_max_log_level",
"tuwunel-service/release_max_log_level",
@@ -73,10 +80,8 @@ axum-extra.workspace = true
axum.workspace = true
base64.workspace = true
bytes.workspace = true
ctor.workspace = true
tuwunel-core.workspace = true
tuwunel-service.workspace = true
const-str.workspace = true
ctor.workspace = true
futures.workspace = true
hmac.workspace = true
http.workspace = true
@@ -94,6 +99,8 @@ serde.workspace = true
sha1.workspace = true
tokio.workspace = true
tracing.workspace = true
tuwunel-core.workspace = true
tuwunel-service.workspace = true
[lints]
workspace = true
+17 -824
View File
@@ -1,599 +1,13 @@
use std::fmt::Write;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use futures::{FutureExt, StreamExt};
use register::RegistrationKind;
use ruma::{
OwnedRoomId, UserId,
api::client::{
account::{
ThirdPartyIdRemovalStatus, 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,
},
uiaa::{AuthFlow, AuthType, UiaaInfo},
},
events::{
GlobalAccountDataEventType, StateEventType,
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
push,
use ruma::api::client::account::{
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, whoami,
};
use tuwunel_core::{
Err, Error, Result, debug_info, err, error, info, is_equal_to,
matrix::{Event, pdu::PduBuilder},
utils,
utils::{ReadyExt, stream::BroadbandExt},
warn,
};
use tuwunel_service::Services;
use tuwunel_core::{Err, Result, err, info, utils::ReadyExt};
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
use crate::Ruma;
const RANDOM_USER_ID_LENGTH: usize = 10;
/// # `GET /_matrix/client/v3/register/available`
///
/// Checks if a username is valid and available on this server.
///
/// Conditions for returning true:
/// - The user id is not historical
/// - The server name of the user id matches this server
/// - No user or appservice on this server already claimed this username
///
/// Note: This will not reserve the username, so the username might become
/// 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,
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
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")
});
if services
.globals
.forbidden_usernames()
.is_match(&body.username)
{
return Err!(Request(Forbidden("Username is forbidden")));
}
// don't force the username lowercase if it's from matrix-appservice-irc
let body_username = if is_matrix_appservice_irc {
body.username.clone()
} else {
body.username.to_lowercase()
};
// Validate user id
let user_id =
match UserId::parse_with_server_name(&body_username, services.globals.server_name()) {
| Ok(user_id) => {
if let Err(e) = user_id.validate_strict() {
// unless the username is from the broken matrix appservice IRC bridge, we
// should follow synapse's behaviour on not allowing things like spaces
// and UTF-8 characters in usernames
if !is_matrix_appservice_irc {
return Err!(Request(InvalidUsername(debug_warn!(
"Username {body_username} contains disallowed characters or spaces: \
{e}"
))));
}
}
user_id
},
| Err(e) => {
return Err!(Request(InvalidUsername(debug_warn!(
"Username {body_username} is not valid: {e}"
))));
},
};
// Check if username is creative enough
if services.users.exists(&user_id).await {
return Err!(Request(UserInUse("User ID is not available.")));
}
if let Some(ref info) = body.appservice_info {
if !info.is_user_match(&user_id) {
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
}
}
if services
.appservice
.is_exclusive_user_id(&user_id)
.await
{
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
}
Ok(get_username_availability::v3::Response { available: true })
}
/// # `POST /_matrix/client/v3/register`
///
/// Register an account on this homeserver.
///
/// You can use [`GET
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
/// html) to check if the user id is valid and available.
///
/// - Only works if registration is enabled
/// - If type is guest: ignores all parameters except
/// initial_device_display_name
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
/// - If type is not guest and no username is given: Always fails after UIAA
/// check
/// - Creates a new account and populates it with default account data
/// - If `inhibit_login` is false: Creates a device and returns device id and
/// access_token
#[allow(clippy::doc_markdown)]
#[tracing::instrument(skip_all, fields(%client), name = "register")]
pub(crate) async fn register_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<register::v3::Request>,
) -> Result<register::v3::Response> {
let is_guest = body.kind == RegistrationKind::Guest;
let emergency_mode_enabled = services.config.emergency_password.is_some();
if !services.config.allow_registration && body.appservice_info.is_none() {
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
| (Some(username), Some(device_display_name)) => {
info!(
%is_guest,
user = %username,
device_name = %device_display_name,
"Rejecting registration attempt as registration is disabled"
);
},
| (Some(username), _) => {
info!(
%is_guest,
user = %username,
"Rejecting registration attempt as registration is disabled"
);
},
| (_, Some(device_display_name)) => {
info!(
%is_guest,
device_name = %device_display_name,
"Rejecting registration attempt as registration is disabled"
);
},
| (None, _) => {
info!(
%is_guest,
"Rejecting registration attempt as registration is disabled"
);
},
}
return Err!(Request(Forbidden("Registration has been disabled.")));
}
if is_guest
&& (!services.config.allow_guest_registration
|| (services.config.allow_registration
&& services.globals.registration_token.is_some()))
{
info!(
"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!(Request(GuestAccessForbidden("Guest registration is disabled.")));
}
// forbid guests from registering if there is not a real admin user yet. give
// 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: \"{}\"",
body.initial_device_display_name
.as_deref()
.unwrap_or("")
);
return Err!(Request(Forbidden("Registration is temporarily disabled.")));
}
let user_id = match (body.username.as_ref(), is_guest) {
| (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")
});
if services
.globals
.forbidden_usernames()
.is_match(username)
&& !emergency_mode_enabled
{
return Err!(Request(Forbidden("Username is forbidden")));
}
// don't force the username lowercase if it's from matrix-appservice-irc
let body_username = if is_matrix_appservice_irc {
username.clone()
} else {
username.to_lowercase()
};
let proposed_user_id = match UserId::parse_with_server_name(
&body_username,
services.globals.server_name(),
) {
| Ok(user_id) => {
if let Err(e) = user_id.validate_strict() {
// unless the username is from the broken matrix appservice IRC bridge, or
// we are in emergency mode, we should follow synapse's behaviour on
// not allowing things like spaces and UTF-8 characters in usernames
if !is_matrix_appservice_irc && !emergency_mode_enabled {
return Err!(Request(InvalidUsername(debug_warn!(
"Username {body_username} contains disallowed characters or \
spaces: {e}"
))));
}
}
user_id
},
| Err(e) => {
return Err!(Request(InvalidUsername(debug_warn!(
"Username {body_username} is not valid: {e}"
))));
},
};
if services.users.exists(&proposed_user_id).await {
return Err!(Request(UserInUse("User ID is not available.")));
}
proposed_user_id
},
| _ => loop {
let proposed_user_id = UserId::parse_with_server_name(
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
services.globals.server_name(),
)
.unwrap();
if !services.users.exists(&proposed_user_id).await {
break proposed_user_id;
}
},
};
if body.body.login_type == Some(LoginType::ApplicationService) {
match body.appservice_info {
| Some(ref info) =>
if !info.is_user_match(&user_id) && !emergency_mode_enabled {
return Err!(Request(Exclusive(
"Username is not in an appservice namespace."
)));
},
| _ => {
return Err!(Request(MissingToken("Missing appservice token.")));
},
}
} else if services
.appservice
.is_exclusive_user_id(&user_id)
.await && !emergency_mode_enabled
{
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
}
// UIAA
let mut uiaainfo;
let skip_auth = if services.globals.registration_token.is_some() {
// Registration token required
uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::RegistrationToken],
}],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
body.appservice_info.is_some()
} else {
// No registration token necessary, but clients must still go through the flow
uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
body.appservice_info.is_some() || is_guest
};
if !skip_auth {
match &body.auth {
| Some(auth) => {
let (worked, uiaainfo) = services
.uiaa
.try_auth(
&UserId::parse_with_server_name("", services.globals.server_name())
.unwrap(),
"".into(),
auth,
&uiaainfo,
)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
},
| _ => match body.json_body {
| Some(ref json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services.uiaa.create(
&UserId::parse_with_server_name("", services.globals.server_name())
.unwrap(),
"".into(),
&uiaainfo,
json,
);
return Err(Error::Uiaa(uiaainfo));
},
| _ => {
return Err!(Request(NotJson("JSON body is not valid")));
},
},
}
}
let password = if is_guest { None } else { body.password.as_deref() };
// Create user
services
.users
.create(&user_id, password, None)
.await?;
// Default to pretty displayname
let mut displayname = user_id.localpart().to_owned();
// 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()
{
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
}
services
.users
.set_displayname(&user_id, Some(displayname.clone()));
// Initial account data
services
.account_data
.update(
None,
&user_id,
GlobalAccountDataEventType::PushRules
.to_string()
.into(),
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
content: ruma::events::push_rules::PushRulesEventContent {
global: push::Ruleset::server_default(&user_id),
},
})?,
)
.await?;
if (!is_guest && body.inhibit_login)
|| body
.appservice_info
.as_ref()
.is_some_and(|appservice| appservice.registration.device_management)
{
return Ok(register::v3::Response {
access_token: None,
user_id,
device_id: None,
refresh_token: None,
expires_in: None,
});
}
// 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());
// Generate new token for the device
let token = utils::random_string(TOKEN_LENGTH);
// Create device for this account
services
.users
.create_device(
&user_id,
&device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
.await?;
debug_info!(%user_id, %device_id, "User account was created");
let device_display_name = body
.initial_device_display_name
.as_deref()
.unwrap_or("");
// log in conduit admin channel if a non-guest user registered
if body.appservice_info.is_none() && !is_guest {
if !device_display_name.is_empty() {
let notice = format!(
"New user \"{user_id}\" registered on this server from IP {client} and device \
display name \"{device_display_name}\""
);
info!("{notice}");
if services.server.config.admin_room_notices {
services.admin.notice(&notice).await;
}
} else {
let notice = format!("New user \"{user_id}\" registered on this server.");
info!("{notice}");
if services.server.config.admin_room_notices {
services.admin.notice(&notice).await;
}
}
}
// log in conduit admin channel if a guest registered
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
debug_info!("New guest user \"{user_id}\" registered on this server.");
if !device_display_name.is_empty() {
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!(
"Guest user \"{user_id}\" with device display name \
\"{device_display_name}\" registered on this server from IP {client}"
))
.await;
}
} else {
#[allow(clippy::collapsible_else_if)]
if services.server.config.admin_room_notices {
services
.admin
.notice(&format!(
"Guest user \"{user_id}\" with no device display name registered on \
this server from IP {client}",
))
.await;
}
}
}
// If this is the first real user, grant them admin privileges except for guest
// users
// Note: the server user is generated first
if !is_guest {
if let Ok(admin_room) = services.admin.get_admin_room().await {
if services
.rooms
.state_cache
.room_joined_count(&admin_room)
.await
.is_ok_and(is_equal_to!(1))
{
services.admin.make_user_admin(&user_id).await?;
warn!("Granting {user_id} admin privileges as the first user");
}
}
}
if body.appservice_info.is_none()
&& !services.server.config.auto_join_rooms.is_empty()
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
{
for room in &services.server.config.auto_join_rooms {
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
error!(
"Failed to resolve room alias to room ID when attempting to auto join \
{room}, skipping"
);
continue;
};
if !services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), &room_id)
.await
{
warn!(
"Skipping room {room} to automatically join as we have never joined before."
);
continue;
}
if let Some(room_server_name) = room.server_name() {
match join_room_by_id_helper(
&services,
&user_id,
&room_id,
Some("Automatically joining this room upon registration".to_owned()),
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
None,
&body.appservice_info,
)
.boxed()
.await
{
| Err(e) => {
// don't return this error so we don't fail registrations
error!(
"Failed to automatically join room {room} for user {user_id}: {e}"
);
},
| _ => {
info!("Automatically joined room {room} for user {user_id}");
},
}
}
}
}
Ok(register::v3::Response {
access_token: Some(token),
user_id,
device_id: Some(device_id),
refresh_token: None,
expires_in: None,
})
}
use crate::{Ruma, router::auth_uiaa};
/// # `POST /_matrix/client/r0/account/password`
///
@@ -618,48 +32,7 @@ pub(crate) async fn change_password_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<change_password::v3::Request>,
) -> Result<change_password::v3::Response> {
// Authentication for this endpoint was made optional, but we need
// authentication currently
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
match &body.auth {
| Some(auth) => {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
},
| _ => match body.json_body {
| Some(ref json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo));
},
| _ => {
return Err!(Request(NotJson("JSON body is not valid")));
},
},
}
let ref sender_user = auth_uiaa(&services, &body).await?;
services
.users
@@ -671,32 +44,9 @@ pub(crate) async fn change_password_route(
services
.users
.all_device_ids(sender_user)
.ready_filter(|id| *id != body.sender_device())
.ready_filter(|&id| Some(id) != body.sender_device.as_deref())
.for_each(|id| services.users.remove_device(sender_user, id))
.await;
// Remove all pushers except the ones associated with this session
services
.pusher
.get_pushkeys(sender_user)
.map(ToOwned::to_owned)
.broad_filter_map(async |pushkey| {
services
.pusher
.get_pusher_device(&pushkey)
.await
.ok()
.filter(|pusher_device| pusher_device != body.sender_device())
.is_some()
.then_some(pushkey)
})
.for_each(async |pushkey| {
services
.pusher
.delete_pusher(sender_user, &pushkey)
.await;
})
.await;
}
info!("User {sender_user} changed their password.");
@@ -723,10 +73,12 @@ pub(crate) async fn whoami_route(
Ok(whoami::v3::Response {
user_id: body.sender_user().to_owned(),
device_id: body.sender_device.clone(),
is_guest: services
.users
.is_deactivated(body.sender_user())
.await? && body.appservice_info.is_none(),
is_guest: body.appservice_info.is_none()
&& services
.users
.is_deactivated(body.sender_user())
.await
.map_err(|_| err!(Request(Forbidden("User does not exist."))))?,
})
}
@@ -747,66 +99,15 @@ pub(crate) async fn deactivate_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<deactivate::v3::Request>,
) -> Result<deactivate::v3::Response> {
// Authentication for this endpoint was made optional, but we need
// authentication currently
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let ref sender_user = auth_uiaa(&services, &body).await?;
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
match &body.auth {
| Some(auth) => {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
},
| _ => match body.json_body {
| Some(ref json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo));
},
| _ => {
return Err!(Request(NotJson("JSON body is not valid")));
},
},
}
// Remove profile pictures and display name
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(sender_user)
.map(Into::into)
.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;
full_user_deactivate(&services, sender_user, &all_joined_rooms)
services
.deactivate
.full_deactivate(sender_user)
.boxed()
.await?;
info!("User {sender_user} deactivated their account.");
if services.server.config.admin_room_notices {
services
.admin
@@ -860,111 +161,3 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
}
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
///
/// Checks if the provided registration token is valid at the time of checking
///
/// Currently does not have any ratelimiting, and this isn't very practical as
/// there is only one registration token allowed.
pub(crate) async fn check_registration_token_validity(
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!(Request(Forbidden("Server does not allow token registration")));
};
Ok(check_registration_token_validity::v1::Response { valid: reg_token == body.token })
}
/// Runs through all the deactivation steps:
///
/// - Mark as deactivated
/// - Removing display name
/// - Removing avatar URL and blurhash
/// - 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],
) -> Result<()> {
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);
})
.await;
for room_id in all_joined_rooms {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let room_power_levels = services
.rooms
.state_accessor
.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);
if user_can_demote_self {
let mut power_levels_content = room_power_levels.unwrap_or_default();
power_levels_content.users.remove(user_id);
// ignore errors so deactivation doesn't fail
match services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(String::new(), &power_levels_content),
user_id,
room_id,
&state_lock,
)
.await
{
| Err(e) => {
warn!(%room_id, %user_id, "Failed to demote user's own power level: {e}");
},
| _ => {
info!("Demoted {user_id} in {room_id} as part of account deactivation");
},
}
}
}
super::leave_all_rooms(services, user_id)
.boxed()
.await;
Ok(())
}
+6 -15
View File
@@ -5,7 +5,7 @@
OwnedServerName, RoomAliasId, RoomId,
api::client::alias::{create_alias, delete_alias, get_alias},
};
use tuwunel_core::{Err, Result, debug};
use tuwunel_core::{Err, Result, debug, err};
use tuwunel_service::Services;
use crate::Ruma;
@@ -19,7 +19,6 @@ pub(crate) async fn create_alias_route(
) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user();
services
.rooms
.alias
.appservice_checks(&body.room_alias, &body.appservice_info)
.await?;
@@ -27,15 +26,14 @@ pub(crate) async fn create_alias_route(
// this isn't apart of alias_checks or delete alias route because we should
// allow removing forbidden room aliases
if services
.globals
.forbidden_alias_names()
.config
.forbidden_alias_names
.is_match(body.room_alias.alias())
{
return Err!(Request(Forbidden("Room alias is forbidden.")));
}
if services
.rooms
.alias
.resolve_local_alias(&body.room_alias)
.await
@@ -45,7 +43,6 @@ pub(crate) async fn create_alias_route(
}
services
.rooms
.alias
.set_alias(&body.room_alias, &body.room_id, sender_user)?;
@@ -63,13 +60,11 @@ pub(crate) async fn delete_alias_route(
) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user();
services
.rooms
.alias
.appservice_checks(&body.room_alias, &body.appservice_info)
.await?;
services
.rooms
.alias
.remove_alias(&body.room_alias, sender_user)
.await?;
@@ -88,14 +83,11 @@ pub(crate) async fn get_alias_route(
) -> Result<get_alias::v3::Response> {
let room_alias = body.body.room_alias;
let Ok((room_id, servers)) = services
.rooms
let (room_id, servers) = services
.alias
.resolve_alias(&room_alias, None)
.resolve_alias(&room_alias)
.await
else {
return Err!(Request(NotFound("Room with alias not found.")));
};
.map_err(|_| err!(Request(NotFound("Room with alias not found."))))?;
let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
debug!(?room_alias, ?room_id, "available servers: {servers:?}");
@@ -111,7 +103,6 @@ async fn room_available_servers(
) -> Vec<OwnedServerName> {
// find active servers in room state cache to suggest
let mut servers: Vec<OwnedServerName> = services
.rooms
.state_cache
.room_servers(room_id)
.map(ToOwned::to_owned)
+4 -7
View File
@@ -37,13 +37,10 @@ pub(crate) async fn appservice_ping(
let timer = tokio::time::Instant::now();
let _response = services
.sending
.send_appservice_request(
appservice_info.registration.clone(),
ping::send_ping::v1::Request {
transaction_id: body.transaction_id.clone(),
},
)
.appservice
.send_request(appservice_info.registration.clone(), ping::send_ping::v1::Request {
transaction_id: body.transaction_id.clone(),
})
.await?
.expect("We already validated if an appservice URL exists above");
+7 -5
View File
@@ -3,9 +3,12 @@
use axum::extract::State;
use ruma::{
RoomVersionId,
api::client::discovery::get_capabilities::{
self, Capabilities, GetLoginTokenCapability, RoomVersionStability,
RoomVersionsCapability, ThirdPartyIdChangesCapability,
api::client::discovery::{
get_capabilities,
get_capabilities::v3::{
Capabilities, GetLoginTokenCapability, ProfileFieldsCapability, RoomVersionStability,
RoomVersionsCapability, ThirdPartyIdChangesCapability,
},
},
};
use serde_json::json;
@@ -41,8 +44,7 @@ pub(crate) async fn get_capabilities_route(
enabled: services.server.config.login_via_existing_session,
};
// MSC4133 capability
capabilities.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))?;
capabilities.profile_fields = ProfileFieldsCapability::new(true).into();
capabilities.set(
"org.matrix.msc4267.forget_forced_upon_leave",
+11 -24
View File
@@ -38,7 +38,7 @@ pub(crate) async fn get_context_route(
let event_id = &body.event_id;
let filter = &body.filter;
if !services.rooms.metadata.exists(room_id).await {
if !services.metadata.exists(room_id).await {
return Err!(Request(Forbidden("Room does not exist to this server")));
}
@@ -50,19 +50,16 @@ pub(crate) async fn get_context_route(
.min(LIMIT_MAX);
let base_id = services
.rooms
.timeline
.get_pdu_id(event_id)
.map_err(|_| err!(Request(NotFound("Event not found."))));
let base_pdu = services
.rooms
.timeline
.get_pdu(event_id)
.map_err(|_| err!(Request(NotFound("Base event not found."))));
let visible = services
.rooms
.state_accessor
.user_can_see_event(sender_user, room_id, event_id)
.map(Ok);
@@ -74,7 +71,11 @@ pub(crate) async fn get_context_route(
}
if !visible {
debug_warn!(req_evt = ?event_id, ?base_id, ?room_id, "Event requested by {sender_user} but is not allowed to see it, returning 404");
debug_warn!(
req_evt = ?event_id, ?base_id, ?room_id,
"Event requested by {sender_user} but is not allowed to see it."
);
return Err!(Request(NotFound("Event not found.")));
}
@@ -83,7 +84,6 @@ pub(crate) async fn get_context_route(
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);
let events_before = services
.rooms
.timeline
.pdus_rev(Some(sender_user), room_id, Some(base_count))
.ignore_err()
@@ -94,14 +94,13 @@ pub(crate) async fn get_context_route(
.collect();
let events_after = services
.rooms
.timeline
.pdus(Some(sender_user), room_id, Some(base_count))
.ignore_err()
.ready_filter_map(|item| event_filter(item, filter))
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
.take(limit / 2)
.take(limit.div_ceil(2))
.collect();
let (base_event, events_before, events_after): (_, Vec<_>, Vec<_>) =
@@ -132,21 +131,14 @@ pub(crate) async fn get_context_route(
let state_at = events_after
.last()
.map(ref_at!(1))
.map_or(body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
let state_ids = services
.rooms
.state_accessor
.state
.pdu_shortstatehash(state_at)
.or_else(|_| {
services
.rooms
.state
.get_room_shortstatehash(room_id)
})
.or_else(|_| services.state.get_room_shortstatehash(room_id))
.map_ok(|shortstatehash| {
services
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.map(Ok)
@@ -163,7 +155,6 @@ pub(crate) async fn get_context_route(
let shorteventids = state_ids.iter().map(ref_at!(1)).stream();
let lazy_loading_witnessed = lazy_loading_witnessed.unwrap_or_default();
let state: Vec<_> = services
.rooms
.short
.multi_get_statekey_from_short(shortstatekeys)
.zip(shorteventids)
@@ -182,11 +173,7 @@ pub(crate) async fn get_context_route(
Some(event_id)
})
.broad_filter_map(|event_id: &OwnedEventId| {
services
.rooms
.timeline
.get_pdu(event_id.as_ref())
.ok()
services.timeline.get_pdu(event_id.as_ref()).ok()
})
.map(Event::into_format)
.collect()
+132
View File
@@ -0,0 +1,132 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use futures::StreamExt;
use ruma::api::client::dehydrated_device::{
delete_dehydrated_device::unstable as delete_dehydrated_device,
get_dehydrated_device::unstable as get_dehydrated_device, get_events::unstable as get_events,
put_dehydrated_device::unstable as put_dehydrated_device,
};
use tuwunel_core::{Err, Result, at, utils::result::IsErrOr};
use crate::Ruma;
const MAX_BATCH_EVENTS: usize = 50;
/// # `PUT /_matrix/client/../dehydrated_device`
///
/// Creates or overwrites the user's dehydrated device.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn put_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<put_dehydrated_device::Request>,
) -> Result<put_dehydrated_device::Response> {
let sender_user = body
.sender_user
.as_deref()
.expect("AccessToken authentication required");
let device_id = body.body.device_id.clone();
services
.users
.set_dehydrated_device(sender_user, body.body)
.await?;
Ok(put_dehydrated_device::Response { device_id })
}
/// # `DELETE /_matrix/client/../dehydrated_device`
///
/// Deletes the user's dehydrated device without replacement.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn delete_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<delete_dehydrated_device::Request>,
) -> Result<delete_dehydrated_device::Response> {
let sender_user = body.sender_user();
let device_id = services
.users
.get_dehydrated_device_id(sender_user)
.await?;
services
.users
.remove_device(sender_user, &device_id)
.await;
Ok(delete_dehydrated_device::Response { device_id })
}
/// # `GET /_matrix/client/../dehydrated_device`
///
/// Gets the user's dehydrated device
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn get_dehydrated_device_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_dehydrated_device::Request>,
) -> Result<get_dehydrated_device::Response> {
let sender_user = body.sender_user();
let device = services
.users
.get_dehydrated_device(sender_user)
.await?;
Ok(get_dehydrated_device::Response {
device_id: device.device_id,
device_data: device.device_data,
})
}
/// # `GET /_matrix/client/../dehydrated_device/{device_id}/events`
///
/// Paginates the events of the dehydrated device.
#[tracing::instrument(skip_all, fields(%client))]
pub(crate) async fn get_dehydrated_events_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_events::Request>,
) -> Result<get_events::Response> {
let sender_user = body.sender_user();
let device_id = &body.body.device_id;
let existing_id = services
.users
.get_dehydrated_device_id(sender_user)
.await;
if existing_id
.as_ref()
.is_err_or(|existing_id| existing_id != device_id)
{
return Err!(Request(Forbidden("Not the dehydrated device_id.")));
}
let since: Option<u64> = body
.body
.next_batch
.as_deref()
.map(str::parse)
.transpose()?;
let mut next_batch: Option<u64> = None;
let events = services
.users
.get_to_device_events(sender_user, device_id, since, None)
.take(MAX_BATCH_EVENTS)
.inspect(|&(count, _)| {
next_batch.replace(count);
})
.map(at!(1))
.collect()
.await;
Ok(get_events::Response {
events,
next_batch: next_batch.as_ref().map(ToString::to_string),
})
}
+17 -83
View File
@@ -3,16 +3,13 @@
use futures::StreamExt;
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
api::client::{
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
error::ErrorKind,
uiaa::{AuthFlow, AuthType, UiaaInfo},
api::client::device::{
self, delete_device, delete_devices, get_device, get_devices, update_device,
},
};
use tuwunel_core::{Err, Error, Result, debug, err, utils};
use tuwunel_core::{Err, Result, debug, err, utils, utils::string::to_small_string};
use super::SESSION_ID_LENGTH;
use crate::{Ruma, client::DEVICE_ID_LENGTH};
use crate::{Ruma, client::DEVICE_ID_LENGTH, router::auth_uiaa};
/// # `GET /_matrix/client/r0/devices`
///
@@ -64,18 +61,21 @@ pub(crate) async fn update_device_route(
.await
{
| Ok(mut device) => {
let notify = device.display_name != body.display_name;
device.display_name.clone_from(&body.display_name);
device
.last_seen_ip
.clone_from(&Some(client.to_string()));
.clone_from(&Some(to_small_string(client)));
device
.last_seen_ts
.clone_from(&Some(MilliSecondsSinceUnixEpoch::now()));
assert_eq!(device.device_id, body.device_id, "device_id mismatch");
services
.users
.update_device_metadata(sender_user, &body.device_id, &device)
.await?;
.put_device_metadata(sender_user, notify, &device);
Ok(update_device::v3::Response {})
},
@@ -83,6 +83,7 @@ pub(crate) async fn update_device_route(
let Some(appservice) = appservice else {
return Err!(Request(NotFound("Device not found.")));
};
if !appservice.registration.device_management {
return Err!(Request(NotFound("Device not found.")));
}
@@ -100,7 +101,8 @@ pub(crate) async fn update_device_route(
.create_device(
sender_user,
&device_id,
&appservice.registration.as_token,
(Some(&appservice.registration.as_token), None),
None,
None,
Some(client.to_string()),
)
@@ -125,10 +127,10 @@ pub(crate) async fn delete_device_route(
State(services): State<crate::State>,
body: Ruma<delete_device::v3::Request>,
) -> Result<delete_device::v3::Response> {
let (sender_user, sender_device) = body.sender();
let appservice = body.appservice_info.as_ref();
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
let sender_user = body.sender_user();
debug!(
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
enabled"
@@ -141,41 +143,7 @@ pub(crate) async fn delete_device_route(
return Ok(delete_device::v3::Response {});
}
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
match &body.auth {
| Some(auth) => {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err!(Uiaa(uiaainfo));
}
// Success!
},
| _ => match body.json_body {
| Some(ref json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, json);
return Err!(Uiaa(uiaainfo));
},
| _ => {
return Err!(Request(NotJson("Not json.")));
},
},
}
let ref sender_user = auth_uiaa(&services, &body).await?;
services
.users
@@ -202,10 +170,10 @@ pub(crate) async fn delete_devices_route(
State(services): State<crate::State>,
body: Ruma<delete_devices::v3::Request>,
) -> Result<delete_devices::v3::Response> {
let (sender_user, sender_device) = body.sender();
let appservice = body.appservice_info.as_ref();
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
let sender_user = body.sender_user();
debug!(
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
enabled"
@@ -220,41 +188,7 @@ pub(crate) async fn delete_devices_route(
return Ok(delete_devices::v3::Response {});
}
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
match &body.auth {
| Some(auth) => {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
},
| _ => match body.json_body {
| Some(ref json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo));
},
| _ => {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
},
},
}
let ref sender_user = auth_uiaa(&services, &body).await?;
for device_id in &body.devices {
services
+95 -90
View File
@@ -16,24 +16,21 @@
},
federation,
},
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter},
events::{
StateEventType,
room::{
join_rules::{JoinRule, RoomJoinRulesEventContent},
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
room::join_rules::{JoinRule, RoomJoinRulesEventContent},
},
uint,
};
use tuwunel_core::{
Err, Result, err, info,
Err, Result, err, info, is_true,
matrix::Event,
utils::{
TryFutureExtExt,
math::Expected,
result::FlatOk,
stream::{ReadyExt, WidebandExt},
stream::{IterStream, ReadyExt, WidebandExt},
},
};
use tuwunel_service::Services;
@@ -51,19 +48,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms_filtered::v3::Request>,
) -> Result<get_public_rooms_filtered::v3::Response> {
if let Some(server) = &body.server {
if services
.config
.forbidden_remote_room_directory_server_names
.is_match(server.host())
|| services
.config
.forbidden_remote_server_names
.is_match(server.host())
{
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
}
check_server_banned(&services, body.server.as_deref())?;
let response = get_public_rooms_filtered_helper(
&services,
@@ -92,19 +77,7 @@ pub(crate) async fn get_public_rooms_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_public_rooms::v3::Request>,
) -> Result<get_public_rooms::v3::Response> {
if let Some(server) = &body.server {
if services
.config
.forbidden_remote_room_directory_server_names
.is_match(server.host())
|| services
.config
.forbidden_remote_server_names
.is_match(server.host())
{
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
}
check_server_banned(&services, body.server.as_deref())?;
let response = get_public_rooms_filtered_helper(
&services,
@@ -138,12 +111,7 @@ pub(crate) async fn set_room_visibility_route(
) -> Result<set_room_visibility::v3::Response> {
let sender_user = body.sender_user();
if !services
.rooms
.metadata
.exists(&body.room_id)
.await
{
if !services.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
return Err!(Request(NotFound("Room not found")));
}
@@ -193,7 +161,7 @@ pub(crate) async fn set_room_visibility_route(
)));
}
services.rooms.directory.set_public(&body.room_id);
services.directory.set_public(&body.room_id);
if services.server.config.admin_room_notices {
services
@@ -206,10 +174,7 @@ pub(crate) async fn set_room_visibility_route(
}
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.directory.set_not_public(&body.room_id),
| _ => {
return Err!(Request(InvalidParam("Room visibility type is not supported.",)));
},
@@ -225,19 +190,13 @@ pub(crate) async fn get_room_visibility_route(
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
{
if !services.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
return Err!(Request(NotFound("Room not found")));
}
Ok(get_room_visibility::v3::Response {
visibility: if services
.rooms
.directory
.is_public_room(&body.room_id)
.await
@@ -261,8 +220,8 @@ pub(crate) async fn get_public_rooms_filtered_helper(
server.filter(|server_name| !services.globals.server_is_ours(server_name))
{
let response = services
.sending
.send_federation_request(
.federation
.execute(
other_server,
federation::directory::get_public_rooms_filtered::v1::Request {
limit,
@@ -308,32 +267,61 @@ pub(crate) async fn get_public_rooms_filtered_helper(
}
}
let search_term = filter
.generic_search_term
.as_deref()
.map(str::to_lowercase);
let search_room_id = filter
.generic_search_term
.as_deref()
.filter(|_| services.config.allow_public_room_search_by_id)
.filter(|s| s.starts_with('!'))
.filter(|s| s.len() > 5); // require some characters to limit scope.
let meta_public_rooms = search_room_id
.filter(|_| services.config.allow_unlisted_room_search_by_id)
.map(|prefix| services.metadata.public_ids_prefix(prefix))
.into_iter()
.stream()
.flatten();
let mut all_rooms: Vec<PublicRoomsChunk> = services
.rooms
.directory
.public_rooms()
.map(ToOwned::to_owned)
.chain(meta_public_rooms)
.wide_then(|room_id| public_rooms_chunk(services, room_id))
.ready_filter_map(|chunk| {
if !filter.room_types.is_empty() && !filter.room_types.contains(&RoomTypeFilter::from(chunk.room_type.clone())) {
if !filter.room_types.is_empty()
&& !filter
.room_types
.contains(&RoomTypeFilter::from(chunk.room_type.clone()))
{
return None;
}
if let Some(query) = filter.generic_search_term.as_ref().map(|q| q.to_lowercase()) {
if let Some(query) = search_room_id {
if chunk.room_id.as_str().contains(query) {
return Some(chunk);
}
}
if let Some(query) = search_term.as_deref() {
if let Some(name) = &chunk.name {
if name.as_str().to_lowercase().contains(&query) {
if name.as_str().to_lowercase().contains(query) {
return Some(chunk);
}
}
if let Some(topic) = &chunk.topic {
if topic.to_lowercase().contains(&query) {
if topic.to_lowercase().contains(query) {
return Some(chunk);
}
}
if let Some(canonical_alias) = &chunk.canonical_alias {
if canonical_alias.as_str().to_lowercase().contains(&query) {
if canonical_alias.as_str().to_lowercase().contains(query) {
return Some(chunk);
}
}
@@ -385,20 +373,14 @@ async fn user_can_publish_room(
room_id: &RoomId,
) -> Result<bool> {
match services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")
.get_power_levels(room_id)
.await
{
| Ok(event) => serde_json::from_str(event.content().get())
.map_err(|_| err!(Database("Invalid event content for m.room.power_levels")))
.map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content)
.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
}),
| Ok(power_levels) =>
Ok(power_levels.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)),
| _ => {
match services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
@@ -411,57 +393,57 @@ async fn user_can_publish_room(
}
async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> PublicRoomsChunk {
let name = services
.rooms
.state_accessor
.get_name(&room_id)
.ok();
let name = services.state_accessor.get_name(&room_id).ok();
let room_type = services
.rooms
.state_accessor
.get_room_type(&room_id)
.ok();
let canonical_alias = services
.rooms
.state_accessor
.get_canonical_alias(&room_id)
.ok()
.then(async |alias| {
if let Some(alias) = alias
&& services.globals.alias_is_local(&alias)
&& let Ok(alias_room_id) = services.alias.resolve_local_alias(&alias).await
&& alias_room_id == room_id
{
Some(alias)
} else {
None
}
});
let avatar_url = services
.state_accessor
.get_avatar(&room_id)
.map_ok(|content| content.url)
.ok();
let avatar_url = services.rooms.state_accessor.get_avatar(&room_id);
let topic = services
.rooms
.state_accessor
.get_room_topic(&room_id)
.ok();
let world_readable = services
.rooms
.state_accessor
.is_world_readable(&room_id);
let join_rule = services
.rooms
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "")
.map_ok(|c: RoomJoinRulesEventContent| match c.join_rule {
| JoinRule::Public => PublicRoomJoinRule::Public,
| JoinRule::Public => "public".into(),
| JoinRule::Knock => "knock".into(),
| JoinRule::KnockRestricted(_) => "knock_restricted".into(),
| _ => "invite".into(),
});
let guest_can_join = services
.rooms
.state_accessor
.guest_can_join(&room_id);
let guest_can_join = services.state_accessor.guest_can_join(&room_id);
let num_joined_members = services
.rooms
.state_cache
.room_joined_count(&room_id);
let num_joined_members = services.state_cache.room_joined_count(&room_id);
let (
(avatar_url, canonical_alias, guest_can_join, join_rule, name),
@@ -474,7 +456,7 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
.await;
PublicRoomsChunk {
avatar_url: avatar_url.into_option().unwrap_or_default().url,
avatar_url: avatar_url.flatten(),
canonical_alias,
guest_can_join,
join_rule: join_rule.unwrap_or_default(),
@@ -490,3 +472,26 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
world_readable,
}
}
fn check_server_banned(services: &Services, server: Option<&ServerName>) -> Result {
let Some(server) = server else {
return Ok(());
};
let conditions = [
services
.config
.forbidden_remote_room_directory_server_names
.is_match(server.host()),
services
.config
.forbidden_remote_server_names
.is_match(server.host()),
];
if conditions.iter().any(is_true!()) {
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
Ok(())
}
+116
View File
@@ -0,0 +1,116 @@
use std::iter::once;
use axum::extract::State;
use futures::StreamExt;
use ruma::api::client::peeking::listen_to_new_events::v3::{Request, Response};
use tokio::time::{Duration, Instant, timeout_at};
use tuwunel_core::{
Err, Event, Result, at,
matrix::PduCount,
utils::{
BoolExt,
result::FlatOk,
stream::{IterStream, ReadyExt},
},
};
use crate::Ruma;
const EVENT_LIMIT: usize = 50;
/// GET `/_matrix/client/v3/events`
pub(crate) async fn events_route(
State(services): State<crate::State>,
body: Ruma<Request>,
) -> Result<Response> {
let (sender_user, sender_device) = body.sender();
let from = body
.body
.from
.as_deref()
.map(str::parse)
.flat_ok()
.unwrap_or_default();
let timeout = body
.body
.timeout
.as_ref()
.map(Duration::as_millis)
.map(TryInto::try_into)
.flat_ok()
.unwrap_or(services.config.client_sync_timeout_default)
.max(services.config.client_sync_timeout_min)
.min(services.config.client_sync_timeout_max);
let Some(room_id) = body.room_id.as_deref() else {
//TODO: upgrade ruma
return Err!(Request(InvalidParam("Missing RoomId parameter.")));
};
if !services
.state_accessor
.user_can_see_state_events(sender_user, room_id)
.await
{
return Err!(Request(Forbidden("No room preview available.")));
}
let stop_at = Instant::now()
.checked_add(Duration::from_millis(timeout))
.expect("configuration must limit maximum timeout");
loop {
let watchers = services
.sync
.watch(sender_user, sender_device, once(room_id).stream());
let next_batch = services.globals.wait_pending().await?;
let events = services
.timeline
.pdus(Some(sender_user), room_id, Some(PduCount::Normal(from)))
.ready_filter_map(Result::ok)
.ready_take_while(|(count, _)| PduCount::Normal(next_batch).ge(count))
.take(EVENT_LIMIT)
.collect::<Vec<_>>()
.await;
if !events.is_empty() {
return Ok(Response {
start: events
.first()
.map(at!(0))
.as_ref()
.map(ToString::to_string),
end: events
.last()
.map(at!(0))
.as_ref()
.map(ToString::to_string),
chunk: events
.into_iter()
.map(at!(1))
.map(Event::into_format)
.collect(),
});
}
if timeout_at(stop_at, watchers).await.is_err() || services.server.is_stopping() {
return Ok(Response {
chunk: Default::default(),
start: body.body.from,
end: services
.server
.is_stopping()
.is_false()
.then_some(next_batch)
.as_ref()
.map(ToString::to_string),
});
}
}
}
+71 -107
View File
@@ -3,16 +3,13 @@
use axum::extract::State;
use futures::{StreamExt, stream::FuturesUnordered};
use ruma::{
OneTimeKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
CanonicalJsonObject, CanonicalJsonValue, OneTimeKeyAlgorithm, OwnedDeviceId, OwnedUserId,
UserId,
api::{
client::{
error::ErrorKind,
keys::{
claim_keys, get_key_changes, get_keys, upload_keys,
upload_signatures::{self},
upload_signing_keys,
},
uiaa::{AuthFlow, AuthType, UiaaInfo},
client::keys::{
claim_keys, get_key_changes, get_keys, upload_keys,
upload_signatures::{self},
upload_signing_keys,
},
federation,
},
@@ -20,11 +17,12 @@
serde::Raw,
};
use serde_json::json;
use tuwunel_core::{Err, Error, Result, debug, debug_warn, err, result::NotFound, utils};
use tuwunel_core::{
Err, Result, debug, debug_error, debug_warn, err, result::NotFound, utils::json,
};
use tuwunel_service::{Services, users::parse_master_key};
use super::SESSION_ID_LENGTH;
use crate::Ruma;
use crate::{Ruma, router::auth_uiaa};
/// # `POST /_matrix/client/r0/keys/upload`
///
@@ -39,26 +37,16 @@ pub(crate) async fn upload_keys_route(
) -> Result<upload_keys::v3::Response> {
let (sender_user, sender_device) = body.sender();
for (key_id, one_time_key) in &body.one_time_keys {
if one_time_key
.deserialize()
.inspect_err(|e| {
debug_warn!(
?key_id,
?one_time_key,
"Invalid one time key JSON submitted by client, skipping: {e}"
);
})
.is_err()
{
continue;
}
let one_time_keys = body
.one_time_keys
.iter()
.take(services.config.one_time_key_limit)
.map(|(id, val)| (id.as_ref(), val));
services
.users
.add_one_time_key(sender_user, sender_device, key_id, one_time_key)
.await?;
}
services
.users
.add_one_time_keys(sender_user, sender_device, one_time_keys)
.await?;
if let Some(device_keys) = &body.device_keys {
let deser_device_keys = device_keys.deserialize().map_err(|e| {
@@ -83,8 +71,11 @@ pub(crate) async fn upload_keys_route(
.users
.get_device_keys(sender_user, sender_device)
.await
.and_then(|keys| keys.deserialize().map_err(Into::into))
{
if existing_keys.json().get() == device_keys.json().get() {
// NOTE: also serves as a workaround for a nheko bug which omits cross-signing
// NOTE: signatures when re-uploading the same DeviceKeys.
if existing_keys.keys == deser_device_keys.keys {
debug!(
?sender_user,
?sender_device,
@@ -157,26 +148,17 @@ pub(crate) async fn upload_signing_keys_route(
State(services): State<crate::State>,
body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::v3::Response> {
let (sender_user, sender_device) = body.sender();
// UIAA
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
completed: Vec::new(),
params: Box::default(),
session: None,
auth_error: None,
};
// Access token is required for this endpoint regardless of conditional UIAA so
// we'll always have a sender_user.
match check_for_new_keys(
services,
sender_user,
body.sender_user(),
body.self_signing_key.as_ref(),
body.user_signing_key.as_ref(),
body.master_key.as_ref(),
)
.await
.inspect_err(|e| debug!(?e))
.inspect_err(|e| debug_error!(?e))
{
| Ok(exists) => {
if let Some(result) = exists {
@@ -184,45 +166,25 @@ pub(crate) async fn upload_signing_keys_route(
// (lost connection for example)
return Ok(result);
}
debug!(
"Skipping UIA in accordance with MSC3967, the user didn't have any existing keys"
);
// Some of the keys weren't found, so we let them upload
debug!("Skipping UIA in accordance with MSC3967, user had no existing keys");
},
| _ => {
match &body.auth {
| Some(auth) => {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
},
| _ => match body.json_body.as_ref() {
| Some(json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo));
},
| _ => {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
},
},
}
let authed_user = auth_uiaa(&services, &body).await?;
assert_eq!(
body.sender_user(),
authed_user,
"Expected UIAA of {0} and not {authed_user}",
body.sender_user(),
);
},
}
services
.users
.add_cross_signing_keys(
sender_user,
body.sender_user(),
&body.master_key,
&body.self_signing_key,
&body.user_signing_key,
@@ -248,6 +210,7 @@ async fn check_for_new_keys(
.users
.get_master_key(None, user_id, &|_| true)
.await;
if result.is_not_found() {
empty = true;
} else {
@@ -260,6 +223,7 @@ async fn check_for_new_keys(
}
}
}
if let Some(user_signing_key) = user_signing_key {
let key = services.users.get_user_signing_key(user_id).await;
if key.is_not_found() && !empty {
@@ -267,6 +231,7 @@ async fn check_for_new_keys(
"Tried to update an existing user signing key, UIA required"
)));
}
if !key.is_not_found() {
let existing_signing_key = key?.deserialize()?;
if existing_signing_key != user_signing_key.deserialize()? {
@@ -276,17 +241,20 @@ async fn check_for_new_keys(
}
}
}
if let Some(self_signing_key) = self_signing_key {
let key = services
.users
.get_self_signing_key(None, user_id, &|_| true)
.await;
if key.is_not_found() && !empty {
debug!(?key);
debug_error!(?key);
return Err!(Request(Forbidden(
"Tried to add a new signing key independently from the master key"
)));
}
if !key.is_not_found() {
let existing_signing_key = key?.deserialize()?;
if existing_signing_key != self_signing_key.deserialize()? {
@@ -296,6 +264,7 @@ async fn check_for_new_keys(
}
}
}
if empty {
return Ok(None);
}
@@ -379,12 +348,12 @@ pub(crate) async fn get_key_changes_route(
let from = body
.from
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?;
.map_err(|_| err!(Request(InvalidParam("Invalid `from`."))))?;
let to = body
.to
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?;
.map_err(|_| err!(Request(InvalidParam("Invalid `to`."))))?;
device_list_updates.extend(
services
@@ -396,7 +365,6 @@ pub(crate) async fn get_key_changes_route(
);
let mut rooms_joined = services
.rooms
.state_cache
.rooms_joined(sender_user)
.boxed();
@@ -526,7 +494,7 @@ pub(crate) async fn get_keys_helper<F>(
let mut futures: FuturesUnordered<_> = get_over_federation
.into_iter()
.map(|(server, vec)| async move {
.map(async |(server, vec)| {
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());
@@ -535,10 +503,7 @@ pub(crate) async fn get_keys_helper<F>(
let request =
federation::keys::get_keys::v1::Request { device_keys: device_keys_input_fed };
let response = services
.sending
.send_federation_request(server, request)
.await;
let response = services.federation.execute(server, request).await;
(server, response)
})
@@ -560,16 +525,16 @@ pub(crate) async fn get_keys_helper<F>(
.signatures
.append(&mut our_master_key.signatures);
}
let json = serde_json::to_value(master_key).expect("to_value always works");
let raw = serde_json::from_value(json).expect("Raw::from_value always works");
// Dont notify. A notification would trigger another key request resulting in
// an endless loop.
let notify = false;
let raw = Some(json::to_raw(master_key)?);
services
.users
.add_cross_signing_keys(
&user, &raw, &None, &None,
false, /* Dont notify. A notification would trigger another key
* request resulting in an endless loop */
)
.add_cross_signing_keys(&user, &raw, &None, &None, notify)
.await?;
if let Some(raw) = raw {
master_keys.insert(user.clone(), raw);
}
@@ -597,22 +562,21 @@ fn add_unsigned_device_display_name(
keys: &mut Raw<ruma::encryption::DeviceKeys>,
metadata: ruma::api::client::device::Device,
include_display_names: bool,
) -> serde_json::Result<()> {
) -> Result {
if let Some(display_name) = metadata.display_name {
let mut object = keys.deserialize_as::<serde_json::Map<String, serde_json::Value>>()?;
let mut object = keys.deserialize_as_unchecked::<CanonicalJsonObject>()?;
let unsigned = object
.entry("unsigned")
.or_insert_with(|| json!({}));
if let serde_json::Value::Object(unsigned_object) = unsigned {
if include_display_names {
unsigned_object.insert("device_display_name".to_owned(), display_name.into());
if let CanonicalJsonValue::Object(unsigned) = object
.entry("unsigned".into())
.or_insert_with(|| CanonicalJsonObject::default().into())
{
let display_name = if include_display_names {
CanonicalJsonValue::String(display_name.to_string())
} else {
unsigned_object.insert(
"device_display_name".to_owned(),
Some(metadata.device_id.as_str().to_owned()).into(),
);
}
CanonicalJsonValue::String(metadata.device_id.into())
};
unsigned.insert("device_display_name".into(), display_name);
}
*keys = Raw::from_json(serde_json::value::to_raw_value(&object)?);
@@ -656,7 +620,7 @@ pub(crate) async fn claim_keys_helper(
let mut futures: FuturesUnordered<_> = get_over_federation
.into_iter()
.map(|(server, vec)| async move {
.map(async |(server, vec)| {
let mut one_time_keys_input_fed = BTreeMap::new();
for (user_id, keys) in vec {
one_time_keys_input_fed.insert(user_id.clone(), keys.clone());
@@ -664,8 +628,8 @@ pub(crate) async fn claim_keys_helper(
(
server,
services
.sending
.send_federation_request(server, federation::keys::claim_keys::v1::Request {
.federation
.execute(server, federation::keys::claim_keys::v1::Request {
one_time_keys: one_time_keys_input_fed,
})
.await,
+7 -35
View File
@@ -1,9 +1,7 @@
use axum::extract::State;
use ruma::{
api::client::membership::ban_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
use futures::FutureExt;
use ruma::api::client::membership::ban_user;
use tuwunel_core::{Err, Result};
use crate::Ruma;
@@ -20,38 +18,12 @@ pub(crate) async fn ban_user_route(
return Err!(Request(Forbidden("You cannot ban yourself.")));
}
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let current_member_content = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Ban));
let state_lock = services.state.mutex.lock(&body.room_id).await;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Ban,
reason: body.reason.clone(),
displayname: None, // display name may be offensive
avatar_url: None, // avatar may be offensive
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
..current_member_content
}),
sender_user,
&body.room_id,
&state_lock,
)
.membership
.ban(&body.room_id, &body.user_id, body.reason.as_ref(), sender_user, &state_lock)
.boxed()
.await?;
drop(state_lock);
+4 -18
View File
@@ -21,18 +21,9 @@ pub(crate) async fn forget_room_route(
let user_id = body.sender_user();
let room_id = &body.room_id;
let joined = services
.rooms
.state_cache
.is_joined(user_id, room_id);
let knocked = services
.rooms
.state_cache
.is_knocked(user_id, room_id);
let invited = services
.rooms
.state_cache
.is_invited(user_id, room_id);
let joined = services.state_cache.is_joined(user_id, room_id);
let knocked = services.state_cache.is_knocked(user_id, room_id);
let invited = services.state_cache.is_invited(user_id, room_id);
pin_mut!(joined, knocked, invited);
if joined.or(knocked).or(invited).await {
@@ -40,7 +31,6 @@ pub(crate) async fn forget_room_route(
}
let membership = services
.rooms
.state_accessor
.get_member(room_id, user_id)
.await;
@@ -55,15 +45,11 @@ pub(crate) async fn forget_room_route(
if non_membership
|| services
.rooms
.state_cache
.is_left(user_id, room_id)
.await
{
services
.rooms
.state_cache
.forget(room_id, user_id);
services.state_cache.forget(room_id, user_id);
}
Ok(forget_room::v3::Response::new())
+50 -222
View File
@@ -1,19 +1,11 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use futures::{FutureExt, join};
use ruma::{
OwnedServerName, RoomId, UserId,
api::{client::membership::invite_user, federation::membership::create_invite},
events::room::member::{MembershipState, RoomMemberEventContent},
};
use tuwunel_core::{
Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
};
use tuwunel_service::Services;
use ruma::{api::client::membership::invite_user, events::room::member::MembershipState};
use tuwunel_core::{Err, Result};
use super::banned_room_check;
use crate::Ruma;
use crate::{Ruma, client::utils::invite_check};
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
///
@@ -26,220 +18,56 @@ pub(crate) async fn invite_user_route(
) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user();
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
debug_error!(
"User {sender_user} is not an admin and attempted to send an invite to room {}",
&body.room_id
);
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
let room_id = &body.room_id;
banned_room_check(
&services,
sender_user,
Some(&body.room_id),
body.room_id.server_name(),
client,
)
.await?;
invite_check(&services, sender_user, room_id).await?;
match &body.recipient {
| invite_user::v3::InvitationRecipient::UserId { user_id } => {
let sender_ignored_recipient = services
.users
.user_is_ignored(sender_user, user_id);
let recipient_ignored_by_sender = services
.users
.user_is_ignored(user_id, sender_user);
let (sender_ignored_recipient, recipient_ignored_by_sender) =
join!(sender_ignored_recipient, recipient_ignored_by_sender);
if sender_ignored_recipient {
return Ok(invite_user::v3::Response {});
}
if let Ok(target_user_membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, user_id)
.await
{
if target_user_membership.membership == MembershipState::Ban {
return Err!(Request(Forbidden("User is banned from this room.")));
}
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
}
invite_helper(
&services,
sender_user,
user_id,
&body.room_id,
body.reason.clone(),
false,
)
.boxed()
.await?;
Ok(invite_user::v3::Response {})
},
| _ => {
Err!(Request(NotFound("User not found.")))
},
}
}
pub(crate) async fn invite_helper(
services: &Services,
sender_user: &UserId,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
is_direct: bool,
) -> Result {
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
info!(
"User {sender_user} is not an admin and attempted to send an invite to room \
{room_id}"
);
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
if !services.globals.user_is_local(user_id) {
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
avatar_url: services.users.avatar_url(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
let (pdu, pdu_json) = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await?;
let invite_room_state = services.rooms.state.summary_stripped(&pdu).await;
drop(state_lock);
(pdu, pdu_json, invite_room_state)
};
let room_version_id = services
.rooms
.state
.get_room_version(room_id)
.await?;
let response = services
.sending
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
event: services
.sending
.convert_to_outgoing_federation_event(pdu_json.clone())
.await,
invite_room_state,
via: services
.rooms
.state_cache
.servers_route_via(room_id)
.await
.ok(),
})
.await?;
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
let (event_id, value) = gen_event_id_canonical_json(&response.event, &room_version_id)
.map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
if pdu.event_id != event_id {
return Err!(Request(BadJson(warn!(
%pdu.event_id, %event_id,
"Server {} sent event with wrong event ID",
user_id.server_name()
))));
}
let origin: OwnedServerName = serde_json::from_value(serde_json::to_value(
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event missing origin field."))))?,
)?)
.map_err(|e| {
err!(Request(BadJson(warn!("Origin field in event is not a valid server name: {e}"))))
})?;
let pdu_id = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
.boxed()
.await?
.ok_or_else(|| {
err!(Request(InvalidParam("Could not accept incoming PDU as timeline event.")))
})?;
return services
.sending
.send_pdu_room(room_id, &pdu_id)
.await;
}
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
return Err!(Request(Forbidden(
"You must be joined in the room you are trying to invite from."
)));
}
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
displayname: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
blurhash: services.users.blurhash(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
banned_room_check(&services, sender_user, Some(room_id), room_id.server_name(), client)
.await?;
drop(state_lock);
let invite_user::v3::InvitationRecipient::UserId { user_id } = &body.recipient else {
return Err!(Request(ThreepidDenied("Third party identifiers are not implemented")));
};
Ok(())
let sender_ignored_recipient = services
.users
.user_is_ignored(sender_user, user_id);
let recipient_ignored_by_sender = services
.users
.user_is_ignored(user_id, sender_user);
let (sender_ignored_recipient, recipient_ignored_by_sender) =
join!(sender_ignored_recipient, recipient_ignored_by_sender);
if sender_ignored_recipient {
return Ok(invite_user::v3::Response {});
}
// TODO: this should be in the service, but moving it from here would
// trigger the recipient_ignored_by_sender check before the banned check,
// revealing the ignore state to the sending user if the recipient is banned
if let Ok(target_user_membership) = services
.state_accessor
.get_member(room_id, user_id)
.await
{
if target_user_membership.membership == MembershipState::Ban {
return Err!(Request(Forbidden("User is banned from this room.")));
}
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
}
services
.membership
.invite(sender_user, user_id, room_id, body.reason.as_ref(), false)
.boxed()
.await?;
Ok(invite_user::v3::Response {})
}
File diff suppressed because it is too large Load Diff
+11 -45
View File
@@ -1,9 +1,7 @@
use axum::extract::State;
use ruma::{
api::client::membership::kick_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use tuwunel_core::{Err, Result, matrix::pdu::PduBuilder};
use futures::FutureExt;
use ruma::api::client::membership::kick_user;
use tuwunel_core::{Err, Result};
use crate::Ruma;
@@ -14,50 +12,18 @@ pub(crate) async fn kick_user_route(
State(services): State<crate::State>,
body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> {
let state_lock = services
.rooms
.state
.mutex
.lock(&body.room_id)
.await;
let sender_user = body.sender_user();
let Ok(event) = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
else {
// copy synapse's behaviour of returning 200 without any change to the state
// instead of erroring on left users
return Ok(kick_user::v3::Response::new());
};
if !matches!(
event.membership,
MembershipState::Invite | MembershipState::Knock | MembershipState::Join,
) {
return Err!(Request(Forbidden(
"Cannot kick a user who is not apart of the room (current membership: {})",
event.membership
)));
if sender_user == body.user_id {
return Err!(Request(Forbidden("You cannot kick yourself.")));
}
let state_lock = services.state.mutex.lock(&body.room_id).await;
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
..event
}),
body.sender_user(),
&body.room_id,
&state_lock,
)
.membership
.kick(&body.room_id, &body.user_id, body.reason.as_ref(), sender_user, &state_lock)
.boxed()
.await?;
drop(state_lock);
+72 -160
View File
@@ -4,11 +4,14 @@
use axum_client_ip::InsecureClientIp;
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, RoomId,
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedServerName, RoomId,
RoomVersionId, UserId,
api::{
client::knock::knock_room,
federation::{self},
federation::{
membership::RawStrippedState,
{self},
},
},
canonical_json::to_canonical_value,
events::{
@@ -17,14 +20,14 @@
},
};
use tuwunel_core::{
Err, Result, debug, debug_info, debug_warn, err, info,
Err, Result, debug, debug_info, debug_warn, err, extract_variant, info,
matrix::{
PduCount,
event::{Event, gen_event_id},
pdu::{PduBuilder, PduEvent},
},
result::FlatOk,
trace,
utils::{self, shuffle, stream::IterStream},
utils::{self},
warn,
};
use tuwunel_service::{
@@ -36,7 +39,7 @@
};
use super::banned_room_check;
use crate::Ruma;
use crate::{Ruma, client::membership::get_join_params};
/// # `POST /_matrix/client/*/knock/{roomIdOrAlias}`
///
@@ -50,102 +53,13 @@ pub(crate) async fn knock_room_route(
let sender_user = body.sender_user();
let body = &body.body;
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => {
banned_room_check(
&services,
sender_user,
Some(&room_id),
room_id.server_name(),
client,
)
.await?;
let (room_id, servers) =
get_join_params(&services, sender_user, &body.room_id_or_alias, &body.via).await?;
let mut servers = body.via.clone();
servers.extend(
services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client)
.await?;
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Some(server) = room_id.server_name() {
servers.push(server.to_owned());
}
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
(servers, room_id)
},
| Err(room_alias) => {
let (room_id, mut servers) = services
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
banned_room_check(
&services,
sender_user,
Some(&room_id),
Some(room_alias.server_name()),
client,
)
.await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
let addl_state_servers = services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default();
let mut addl_servers: Vec<_> = addl_state_servers
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
.await;
addl_servers.sort_unstable();
addl_servers.dedup();
shuffle(&mut addl_servers);
servers.append(&mut addl_servers);
(servers, room_id)
},
};
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers)
.boxed()
.await
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers).await
}
async fn knock_room_by_id_helper(
@@ -155,10 +69,9 @@ async fn knock_room_by_id_helper(
reason: Option<String>,
servers: &[OwnedServerName],
) -> Result<knock_room::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let state_lock = services.state.mutex.lock(room_id).await;
if services
.rooms
.state_cache
.is_invited(sender_user, room_id)
.await
@@ -170,7 +83,6 @@ async fn knock_room_by_id_helper(
}
if services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
@@ -180,7 +92,6 @@ async fn knock_room_by_id_helper(
}
if services
.rooms
.state_cache
.is_knocked(sender_user, room_id)
.await
@@ -190,7 +101,6 @@ async fn knock_room_by_id_helper(
}
if let Ok(membership) = services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.await
@@ -202,7 +112,6 @@ async fn knock_room_by_id_helper(
}
let server_in_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await;
@@ -234,11 +143,7 @@ async fn knock_room_helper_local(
) -> Result {
debug_info!("We can knock locally");
let room_version_id = services
.rooms
.state
.get_room_version(room_id)
.await?;
let room_version_id = services.state.get_room_version(room_id).await?;
if matches!(
room_version_id,
@@ -262,7 +167,6 @@ async fn knock_room_helper_local(
// Try normal knock first
let Err(error) = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
@@ -306,11 +210,11 @@ async fn knock_room_helper_local(
})?;
knock_event_stub.insert(
"origin".to_owned(),
"origin".into(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
knock_event_stub.insert(
"origin_server_ts".to_owned(),
"origin_server_ts".into(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
@@ -318,7 +222,7 @@ async fn knock_room_helper_local(
),
);
knock_event_stub.insert(
"content".to_owned(),
"content".into(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
@@ -340,30 +244,29 @@ async fn knock_room_helper_local(
// Add event_id
knock_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
.insert("event_id".into(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
let send_knock_request = federation::membership::create_knock_event::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.federation
.format_pdu_into(knock_event.clone(), Some(&room_version_id))
.await,
};
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.federation
.execute(&remote_server, send_knock_request)
.await?;
info!("send_knock finished");
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
@@ -374,8 +277,8 @@ async fn knock_room_helper_local(
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
info!("Updating membership locally to knock state with provided stripped state events");
let count = services.globals.next_count();
services
.rooms
.state_cache
.update_membership(
room_id,
@@ -384,15 +287,21 @@ async fn knock_room_helper_local(
.get_content::<RoomMemberEventContent>()
.expect("we just created this"),
sender_user,
Some(send_knock_response.knock_room_state),
Some(
send_knock_response
.knock_room_state
.into_iter()
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
.collect(),
),
None,
false,
PduCount::Normal(*count),
)
.await?;
info!("Appending room knock event locally");
services
.rooms
.timeline
.append_pdu(
&parsed_knock_pdu,
@@ -437,11 +346,11 @@ async fn knock_room_helper_remote(
})?;
knock_event_stub.insert(
"origin".to_owned(),
"origin".into(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
knock_event_stub.insert(
"origin_server_ts".to_owned(),
"origin_server_ts".into(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
@@ -449,7 +358,7 @@ async fn knock_room_helper_remote(
),
);
knock_event_stub.insert(
"content".to_owned(),
"content".into(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
@@ -471,30 +380,29 @@ async fn knock_room_helper_remote(
// Add event_id
knock_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
.insert("event_id".into(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let knock_event = knock_event_stub;
info!("Asking {remote_server} for send_knock in room {room_id}");
let send_knock_request = federation::knock::send_knock::v1::Request {
let send_knock_request = federation::membership::create_knock_event::v1::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
pdu: services
.sending
.convert_to_outgoing_federation_event(knock_event.clone())
.federation
.format_pdu_into(knock_event.clone(), Some(&room_version_id))
.await,
};
let send_knock_response = services
.sending
.send_federation_request(&remote_server, send_knock_request)
.federation
.execute(&remote_server, send_knock_request)
.await?;
info!("send_knock finished");
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
@@ -507,7 +415,14 @@ async fn knock_room_helper_remote(
let state = send_knock_response
.knock_room_state
.iter()
.map(|event| serde_json::from_str::<CanonicalJsonObject>(event.clone().into_json().get()))
.map(|event| {
serde_json::from_str::<CanonicalJsonObject>(
extract_variant!(event.clone(), RawStrippedState::Stripped)
.expect("Raw<AnyStrippedStateEvent>")
.json()
.get(),
)
})
.filter_map(Result::ok);
let mut state_map: HashMap<u64, OwnedEventId> = HashMap::new();
@@ -534,21 +449,19 @@ async fn knock_room_helper_remote(
let event_id = gen_event_id(&event, &room_version_id)?;
let shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&event_type, &state_key)
.await;
services
.rooms
.outlier
.timeline
.add_pdu_outlier(&event_id, &event);
state_map.insert(shortstatekey, event_id.clone());
}
info!("Compressing state from send_knock");
let compressed: CompressedState = services
.rooms
.state_compressor
.compress_state_events(
state_map
@@ -564,27 +477,24 @@ async fn knock_room_helper_remote(
added,
removed,
} = services
.rooms
.state_compressor
.save_state(room_id, Arc::new(compressed))
.await?;
debug!("Forcing state for new room");
services
.rooms
.state
.force_state(room_id, statehash_before_knock, added, removed, &state_lock)
.await?;
let statehash_after_knock = services
.rooms
.state
.append_to_state(&parsed_knock_pdu)
.await?;
info!("Updating membership locally to knock state with provided stripped state events");
let count = services.globals.next_count();
services
.rooms
.state_cache
.update_membership(
room_id,
@@ -593,15 +503,21 @@ async fn knock_room_helper_remote(
.get_content::<RoomMemberEventContent>()
.expect("we just created this"),
sender_user,
Some(send_knock_response.knock_room_state),
Some(
send_knock_response
.knock_room_state
.into_iter()
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
.collect(),
),
None,
false,
PduCount::Normal(*count),
)
.await?;
info!("Appending room knock event locally");
services
.rooms
.timeline
.append_pdu(
&parsed_knock_pdu,
@@ -615,7 +531,6 @@ async fn knock_room_helper_remote(
// We set the room state after inserting the pdu, so that we never have a moment
// in time where events in the current room state do not exist
services
.rooms
.state
.set_room_state(room_id, statehash_after_knock, &state_lock);
@@ -627,7 +542,7 @@ async fn make_knock_request(
sender_user: &UserId,
room_id: &RoomId,
servers: &[OwnedServerName],
) -> Result<(federation::knock::create_knock_event_template::v1::Response, OwnedServerName)> {
) -> Result<(federation::membership::prepare_knock_event::v1::Response, OwnedServerName)> {
let mut make_knock_response_and_server =
Err!(BadServerResponse("No server available to assist in knocking."));
@@ -641,18 +556,15 @@ async fn make_knock_request(
info!("Asking {remote_server} for make_knock ({make_knock_counter})");
let make_knock_response = services
.sending
.send_federation_request(
remote_server,
federation::knock::create_knock_event_template::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services
.server
.supported_room_versions()
.collect(),
},
)
.federation
.execute(remote_server, federation::membership::prepare_knock_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services
.server
.supported_room_versions()
.collect(),
})
.await;
trace!("make_knock response: {make_knock_response:?}");
+17 -380
View File
@@ -1,25 +1,7 @@
use std::collections::HashSet;
use axum::extract::State;
use futures::{FutureExt, StreamExt, TryFutureExt, pin_mut};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, RoomVersionId, UserId,
api::{
client::membership::leave_room,
federation::{self},
},
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use tuwunel_core::{
Err, Result, debug_info, debug_warn, err,
matrix::{event::gen_event_id, pdu::PduBuilder},
utils::{self, FutureBoolExt, future::ReadyEqExt},
warn,
};
use tuwunel_service::Services;
use futures::FutureExt;
use ruma::api::client::membership::leave_room;
use tuwunel_core::Result;
use crate::Ruma;
@@ -32,366 +14,21 @@ pub(crate) async fn leave_room_route(
State(services): State<crate::State>,
body: Ruma<leave_room::v3::Request>,
) -> Result<leave_room::v3::Response> {
leave_room(&services, body.sender_user(), &body.room_id, body.reason.clone())
let state_lock = services.state.mutex.lock(&body.room_id).await;
services
.membership
.leave(body.sender_user(), &body.room_id, body.reason.clone(), false, &state_lock)
.boxed()
.await
.map(|()| leave_room::v3::Response::new())
}
// Make a user leave all their joined rooms, rescinds knocks, forgets all rooms,
// and ignores errors
pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
let rooms_joined = services
.rooms
.state_cache
.rooms_joined(user_id)
.map(ToOwned::to_owned);
let rooms_invited = services
.rooms
.state_cache
.rooms_invited(user_id)
.map(|(r, _)| r);
let rooms_knocked = services
.rooms
.state_cache
.rooms_knocked(user_id)
.map(|(r, _)| r);
let all_rooms: Vec<_> = rooms_joined
.chain(rooms_invited)
.chain(rooms_knocked)
.collect()
.await;
for room_id in all_rooms {
// ignore errors
if let Err(e) = leave_room(services, user_id, &room_id, None)
.boxed()
.await
{
warn!(%user_id, "Failed to leave {room_id} remotely: {e}");
}
services
.rooms
.state_cache
.forget(&room_id, user_id);
}
}
pub async fn leave_room(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
) -> Result {
let default_member_content = RoomMemberEventContent {
membership: MembershipState::Leave,
reason: reason.clone(),
join_authorized_via_users_server: None,
is_direct: None,
avatar_url: None,
displayname: None,
third_party_invite: None,
blurhash: None,
};
let is_banned = services.rooms.metadata.is_banned(room_id);
let is_disabled = services.rooms.metadata.is_disabled(room_id);
pin_mut!(is_banned, is_disabled);
if is_banned.or(is_disabled).await {
// the room is banned/disabled, the room must be rejected locally since we
// cant/dont want to federate with this server
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
None,
None,
true,
)
.await?;
return Ok(());
}
let dont_have_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.eq(&false);
let not_knocked = services
.rooms
.state_cache
.is_knocked(user_id, room_id)
.eq(&false);
// Ask a remote server if we don't have this room and are not knocking on it
if dont_have_room.and(not_knocked).await {
if let Err(e) = remote_leave_room(services, user_id, room_id)
.boxed()
.await
{
warn!(%user_id, "Failed to leave room {room_id} remotely: {e}");
// Don't tell the client about this error
}
let last_state = services
.rooms
.state_cache
.invite_state(user_id, room_id)
.or_else(|_| {
services
.rooms
.state_cache
.knock_state(user_id, room_id)
})
.or_else(|_| {
services
.rooms
.state_cache
.left_state(user_id, room_id)
})
.await
.ok();
// We always drop the invite, we can't rely on other servers
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
last_state,
None,
true,
)
.await?;
} else {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let Ok(event) = services
.rooms
.state_accessor
.room_state_get_content::<RoomMemberEventContent>(
room_id,
&StateEventType::RoomMember,
user_id.as_str(),
)
.await
else {
debug_warn!(
"Trying to leave a room you are not a member of, marking room as left locally."
);
return services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
default_member_content,
user_id,
None,
None,
true,
)
.await;
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason,
join_authorized_via_users_server: None,
is_direct: None,
..event
}),
user_id,
room_id,
&state_lock,
)
.await?;
}
Ok(())
}
async fn remote_leave_room(
services: &Services,
user_id: &UserId,
room_id: &RoomId,
) -> Result<()> {
let mut make_leave_response_and_server =
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
let mut servers: HashSet<OwnedServerName> = services
.rooms
.state_cache
.servers_invite_via(room_id)
.map(ToOwned::to_owned)
.collect()
.await;
match services
.rooms
.state_cache
.invite_state(user_id, room_id)
.await
{
| Ok(invite_state) => {
servers.extend(
invite_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
},
| _ => {
match services
.rooms
.state_cache
.knock_state(user_id, room_id)
.await
{
| Ok(knock_state) => {
servers.extend(
knock_state
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.filter_map(|sender| {
if !services.globals.user_is_local(sender) {
Some(sender.server_name().to_owned())
} else {
None
}
}),
);
},
| _ => {},
}
},
}
if let Some(room_id_server_name) = room_id.server_name() {
servers.insert(room_id_server_name.to_owned());
}
debug_info!("servers in remote_leave_room: {servers:?}");
for remote_server in servers {
let make_leave_response = services
.sending
.send_federation_request(
&remote_server,
federation::membership::prepare_leave_event::v1::Request {
room_id: room_id.to_owned(),
user_id: user_id.to_owned(),
},
)
.await;
make_leave_response_and_server = make_leave_response.map(|r| (r, remote_server));
if make_leave_response_and_server.is_ok() {
break;
}
}
let (make_leave_response, remote_server) = make_leave_response_and_server?;
let Some(room_version_id) = make_leave_response.room_version else {
return Err!(BadServerResponse(warn!(
"No room version was returned by {remote_server} for {room_id}, room version is \
likely not supported by tuwunel"
)));
};
if !services
.server
.supported_room_version(&room_version_id)
{
return Err!(BadServerResponse(warn!(
"Remote room version {room_version_id} for {room_id} is not supported by conduwuit",
)));
}
let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(
make_leave_response.event.get(),
)
.map_err(|e| {
err!(BadServerResponse(warn!(
"Invalid make_leave event json received from {remote_server} for {room_id}: {e:?}"
)))
})?;
// TODO: Is origin needed?
leave_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
leave_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
// room v3 and above removed the "event_id" field from remote PDU format
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
leave_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&leave_event_stub, &room_version_id)?;
// Add event_id back
leave_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let leave_event = leave_event_stub;
services
.sending
.send_federation_request(
&remote_server,
federation::membership::create_leave_event::v2::Request {
room_id: room_id.to_owned(),
event_id,
pdu: services
.sending
.convert_to_outgoing_federation_event(leave_event.clone())
.await,
},
)
.await?;
Ok(())
if services.config.delete_rooms_after_leave {
services
.delete
.delete_if_empty_local(&body.room_id, state_lock)
.boxed()
.await;
}
Ok(leave_room::v3::Response {})
}
+52 -40
View File
@@ -1,21 +1,24 @@
use axum::extract::State;
use futures::{FutureExt, StreamExt, future::join};
use futures::{FutureExt, StreamExt, pin_mut};
use ruma::{
api::client::membership::{
get_member_events::{self, v3::MembershipEventFilter},
get_member_events::{self},
joined_members::{self, v3::RoomMember},
},
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
room::{
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
member::{MembershipState, RoomMemberEventContent},
},
},
};
use tuwunel_core::{
Err, Result, at,
matrix::Event,
utils::{
future::TryExtExt,
stream::{BroadbandExt, ReadyExt},
future::{BoolExt, TryExtExt},
stream::ReadyExt,
},
};
@@ -31,22 +34,20 @@ pub(crate) async fn get_member_events_route(
State(services): State<crate::State>,
body: Ruma<get_member_events::v3::Request>,
) -> Result<get_member_events::v3::Response> {
let sender_user = body.sender_user();
let membership = body.membership.as_ref();
let not_membership = body.not_membership.as_ref();
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)
.user_can_see_state_events(body.sender_user(), &body.room_id)
.await
{
return Err!(Request(Forbidden("You don't have permission to view this room.")));
return Err!(Request(Forbidden(
"You aren't a member of the room and weren't previously a member of the room."
)));
}
let membership = body.membership.as_ref();
let not_membership = body.not_membership.as_ref();
Ok(get_member_events::v3::Response {
chunk: services
.rooms
.state_accessor
.room_state_full(&body.room_id)
.ready_filter_map(Result::ok)
@@ -70,53 +71,64 @@ pub(crate) async fn joined_members_route(
State(services): State<crate::State>,
body: Ruma<joined_members::v3::Request>,
) -> Result<joined_members::v3::Response> {
if !services
.rooms
let is_joined = services
.state_cache
.is_joined(body.sender_user(), &body.room_id);
let is_world_readable = services
.state_accessor
.user_can_see_state_events(body.sender_user(), &body.room_id)
.await
{
return Err!(Request(Forbidden("You don't have permission to view this room.")));
.room_state_get_content(&body.room_id, &StateEventType::RoomHistoryVisibility, "")
.map_ok_or(false, |c: RoomHistoryVisibilityEventContent| {
c.history_visibility == HistoryVisibility::WorldReadable
});
pin_mut!(is_joined, is_world_readable);
if !is_joined.or(is_world_readable).await {
return Err!(Request(Forbidden("You aren't a member of the room.")));
}
Ok(joined_members::v3::Response {
joined: services
.rooms
.state_cache
.room_members(&body.room_id)
.map(ToOwned::to_owned)
.broad_then(|user_id| async move {
let (display_name, avatar_url) = join(
services.users.displayname(&user_id).ok(),
services.users.avatar_url(&user_id).ok(),
)
.await;
.state_accessor
.room_state_full(&body.room_id)
.ready_filter_map(Result::ok)
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
.map(at!(1))
.ready_filter_map(|pdu| membership_filter(pdu, Some(&MembershipState::Join), None))
.ready_filter_map(|pdu| {
let content = pdu.get_content::<RoomMemberEventContent>().ok()?;
let sender = pdu.sender().to_owned();
let member = RoomMember {
display_name: content.displayname,
avatar_url: content.avatar_url,
};
(user_id, RoomMember { display_name, avatar_url })
Some((sender, member))
})
.collect()
.boxed()
.await,
})
}
fn membership_filter<Pdu: Event>(
pdu: Pdu,
for_membership: Option<&MembershipEventFilter>,
not_membership: Option<&MembershipEventFilter>,
for_membership: Option<&MembershipState>,
not_membership: Option<&MembershipState>,
) -> Option<impl Event> {
let membership_state_filter = match for_membership {
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
| Some(MembershipEventFilter::Leave) => MembershipState::Leave,
| Some(MembershipState::Ban) => MembershipState::Ban,
| Some(MembershipState::Invite) => MembershipState::Invite,
| Some(MembershipState::Knock) => MembershipState::Knock,
| Some(MembershipState::Leave) => MembershipState::Leave,
| Some(_) | None => MembershipState::Join,
};
let not_membership_state_filter = match not_membership {
| Some(MembershipEventFilter::Ban) => MembershipState::Ban,
| Some(MembershipEventFilter::Invite) => MembershipState::Invite,
| Some(MembershipEventFilter::Join) => MembershipState::Join,
| Some(MembershipEventFilter::Knock) => MembershipState::Knock,
| Some(MembershipState::Ban) => MembershipState::Ban,
| Some(MembershipState::Invite) => MembershipState::Invite,
| Some(MembershipState::Join) => MembershipState::Join,
| Some(MembershipState::Knock) => MembershipState::Knock,
| Some(_) | None => MembershipState::Leave,
};

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