mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-08-07 06:21:08 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a029ee09 | ||
|
|
a3f6971579 | ||
|
|
51681aec1b | ||
|
|
39c84fabb4 | ||
|
|
f77bd41837 | ||
|
|
6f34b8e9ca | ||
|
|
5051da493a | ||
|
|
ff0e007c45 | ||
|
|
b85fb5ea6f | ||
|
|
e905538269 | ||
|
|
6f672b7304 | ||
|
|
4363ed6ec3 | ||
|
|
dd50a4cb0b | ||
|
|
e0a997c227 | ||
|
|
e1f89b69ea | ||
|
|
888f72d8d0 | ||
|
|
06618eadab | ||
|
|
05390d6097 | ||
|
|
1f803fe3a9 | ||
|
|
1492d68e25 | ||
|
|
c1aa94fb91 | ||
|
|
7320d0a40b | ||
|
|
abded2d442 | ||
|
|
4afd6f347b | ||
|
|
6b8d6956a3 | ||
|
|
f59d62c01c | ||
|
|
a14556da97 | ||
|
|
8b1de3d8db | ||
|
|
240b498489 | ||
|
|
d680a6ba53 | ||
|
|
aa3f14cd57 | ||
|
|
15627bc8d0 | ||
|
|
084facf474 | ||
|
|
d24986edf1 | ||
|
|
ce1ac277a6 | ||
|
|
7aeed0a95a | ||
|
|
9265748a57 | ||
|
|
e85cfdf48a | ||
|
|
48923b3657 | ||
|
|
aedaf3f0c1 | ||
|
|
b24b59dc38 | ||
|
|
738b5e3fa5 | ||
|
|
1f0cfec5ca | ||
|
|
9c5caa3a5f | ||
|
|
1cf4a26ae9 | ||
|
|
3694ffbab3 | ||
|
|
af0e01e016 | ||
|
|
5e89f0acae | ||
|
|
563873af77 | ||
|
|
d2072080c9 | ||
|
|
e191730950 | ||
|
|
f660e00bb5 | ||
|
|
f613d0c2ad | ||
|
|
7596ad2019 | ||
|
|
384add9784 | ||
|
|
a234f019b1 | ||
|
|
e8a87bdfa3 | ||
|
|
6a4aff424f | ||
|
|
8959d9e2c1 | ||
|
|
a30c043386 | ||
|
|
a39ef994d2 | ||
|
|
b714f24029 | ||
|
|
2263f2e874 | ||
|
|
9abe9becd6 | ||
|
|
4b74c01895 | ||
|
|
1223763e2b | ||
|
|
9b64c1f105 | ||
|
|
212a8434a8 | ||
|
|
bfaac8b5a2 | ||
|
|
9af15ecbba | ||
|
|
f66a83763e | ||
|
|
718c3adcb2 | ||
|
|
af80482c04 | ||
|
|
a20ddcd586 | ||
|
|
b483306367 | ||
|
|
f5e98467be | ||
|
|
d44db45f83 | ||
|
|
0397bb8237 | ||
|
|
4010fc62bc | ||
|
|
0d823a2822 | ||
|
|
9cd175b125 | ||
|
|
050a1a350a | ||
|
|
ec0f872f8f | ||
|
|
024e8eae62 | ||
|
|
4fd60b2605 | ||
|
|
e53968d9eb | ||
|
|
7cbc2ee385 | ||
|
|
0df5e5e7ac | ||
|
|
312eb69450 | ||
|
|
5b620a2c37 | ||
|
|
3b4fbb8c1a | ||
|
|
158d44e1a9 | ||
|
|
d2aab468cf | ||
|
|
aa4486dfdf | ||
|
|
2a662445b6 | ||
|
|
5b3f0fde23 | ||
|
|
9640afebff | ||
|
|
dd5c5c7a4a | ||
|
|
fc0f04defa | ||
|
|
7d8f7cbe5d | ||
|
|
4e0249cd2f | ||
|
|
d5b39aa995 | ||
|
|
46c940b863 | ||
|
|
ab8536d5c3 | ||
|
|
4918868632 | ||
|
|
9e00f70197 | ||
|
|
d3aaf9e4a9 | ||
|
|
96dc56ad07 | ||
|
|
e12b0262da | ||
|
|
e5bf005eaf | ||
|
|
02ccb1dceb | ||
|
|
a450eb96eb | ||
|
|
155af0fda3 | ||
|
|
5c61b4d4a3 | ||
|
|
97e709492c | ||
|
|
51fc2342a4 | ||
|
|
4ca68deef8 | ||
|
|
4d8d64f5c7 | ||
|
|
dff30e5924 | ||
|
|
7fee459b1a | ||
|
|
a6127fcd1a | ||
|
|
6c724bbc2f | ||
|
|
b1ea7b101d | ||
|
|
4baa25f66f | ||
|
|
227b77e58e | ||
|
|
54057da84e | ||
|
|
732825a390 | ||
|
|
6b74425f76 | ||
|
|
74a576caf7 | ||
|
|
2b7cf7d5d5 | ||
|
|
443248965d | ||
|
|
ee777bc287 | ||
|
|
aef38b1178 | ||
|
|
4743a8d968 | ||
|
|
53ab6742c8 | ||
|
|
46193de7e8 | ||
|
|
9253f46c80 | ||
|
|
5c127b5abd | ||
|
|
17f6f1a5a6 | ||
|
|
75509d50ca | ||
|
|
1d14426018 | ||
|
|
2b9563be67 | ||
|
|
68c4f60bb3 | ||
|
|
d95c3f126f | ||
|
|
f1c2548807 | ||
|
|
eda45e445c | ||
|
|
049defe977 | ||
|
|
3c073110b8 | ||
|
|
8d6bfde5a0 | ||
|
|
43f0882d83 | ||
|
|
fed52d24e4 | ||
|
|
e6c85c97c6 | ||
|
|
368ead20a6 | ||
|
|
a803b84b27 | ||
|
|
1058fbe9a7 | ||
|
|
ae4aad3641 | ||
|
|
95435ffe98 | ||
|
|
63e2cfa21b | ||
|
|
9383922d09 | ||
|
|
ae52676e33 | ||
|
|
292b601755 | ||
|
|
1313eb0b64 | ||
|
|
ba12773a5a | ||
|
|
83afe81f60 | ||
|
|
f2740822e2 | ||
|
|
2417764771 | ||
|
|
41ff81f843 | ||
|
|
fbcf4ba4f3 | ||
|
|
af3cdf9263 | ||
|
|
9d9ace1452 | ||
|
|
83d64e0879 | ||
|
|
4cae17e83d | ||
|
|
1c6992ccd4 | ||
|
|
89be6dc097 | ||
|
|
a47f8f8a82 | ||
|
|
aac5006bf5 | ||
|
|
6d3ed09a2b | ||
|
|
377b7166f0 | ||
|
|
85b3de055d | ||
|
|
b9c790326a | ||
|
|
4e5910471b | ||
|
|
c3bc8c14f7 | ||
|
|
ac3ceb1b95 | ||
|
|
aa37e32471 | ||
|
|
887a22dabd | ||
|
|
6dca02860c | ||
|
|
86103183b3 | ||
|
|
8f4cc87051 | ||
|
|
14721c90c9 | ||
|
|
6d3b2d864f | ||
|
|
7cf246eb73 | ||
|
|
bffb7f89c8 | ||
|
|
5467c9e486 | ||
|
|
89a67af607 | ||
|
|
6bb101ac51 | ||
|
|
1c0b4e94ac | ||
|
|
9c4d376bec | ||
|
|
d6e95c51c0 | ||
|
|
d5ce4b316f | ||
|
|
8f07a6c60f | ||
|
|
b882e7efdb | ||
|
|
e0169e3dca | ||
|
|
fe46755418 | ||
|
|
bbac80d2e6 | ||
|
|
a31c9b0c62 | ||
|
|
cf4e65c607 | ||
|
|
f32b6ae17d | ||
|
|
27222f23d2 | ||
|
|
ed324d5972 | ||
|
|
69075b166f | ||
|
|
e3a711482a | ||
|
|
6759187b37 | ||
|
|
83ed29eb65 | ||
|
|
6a685b7ee9 | ||
|
|
fb4dfafe2d | ||
|
|
8dbea51968 | ||
|
|
7b5905bbca | ||
|
|
42d143c013 | ||
|
|
10fb1cd192 | ||
|
|
1322ba1b00 | ||
|
|
d73b4332bf | ||
|
|
11eeca7e01 | ||
|
|
19dc5fafd4 | ||
|
|
54b347b855 | ||
|
|
8ed61aecb0 | ||
|
|
bf8aa57d03 | ||
|
|
a9f1926654 | ||
|
|
248d778290 | ||
|
|
2e13e87e43 | ||
|
|
ba19d407d0 | ||
|
|
de7c5dcbc8 | ||
|
|
adadafa88f | ||
|
|
deda746222 | ||
|
|
29c2c8a333 | ||
|
|
296018f0cc | ||
|
|
6295ca135a | ||
|
|
b5a9884194 | ||
|
|
1be7fd9247 | ||
|
|
be1264965a | ||
|
|
f6d2ce2f22 | ||
|
|
3cbe1e25cd | ||
|
|
897322964c | ||
|
|
996a5488be | ||
|
|
0ed4bd1e07 | ||
|
|
da79de5381 | ||
|
|
1bcf3ae19a | ||
|
|
91e500efe3 | ||
|
|
35b1ebaf3b | ||
|
|
ea5deb7e85 | ||
|
|
542cc51047 | ||
|
|
3225db0ddd | ||
|
|
a164da8b58 | ||
|
|
7221d466ce | ||
|
|
f6c88e3a16 | ||
|
|
3deebeab78 | ||
|
|
1d2818de58 | ||
|
|
86781522b6 | ||
|
|
4b49aaad53 | ||
|
|
3a78ba2b16 | ||
|
|
685946faed | ||
|
|
13c9385ef7 | ||
|
|
6a583a359e | ||
|
|
eec6e45358 | ||
|
|
804257eb52 |
+56
-17
@@ -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-v1-linux-gnu"]'
|
||||
default_sys_targets: '["x86_64-v1-linux-gnu", "x86_64-v3-linux-gnu"]'
|
||||
default_machines: '["X64"]'
|
||||
|
||||
outputs:
|
||||
@@ -38,14 +38,18 @@ 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 || !contains(github.ref, 'refs/pull/')}}
|
||||
publish: ${{vars.PUBLISH || !contains(github.ref, 'refs/pull/')}}
|
||||
build_pkgs: ${{vars.BUILD_PKGS || github.ref == 'refs/heads/main' || contains(github.ref, 'tags/v')}}
|
||||
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 || 'false'}}
|
||||
complement: ${{vars.COMPLEMENT || 'true'}}
|
||||
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
|
||||
@@ -58,8 +62,8 @@ jobs:
|
||||
- name: Initialize Builder
|
||||
env:
|
||||
runner: ${{matrix.runner}}
|
||||
reserved_space: '{"het": "128GB", "aws": "48GB", "gcp": "160GB"}'
|
||||
max_used_space: '{"het": "256GB", "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}"
|
||||
@@ -114,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]
|
||||
@@ -129,17 +135,22 @@ 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-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
@@ -157,11 +168,15 @@ jobs:
|
||||
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:
|
||||
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}}
|
||||
@@ -177,12 +192,13 @@ jobs:
|
||||
{"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-v1-linux-gnu"},
|
||||
{"rust_target": "aarch64-unknown-linux-gnu", "sys_target": "x86_64-v2-linux-gnu"},
|
||||
@@ -206,14 +222,19 @@ jobs:
|
||||
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}}
|
||||
@@ -226,8 +247,10 @@ 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"},
|
||||
@@ -236,6 +259,8 @@ jobs:
|
||||
{"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", "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"},
|
||||
@@ -245,16 +270,27 @@ jobs:
|
||||
{"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]
|
||||
@@ -262,6 +298,7 @@ jobs:
|
||||
with:
|
||||
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}}
|
||||
@@ -273,11 +310,13 @@ 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_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"},
|
||||
@@ -286,11 +325,11 @@ jobs:
|
||||
{"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-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-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"},
|
||||
]
|
||||
|
||||
secrets:
|
||||
|
||||
@@ -44,6 +44,9 @@ on:
|
||||
check_pkgs:
|
||||
type: string
|
||||
default: 'false'
|
||||
build_nix:
|
||||
type: string
|
||||
default: 'true'
|
||||
|
||||
jobs:
|
||||
book:
|
||||
@@ -155,7 +158,7 @@ jobs:
|
||||
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"]'
|
||||
@@ -176,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.sys_targets), 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
|
||||
|
||||
+188
-22
@@ -38,24 +38,53 @@ 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.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}}
|
||||
@@ -67,9 +96,58 @@ jobs:
|
||||
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,14 +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"},
|
||||
@@ -104,6 +186,7 @@ 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"},
|
||||
@@ -111,6 +194,7 @@ jobs:
|
||||
{"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", "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"},
|
||||
@@ -121,36 +205,118 @@ jobs:
|
||||
{"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.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[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: '["x86_64-v1-linux-gnu"]'
|
||||
sys_targets: '["x86_64-v3-linux-gnu"]'
|
||||
machines: ${{inputs.machines}}
|
||||
runner: ${{inputs.complement_runner}}
|
||||
excludes: ${{inputs.excludes}}
|
||||
@@ -159,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.sys_targets), fromJSON('["x86_64-v1-linux-gnu"]')[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
|
||||
@@ -176,13 +342,13 @@ 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('["x86_64-v1-linux-gnu"]')}}
|
||||
sys_target: ${{fromJSON('["x86_64-v3-linux-gnu"]')}}
|
||||
machine: ${{fromJSON(inputs.machines)}}
|
||||
exclude: ${{fromJSON(inputs.excludes)}}
|
||||
include: ${{fromJSON(inputs.includes)}}
|
||||
@@ -213,7 +379,7 @@ 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
|
||||
|
||||
- if: success() || failure() && steps.execute.outcome == 'failure'
|
||||
name: Upload New Results
|
||||
@@ -221,7 +387,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_results-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
|
||||
path: ./tests/test_results/complement/test_results.jsonl
|
||||
path: ./tests/complement/results.jsonl
|
||||
|
||||
- if: failure() && steps.execute.outcome == 'failure'
|
||||
name: Upload Failure Output
|
||||
|
||||
Generated
+743
-737
File diff suppressed because it is too large
Load Diff
+53
-38
@@ -28,8 +28,8 @@ keywords = [
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/matrix-construct/tuwunel"
|
||||
rust-version = "1.88.0"
|
||||
version = "1.4.1"
|
||||
rust-version = "1.89.0"
|
||||
version = "1.4.6"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "tuwunel"
|
||||
@@ -104,7 +104,7 @@ features = [
|
||||
version = "1.10"
|
||||
|
||||
[workspace.dependencies.bytesize]
|
||||
version = "2.0"
|
||||
version = "2.1"
|
||||
|
||||
[workspace.dependencies.cargo_toml]
|
||||
version = "0.22"
|
||||
@@ -140,10 +140,19 @@ features = [
|
||||
version = "0.8.3"
|
||||
|
||||
[workspace.dependencies.const-str]
|
||||
version = "0.6"
|
||||
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"
|
||||
version = "0.5"
|
||||
|
||||
[workspace.dependencies.cyborgtime]
|
||||
version = "2.1"
|
||||
@@ -163,15 +172,6 @@ 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"
|
||||
default-features = false
|
||||
@@ -192,7 +192,7 @@ version = "1.3"
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.hyper]
|
||||
version = "1.6"
|
||||
version = "1.7"
|
||||
default-features = false
|
||||
features = [
|
||||
"server",
|
||||
@@ -220,7 +220,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.insta]
|
||||
version = "1.43.1"
|
||||
version = "1.43"
|
||||
features = [
|
||||
"json",
|
||||
]
|
||||
@@ -262,7 +262,7 @@ version = "0.1"
|
||||
version = "1.0"
|
||||
|
||||
[workspace.dependencies.minicbor]
|
||||
version = "2.0"
|
||||
version = "2.1"
|
||||
features = ["std"]
|
||||
|
||||
[workspace.dependencies.minicbor-serde]
|
||||
@@ -272,13 +272,16 @@ features = ["std"]
|
||||
[workspace.dependencies.nix]
|
||||
version = "0.30"
|
||||
default-features = false
|
||||
features = ["resource"]
|
||||
features = [
|
||||
"resource",
|
||||
"user",
|
||||
]
|
||||
|
||||
[workspace.dependencies.num-traits]
|
||||
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]
|
||||
@@ -286,7 +289,7 @@ version = "0.30.0"
|
||||
#features = ["rt-tokio"]
|
||||
|
||||
[workspace.dependencies.opentelemetry_sdk]
|
||||
version = "0.30"
|
||||
version = "0.31"
|
||||
features = ["rt-tokio"]
|
||||
|
||||
[workspace.dependencies.proc-macro2]
|
||||
@@ -299,7 +302,7 @@ version = "1.0"
|
||||
version = "0.8"
|
||||
|
||||
[workspace.dependencies.regex]
|
||||
version = "1.11"
|
||||
version = "1.12"
|
||||
|
||||
[workspace.dependencies.reqwest]
|
||||
version = "0.12"
|
||||
@@ -317,7 +320,7 @@ default-features = false
|
||||
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://github.com/matrix-construct/ruma"
|
||||
rev = "8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6"
|
||||
rev = "db4bf59eaa1757735f38489f8924348ae05a6a17"
|
||||
features = [
|
||||
"__compat",
|
||||
"appservice-api-c",
|
||||
@@ -336,6 +339,7 @@ features = [
|
||||
"unstable-msc2870",
|
||||
"unstable-msc3026",
|
||||
"unstable-msc3061",
|
||||
"unstable-msc3814",
|
||||
"unstable-msc3245",
|
||||
"unstable-msc3381", # polls
|
||||
"unstable-msc3489", # beacon / live location
|
||||
@@ -345,11 +349,12 @@ features = [
|
||||
"unstable-msc4121",
|
||||
"unstable-msc4125",
|
||||
"unstable-msc4133",
|
||||
"unstable-msc4143",
|
||||
"unstable-msc4186",
|
||||
"unstable-msc4203", # sending to-device events to appservices
|
||||
"unstable-msc4310",
|
||||
"unstable-msc4311",
|
||||
"unstable-extensible-events",
|
||||
"unstable-hydra",
|
||||
]
|
||||
|
||||
[workspace.dependencies.rustls]
|
||||
@@ -381,7 +386,7 @@ version = "0.4"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.sentry]
|
||||
version = "0.42"
|
||||
version = "0.45"
|
||||
default-features = false
|
||||
features = [
|
||||
"backtrace",
|
||||
@@ -397,10 +402,10 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.sentry-tower]
|
||||
version = "0.42"
|
||||
version = "0.45"
|
||||
|
||||
[workspace.dependencies.sentry-tracing]
|
||||
version = "0.42"
|
||||
version = "0.45"
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
version = "1.0"
|
||||
@@ -430,12 +435,13 @@ version = "0.10"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.similar]
|
||||
version = "2.7.0"
|
||||
version = "2.7"
|
||||
|
||||
[workspace.dependencies.smallstr]
|
||||
version = "0.3"
|
||||
features = [
|
||||
"ffi",
|
||||
"serde",
|
||||
"std",
|
||||
"union",
|
||||
]
|
||||
@@ -462,7 +468,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.termimad]
|
||||
version = "0.33"
|
||||
version = "0.34"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.thiserror]
|
||||
@@ -494,7 +500,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
version = "1.47"
|
||||
version = "1.48"
|
||||
default-features = false
|
||||
features = [
|
||||
"fs",
|
||||
@@ -536,21 +542,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.20"
|
||||
version = "0.3"
|
||||
default-features = false
|
||||
features = [
|
||||
"ansi",
|
||||
@@ -699,7 +705,7 @@ inherits = "release-native.build-override"
|
||||
|
||||
[profile.bench]
|
||||
debug = "limited"
|
||||
strip = false
|
||||
strip = "none"
|
||||
#rustflags = [
|
||||
# "-Cremark=all",
|
||||
# '-Ztime-passes',
|
||||
@@ -719,7 +725,7 @@ strip = false
|
||||
# and can be raised if build times are tolerable.
|
||||
|
||||
[profile.dev]
|
||||
debug = "full"
|
||||
debug = 0
|
||||
#rustflags = [
|
||||
# '--cfg', 'tuwunel_mods',
|
||||
# '-Ztime-passes',
|
||||
@@ -779,7 +785,7 @@ inherits = "dev"
|
||||
|
||||
[profile.dev.package.'*']
|
||||
inherits = "dev"
|
||||
debug = "limited"
|
||||
debug = 0
|
||||
incremental = false
|
||||
codegen-units = 1
|
||||
opt-level = 'z'
|
||||
@@ -796,6 +802,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"
|
||||
@@ -887,6 +898,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,6 +913,7 @@ 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 }
|
||||
@@ -913,6 +928,7 @@ 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 }
|
||||
@@ -958,7 +974,6 @@ 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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+42
-4
@@ -1,9 +1,47 @@
|
||||
# Tuwunel 1.4.1
|
||||
# Tuwunel 1.4.6
|
||||
|
||||
September 2, 2025
|
||||
November 6, 2025
|
||||
|
||||
Bridge and Application Service users must update from Tuwunel 1.4.0 to this patch. All other users are encouraged to update at their convenience.
|
||||
### New Features
|
||||
|
||||
- Element Call discovery support was implemented by @tototomate123 in (#209). Adding a `[[global.well_known.rtc_transports]]` section in your [config file](https://github.com/matrix-construct/tuwunel/blob/e1f89b69ea117f166be423f035a5a34f4c0e7366/tuwunel-example.toml#L1835-L1851) enables discovery. More information on setting up Element Call can be found at [Spaetzblog](https://sspaeth.de/2024/11/sfu/), skipping step one, and performing step three in your Tuwunel config.
|
||||
|
||||
- Dehydrated Device support (MSC3814) is now available (#200). This feature allows users to receive encrypted messages without being logged in. Supporting clients will setup the dehydrated device automatically and it will "just work" behind the scenes; in fact, these clients will also hide it from the sessions list to avoid confusion. Support is not widespread yet but it has been tested with matrix-js-sdk clients such as Element-web. This feature was commissioned and made public by an enterprise sponsor.
|
||||
|
||||
- Notification panel (the 🔔 button) has been implemented in (#201). Even though Element-web now requires enabling it in the Labs menu, the underlying support (`GET /_matrix/client/v3/notifications`) enhances the push-notification handling of other clients.
|
||||
|
||||
- Live room previews are now available. This support (`GET /_matrix/client/v3/events`) allows users, including guests, to sync updates for a public room without joining (4afd6f347b1).
|
||||
|
||||
- Thanks to a suggestion by @cyberdoors in (#29), the configuration option `encryption_enabled_by_default_for_room_type` is now available. This feature can enable encryption for a room even when the client does not. The values `all` or `invite` are accepted, the latter roughly meaning DM's only. Neither are enabled by default.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Thank you @tototomate123 for improving the reverse-proxy docs, adding dedicated pages for both [Caddy](https://github.com/matrix-construct/tuwunel/blob/e0a997c22784b453735b24907e607412b153ba56/docs/deploying/reverse-proxy-caddy.md) and [Nginx](https://github.com/matrix-construct/tuwunel/blob/e0a997c22784b453735b24907e607412b153ba56/docs/deploying/reverse-proxy-nginx.md) in (#209). Thanks to @tycode for pointing out the docs were missing for alternative proxies in (#197).
|
||||
|
||||
- Thanks to an observation by @iwalkalone69 in (#40), the last-seen time for a device in the session list is now updated acceptably. This function piggybacks on the presence system to prevent writing too frequently; testing has never shown it more than a minute or few out of date.
|
||||
|
||||
- Thanks to an inquiry by @EntityinArray in (#189) guest-accounts can now be enabled while registration tokens are also enabled to prevent fully open account registration. Note that registration tokens don't apply to guest-accounts and those are still fully open.
|
||||
|
||||
- Courtesy of @dasha-uwu the list of servers attempted when joining a room is now properly shuffled to increase the odds of finding a viable server, especially if an additional join attempt is made.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Special thanks to @alaviss for immediately reporting incorrect results from the `/joined_members` endpoint in the v1.4.0 release. This regression primarily affects Application Services and Bridges; the most popular clients have been verified to not make use of this API.
|
||||
- Special thanks to @BVollmerhaus for finding the TURN secret file configured by `turn_secret_file` was broken in (#211), forcing users to configure `turn_secret` directly. Thank you for fixing this in (#212).
|
||||
|
||||
- Thank you @scvalex for updating the nix build for Tuwunel's integration tests and re-enabling all checks. (#215)
|
||||
|
||||
- Thanks to a report by @Anagastes in (#146) **Nheko and NeoChat users can now enjoy properly verified devices.** Special thanks for the assistance of @deepbluev7 with diagnosing the cross-signing signature issue.
|
||||
|
||||
- Database columns intended for deletion, notably `roomsynctoken_shortstatehash`, never had the deletion command actually invoked on them 😭 explaining the lack of enthusiasm after the 1.4.3 release introduced stateless sync. **Users will now see the free disk space they were promised.** This was uncovered during an unrelated issue investigation courtesy of @frebib.
|
||||
|
||||
- Thanks to investigation by @dasha-uwu the pagination tokens in the `/relations` endpoint were buggy and now operate correctly.
|
||||
|
||||
- Thanks to @Polve for identifying the `DynamicUser=yes` directive in the systemd files was invalid and advising a replacement in (#207).
|
||||
|
||||
- Thanks to @daudix for reporting an edge-case where the server will refuse to start rather than robustly reporting errors during startup checks and recreate a missing media directory (#213).
|
||||
|
||||
- Push rule evaluation was never implemented for invites arriving over federation. Notifications are now properly sent in this case.
|
||||
|
||||
- Sliding-sync handlers were susceptible to errors under rare circumstances escaping to cause an HTTP 500, which wreaks havoc on the rust-sdk. This has now been prevented.
|
||||
|
||||
- Federating with Conduit over several non-essential endpoints was broken. It is unclear whether this affected an actual Conduit release version, but thanks to @kladki a fix is scheduled and we have included a workaround now on this end.
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+1
-2
@@ -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
|
||||
|
||||
+14
-8
@@ -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,6 +34,8 @@ 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 . .
|
||||
@@ -42,11 +46,13 @@ 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 \
|
||||
--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 \
|
||||
@@ -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 \
|
||||
|
||||
@@ -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 deb_args=""
|
||||
|
||||
@@ -20,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 \
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# syntax = docker/dockerfile:1.11-labs
|
||||
|
||||
FROM input AS build-rpm
|
||||
ARG rust_target
|
||||
ARG rust_toolchain
|
||||
ARG RUSTUP_HOME
|
||||
ARG CARGO_HOME
|
||||
@@ -9,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=""
|
||||
|
||||
@@ -23,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 \
|
||||
|
||||
@@ -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,12 +46,11 @@ 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_check_for_updates = false
|
||||
allow_device_name_federation = true
|
||||
allow_guest_registration = true
|
||||
allow_invalid_tls_certificates = true
|
||||
@@ -89,7 +72,6 @@ COPY <<EOF complement.toml
|
||||
log_thread_ids = true
|
||||
media_compat_file_link = false
|
||||
media_startup_check = true
|
||||
only_query_trusted_key_servers = false
|
||||
port = [8008, 8448]
|
||||
prune_missing_media = true
|
||||
query_trusted_key_servers_first = false
|
||||
@@ -121,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/
|
||||
@@ -133,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/
|
||||
@@ -152,22 +128,13 @@ ENTRYPOINT valgrind \
|
||||
FROM input AS complement-base
|
||||
ARG var_cache
|
||||
ARG var_lib_apt
|
||||
ARG complement_ref="426bc3458e5287398ec39ab7d2b8f2e3103dcf39"
|
||||
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#403840348f6bcc9cc8ed1671dc2f638c2b1ce4ac complement
|
||||
ADD https://github.com/matrix-construct/complement.git#${complement_ref} complement
|
||||
|
||||
WORKDIR /usr/src/complement
|
||||
ENV COMPLEMENT_BASE_IMAGE="complement-testee"
|
||||
@@ -183,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=".*"
|
||||
@@ -193,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"
|
||||
@@ -207,6 +172,7 @@ 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"
|
||||
|
||||
@@ -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
|
||||
@@ -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,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
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
@@ -30,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
|
||||
|
||||
@@ -67,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 \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+253
-35
@@ -1,4 +1,4 @@
|
||||
variable "CI" {}
|
||||
|
||||
variable "GITHUB_ACTOR" {}
|
||||
variable "GITHUB_REPOSITORY" {}
|
||||
variable "GITHUB_REF" {}
|
||||
@@ -29,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,bzip2_compression,tuwunel_mods,console,default,direct_tls,element_hacks,gzip_compression,hardened_malloc,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,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"
|
||||
@@ -180,7 +184,6 @@ dynamic_libs = [
|
||||
|
||||
nightly_rustflags = [
|
||||
"--cfg tokio_unstable",
|
||||
"--cfg tuwunel_bench",
|
||||
"--allow=unstable-features",
|
||||
"-Z crate-attr=feature(test)",
|
||||
"-Z enforce-type-length-limit",
|
||||
@@ -233,9 +236,18 @@ group "lints" {
|
||||
|
||||
group "tests" {
|
||||
targets = [
|
||||
"docs",
|
||||
"unit",
|
||||
"smoke",
|
||||
"integration",
|
||||
"matrix-compliance",
|
||||
]
|
||||
}
|
||||
|
||||
group "matrix-compliance" {
|
||||
targets = [
|
||||
"complement",
|
||||
"rust-sdk-integ",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -403,7 +415,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
|
||||
}
|
||||
@@ -424,6 +436,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
|
||||
#
|
||||
@@ -432,11 +554,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 = [
|
||||
@@ -509,6 +646,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
|
||||
#
|
||||
@@ -673,6 +874,7 @@ target "install" {
|
||||
|
||||
group "pkg" {
|
||||
targets = [
|
||||
"nix",
|
||||
"deb",
|
||||
"rpm",
|
||||
"deb-install",
|
||||
@@ -784,28 +986,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]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,15 +1040,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 = [
|
||||
@@ -898,7 +1107,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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,7 +1244,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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1176,6 +1385,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 = (
|
||||
@@ -1186,8 +1397,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"
|
||||
@@ -1214,7 +1425,7 @@ target "deps-base" {
|
||||
"-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),
|
||||
@@ -1236,7 +1447,7 @@ target "deps-base" {
|
||||
"-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),
|
||||
@@ -1256,7 +1467,7 @@ target "deps-base" {
|
||||
"-C link-arg=-l:libgcc.a": "",
|
||||
]):
|
||||
|
||||
rust_toolchain == "stable"?
|
||||
substr(rust_toolchain, 0, 6) == "stable"?
|
||||
join(" ", [
|
||||
join(" ", rustflags),
|
||||
join(" ", static_rustflags),
|
||||
@@ -1276,7 +1487,7 @@ target "deps-base" {
|
||||
"-C link-arg=-l:libgcc.a": "",
|
||||
]):
|
||||
|
||||
rust_toolchain == "nightly"?
|
||||
substr(rust_toolchain, 0, 7) == "nightly"?
|
||||
join(" ", [
|
||||
join(" ", rustflags),
|
||||
join(" ", nightly_rustflags),
|
||||
@@ -1499,11 +1710,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",
|
||||
@@ -1587,13 +1799,20 @@ kitchen_packages = [
|
||||
"clang",
|
||||
"cmake",
|
||||
"curl",
|
||||
"gawk",
|
||||
"git",
|
||||
"golang-go",
|
||||
"gzip",
|
||||
"jq",
|
||||
"libc6-dev",
|
||||
"libssl-dev",
|
||||
"libsqlite3-dev",
|
||||
"make",
|
||||
"nix-bin",
|
||||
"openssl",
|
||||
"pkg-config",
|
||||
"pkgconf",
|
||||
"valgrind",
|
||||
"xz-utils",
|
||||
]
|
||||
|
||||
@@ -1613,7 +1832,6 @@ target "kitchen" {
|
||||
args = {
|
||||
packages = join(" ", [
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")? "libbz2-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "hardened_malloc")? "g++": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? "liburing-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? "libjemalloc-dev": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")? "liblz4-dev": "",
|
||||
|
||||
@@ -16,6 +16,7 @@ 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
|
||||
@@ -31,9 +32,7 @@ skip="${skip}|TestRoomCreate/Parallel/POST_/createRoom_makes_a_room_with_a_topic
|
||||
skip="${skip}|TestLogin/parallel/POST_/"
|
||||
skip="${skip}|TestUnbanViaInvite"
|
||||
skip="${skip}|TestRoomState/Parallel/GET_/publicRooms_lists_newly-created_room"
|
||||
# flakes due to timeout in debug-mode
|
||||
skip="${skip}|TestMSC4297StateResolutionV2_1_starts_from_empty_set"
|
||||
skip="${skip}|TestMSC4297StateResolutionV2_1_includes_conflicted_subgraph"
|
||||
skip="${skip}|TestThreadReceiptsInSyncMSC4102"
|
||||
|
||||
set -a
|
||||
cargo_profile="${cargo_profile:-$default_cargo_profile}"
|
||||
@@ -53,6 +52,7 @@ 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}"
|
||||
@@ -88,7 +88,7 @@ extract_output() {
|
||||
}
|
||||
|
||||
result_src="$cid:/usr/src/complement/new_results.jsonl"
|
||||
result_dst="tests/test_results/complement/test_results.jsonl"
|
||||
result_dst="tests/complement/results.jsonl"
|
||||
extract_results() {
|
||||
docker cp "$result_src" "$result_dst"
|
||||
}
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../CODE_OF_CONDUCT.md
|
||||
+3
-1
@@ -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)
|
||||
|
||||
+39
-61
@@ -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 localhost:8008
|
||||
# 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,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.
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
+46
-23
@@ -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
|
||||
|
||||
@@ -99,13 +93,17 @@ #### Database corruption
|
||||
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.
|
||||
|
||||
> [!NOTE]
|
||||
> [!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 only available on the command-line, but is equivalent to
|
||||
> configuring `startup_netburst = false` and `listening = false`.
|
||||
> `--maintenance` is equivalent to configuring `startup_netburst = false` and
|
||||
> `listening = false`.
|
||||
|
||||
0. Start the server with the following options:
|
||||
> [!IMPORTANT]
|
||||
> Always create a backup of the database before running any operation. This is
|
||||
> critical for steps 3 and above.
|
||||
|
||||
**0. Start the server with the following options:**
|
||||
|
||||
`tuwunel --maintenance -O rocksdb_recovery_mode=0`
|
||||
|
||||
@@ -115,7 +113,7 @@ #### Database corruption
|
||||
certain there is deep corruption skip to step 4, otherwise you are finished
|
||||
without any modifications.
|
||||
|
||||
1. Start the server in Tolerate-Corrupted-Tail-Records mode:
|
||||
**1. Start the server in Tolerate-Corrupted-Tail-Records mode:**
|
||||
|
||||
`tuwunel --maintenance -O rocksdb_recovery_mode=1`
|
||||
|
||||
@@ -127,7 +125,7 @@ #### Database corruption
|
||||
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:
|
||||
**2. Start the server in Point-In-Time mode:**
|
||||
|
||||
`tuwunel --maintenance -O rocksdb_recovery_mode=2`
|
||||
|
||||
@@ -137,11 +135,11 @@ #### Database corruption
|
||||
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:
|
||||
**3. Start the server in Skip-Any-Corrupted-Record mode:**
|
||||
|
||||
> [!CAUTION]
|
||||
> [!WARNING]
|
||||
> Salvage mode potentially impacting the application's ability to function.
|
||||
> We cannot provide any further support for users who have entered this mode.
|
||||
> We cannot provide support for users who have entered this mode.
|
||||
|
||||
`tuwunel --maintenance -O rocksdb_recovery_mode=3`
|
||||
|
||||
@@ -152,11 +150,16 @@ #### Database corruption
|
||||
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.
|
||||
**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]
|
||||
> Salvage mode potentially impacting the application's ability to function.
|
||||
> We cannot provide any further support for users who have entered this mode.
|
||||
> 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`
|
||||
|
||||
@@ -167,6 +170,25 @@ #### Database corruption
|
||||
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
|
||||
|
||||
Note that users should not really be debugging things. If you find yourself
|
||||
@@ -178,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
+94
-134
@@ -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": 1761150191,
|
||||
"narHash": "sha256-fPQmV+K836jR/Tu6slfMdm9HL3p0IEPB5x5I7Zc6v5E=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
|
||||
"rev": "5629a7d84c91df9c06650b18eb428cce4166c4fc",
|
||||
"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": 1756358878,
|
||||
"narHash": "sha256-H/4TkbFcGsMRDZ7gNaXZ7NxeO3b1YgkVt9zPb+3ATSg=",
|
||||
"owner": "matrix-construct",
|
||||
"repo": "complement",
|
||||
"rev": "e587b3df569cba411aeac7c20b6366d03c143745",
|
||||
"rev": "403840348f6bcc9cc8ed1671dc2f638c2b1ce4ac",
|
||||
"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": 1760924934,
|
||||
"narHash": "sha256-tuuqY5aU7cUkR71sO2TraVKK2boYrdW3gCSXUkF4i44=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "323b5746d89e04b22554b061522dfce9e4c49b18",
|
||||
"rev": "c6b4d5308293d0d04fcfeee92705017537cad02f",
|
||||
"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": 1761547629,
|
||||
"narHash": "sha256-4OH1CVm2PdjKRqEJ3RLfkQMDSBdn7VId6iyYCwKOK+U=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "a804172f150bcf81262655324e583bb0cd0f28dd",
|
||||
"rev": "d82a7c64ea441e397914577c9a18f2867e5b364b",
|
||||
"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": {
|
||||
@@ -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": 1761487364,
|
||||
"narHash": "sha256-PmRdt9dQUFGcVrX7XCh7z2FpRm1SqKgiYbIwpkJPjRU=",
|
||||
"owner": "axboe",
|
||||
"repo": "liburing",
|
||||
"rev": "ad83d3ab64894c16eaf21ef869656a5bddb93ca4",
|
||||
"rev": "65ec4223032a38870dde2bce646ba4de13d2feff",
|
||||
"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": 1761349956,
|
||||
"narHash": "sha256-tH3wHnOJms+U4k/rK2Nn1RfBrhffX92jLP/2VndSn0w=",
|
||||
"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": "02f2cb8e0feb4596d20cc52fda73ccee960e3538",
|
||||
"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": 1761500479,
|
||||
"narHash": "sha256-syeBTCCU96qPJHcVpwHeCwmPCiLTDHHgYQYhpZ0iwLo=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "a497f4114ccf24978accb56190e60d1e1659e0c7",
|
||||
"rev": "049767e6faa84b2d1a951d8f227e6ebd99d728a2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
file = ./rust-toolchain.toml;
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-Qxt8XAuaUR2OMdKbN4u8dBJOhSHxS+uS06Wl9+flVEk=";
|
||||
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"
|
||||
];
|
||||
|
||||
@@ -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
@@ -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
@@ -9,7 +9,7 @@
|
||||
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||
|
||||
[toolchain]
|
||||
channel = "1.88.0"
|
||||
channel = "1.89.0"
|
||||
profile = "minimal"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
|
||||
@@ -11,6 +11,7 @@ version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "mod.rs"
|
||||
bench = false
|
||||
crate-type = [
|
||||
"rlib",
|
||||
# "dylib",
|
||||
|
||||
+14
-27
@@ -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;
|
||||
|
||||
@@ -138,16 +135,8 @@ 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
|
||||
@@ -304,7 +293,7 @@ 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.alias.resolve(&room).await?;
|
||||
let room_id = self.services.alias.maybe_resolve(&room).await?;
|
||||
let room_state: Vec<Raw<AnyStateEvent>> = self
|
||||
.services
|
||||
.state_accessor
|
||||
@@ -918,7 +907,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<_>>()?;
|
||||
|
||||
@@ -974,10 +963,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;
|
||||
@@ -994,21 +983,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)
|
||||
@@ -1025,7 +1012,7 @@ pub(super) async fn resync_database(&self) -> Result {
|
||||
|
||||
self.services
|
||||
.db
|
||||
.db
|
||||
.engine
|
||||
.update()
|
||||
.map_err(|e| err!("Failed to update from primary: {e:?}"))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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```"))
|
||||
|
||||
@@ -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),
|
||||
|
||||
+55
-23
@@ -16,16 +16,16 @@
|
||||
|
||||
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,
|
||||
|
||||
@@ -37,17 +37,8 @@ pub(crate) enum RawCommand {
|
||||
base64: bool,
|
||||
},
|
||||
|
||||
/// - Raw database delete (for string keys)
|
||||
RawDel {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
/// Key
|
||||
key: String,
|
||||
},
|
||||
|
||||
/// - Raw database keys iteration
|
||||
RawKeys {
|
||||
Keys {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
@@ -56,7 +47,7 @@ pub(crate) enum RawCommand {
|
||||
},
|
||||
|
||||
/// - Raw database key size breakdown
|
||||
RawKeysSizes {
|
||||
KeysSizes {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
@@ -65,7 +56,7 @@ pub(crate) enum RawCommand {
|
||||
},
|
||||
|
||||
/// - Raw database keys total bytes
|
||||
RawKeysTotal {
|
||||
KeysTotal {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
@@ -74,7 +65,7 @@ pub(crate) enum RawCommand {
|
||||
},
|
||||
|
||||
/// - Raw database values size breakdown
|
||||
RawValsSizes {
|
||||
ValsSizes {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
@@ -83,7 +74,7 @@ pub(crate) enum RawCommand {
|
||||
},
|
||||
|
||||
/// - Raw database values total bytes
|
||||
RawValsTotal {
|
||||
ValsTotal {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
@@ -92,7 +83,7 @@ pub(crate) enum RawCommand {
|
||||
},
|
||||
|
||||
/// - Raw database items iteration
|
||||
RawIter {
|
||||
Iter {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
@@ -101,7 +92,7 @@ pub(crate) enum RawCommand {
|
||||
},
|
||||
|
||||
/// - Raw database keys iteration
|
||||
RawKeysFrom {
|
||||
KeysFrom {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
@@ -114,7 +105,7 @@ pub(crate) enum RawCommand {
|
||||
},
|
||||
|
||||
/// - Raw database items iteration
|
||||
RawIterFrom {
|
||||
IterFrom {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
@@ -127,7 +118,7 @@ pub(crate) enum RawCommand {
|
||||
},
|
||||
|
||||
/// - Raw database record count
|
||||
RawCount {
|
||||
Count {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
@@ -135,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>>,
|
||||
@@ -165,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>,
|
||||
@@ -425,6 +435,28 @@ pub(super) async fn raw_del(&self, map: String, key: String) -> Result {
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
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)?;
|
||||
|
||||
@@ -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,6 +35,13 @@ 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.alias.resolve_local_alias(&alias).await;
|
||||
|
||||
@@ -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 {
|
||||
@@ -282,7 +286,7 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.state_cache
|
||||
.rooms_invited(&user_id)
|
||||
.rooms_invited_state(&user_id)
|
||||
.collect()
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
@@ -297,7 +301,7 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.state_cache
|
||||
.rooms_left(&user_id)
|
||||
.rooms_left_state(&user_id)
|
||||
.collect()
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
@@ -316,6 +320,22 @@ pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Context
|
||||
.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```"
|
||||
|
||||
@@ -25,7 +25,11 @@ pub(crate) enum RoomTimelineCommand {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result {
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
let room_id = self
|
||||
.services
|
||||
.alias
|
||||
.maybe_resolve(&room_id)
|
||||
.await?;
|
||||
|
||||
let result = self
|
||||
.services
|
||||
@@ -43,7 +47,11 @@ pub(super) async fn pdus(
|
||||
from: Option<String>,
|
||||
limit: Option<usize>,
|
||||
) -> Result {
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
let room_id = self
|
||||
.services
|
||||
.alias
|
||||
.maybe_resolve(&room_id)
|
||||
.await?;
|
||||
|
||||
let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?;
|
||||
|
||||
|
||||
@@ -30,7 +30,11 @@ pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn short_room_id(&self, room_id: OwnedRoomOrAliasId) -> Result {
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
let room_id = self
|
||||
.services
|
||||
.alias
|
||||
.maybe_resolve(&room_id)
|
||||
.await?;
|
||||
|
||||
let shortid = self
|
||||
.services
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -67,7 +67,7 @@ pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
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");
|
||||
}
|
||||
@@ -76,7 +76,7 @@ pub(super) async fn delete_room(&self, room_id: OwnedRoomId) -> Result {
|
||||
|
||||
self.services
|
||||
.delete
|
||||
.delete_room(&room_id, state_lock)
|
||||
.delete_room(&room_id, force, state_lock)
|
||||
.await?;
|
||||
|
||||
self.write_str("Successfully deleted the room from our database.")
|
||||
|
||||
@@ -60,5 +60,8 @@ pub(super) enum RoomCommand {
|
||||
/// - Delete room
|
||||
DeleteRoom {
|
||||
room_id: OwnedRoomId,
|
||||
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -48,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) {
|
||||
@@ -105,7 +105,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
match self
|
||||
.services
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.resolve_alias(room_alias)
|
||||
.await
|
||||
{
|
||||
| Ok((room_id, servers)) => {
|
||||
@@ -156,7 +156,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
if let Err(e) = self
|
||||
.services
|
||||
.membership
|
||||
.leave(user_id, &room_id, None, &state_lock)
|
||||
.leave(user_id, &room_id, None, false, &state_lock)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
@@ -209,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();
|
||||
@@ -260,7 +260,7 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
match self
|
||||
.services
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.resolve_alias(room_alias)
|
||||
.await
|
||||
{
|
||||
| Ok((room_id, servers)) => {
|
||||
@@ -331,7 +331,7 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
if let Err(e) = self
|
||||
.services
|
||||
.membership
|
||||
.leave(user_id, &room_id, None, &state_lock)
|
||||
.leave(user_id, &room_id, None, false, &state_lock)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
@@ -423,7 +423,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
match self
|
||||
.services
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.resolve_alias(room_alias)
|
||||
.await
|
||||
{
|
||||
| Ok((room_id, servers)) => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
Int, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
|
||||
Int, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
|
||||
events::{
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
room::{
|
||||
@@ -119,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.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"
|
||||
@@ -224,6 +224,25 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
.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)?;
|
||||
@@ -408,7 +427,7 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
.alias
|
||||
.resolve_with_servers(&room_id, None)
|
||||
.maybe_resolve_with_servers(&room_id, None)
|
||||
.await?;
|
||||
|
||||
if !self
|
||||
@@ -532,7 +551,7 @@ pub(super) async fn force_join_all_local_users(
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
.alias
|
||||
.resolve_with_servers(&room_id, None)
|
||||
.maybe_resolve_with_servers(&room_id, None)
|
||||
.await?;
|
||||
|
||||
if !self
|
||||
@@ -617,7 +636,7 @@ pub(super) async fn force_join_room(
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
.alias
|
||||
.resolve_with_servers(&room_id, None)
|
||||
.maybe_resolve_with_servers(&room_id, None)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
@@ -645,7 +664,11 @@ pub(super) async fn force_leave_room(
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
) -> Result {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
let room_id = self
|
||||
.services
|
||||
.alias
|
||||
.maybe_resolve(&room_id)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
@@ -665,7 +688,7 @@ pub(super) async fn force_leave_room(
|
||||
|
||||
self.services
|
||||
.membership
|
||||
.leave(&user_id, &room_id, None, &state_lock)
|
||||
.leave(&user_id, &room_id, None, false, &state_lock)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
@@ -678,7 +701,11 @@ pub(super) async fn force_leave_room(
|
||||
#[admin_command]
|
||||
pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAliasId) -> Result {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
let room_id = self
|
||||
.services
|
||||
.alias
|
||||
.maybe_resolve(&room_id)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
@@ -744,7 +771,11 @@ pub(super) async fn force_promote(
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
) -> Result {
|
||||
let target_id = parse_user_id(self.services, &target_id)?;
|
||||
let room_id = self.services.alias.resolve(&room_id).await?;
|
||||
let room_id = self
|
||||
.services
|
||||
.alias
|
||||
.maybe_resolve(&room_id)
|
||||
.await?;
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -11,6 +11,7 @@ version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "mod.rs"
|
||||
bench = false
|
||||
crate-type = [
|
||||
"rlib",
|
||||
# "dylib",
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::api::client::{
|
||||
account::{
|
||||
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
whoami,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
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, err, info, utils, utils::ReadyExt};
|
||||
use tuwunel_core::{Err, Result, info, utils::ReadyExt};
|
||||
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::Ruma;
|
||||
use crate::{Ruma, router::auth_uiaa};
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/password`
|
||||
///
|
||||
@@ -37,45 +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] }],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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
|
||||
@@ -87,7 +44,7 @@ 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;
|
||||
}
|
||||
@@ -140,44 +97,7 @@ 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 mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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
|
||||
.deactivate
|
||||
@@ -186,7 +106,6 @@ pub(crate) async fn deactivate_route(
|
||||
.await?;
|
||||
|
||||
info!("User {sender_user} deactivated their account.");
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
|
||||
@@ -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;
|
||||
@@ -26,8 +26,8 @@ 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.")));
|
||||
@@ -83,13 +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
|
||||
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:?}");
|
||||
|
||||
@@ -100,7 +100,7 @@ pub(crate) async fn get_context_route(
|
||||
.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<_>) =
|
||||
@@ -134,7 +134,7 @@ pub(crate) async fn get_context_route(
|
||||
.map_or_else(|| body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
|
||||
|
||||
let state_ids = services
|
||||
.state_accessor
|
||||
.state
|
||||
.pdu_shortstatehash(state_at)
|
||||
.or_else(|_| services.state.get_room_shortstatehash(room_id))
|
||||
.map_ok(|shortstatehash| {
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
+16
-77
@@ -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,7 @@ pub(crate) async fn update_device_route(
|
||||
.create_device(
|
||||
sender_user,
|
||||
&device_id,
|
||||
(&appservice.registration.as_token, None),
|
||||
(Some(&appservice.registration.as_token), None),
|
||||
None,
|
||||
None,
|
||||
Some(client.to_string()),
|
||||
@@ -126,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"
|
||||
@@ -142,38 +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] }],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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
|
||||
@@ -200,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"
|
||||
@@ -218,38 +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] }],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
@@ -403,9 +403,24 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
|
||||
let canonical_alias = services
|
||||
.state_accessor
|
||||
.get_canonical_alias(&room_id)
|
||||
.ok();
|
||||
.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);
|
||||
let avatar_url = services
|
||||
.state_accessor
|
||||
.get_avatar(&room_id)
|
||||
.map_ok(|content| content.url)
|
||||
.ok();
|
||||
|
||||
let topic = services
|
||||
.state_accessor
|
||||
@@ -441,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(),
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
-82
@@ -6,14 +6,10 @@
|
||||
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,
|
||||
},
|
||||
@@ -21,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`
|
||||
///
|
||||
@@ -40,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| {
|
||||
@@ -84,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,
|
||||
@@ -158,23 +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] }],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// 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 {
|
||||
@@ -182,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,
|
||||
@@ -246,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 {
|
||||
@@ -258,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 {
|
||||
@@ -265,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()? {
|
||||
@@ -274,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()? {
|
||||
@@ -294,6 +264,7 @@ async fn check_for_new_keys(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if empty {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -377,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
|
||||
@@ -557,16 +528,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);
|
||||
}
|
||||
@@ -603,7 +574,7 @@ fn add_unsigned_device_display_name(
|
||||
.or_insert_with(|| CanonicalJsonObject::default().into())
|
||||
{
|
||||
let display_name = if include_display_names {
|
||||
CanonicalJsonValue::String(display_name)
|
||||
CanonicalJsonValue::String(display_name.to_string())
|
||||
} else {
|
||||
CanonicalJsonValue::String(metadata.device_id.into())
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug, debug_info, debug_warn, err, extract_variant, info,
|
||||
matrix::{
|
||||
PduCount,
|
||||
event::{Event, gen_event_id},
|
||||
pdu::{PduBuilder, PduEvent},
|
||||
},
|
||||
@@ -209,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()
|
||||
@@ -221,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(),
|
||||
@@ -243,7 +244,7 @@ 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;
|
||||
@@ -276,6 +277,7 @@ 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
|
||||
.state_cache
|
||||
.update_membership(
|
||||
@@ -294,6 +296,7 @@ async fn knock_room_helper_local(
|
||||
),
|
||||
None,
|
||||
false,
|
||||
PduCount::Normal(*count),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -343,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()
|
||||
@@ -355,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(),
|
||||
@@ -377,7 +380,7 @@ 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;
|
||||
@@ -490,6 +493,7 @@ async fn knock_room_helper_remote(
|
||||
.await?;
|
||||
|
||||
info!("Updating membership locally to knock state with provided stripped state events");
|
||||
let count = services.globals.next_count();
|
||||
services
|
||||
.state_cache
|
||||
.update_membership(
|
||||
@@ -508,6 +512,7 @@ async fn knock_room_helper_remote(
|
||||
),
|
||||
None,
|
||||
false,
|
||||
PduCount::Normal(*count),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(crate) async fn leave_room_route(
|
||||
|
||||
services
|
||||
.membership
|
||||
.leave(body.sender_user(), &body.room_id, body.reason.clone(), &state_lock)
|
||||
.leave(body.sender_user(), &body.room_id, body.reason.clone(), false, &state_lock)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
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::{BoolExt, TryExtExt},
|
||||
stream::ReadyExt,
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{Err, Result, at, matrix::Event, utils::stream::ReadyExt};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -29,7 +39,9 @@ pub(crate) async fn get_member_events_route(
|
||||
.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();
|
||||
@@ -59,12 +71,20 @@ 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
|
||||
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 {
|
||||
@@ -74,9 +94,7 @@ pub(crate) async fn joined_members_route(
|
||||
.ready_filter_map(Result::ok)
|
||||
.ready_filter(|((ty, _), _)| *ty == StateEventType::RoomMember)
|
||||
.map(at!(1))
|
||||
.ready_filter_map(|pdu| {
|
||||
membership_filter(pdu, Some(&MembershipEventFilter::Join), None)
|
||||
})
|
||||
.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();
|
||||
@@ -95,22 +113,22 @@ pub(crate) async fn joined_members_route(
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -165,10 +165,7 @@ async fn get_join_params(
|
||||
},
|
||||
// ... if room alias, resolve and don't shuffle ...
|
||||
| Err(room_alias) => {
|
||||
let (room_id, servers) = services
|
||||
.alias
|
||||
.resolve_alias(&room_alias, Some(via.to_vec()))
|
||||
.await?;
|
||||
let (room_id, servers) = services.alias.resolve_alias(&room_alias).await?;
|
||||
|
||||
(room_id, servers, Vec::new())
|
||||
},
|
||||
@@ -198,12 +195,10 @@ async fn get_join_params(
|
||||
);
|
||||
|
||||
// shuffle additionals, append to base servers
|
||||
additional_servers.sort_unstable();
|
||||
additional_servers.dedup();
|
||||
shuffle(&mut additional_servers);
|
||||
servers.append(&mut additional_servers);
|
||||
servers.sort_unstable();
|
||||
servers.dedup();
|
||||
servers.append(&mut additional_servers);
|
||||
shuffle(&mut servers);
|
||||
|
||||
// sort deprioritized servers last
|
||||
servers.sort_by(|a, b| {
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
pub(super) mod backup;
|
||||
pub(super) mod capabilities;
|
||||
pub(super) mod context;
|
||||
pub(super) mod dehydrated_device;
|
||||
pub(super) mod device;
|
||||
pub(super) mod directory;
|
||||
pub(super) mod events;
|
||||
pub(super) mod filter;
|
||||
pub(super) mod keys;
|
||||
pub(super) mod media;
|
||||
@@ -49,8 +51,10 @@
|
||||
pub(super) use backup::*;
|
||||
pub(super) use capabilities::*;
|
||||
pub(super) use context::*;
|
||||
pub(super) use dehydrated_device::*;
|
||||
pub(super) use device::*;
|
||||
pub(super) use directory::*;
|
||||
pub(super) use events::*;
|
||||
pub(super) use filter::*;
|
||||
pub(super) use keys::*;
|
||||
pub(super) use media::*;
|
||||
|
||||
+11
-15
@@ -46,13 +46,11 @@ pub(crate) async fn set_displayname_route(
|
||||
.update_displayname(&body.user_id, body.displayname.clone(), &all_joined_rooms)
|
||||
.await;
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.maybe_ping_presence(&body.user_id, body.sender_device.as_deref(), &PresenceState::Online)
|
||||
.await?;
|
||||
|
||||
Ok(set_display_name::v3::Response {})
|
||||
}
|
||||
@@ -148,14 +146,12 @@ pub(crate) async fn set_avatar_url_route(
|
||||
)
|
||||
.await;
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.maybe_ping_presence(&body.user_id, body.sender_device.as_deref(), &PresenceState::Online)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok(set_avatar_url::v3::Response {})
|
||||
}
|
||||
|
||||
+108
-5
@@ -1,10 +1,11 @@
|
||||
use axum::extract::State;
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue,
|
||||
CanonicalJsonObject, CanonicalJsonValue, MilliSecondsSinceUnixEpoch,
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
push::{
|
||||
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions,
|
||||
delete_pushrule, get_notifications, get_pushers, get_pushrule, get_pushrule_actions,
|
||||
get_pushrule_enabled, get_pushrules_all, get_pushrules_global_scope, set_pusher,
|
||||
set_pushrule, set_pushrule_actions, set_pushrule_enabled,
|
||||
},
|
||||
@@ -14,15 +15,117 @@
|
||||
push_rules::{PushRulesEvent, PushRulesEventContent},
|
||||
},
|
||||
push::{
|
||||
InsertPushRuleError, PredefinedContentRuleId, PredefinedOverrideRuleId,
|
||||
Action, InsertPushRuleError, PredefinedContentRuleId, PredefinedOverrideRuleId,
|
||||
RemovePushRuleError, Ruleset,
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result, err};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, at, err,
|
||||
matrix::{Event, PduId},
|
||||
utils::{
|
||||
stream::{ReadyExt, WidebandExt},
|
||||
string::to_small_string,
|
||||
},
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /_matrix/client/r0/notifications/`
|
||||
///
|
||||
/// Paginate through the list of events the user has been, or would have been
|
||||
/// notified about.
|
||||
pub(crate) async fn get_notifications_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_notifications::v3::Request>,
|
||||
) -> Result<get_notifications::v3::Response> {
|
||||
use get_notifications::v3::Notification;
|
||||
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
let from = body
|
||||
.body
|
||||
.from
|
||||
.as_deref()
|
||||
.map(str::parse)
|
||||
.transpose()
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid `from' parameter: {e}"))))?;
|
||||
|
||||
let limit: usize = body
|
||||
.body
|
||||
.limit
|
||||
.map(TryInto::try_into)
|
||||
.transpose()?
|
||||
.unwrap_or(50)
|
||||
.clamp(1, 100);
|
||||
|
||||
let only_highlight = body
|
||||
.body
|
||||
.only
|
||||
.as_deref()
|
||||
.is_some_and(|only| only.contains("highlight"));
|
||||
|
||||
let mut next_token: Option<u64> = None;
|
||||
let notifications = services
|
||||
.pusher
|
||||
.get_notifications(sender_user, from)
|
||||
.ready_filter(|(_, notify)| {
|
||||
if only_highlight && !notify.actions.iter().any(Action::is_highlight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.wide_filter_map(async |(count, notify)| {
|
||||
let pdu_id = PduId {
|
||||
shortroomid: notify.sroomid,
|
||||
count: count.into(),
|
||||
};
|
||||
|
||||
let event = services
|
||||
.timeline
|
||||
.get_pdu_from_id(&pdu_id.into())
|
||||
.await
|
||||
.ok()
|
||||
.filter(|event| !event.is_redacted())?;
|
||||
|
||||
let read = services
|
||||
.pusher
|
||||
.last_notification_read(sender_user, event.room_id())
|
||||
.await
|
||||
.is_ok_and(|last_read| last_read.ge(&count));
|
||||
|
||||
let ts = notify
|
||||
.ts
|
||||
.try_into()
|
||||
.map(MilliSecondsSinceUnixEpoch)
|
||||
.ok()?;
|
||||
|
||||
let notification = Notification {
|
||||
room_id: event.room_id().into(),
|
||||
event: event.into_format(),
|
||||
ts,
|
||||
read,
|
||||
profile_tag: notify.tag,
|
||||
actions: notify.actions,
|
||||
};
|
||||
|
||||
Some((count, notification))
|
||||
})
|
||||
.take(limit)
|
||||
.inspect(|(count, _)| {
|
||||
next_token.replace(*count);
|
||||
})
|
||||
.map(at!(1))
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
Ok(get_notifications::v3::Response {
|
||||
next_token: next_token.map(to_small_string),
|
||||
notifications,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/pushrules/`
|
||||
///
|
||||
/// Retrieves the push rules event for this user.
|
||||
@@ -335,7 +438,7 @@ pub(crate) async fn set_pushrule_actions_route(
|
||||
if account_data
|
||||
.content
|
||||
.global
|
||||
.set_actions(body.kind.clone(), &body.rule_id, body.actions.clone())
|
||||
.set_actions(body.kind.clone(), &body.rule_id, body.actions.clone().into())
|
||||
.is_err()
|
||||
{
|
||||
return Err!(Request(NotFound("Push rule not found.")));
|
||||
|
||||
@@ -26,6 +26,12 @@ pub(crate) async fn set_read_marker_route(
|
||||
) -> Result<set_read_marker::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if body.private_read_receipt.is_some() || body.read_receipt.is_some() {
|
||||
services
|
||||
.pusher
|
||||
.reset_notification_counts(sender_user, &body.room_id);
|
||||
}
|
||||
|
||||
if let Some(event) = &body.fully_read {
|
||||
let fully_read_event = ruma::events::fully_read::FullyReadEvent {
|
||||
content: ruma::events::fully_read::FullyReadEventContent { event_id: event.clone() },
|
||||
@@ -39,21 +45,26 @@ pub(crate) async fn set_read_marker_route(
|
||||
RoomAccountDataEventType::FullyRead,
|
||||
&serde_json::to_value(fully_read_event)?,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
if body.private_read_receipt.is_some() || body.read_receipt.is_some() {
|
||||
services
|
||||
.user
|
||||
.reset_notification_counts(sender_user, &body.room_id);
|
||||
}
|
||||
if let Some(event) = &body.private_read_receipt {
|
||||
let count = services
|
||||
.timeline
|
||||
.get_pdu_count(event)
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
|
||||
|
||||
let PduCount::Normal(count) = count else {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Event is a backfilled PDU and cannot be marked as read."
|
||||
)));
|
||||
};
|
||||
|
||||
// ping presence
|
||||
if services.config.allow_local_presence {
|
||||
services
|
||||
.presence
|
||||
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
|
||||
.await?;
|
||||
.read_receipt
|
||||
.private_read_set(&body.room_id, sender_user, count);
|
||||
}
|
||||
|
||||
if let Some(event) = &body.read_receipt {
|
||||
@@ -79,24 +90,16 @@ pub(crate) async fn set_read_marker_route(
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(event) = &body.private_read_receipt {
|
||||
let count = services
|
||||
.timeline
|
||||
.get_pdu_count(event)
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Event not found."))))?;
|
||||
|
||||
let PduCount::Normal(count) = count else {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Event is a backfilled PDU and cannot be marked as read."
|
||||
)));
|
||||
};
|
||||
|
||||
services
|
||||
.read_receipt
|
||||
.private_read_set(&body.room_id, sender_user, count);
|
||||
.presence
|
||||
.maybe_ping_presence(
|
||||
sender_user,
|
||||
body.sender_device.as_deref(),
|
||||
&ruma::presence::PresenceState::Online,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
Ok(set_read_marker::v3::Response {})
|
||||
@@ -116,18 +119,10 @@ pub(crate) async fn create_receipt_route(
|
||||
create_receipt::v3::ReceiptType::Read | create_receipt::v3::ReceiptType::ReadPrivate
|
||||
) {
|
||||
services
|
||||
.user
|
||||
.pusher
|
||||
.reset_notification_counts(sender_user, &body.room_id);
|
||||
}
|
||||
|
||||
// ping presence
|
||||
if services.config.allow_local_presence {
|
||||
services
|
||||
.presence
|
||||
.ping_presence(sender_user, &ruma::presence::PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
|
||||
match body.receipt_type {
|
||||
| create_receipt::v3::ReceiptType::FullyRead => {
|
||||
let fully_read_event = ruma::events::fully_read::FullyReadEvent {
|
||||
@@ -171,6 +166,16 @@ pub(crate) async fn create_receipt_route(
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
services
|
||||
.presence
|
||||
.maybe_ping_presence(
|
||||
sender_user,
|
||||
body.sender_device.as_deref(),
|
||||
&ruma::presence::PresenceState::Online,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
},
|
||||
| create_receipt::v3::ReceiptType::ReadPrivate => {
|
||||
let count = services
|
||||
|
||||
+30
-24
@@ -16,7 +16,9 @@
|
||||
events::GlobalAccountDataEventType,
|
||||
push,
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result, debug_info, error, info, is_equal_to, utils, warn};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, debug_info, debug_warn, error, info, is_equal_to, utils, warn,
|
||||
};
|
||||
use tuwunel_service::users::device::generate_refresh_token;
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH};
|
||||
@@ -58,8 +60,8 @@ pub(crate) async fn get_register_available_route(
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.config
|
||||
.forbidden_usernames
|
||||
.is_match(&body.username)
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
@@ -181,31 +183,33 @@ pub(crate) async fn register_route(
|
||||
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!(
|
||||
if is_guest && !services.config.allow_guest_registration {
|
||||
let display_name = body
|
||||
.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
|
||||
debug_warn!(
|
||||
"Guest registration disabled / registration enabled with token configured, \
|
||||
rejecting guest registration attempt, initial device name: \"{}\"",
|
||||
body.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
rejecting guest registration attempt, initial device name: \"{display_name}\""
|
||||
);
|
||||
|
||||
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 {
|
||||
let display_name = body
|
||||
.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
|
||||
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("")
|
||||
rejecting registration. Guest's initial device name: \"{display_name}\""
|
||||
);
|
||||
|
||||
return Err!(Request(Forbidden("Registration is temporarily disabled.")));
|
||||
}
|
||||
|
||||
@@ -228,8 +232,8 @@ pub(crate) async fn register_route(
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.config
|
||||
.forbidden_usernames
|
||||
.is_match(username)
|
||||
&& !emergency_mode_enabled
|
||||
{
|
||||
@@ -309,7 +313,7 @@ pub(crate) async fn register_route(
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo;
|
||||
let skip_auth = if services.globals.registration_token.is_some() {
|
||||
let skip_auth = if services.globals.registration_token.is_some() && !is_guest {
|
||||
// Registration token required
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
@@ -320,6 +324,7 @@ pub(crate) async fn register_route(
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
body.appservice_info.is_some()
|
||||
} else {
|
||||
// No registration token necessary, but clients must still go through the flow
|
||||
@@ -330,6 +335,7 @@ pub(crate) async fn register_route(
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
body.appservice_info.is_some() || is_guest
|
||||
};
|
||||
|
||||
@@ -384,8 +390,8 @@ pub(crate) async fn register_route(
|
||||
// If `new_user_displayname_suffix` is set, registration will push whatever
|
||||
// content is set to the user's display name with a space before it
|
||||
if !services
|
||||
.globals
|
||||
.new_user_displayname_suffix()
|
||||
.config
|
||||
.new_user_displayname_suffix
|
||||
.is_empty()
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
@@ -446,7 +452,7 @@ pub(crate) async fn register_route(
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
(&access_token, expires_in),
|
||||
(Some(&access_token), expires_in),
|
||||
refresh_token.as_deref(),
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
@@ -535,7 +541,7 @@ pub(crate) async fn register_route(
|
||||
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
||||
{
|
||||
for room in &services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = services.alias.resolve(room).await else {
|
||||
let Ok(room_id) = services.alias.maybe_resolve(room).await else {
|
||||
error!(
|
||||
"Failed to resolve room alias to room ID when attempting to auto join \
|
||||
{room}, skipping"
|
||||
|
||||
@@ -154,13 +154,11 @@ async fn paginate_relations_with_filter(
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let next_batch = match dir {
|
||||
| Direction::Forward => events.last(),
|
||||
| Direction::Backward => events.first(),
|
||||
}
|
||||
.map(at!(0))
|
||||
.as_ref()
|
||||
.map(ToString::to_string);
|
||||
let next_batch = events
|
||||
.last()
|
||||
.map(at!(0))
|
||||
.as_ref()
|
||||
.map(ToString::to_string);
|
||||
|
||||
Ok(get_relating_events::v1::Response {
|
||||
next_batch,
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, future::OptionFuture};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId,
|
||||
CanonicalJsonObject, EventEncryptionAlgorithm, Int, OwnedRoomAliasId, OwnedRoomId,
|
||||
OwnedUserId, RoomId, RoomVersionId,
|
||||
api::client::room::{
|
||||
self, create_room,
|
||||
create_room::v3::{CreationContent, RoomPreset},
|
||||
@@ -13,6 +14,7 @@
|
||||
room::{
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
create::RoomCreateEventContent,
|
||||
encryption::RoomEncryptionEventContent,
|
||||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
@@ -262,6 +264,7 @@ pub(crate) async fn create_room_route(
|
||||
.await?;
|
||||
|
||||
// 6. Events listed in initial_state
|
||||
let mut is_encrypted = false;
|
||||
for event in &body.initial_state {
|
||||
let mut pdu_builder = event
|
||||
.deserialize_as_unchecked::<PduBuilder>()
|
||||
@@ -292,6 +295,10 @@ pub(crate) async fn create_room_route(
|
||||
continue;
|
||||
}
|
||||
|
||||
if pdu_builder.event_type == TimelineEventType::RoomEncryption {
|
||||
is_encrypted = true;
|
||||
}
|
||||
|
||||
services
|
||||
.timeline
|
||||
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
|
||||
@@ -299,6 +306,33 @@ pub(crate) async fn create_room_route(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if services.config.allow_encryption && !is_encrypted {
|
||||
use RoomPreset::*;
|
||||
|
||||
let config = services
|
||||
.config
|
||||
.encryption_enabled_by_default_for_room_type
|
||||
.as_deref()
|
||||
.unwrap_or("off");
|
||||
|
||||
let invite = matches!(config, "invite");
|
||||
let always = matches!(config, "all" | "invite");
|
||||
if always || (invite && matches!(preset, PrivateChat | TrustedPrivateChat)) {
|
||||
let algorithm = EventEncryptionAlgorithm::MegolmV1AesSha2;
|
||||
let content = RoomEncryptionEventContent::new(algorithm);
|
||||
services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(String::new(), &content),
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Events implied by name and topic
|
||||
if let Some(name) = &body.name {
|
||||
services
|
||||
@@ -413,6 +447,12 @@ async fn create_create_event(
|
||||
))))
|
||||
})?;
|
||||
|
||||
if !services.config.federate_created_rooms {
|
||||
if !services.config.allow_federation || !content.contains_key("m.federate") {
|
||||
content.insert("m.federate".into(), json!(false).try_into()?);
|
||||
}
|
||||
}
|
||||
|
||||
content.insert(
|
||||
"room_version".into(),
|
||||
json!(room_version.as_str())
|
||||
@@ -428,6 +468,10 @@ async fn create_create_event(
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
|
||||
|
||||
if !services.config.federate_created_rooms {
|
||||
content.insert("m.federate".into(), json!(false).try_into()?);
|
||||
}
|
||||
|
||||
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
|
||||
content
|
||||
},
|
||||
@@ -535,6 +579,12 @@ async fn create_create_event_legacy(
|
||||
},
|
||||
}
|
||||
|
||||
if !services.config.federate_created_rooms {
|
||||
if !services.config.allow_federation || !content.contains_key("m.federate") {
|
||||
content.insert("m.federate".into(), json!(false).try_into()?);
|
||||
}
|
||||
}
|
||||
|
||||
content.insert(
|
||||
"room_version".into(),
|
||||
json!(room_version.as_str())
|
||||
@@ -556,6 +606,10 @@ async fn create_create_event_legacy(
|
||||
let mut content =
|
||||
serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
|
||||
|
||||
if !services.config.federate_created_rooms {
|
||||
content.insert("m.federate".into(), json!(false).try_into()?);
|
||||
}
|
||||
|
||||
content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
|
||||
content
|
||||
},
|
||||
@@ -658,8 +712,8 @@ async fn room_alias_check(
|
||||
|
||||
// check if room alias is forbidden
|
||||
if services
|
||||
.globals
|
||||
.forbidden_alias_names()
|
||||
.config
|
||||
.forbidden_alias_names
|
||||
.is_match(room_alias_name)
|
||||
{
|
||||
return Err!(Request(Unknown("Room alias name is forbidden.")));
|
||||
@@ -705,8 +759,8 @@ async fn room_alias_check(
|
||||
async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
|
||||
// apply forbidden room alias checks to custom room IDs too
|
||||
if services
|
||||
.globals
|
||||
.forbidden_alias_names()
|
||||
.config
|
||||
.forbidden_alias_names
|
||||
.is_match(custom_room_id)
|
||||
{
|
||||
return Err!(Request(Unknown("Custom room ID is forbidden.")));
|
||||
@@ -778,7 +832,7 @@ async fn can_create_room_check(
|
||||
services: &Services,
|
||||
body: &Ruma<create_room::v3::Request>,
|
||||
) -> Result {
|
||||
if !services.globals.allow_room_creation()
|
||||
if !services.config.allow_room_creation
|
||||
&& body.appservice_info.is_none()
|
||||
&& !services.users.is_admin(body.sender_user()).await
|
||||
{
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, TryStreamExt, future::try_join4};
|
||||
use ruma::api::client::room::initial_sync::v3::{PaginationChunk, Request, Response};
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::try_join5};
|
||||
use ruma::{
|
||||
api::client::room::initial_sync::v3::{PaginationChunk, Request, Response},
|
||||
events::AnyRawAccountDataEvent,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Event, Result, at,
|
||||
utils::{BoolExt, stream::TryTools},
|
||||
Err, Event, Result, at, extract_variant,
|
||||
matrix::PduCount,
|
||||
utils::stream::{ReadyExt, TryTools},
|
||||
};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
const LIMIT_MAX: usize = 100;
|
||||
const LIMIT_MAX: usize = 50;
|
||||
|
||||
/// GET `/_matrix/client/v3/rooms/{roomId}/initialSync`
|
||||
pub(crate) async fn room_initial_sync_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<Request>,
|
||||
@@ -24,13 +29,15 @@ pub(crate) async fn room_initial_sync_route(
|
||||
return Err!(Request(Forbidden("No room preview available.")));
|
||||
}
|
||||
|
||||
let next_batch = services.globals.current_count();
|
||||
|
||||
let visibility = services.directory.visibility(room_id).map(Ok);
|
||||
|
||||
let membership = services
|
||||
.state_cache
|
||||
.user_membership(body.sender_user(), room_id)
|
||||
.map(Ok);
|
||||
|
||||
let visibility = services.directory.visibility(room_id).map(Ok);
|
||||
|
||||
let state = services
|
||||
.state_accessor
|
||||
.room_state_full_pdus(room_id)
|
||||
@@ -40,42 +47,52 @@ pub(crate) async fn room_initial_sync_route(
|
||||
let limit = LIMIT_MAX;
|
||||
let events = services
|
||||
.timeline
|
||||
.pdus_rev(None, room_id, None)
|
||||
.pdus_rev(None, room_id, Some(PduCount::Normal(next_batch).saturating_add(1)))
|
||||
.try_take(limit)
|
||||
.try_collect::<Vec<_>>();
|
||||
.try_collect()
|
||||
.map_ok(|mut vec: Vec<_>| {
|
||||
vec.reverse();
|
||||
vec
|
||||
});
|
||||
|
||||
let (membership, visibility, state, events) =
|
||||
try_join4(membership, visibility, state, events)
|
||||
let account_data = services
|
||||
.account_data
|
||||
.changes_since(Some(room_id), body.sender_user(), 0, Some(next_batch))
|
||||
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
|
||||
.collect::<Vec<_>>()
|
||||
.map(Ok);
|
||||
|
||||
let (membership, visibility, state, events, account_data) =
|
||||
try_join5(membership, visibility, state, events, account_data)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
let messages = PaginationChunk {
|
||||
start: events
|
||||
.last()
|
||||
.map(at!(0))
|
||||
.as_ref()
|
||||
.map(ToString::to_string),
|
||||
|
||||
end: events
|
||||
.first()
|
||||
.map(at!(0))
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_default(),
|
||||
|
||||
chunk: events
|
||||
.into_iter()
|
||||
.map(at!(1))
|
||||
.map(Event::into_format)
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Ok(Response {
|
||||
room_id: room_id.to_owned(),
|
||||
account_data: None,
|
||||
state: state.into(),
|
||||
messages: messages.chunk.is_empty().or_some(messages),
|
||||
visibility: visibility.into(),
|
||||
membership,
|
||||
visibility: visibility.into(),
|
||||
account_data: Some(account_data),
|
||||
state: state.into(),
|
||||
messages: PaginationChunk {
|
||||
start: events
|
||||
.first()
|
||||
.map(at!(0))
|
||||
.as_ref()
|
||||
.map(ToString::to_string),
|
||||
|
||||
end: events
|
||||
.last()
|
||||
.map(at!(0))
|
||||
.as_ref()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_default(),
|
||||
|
||||
chunk: events
|
||||
.into_iter()
|
||||
.map(at!(1))
|
||||
.map(Event::into_format)
|
||||
.collect(),
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{
|
||||
FutureExt, StreamExt,
|
||||
FutureExt, StreamExt, TryFutureExt,
|
||||
future::{OptionFuture, join3},
|
||||
stream::FuturesUnordered,
|
||||
};
|
||||
@@ -55,7 +55,7 @@ pub(crate) async fn get_room_summary(
|
||||
) -> Result<get_summary::v1::Response> {
|
||||
let (room_id, servers) = services
|
||||
.alias
|
||||
.resolve_with_servers(&body.room_id_or_alias, Some(body.via.clone()))
|
||||
.maybe_resolve_with_servers(&body.room_id_or_alias, Some(body.via.clone()))
|
||||
.await?;
|
||||
|
||||
if services.metadata.is_banned(&room_id).await {
|
||||
@@ -142,7 +142,9 @@ async fn local_room_summary_response(
|
||||
let avatar_url = services
|
||||
.state_accessor
|
||||
.get_avatar(room_id)
|
||||
.map(|res| res.into_option().unwrap_or_default().url);
|
||||
.map_ok(|content| content.url)
|
||||
.ok()
|
||||
.map(Option::flatten);
|
||||
|
||||
let room_version = services.state.get_room_version(room_id).ok();
|
||||
|
||||
|
||||
@@ -22,20 +22,9 @@ pub(super) async fn handle_login(
|
||||
_body: &Ruma<Request>,
|
||||
info: &Token,
|
||||
) -> Result<OwnedUserId> {
|
||||
let config = &services.config.jwt;
|
||||
|
||||
if !config.enable {
|
||||
return Err!(Request(Unknown("JWT login is not enabled.")));
|
||||
}
|
||||
|
||||
let claim = validate(config, &info.token)?;
|
||||
let local = claim.sub.to_lowercase();
|
||||
let server = &services.server.name;
|
||||
let user_id = UserId::parse_with_server_name(local, server).map_err(|e| {
|
||||
err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}")))
|
||||
})?;
|
||||
|
||||
let user_id = validate_user(services, &info.token)?;
|
||||
if !services.users.exists(&user_id).await {
|
||||
let config = &services.config.jwt;
|
||||
if !config.register_user {
|
||||
return Err!(Request(NotFound("User {user_id} is not registered on this server.")));
|
||||
}
|
||||
@@ -49,6 +38,22 @@ pub(super) async fn handle_login(
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_user(services: &Services, token: &str) -> Result<OwnedUserId> {
|
||||
let config = &services.config.jwt;
|
||||
if !config.enable {
|
||||
return Err!(Request(Unauthorized("JWT login is not enabled.")));
|
||||
}
|
||||
|
||||
let claim = validate(config, token)?;
|
||||
let local = claim.sub.to_lowercase();
|
||||
let server = &services.server.name;
|
||||
let user_id = UserId::parse_with_server_name(local, server).map_err(|e| {
|
||||
err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}")))
|
||||
})?;
|
||||
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
fn validate(config: &JwtConfig, token: &str) -> Result<Claim> {
|
||||
let verifier = init_verifier(config)?;
|
||||
let validator = init_validator(config)?;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod appservice;
|
||||
mod jwt;
|
||||
pub(crate) mod jwt;
|
||||
mod ldap;
|
||||
mod logout;
|
||||
mod password;
|
||||
@@ -116,7 +116,7 @@ pub(crate) async fn login_route(
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
(&access_token, expires_in),
|
||||
(Some(&access_token), expires_in),
|
||||
refresh_token.as_deref(),
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
|
||||
@@ -44,7 +44,7 @@ pub(crate) async fn get_hierarchy_route(
|
||||
.as_ref()
|
||||
.and_then(|s| PaginationToken::from_str(s).ok());
|
||||
|
||||
// Should prevent unexpeded behaviour in (bad) clients
|
||||
// Should prevent unexpected behaviour in (bad) clients
|
||||
if let Some(ref token) = key {
|
||||
if token.suggested_only != body.suggested_only || token.max_depth != max_depth {
|
||||
return Err!(Request(InvalidParam(
|
||||
|
||||
@@ -195,6 +195,7 @@ async fn send_state_event_for_key_helper(
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
Ok(event_id)
|
||||
@@ -328,7 +329,7 @@ async fn allowed_to_send_state_event(
|
||||
for alias in aliases {
|
||||
let (alias_room_id, _servers) = services
|
||||
.alias
|
||||
.resolve_alias(&alias, None)
|
||||
.resolve_alias(&alias)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(BadAlias("Failed resolving alias \"{alias}\": {e}")))
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
mod v3;
|
||||
mod v5;
|
||||
|
||||
use futures::{StreamExt, pin_mut};
|
||||
use ruma::{
|
||||
RoomId, UserId,
|
||||
events::TimelineEventType::{
|
||||
self, Beacon, CallInvite, PollStart, RoomEncrypted, RoomMessage, Sticker,
|
||||
},
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, pin_mut};
|
||||
use ruma::{RoomId, UserId};
|
||||
use tuwunel_core::{
|
||||
Error, PduCount, Result,
|
||||
matrix::pdu::PduEvent,
|
||||
@@ -17,9 +12,6 @@
|
||||
|
||||
pub(crate) use self::{v3::sync_events_route, v5::sync_events_v5_route};
|
||||
|
||||
pub(crate) const DEFAULT_BUMP_TYPES: &[TimelineEventType; 6] =
|
||||
&[CallInvite, PollStart, Beacon, RoomEncrypted, RoomMessage, Sticker];
|
||||
|
||||
async fn load_timeline(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
@@ -50,10 +42,12 @@ async fn load_timeline(
|
||||
.by_ref()
|
||||
.take(limit)
|
||||
.collect()
|
||||
.map(|mut pdus: Vec<_>| {
|
||||
pdus.reverse();
|
||||
pdus
|
||||
})
|
||||
.await;
|
||||
|
||||
let timeline_pdus: Vec<_> = timeline_pdus.into_iter().rev().collect();
|
||||
|
||||
// They /sync response doesn't always return all messages, so we say the output
|
||||
// is limited unless there are events in non_timeline_pdus
|
||||
let limited = non_timeline_pdus.next().await.is_some();
|
||||
|
||||
+400
-311
File diff suppressed because it is too large
Load Diff
+171
-980
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
||||
mod account_data;
|
||||
mod e2ee;
|
||||
mod receipts;
|
||||
mod to_device;
|
||||
mod typing;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use futures::{
|
||||
FutureExt,
|
||||
future::{OptionFuture, join5},
|
||||
};
|
||||
use ruma::{
|
||||
RoomId,
|
||||
api::client::sync::sync_events::v5::{ListId, request::ExtensionRoomConfig, response},
|
||||
};
|
||||
use tuwunel_core::{Result, apply, at, extract_variant, utils::BoolExt};
|
||||
use tuwunel_service::sync::Connection;
|
||||
|
||||
use super::{SyncInfo, Window, share_encrypted_room};
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "extensions",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(
|
||||
next_batch = conn.next_batch,
|
||||
window = window.len(),
|
||||
rooms = conn.rooms.len(),
|
||||
subs = conn.subscriptions.len(),
|
||||
)
|
||||
)]
|
||||
pub(super) async fn handle(
|
||||
sync_info: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
window: &Window,
|
||||
) -> Result<response::Extensions> {
|
||||
let SyncInfo { .. } = sync_info;
|
||||
|
||||
let account_data: OptionFuture<_> = conn
|
||||
.extensions
|
||||
.account_data
|
||||
.enabled
|
||||
.unwrap_or(false)
|
||||
.then(|| account_data::collect(sync_info, conn, window))
|
||||
.into();
|
||||
|
||||
let receipts: OptionFuture<_> = conn
|
||||
.extensions
|
||||
.receipts
|
||||
.enabled
|
||||
.unwrap_or(false)
|
||||
.then(|| receipts::collect(sync_info, conn, window))
|
||||
.into();
|
||||
|
||||
let typing: OptionFuture<_> = conn
|
||||
.extensions
|
||||
.typing
|
||||
.enabled
|
||||
.unwrap_or(false)
|
||||
.then(|| typing::collect(sync_info, conn, window))
|
||||
.into();
|
||||
|
||||
let to_device: OptionFuture<_> = conn
|
||||
.extensions
|
||||
.to_device
|
||||
.enabled
|
||||
.unwrap_or(false)
|
||||
.then(|| to_device::collect(sync_info, conn))
|
||||
.into();
|
||||
|
||||
let e2ee: OptionFuture<_> = conn
|
||||
.extensions
|
||||
.e2ee
|
||||
.enabled
|
||||
.unwrap_or(false)
|
||||
.then(|| e2ee::collect(sync_info, conn))
|
||||
.into();
|
||||
|
||||
let (account_data, receipts, typing, to_device, e2ee) =
|
||||
join5(account_data, receipts, typing, to_device, e2ee)
|
||||
.map(apply!(5, |t: Option<_>| t.unwrap_or(Ok(Default::default()))))
|
||||
.await;
|
||||
|
||||
Ok(response::Extensions {
|
||||
account_data: account_data?,
|
||||
receipts: receipts?,
|
||||
typing: typing?,
|
||||
to_device: to_device?,
|
||||
e2ee: e2ee?,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "selector",
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(?implicit, ?explicit),
|
||||
)]
|
||||
fn selector<'a, ListIter, SubsIter>(
|
||||
SyncInfo { .. }: SyncInfo<'a>,
|
||||
conn: &'a Connection,
|
||||
window: &'a Window,
|
||||
implicit: Option<ListIter>,
|
||||
explicit: Option<SubsIter>,
|
||||
) -> impl Iterator<Item = &'a RoomId> + Send + Sync + 'a
|
||||
where
|
||||
ListIter: Iterator<Item = &'a ListId> + Clone + Debug + Send + Sync + 'a,
|
||||
SubsIter: Iterator<Item = &'a ExtensionRoomConfig> + Clone + Debug + Send + Sync + 'a,
|
||||
{
|
||||
let has_all_subscribed = explicit
|
||||
.clone()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|erc| matches!(erc, ExtensionRoomConfig::AllSubscribed));
|
||||
|
||||
let all_subscribed = has_all_subscribed
|
||||
.then(|| conn.subscriptions.keys())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(AsRef::as_ref);
|
||||
|
||||
let rooms_explicit = has_all_subscribed
|
||||
.is_false()
|
||||
.then(move || {
|
||||
explicit
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|erc| extract_variant!(erc, ExtensionRoomConfig::Room))
|
||||
.map(AsRef::as_ref)
|
||||
})
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let rooms_selected = window
|
||||
.iter()
|
||||
.filter(move |(_, room)| {
|
||||
implicit.as_ref().is_none_or(|lists| {
|
||||
lists
|
||||
.clone()
|
||||
.any(|list| room.lists.contains(list))
|
||||
})
|
||||
})
|
||||
.map(at!(0))
|
||||
.map(AsRef::as_ref);
|
||||
|
||||
all_subscribed
|
||||
.chain(rooms_explicit)
|
||||
.chain(rooms_selected)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use futures::{StreamExt, future::join};
|
||||
use ruma::{api::client::sync::sync_events::v5::response, events::AnyRawAccountDataEvent};
|
||||
use tuwunel_core::{
|
||||
Result, extract_variant,
|
||||
utils::{IterStream, ReadyExt, stream::BroadbandExt},
|
||||
};
|
||||
use tuwunel_service::sync::Room;
|
||||
|
||||
use super::{Connection, SyncInfo, Window, selector};
|
||||
|
||||
#[tracing::instrument(name = "account_data", level = "trace", skip_all)]
|
||||
pub(super) async fn collect(
|
||||
sync_info: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
window: &Window,
|
||||
) -> Result<response::AccountData> {
|
||||
let SyncInfo { services, sender_user, .. } = sync_info;
|
||||
|
||||
let implicit = conn
|
||||
.extensions
|
||||
.account_data
|
||||
.lists
|
||||
.as_deref()
|
||||
.map(<[_]>::iter);
|
||||
|
||||
let explicit = conn
|
||||
.extensions
|
||||
.account_data
|
||||
.rooms
|
||||
.as_deref()
|
||||
.map(<[_]>::iter);
|
||||
|
||||
let rooms = selector(sync_info, conn, window, implicit, explicit)
|
||||
.stream()
|
||||
.broad_filter_map(async |room_id| {
|
||||
let &Room { roomsince, .. } = conn.rooms.get(room_id)?;
|
||||
let changes: Vec<_> = services
|
||||
.account_data
|
||||
.changes_since(Some(room_id), sender_user, roomsince, Some(conn.next_batch))
|
||||
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Room))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
changes
|
||||
.is_empty()
|
||||
.eq(&false)
|
||||
.then(move || (room_id.to_owned(), changes))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let global = services
|
||||
.account_data
|
||||
.changes_since(None, sender_user, conn.globalsince, Some(conn.next_batch))
|
||||
.ready_filter_map(|e| extract_variant!(e, AnyRawAccountDataEvent::Global))
|
||||
.collect();
|
||||
|
||||
let (global, rooms) = join(global, rooms).await;
|
||||
|
||||
Ok(response::AccountData { global, rooms })
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use futures::{
|
||||
FutureExt, StreamExt, TryFutureExt,
|
||||
future::{OptionFuture, join, join3},
|
||||
stream::once,
|
||||
};
|
||||
use ruma::{
|
||||
OwnedUserId, RoomId,
|
||||
api::client::sync::sync_events::{DeviceLists, v5::response},
|
||||
events::{
|
||||
StateEventType, TimelineEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Result, error,
|
||||
matrix::{Event, pdu::PduCount},
|
||||
pair_of,
|
||||
utils::{
|
||||
BoolExt, IterStream, ReadyExt, TryFutureExtExt, future::OptionStream,
|
||||
stream::BroadbandExt,
|
||||
},
|
||||
};
|
||||
use tuwunel_service::sync::Connection;
|
||||
|
||||
use super::{SyncInfo, share_encrypted_room};
|
||||
|
||||
#[tracing::instrument(name = "e2ee", level = "trace", skip_all)]
|
||||
pub(super) async fn collect(
|
||||
sync_info: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
) -> Result<response::E2EE> {
|
||||
let SyncInfo { services, sender_user, sender_device, .. } = sync_info;
|
||||
|
||||
let keys_changed = services
|
||||
.users
|
||||
.keys_changed(sender_user, conn.globalsince, Some(conn.next_batch))
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<HashSet<_>>()
|
||||
.map(|changed| (changed, HashSet::new()));
|
||||
|
||||
let (changed, left) = (HashSet::new(), HashSet::new());
|
||||
let (changed, left) = services
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_filter_map(async |room_id| collect_room(sync_info, conn, &room_id).await.ok())
|
||||
.chain(once(keys_changed))
|
||||
.ready_fold((changed, left), |(mut changed, mut left), room| {
|
||||
changed.extend(room.0);
|
||||
left.extend(room.1);
|
||||
(changed, left)
|
||||
})
|
||||
.await;
|
||||
|
||||
let left = left
|
||||
.into_iter()
|
||||
.stream()
|
||||
.filter_map(async |user_id| {
|
||||
share_encrypted_room(services, sender_user, &user_id, None)
|
||||
.await
|
||||
.is_false()
|
||||
.then_some(user_id)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let device_one_time_keys_count = services
|
||||
.users
|
||||
.last_one_time_keys_update(sender_user)
|
||||
.then(|since| -> OptionFuture<_> {
|
||||
since
|
||||
.gt(&conn.globalsince)
|
||||
.then(|| {
|
||||
services
|
||||
.users
|
||||
.count_one_time_keys(sender_user, sender_device)
|
||||
})
|
||||
.into()
|
||||
})
|
||||
.map(Option::unwrap_or_default);
|
||||
|
||||
let (left, device_one_time_keys_count) = join(left, device_one_time_keys_count)
|
||||
.boxed()
|
||||
.await;
|
||||
|
||||
Ok(response::E2EE {
|
||||
device_one_time_keys_count,
|
||||
device_unused_fallback_key_types: None,
|
||||
device_lists: DeviceLists {
|
||||
changed: changed.into_iter().collect(),
|
||||
left,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, fields(room_id), ret)]
|
||||
async fn collect_room(
|
||||
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
room_id: &RoomId,
|
||||
) -> Result<pair_of!(HashSet<OwnedUserId>)> {
|
||||
let current_shortstatehash = services
|
||||
.state
|
||||
.get_room_shortstatehash(room_id)
|
||||
.inspect_err(|e| error!("Room {room_id} has no state: {e}"));
|
||||
|
||||
let room_keys_changed = services
|
||||
.users
|
||||
.room_keys_changed(room_id, conn.globalsince, Some(conn.next_batch))
|
||||
.map(|(user_id, _)| user_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let (current_shortstatehash, device_list_changed) =
|
||||
join(current_shortstatehash, room_keys_changed)
|
||||
.boxed()
|
||||
.await;
|
||||
|
||||
let lists = (device_list_changed, HashSet::new());
|
||||
let Ok(current_shortstatehash) = current_shortstatehash else {
|
||||
return Ok(lists);
|
||||
};
|
||||
|
||||
if current_shortstatehash <= conn.globalsince {
|
||||
return Ok(lists);
|
||||
}
|
||||
|
||||
let Ok(since_shortstatehash) = services
|
||||
.timeline
|
||||
.prev_shortstatehash(room_id, PduCount::Normal(conn.globalsince).saturating_add(1))
|
||||
.await
|
||||
else {
|
||||
return Ok(lists);
|
||||
};
|
||||
|
||||
if since_shortstatehash == current_shortstatehash {
|
||||
return Ok(lists);
|
||||
}
|
||||
|
||||
let encrypted_room = services
|
||||
.state_accessor
|
||||
.state_get(current_shortstatehash, &StateEventType::RoomEncryption, "")
|
||||
.is_ok();
|
||||
|
||||
let since_encryption = services
|
||||
.state_accessor
|
||||
.state_get(since_shortstatehash, &StateEventType::RoomEncryption, "")
|
||||
.is_ok();
|
||||
|
||||
let sender_joined_count = services
|
||||
.state_cache
|
||||
.get_joined_count(room_id, sender_user);
|
||||
|
||||
let (encrypted_room, since_encryption, sender_joined_count) =
|
||||
join3(encrypted_room, since_encryption, sender_joined_count).await;
|
||||
|
||||
if !encrypted_room {
|
||||
return Ok(lists);
|
||||
}
|
||||
|
||||
let encrypted_since_last_sync = !since_encryption;
|
||||
let joined_since_last_sync = sender_joined_count.is_ok_and(|count| count > conn.globalsince);
|
||||
let joined_members_burst: OptionFuture<_> = (joined_since_last_sync
|
||||
|| encrypted_since_last_sync)
|
||||
.then(|| {
|
||||
services
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.ready_filter(|&user_id| user_id != sender_user)
|
||||
.map(ToOwned::to_owned)
|
||||
.map(|user_id| (MembershipState::Join, user_id))
|
||||
.into_future()
|
||||
})
|
||||
.into();
|
||||
|
||||
services
|
||||
.state_accessor
|
||||
.state_added((since_shortstatehash, current_shortstatehash))
|
||||
.broad_filter_map(async |(_shortstatekey, shorteventid)| {
|
||||
services
|
||||
.timeline
|
||||
.get_pdu_from_shorteventid(shorteventid)
|
||||
.ok()
|
||||
.await
|
||||
})
|
||||
.ready_filter(|event| *event.kind() == TimelineEventType::RoomMember)
|
||||
.ready_filter(|event| {
|
||||
event
|
||||
.state_key()
|
||||
.is_some_and(|state_key| state_key != sender_user)
|
||||
})
|
||||
.ready_filter_map(|event| {
|
||||
let content: RoomMemberEventContent = event.get_content().ok()?;
|
||||
let user_id: OwnedUserId = event.state_key()?.parse().ok()?;
|
||||
|
||||
Some((content.membership, user_id))
|
||||
})
|
||||
.chain(joined_members_burst.stream())
|
||||
.fold(lists, async |(mut changed, mut left), (membership, user_id)| {
|
||||
use MembershipState::*;
|
||||
|
||||
let should_add = async |user_id| {
|
||||
!share_encrypted_room(services, sender_user, user_id, Some(room_id)).await
|
||||
};
|
||||
|
||||
match membership {
|
||||
| Join if should_add(&user_id).await => changed.insert(user_id),
|
||||
| Leave => left.insert(user_id),
|
||||
| _ => false,
|
||||
};
|
||||
|
||||
(changed, left)
|
||||
})
|
||||
.map(Ok)
|
||||
.boxed()
|
||||
.await
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
OwnedRoomId, RoomId,
|
||||
api::client::sync::sync_events::v5::response,
|
||||
events::{AnySyncEphemeralRoomEvent, receipt::SyncReceiptEvent},
|
||||
serde::Raw,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Result,
|
||||
utils::{BoolExt, IterStream, stream::BroadbandExt},
|
||||
};
|
||||
use tuwunel_service::{rooms::read_receipt::pack_receipts, sync::Room};
|
||||
|
||||
use super::{Connection, SyncInfo, Window, selector};
|
||||
|
||||
#[tracing::instrument(name = "receipts", level = "trace", skip_all)]
|
||||
pub(super) async fn collect(
|
||||
sync_info: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
window: &Window,
|
||||
) -> Result<response::Receipts> {
|
||||
let SyncInfo { .. } = sync_info;
|
||||
|
||||
let implicit = conn
|
||||
.extensions
|
||||
.receipts
|
||||
.lists
|
||||
.as_deref()
|
||||
.map(<[_]>::iter);
|
||||
|
||||
let explicit = conn
|
||||
.extensions
|
||||
.receipts
|
||||
.rooms
|
||||
.as_deref()
|
||||
.map(<[_]>::iter);
|
||||
|
||||
let rooms = selector(sync_info, conn, window, implicit, explicit)
|
||||
.stream()
|
||||
.broad_filter_map(|room_id| collect_room(sync_info, conn, window, room_id))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
Ok(response::Receipts { rooms })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all, fields(room_id), ret)]
|
||||
async fn collect_room(
|
||||
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
_window: &Window,
|
||||
room_id: &RoomId,
|
||||
) -> Option<(OwnedRoomId, Raw<SyncReceiptEvent>)> {
|
||||
let &Room { roomsince, .. } = conn.rooms.get(room_id)?;
|
||||
let private_receipt = services
|
||||
.read_receipt
|
||||
.last_privateread_update(sender_user, room_id)
|
||||
.then(async |last_private_update| {
|
||||
if last_private_update <= roomsince || last_private_update > conn.next_batch {
|
||||
return None;
|
||||
}
|
||||
|
||||
services
|
||||
.read_receipt
|
||||
.private_read_get(room_id, sender_user)
|
||||
.map(Some)
|
||||
.await
|
||||
})
|
||||
.map(Option::into_iter)
|
||||
.map(Iterator::flatten)
|
||||
.map(IterStream::stream)
|
||||
.flatten_stream();
|
||||
|
||||
let receipts: Vec<Raw<AnySyncEphemeralRoomEvent>> = services
|
||||
.read_receipt
|
||||
.readreceipts_since(room_id, roomsince, Some(conn.next_batch))
|
||||
.filter_map(async |(read_user, _ts, v)| {
|
||||
services
|
||||
.users
|
||||
.user_is_ignored(read_user, sender_user)
|
||||
.await
|
||||
.or_some(v)
|
||||
})
|
||||
.chain(private_receipt)
|
||||
.collect()
|
||||
.boxed()
|
||||
.await;
|
||||
|
||||
receipts
|
||||
.is_empty()
|
||||
.is_false()
|
||||
.then(|| (room_id.to_owned(), pack_receipts(receipts.into_iter())))
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use futures::StreamExt;
|
||||
use ruma::api::client::sync::sync_events::v5::response;
|
||||
use tuwunel_core::{self, Result, at};
|
||||
|
||||
use super::{Connection, SyncInfo};
|
||||
|
||||
#[tracing::instrument(name = "to_device", level = "trace", skip_all, ret)]
|
||||
pub(super) async fn collect(
|
||||
SyncInfo { services, sender_user, sender_device, .. }: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
) -> Result<Option<response::ToDevice>> {
|
||||
services
|
||||
.users
|
||||
.remove_to_device_events(sender_user, sender_device, conn.globalsince)
|
||||
.await;
|
||||
|
||||
let events: Vec<_> = services
|
||||
.users
|
||||
.get_to_device_events(sender_user, sender_device, None, Some(conn.next_batch))
|
||||
.map(at!(1))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let to_device = events
|
||||
.is_empty()
|
||||
.eq(&false)
|
||||
.then(|| response::ToDevice {
|
||||
next_batch: conn.next_batch.to_string().into(),
|
||||
events,
|
||||
});
|
||||
|
||||
Ok(to_device)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt};
|
||||
use ruma::{
|
||||
api::client::sync::sync_events::v5::response,
|
||||
events::typing::{SyncTypingEvent, TypingEventContent},
|
||||
serde::Raw,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Result, debug_error,
|
||||
utils::{IterStream, ReadyExt},
|
||||
};
|
||||
|
||||
use super::{Connection, SyncInfo, Window, selector};
|
||||
|
||||
#[tracing::instrument(name = "typing", level = "trace", skip_all, ret)]
|
||||
pub(super) async fn collect(
|
||||
sync_info: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
window: &Window,
|
||||
) -> Result<response::Typing> {
|
||||
use response::Typing;
|
||||
|
||||
let SyncInfo { services, sender_user, .. } = sync_info;
|
||||
|
||||
let implicit = conn
|
||||
.extensions
|
||||
.typing
|
||||
.lists
|
||||
.as_deref()
|
||||
.map(<[_]>::iter);
|
||||
|
||||
let explicit = conn
|
||||
.extensions
|
||||
.typing
|
||||
.rooms
|
||||
.as_deref()
|
||||
.map(<[_]>::iter);
|
||||
|
||||
selector(sync_info, conn, window, implicit, explicit)
|
||||
.stream()
|
||||
.filter_map(async |room_id| {
|
||||
services
|
||||
.typing
|
||||
.typing_users_for_user(room_id, sender_user)
|
||||
.inspect_err(|e| debug_error!(%room_id, "Failed to get typing events: {e}"))
|
||||
.await
|
||||
.ok()
|
||||
.filter(|users| !users.is_empty())
|
||||
.map(|users| (room_id, users))
|
||||
})
|
||||
.ready_filter_map(|(room_id, users)| {
|
||||
let content = TypingEventContent::new(users);
|
||||
let event = SyncTypingEvent { content };
|
||||
let event = Raw::new(&event);
|
||||
|
||||
Some((room_id.to_owned(), event.ok()?))
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>()
|
||||
.map(|rooms| Typing { rooms })
|
||||
.map(Ok)
|
||||
.await
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use futures::{StreamExt, future::OptionFuture, pin_mut};
|
||||
use ruma::{
|
||||
RoomId, api::client::sync::sync_events::v5::request::ListFilters, directory::RoomTypeFilter,
|
||||
events::room::member::MembershipState,
|
||||
};
|
||||
use tuwunel_core::{
|
||||
is_equal_to, is_true,
|
||||
utils::{
|
||||
BoolExt, FutureBoolExt, IterStream, ReadyExt,
|
||||
future::{self, OptionExt, ReadyEqExt},
|
||||
},
|
||||
};
|
||||
|
||||
use super::SyncInfo;
|
||||
|
||||
#[tracing::instrument(name = "filter", level = "trace", skip_all)]
|
||||
pub(super) async fn filter_room(
|
||||
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
|
||||
filter: &ListFilters,
|
||||
room_id: &RoomId,
|
||||
membership: Option<&MembershipState>,
|
||||
) -> bool {
|
||||
let match_invite: OptionFuture<_> = filter
|
||||
.is_invite
|
||||
.map(async |is_invite| match (membership, is_invite) {
|
||||
| (Some(MembershipState::Invite), true) => true,
|
||||
| (Some(MembershipState::Invite), false) => false,
|
||||
| (Some(_), true) => false,
|
||||
| (Some(_), false) => true,
|
||||
| _ =>
|
||||
services
|
||||
.state_cache
|
||||
.is_invited(sender_user, room_id)
|
||||
.await == is_invite,
|
||||
})
|
||||
.into();
|
||||
|
||||
let match_direct: OptionFuture<_> = filter
|
||||
.is_dm
|
||||
.map(async |is_dm| {
|
||||
services
|
||||
.account_data
|
||||
.is_direct(sender_user, room_id)
|
||||
.await == is_dm
|
||||
})
|
||||
.into();
|
||||
|
||||
let match_direct_member: OptionFuture<_> = filter
|
||||
.is_dm
|
||||
.map(async |is_dm| {
|
||||
services
|
||||
.state_accessor
|
||||
.is_direct(room_id, sender_user)
|
||||
.await == is_dm
|
||||
})
|
||||
.into();
|
||||
|
||||
let match_encrypted: OptionFuture<_> = filter
|
||||
.is_encrypted
|
||||
.map(async |is_encrypted| {
|
||||
services
|
||||
.state_accessor
|
||||
.is_encrypted_room(room_id)
|
||||
.await == is_encrypted
|
||||
})
|
||||
.into();
|
||||
|
||||
let match_space_child: OptionFuture<_> = filter
|
||||
.spaces
|
||||
.is_empty()
|
||||
.is_false()
|
||||
.then(async || {
|
||||
filter
|
||||
.spaces
|
||||
.iter()
|
||||
.stream()
|
||||
.flat_map(|room_id| services.spaces.get_space_children(room_id))
|
||||
.ready_any(is_equal_to!(room_id))
|
||||
.await
|
||||
})
|
||||
.into();
|
||||
|
||||
let fetch_tags = !filter.tags.is_empty() || !filter.not_tags.is_empty();
|
||||
let match_room_tag: OptionFuture<_> = fetch_tags
|
||||
.then(async || {
|
||||
if let Some(tags) = services
|
||||
.account_data
|
||||
.get_room_tags(sender_user, room_id)
|
||||
.await
|
||||
.ok()
|
||||
.filter(|tags| !tags.is_empty())
|
||||
{
|
||||
tags.keys().any(|tag| {
|
||||
(filter.not_tags.is_empty() || !filter.not_tags.contains(tag))
|
||||
|| (!filter.tags.is_empty() && filter.tags.contains(tag))
|
||||
})
|
||||
} else {
|
||||
filter.tags.is_empty()
|
||||
}
|
||||
})
|
||||
.into();
|
||||
|
||||
let fetch_room_type = !filter.room_types.is_empty() || !filter.not_room_types.is_empty();
|
||||
let match_room_type: OptionFuture<_> = fetch_room_type
|
||||
.then(async || {
|
||||
let room_type = services
|
||||
.state_accessor
|
||||
.get_room_type(room_id)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let room_type = RoomTypeFilter::from(room_type);
|
||||
(filter.not_room_types.is_empty() || !filter.not_room_types.contains(&room_type))
|
||||
&& (filter.room_types.is_empty() || filter.room_types.contains(&room_type))
|
||||
})
|
||||
.into();
|
||||
|
||||
future::and7(
|
||||
match_invite.is_none_or(is_true!()),
|
||||
match_encrypted.is_none_or(is_true!()),
|
||||
match_direct.is_none_or(is_true!()),
|
||||
match_direct_member.is_none_or(is_true!()),
|
||||
match_space_child.is_none_or(is_true!()),
|
||||
match_room_type.is_none_or(is_true!()),
|
||||
match_room_tag.is_none_or(is_true!()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "filter_meta", level = "trace", skip_all)]
|
||||
pub(super) async fn filter_room_meta(
|
||||
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
|
||||
room_id: &RoomId,
|
||||
) -> bool {
|
||||
let not_exists = services.metadata.exists(room_id).eq(&false);
|
||||
|
||||
let is_disabled = services.metadata.is_disabled(room_id);
|
||||
|
||||
let is_banned = services.metadata.is_banned(room_id);
|
||||
|
||||
let not_visible = services
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, room_id)
|
||||
.eq(&false);
|
||||
|
||||
pin_mut!(not_visible, not_exists, is_disabled, is_banned);
|
||||
not_visible
|
||||
.or(not_exists)
|
||||
.or(is_disabled)
|
||||
.or(is_banned)
|
||||
.await
|
||||
.eq(&false)
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, HashSet},
|
||||
};
|
||||
|
||||
use futures::{
|
||||
FutureExt, StreamExt, TryFutureExt, TryStreamExt,
|
||||
future::{OptionFuture, join, join3, join4},
|
||||
};
|
||||
use ruma::{
|
||||
JsOption, MxcUri, OwnedMxcUri, OwnedRoomId, RoomId, UserId,
|
||||
api::client::sync::sync_events::{
|
||||
UnreadNotificationsCount,
|
||||
v5::{DisplayName, response, response::Heroes},
|
||||
},
|
||||
events::{
|
||||
StateEventType,
|
||||
TimelineEventType::{
|
||||
self, Beacon, CallInvite, PollStart, RoomEncrypted, RoomMessage, Sticker,
|
||||
},
|
||||
room::member::MembershipState,
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Result, at, err, error, is_equal_to,
|
||||
matrix::{Event, StateKey, pdu::PduCount},
|
||||
ref_at,
|
||||
utils::{
|
||||
BoolExt, IterStream, ReadyExt, TryFutureExtExt, math::usize_from_ruma, result::FlatOk,
|
||||
stream::BroadbandExt,
|
||||
},
|
||||
};
|
||||
use tuwunel_service::{Services, sync::Room};
|
||||
|
||||
use super::{super::load_timeline, Connection, SyncInfo, Window, WindowRoom};
|
||||
use crate::client::ignored_filter;
|
||||
|
||||
static DEFAULT_BUMP_TYPES: [TimelineEventType; 6] =
|
||||
[CallInvite, PollStart, Beacon, RoomEncrypted, RoomMessage, Sticker];
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "rooms",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(
|
||||
next_batch = conn.next_batch,
|
||||
window = window.len(),
|
||||
)
|
||||
)]
|
||||
pub(super) async fn handle(
|
||||
sync_info: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
window: &Window,
|
||||
) -> Result<BTreeMap<OwnedRoomId, response::Room>> {
|
||||
window
|
||||
.iter()
|
||||
.stream()
|
||||
.broad_filter_map(async |(room_id, room)| {
|
||||
handle_room(sync_info, conn, room)
|
||||
.map_ok(move |room| (room_id.clone(), room))
|
||||
.inspect_err(|e| error!(?room_id, "sync handler: {e:?}"))
|
||||
.await
|
||||
.ok()
|
||||
})
|
||||
.collect()
|
||||
.map(Ok)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "room",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(room_id, roomsince)
|
||||
)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_room(
|
||||
SyncInfo { services, sender_user, .. }: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
WindowRoom {
|
||||
lists, membership, room_id, last_count, ..
|
||||
}: &WindowRoom,
|
||||
) -> Result<response::Room> {
|
||||
debug_assert!(
|
||||
DEFAULT_BUMP_TYPES.is_sorted(),
|
||||
"DEFAULT_BUMP_TYPES must be sorted for binary search"
|
||||
);
|
||||
|
||||
let &Room { roomsince, .. } = conn
|
||||
.rooms
|
||||
.get(room_id)
|
||||
.ok_or_else(|| err!("Missing connection state for {room_id}"))?;
|
||||
|
||||
debug_assert!(
|
||||
*last_count > roomsince || *last_count == 0 || roomsince == 0,
|
||||
"Stale room shouldn't be in the window"
|
||||
);
|
||||
|
||||
if *membership == Some(MembershipState::Leave) {
|
||||
return Ok(response::Room {
|
||||
initial: roomsince.eq(&0).then_some(true),
|
||||
lists: lists.clone(),
|
||||
membership: membership.clone(),
|
||||
prev_batch: Some(conn.next_batch.to_string().into()),
|
||||
limited: true,
|
||||
required_state: vec![
|
||||
services
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomMember, sender_user.as_str())
|
||||
.map_ok(Event::into_format)
|
||||
.await?,
|
||||
],
|
||||
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
let is_invite = *membership == Some(MembershipState::Invite);
|
||||
let default_details = (0_usize, HashSet::new());
|
||||
let (timeline_limit, required_state) = lists
|
||||
.iter()
|
||||
.filter_map(|list_id| conn.lists.get(list_id))
|
||||
.map(|list| &list.room_details)
|
||||
.chain(conn.subscriptions.get(room_id).into_iter())
|
||||
.fold(default_details, |(mut timeline_limit, mut required_state), config| {
|
||||
let limit = usize_from_ruma(config.timeline_limit);
|
||||
|
||||
timeline_limit = timeline_limit.max(limit);
|
||||
required_state.extend(config.required_state.clone());
|
||||
|
||||
(timeline_limit, required_state)
|
||||
});
|
||||
|
||||
let timeline: OptionFuture<_> = is_invite
|
||||
.is_false()
|
||||
.then(|| {
|
||||
load_timeline(
|
||||
services,
|
||||
sender_user,
|
||||
room_id,
|
||||
PduCount::Normal(roomsince),
|
||||
Some(PduCount::from(conn.next_batch)),
|
||||
timeline_limit,
|
||||
)
|
||||
})
|
||||
.into();
|
||||
|
||||
let (timeline_pdus, limited, _lastcount) = timeline
|
||||
.await
|
||||
.flat_ok()
|
||||
.unwrap_or_else(|| (Vec::new(), true, PduCount::default()));
|
||||
|
||||
let required_state = required_state
|
||||
.into_iter()
|
||||
.filter(|_| !timeline_pdus.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let prev_batch = timeline_pdus
|
||||
.first()
|
||||
.map(at!(0))
|
||||
.map(PduCount::into_unsigned)
|
||||
.as_ref()
|
||||
.map(ToString::to_string);
|
||||
|
||||
let bump_stamp = timeline_pdus
|
||||
.iter()
|
||||
.filter(|(_, pdu)| {
|
||||
if *pdu.event_type() == TimelineEventType::RoomMember {
|
||||
return pdu
|
||||
.state_key()
|
||||
.is_some_and(is_equal_to!(sender_user.as_str()));
|
||||
}
|
||||
|
||||
DEFAULT_BUMP_TYPES
|
||||
.binary_search(pdu.event_type())
|
||||
.is_ok()
|
||||
})
|
||||
.filter(|(_, pdu)| !pdu.is_redacted())
|
||||
.map(at!(0))
|
||||
.map(PduCount::into_signed)
|
||||
.max()
|
||||
.map(TryInto::try_into)
|
||||
.flat_ok();
|
||||
|
||||
let num_live: OptionFuture<_> = roomsince
|
||||
.ne(&0)
|
||||
.and_is(limited || timeline_pdus.len() >= timeline_limit)
|
||||
.then(|| {
|
||||
services
|
||||
.timeline
|
||||
.pdus(None, room_id, Some(roomsince.into()))
|
||||
.count()
|
||||
.map(TryInto::try_into)
|
||||
.map(Result::ok)
|
||||
})
|
||||
.into();
|
||||
|
||||
let lazy = required_state
|
||||
.iter()
|
||||
.any(is_equal_to!(&(StateEventType::RoomMember, "$LAZY".into())));
|
||||
|
||||
let mut timeline_senders: Vec<_> = timeline_pdus
|
||||
.iter()
|
||||
.filter(|_| lazy)
|
||||
.map(ref_at!(1))
|
||||
.map(Event::sender)
|
||||
.collect();
|
||||
|
||||
timeline_senders.sort();
|
||||
timeline_senders.dedup();
|
||||
let timeline_senders = timeline_senders
|
||||
.iter()
|
||||
.map(|sender| (StateEventType::RoomMember, StateKey::from_str(sender.as_str())))
|
||||
.stream();
|
||||
|
||||
let wildcard_state = required_state
|
||||
.iter()
|
||||
.filter(|(_, state_key)| state_key == "*")
|
||||
.map(|(event_type, _)| {
|
||||
services
|
||||
.state_accessor
|
||||
.room_state_keys(room_id, event_type)
|
||||
.map_ok(|state_key| (event_type.clone(), state_key))
|
||||
.ready_filter_map(Result::ok)
|
||||
})
|
||||
.stream()
|
||||
.flatten();
|
||||
|
||||
let required_state = required_state
|
||||
.iter()
|
||||
.cloned()
|
||||
.stream()
|
||||
.chain(wildcard_state)
|
||||
.chain(timeline_senders)
|
||||
.broad_filter_map(async |state| {
|
||||
let state_key: StateKey = match state.1.as_str() {
|
||||
| "$LAZY" | "*" => return None,
|
||||
| "$ME" => sender_user.as_str().into(),
|
||||
| _ => state.1.clone(),
|
||||
};
|
||||
|
||||
services
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &state.0, &state_key)
|
||||
.map_ok(Event::into_format)
|
||||
.ok()
|
||||
.await
|
||||
})
|
||||
.collect();
|
||||
|
||||
// TODO: figure out a timestamp we can use for remote invites
|
||||
let invite_state: OptionFuture<_> = is_invite
|
||||
.then(|| {
|
||||
services
|
||||
.state_cache
|
||||
.invite_state(sender_user, room_id)
|
||||
.ok()
|
||||
})
|
||||
.into();
|
||||
|
||||
let room_name = services
|
||||
.state_accessor
|
||||
.get_name(room_id)
|
||||
.map_ok(Into::into)
|
||||
.map(Result::ok);
|
||||
|
||||
let room_avatar = services
|
||||
.state_accessor
|
||||
.get_avatar(room_id)
|
||||
.map_ok(|content| content.url)
|
||||
.ok()
|
||||
.map(Option::flatten);
|
||||
|
||||
let highlight_count = services
|
||||
.pusher
|
||||
.highlight_count(sender_user, room_id)
|
||||
.map(TryInto::try_into)
|
||||
.map(Result::ok);
|
||||
|
||||
let notification_count = services
|
||||
.pusher
|
||||
.notification_count(sender_user, room_id)
|
||||
.map(TryInto::try_into)
|
||||
.map(Result::ok);
|
||||
|
||||
let joined_count = services
|
||||
.state_cache
|
||||
.room_joined_count(room_id)
|
||||
.map_ok(TryInto::try_into)
|
||||
.map_ok(Result::ok)
|
||||
.map(FlatOk::flat_ok);
|
||||
|
||||
let invited_count = services
|
||||
.state_cache
|
||||
.room_invited_count(room_id)
|
||||
.map_ok(TryInto::try_into)
|
||||
.map_ok(Result::ok)
|
||||
.map(FlatOk::flat_ok);
|
||||
|
||||
let is_dm = services
|
||||
.state_accessor
|
||||
.is_direct(room_id, sender_user)
|
||||
.map(|is_dm| is_dm.then_some(is_dm));
|
||||
|
||||
let last_read_count = services
|
||||
.pusher
|
||||
.last_notification_read(sender_user, room_id);
|
||||
|
||||
let timeline = timeline_pdus
|
||||
.iter()
|
||||
.stream()
|
||||
.filter_map(|item| ignored_filter(services, item.clone(), sender_user))
|
||||
.map(at!(1))
|
||||
.map(Event::into_format)
|
||||
.collect();
|
||||
|
||||
let meta = join3(room_name, room_avatar, is_dm);
|
||||
let events = join4(timeline, num_live, required_state, invite_state);
|
||||
let member_counts = join(joined_count, invited_count);
|
||||
let notification_counts = join3(highlight_count, notification_count, last_read_count);
|
||||
let (
|
||||
(room_name, room_avatar, is_dm),
|
||||
(timeline, num_live, required_state, invite_state),
|
||||
(joined_count, invited_count),
|
||||
(highlight_count, notification_count, _last_notification_read),
|
||||
) = join4(meta, events, member_counts, notification_counts)
|
||||
.boxed()
|
||||
.await;
|
||||
|
||||
let (heroes, hero_name, heroes_avatar) = calculate_heroes(
|
||||
services,
|
||||
sender_user,
|
||||
room_id,
|
||||
room_name.as_ref(),
|
||||
room_avatar.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(response::Room {
|
||||
initial: roomsince.eq(&0).then_some(true),
|
||||
lists: lists.clone(),
|
||||
membership: membership.clone(),
|
||||
name: room_name.or(hero_name),
|
||||
avatar: JsOption::from_option(room_avatar.or(heroes_avatar)),
|
||||
is_dm,
|
||||
heroes,
|
||||
required_state,
|
||||
invite_state: invite_state.flatten(),
|
||||
prev_batch: prev_batch.as_deref().map(Into::into),
|
||||
num_live: num_live.flatten(),
|
||||
limited,
|
||||
timeline,
|
||||
bump_stamp,
|
||||
joined_count,
|
||||
invited_count,
|
||||
unread_notifications: UnreadNotificationsCount { highlight_count, notification_count },
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "heroes", level = "trace", skip_all)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
async fn calculate_heroes(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
room_name: Option<&DisplayName>,
|
||||
room_avatar: Option<&MxcUri>,
|
||||
) -> Result<(Option<Heroes>, Option<DisplayName>, Option<OwnedMxcUri>)> {
|
||||
const MAX_HEROES: usize = 5;
|
||||
let heroes: Heroes = services
|
||||
.state_cache
|
||||
.room_members(room_id)
|
||||
.ready_filter(|&member| member != sender_user)
|
||||
.ready_filter_map(|member| room_name.is_none().then_some(member))
|
||||
.map(ToOwned::to_owned)
|
||||
.broadn_filter_map(MAX_HEROES, async |user_id| {
|
||||
let content = services
|
||||
.state_accessor
|
||||
.get_member(room_id, &user_id)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
let name: OptionFuture<_> = content
|
||||
.displayname
|
||||
.is_none()
|
||||
.then(|| services.users.displayname(&user_id).ok())
|
||||
.into();
|
||||
|
||||
let avatar: OptionFuture<_> = content
|
||||
.avatar_url
|
||||
.is_none()
|
||||
.then(|| services.users.avatar_url(&user_id).ok())
|
||||
.into();
|
||||
|
||||
let (name, avatar) = join(name, avatar).await;
|
||||
let hero = response::Hero {
|
||||
user_id,
|
||||
avatar: avatar.unwrap_or(content.avatar_url),
|
||||
name: name
|
||||
.unwrap_or(content.displayname)
|
||||
.map(Into::into),
|
||||
};
|
||||
|
||||
Some(hero)
|
||||
})
|
||||
.take(MAX_HEROES)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let hero_name = match heroes.len().cmp(&(1_usize)) {
|
||||
| Ordering::Less => None,
|
||||
| Ordering::Equal => Some(
|
||||
heroes[0]
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| heroes[0].user_id.as_str().into()),
|
||||
),
|
||||
| Ordering::Greater => {
|
||||
let firsts = heroes[1..]
|
||||
.iter()
|
||||
.map(|h| {
|
||||
h.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| h.user_id.as_str().into())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
let last = heroes[0]
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| heroes[0].user_id.as_str().into());
|
||||
|
||||
Some(format!("{firsts} and {last}")).map(Into::into)
|
||||
},
|
||||
};
|
||||
|
||||
let heroes_avatar = (room_avatar.is_none() && room_name.is_none())
|
||||
.then(|| {
|
||||
heroes
|
||||
.first()
|
||||
.and_then(|hero| hero.avatar.clone())
|
||||
})
|
||||
.flatten();
|
||||
|
||||
Ok((Some(heroes), hero_name, heroes_avatar))
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use futures::{
|
||||
FutureExt, StreamExt, TryFutureExt,
|
||||
future::{OptionFuture, join5},
|
||||
};
|
||||
use ruma::{OwnedRoomId, UInt, events::room::member::MembershipState, uint};
|
||||
use tuwunel_core::{
|
||||
apply, is_true,
|
||||
matrix::PduCount,
|
||||
trace,
|
||||
utils::{
|
||||
BoolExt, TryFutureExtExt,
|
||||
math::usize_from_ruma,
|
||||
stream::{BroadbandExt, IterStream},
|
||||
},
|
||||
};
|
||||
use tuwunel_service::sync::Connection;
|
||||
|
||||
use super::{
|
||||
ListIds, ResponseLists, SyncInfo, Window, WindowRoom,
|
||||
filter::{filter_room, filter_room_meta},
|
||||
};
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub(super) async fn selector(
|
||||
conn: &mut Connection,
|
||||
sync_info: SyncInfo<'_>,
|
||||
) -> (Window, ResponseLists) {
|
||||
use MembershipState::*;
|
||||
|
||||
let SyncInfo { services, sender_user, .. } = sync_info;
|
||||
|
||||
let mut rooms = services
|
||||
.state_cache
|
||||
.user_memberships(sender_user, Some(&[Join, Invite, Knock]))
|
||||
.map(|(membership, room_id)| (room_id.to_owned(), Some(membership)))
|
||||
.broad_filter_map(|(room_id, membership)| matcher(sync_info, conn, room_id, membership))
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
rooms.sort_by(room_sort);
|
||||
rooms
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.for_each(|(i, room)| {
|
||||
room.ranked = i;
|
||||
|
||||
conn.rooms
|
||||
.entry(room.room_id.clone())
|
||||
.or_default();
|
||||
});
|
||||
|
||||
trace!(?rooms);
|
||||
let lists = response_lists(rooms.iter());
|
||||
|
||||
trace!(?lists);
|
||||
let window = window(sync_info, conn, rooms.iter(), &lists).await;
|
||||
|
||||
trace!(?window);
|
||||
(window, lists)
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
name = "matcher",
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(?room_id, ?membership)
|
||||
)]
|
||||
async fn matcher(
|
||||
sync_info: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
room_id: OwnedRoomId,
|
||||
membership: Option<MembershipState>,
|
||||
) -> Option<WindowRoom> {
|
||||
let SyncInfo { services, sender_user, .. } = sync_info;
|
||||
|
||||
let (matched, lists) = conn
|
||||
.lists
|
||||
.iter()
|
||||
.stream()
|
||||
.filter_map(async |(id, list)| {
|
||||
let filter: OptionFuture<_> = list
|
||||
.filters
|
||||
.clone()
|
||||
.map(async |filters| {
|
||||
filter_room(sync_info, &filters, &room_id, membership.as_ref()).await
|
||||
})
|
||||
.into();
|
||||
|
||||
filter
|
||||
.await
|
||||
.is_none_or(is_true!())
|
||||
.then(|| id.clone())
|
||||
})
|
||||
.collect::<ListIds>()
|
||||
.map(|lists| (lists.is_empty().is_false(), lists))
|
||||
.await;
|
||||
|
||||
let last_notification: OptionFuture<_> = matched
|
||||
.then(|| {
|
||||
services
|
||||
.pusher
|
||||
.last_notification_read(sender_user, &room_id)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.into();
|
||||
|
||||
let last_privateread: OptionFuture<_> = matched
|
||||
.then(|| {
|
||||
services
|
||||
.read_receipt
|
||||
.last_privateread_update(sender_user, &room_id)
|
||||
})
|
||||
.into();
|
||||
|
||||
let last_receipt: OptionFuture<_> = matched
|
||||
.then(|| {
|
||||
services
|
||||
.read_receipt
|
||||
.last_receipt_count(&room_id, sender_user.into(), None)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.into();
|
||||
|
||||
let last_account: OptionFuture<_> = matched
|
||||
.then(|| {
|
||||
services
|
||||
.account_data
|
||||
.last_count(Some(room_id.as_ref()), sender_user, Some(conn.next_batch))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.into();
|
||||
|
||||
let last_timeline: OptionFuture<_> = matched
|
||||
.then(|| {
|
||||
services
|
||||
.timeline
|
||||
.last_timeline_count(None, &room_id, Some(conn.next_batch.into()))
|
||||
.map_ok(PduCount::into_unsigned)
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.into();
|
||||
|
||||
let (last_timeline, last_notification, last_account, last_receipt, last_privateread) =
|
||||
join5(last_timeline, last_notification, last_account, last_receipt, last_privateread)
|
||||
.await;
|
||||
|
||||
Some(WindowRoom {
|
||||
room_id: room_id.clone(),
|
||||
membership,
|
||||
lists,
|
||||
ranked: 0,
|
||||
last_count: [
|
||||
last_timeline,
|
||||
last_notification,
|
||||
last_account,
|
||||
last_receipt,
|
||||
last_privateread,
|
||||
]
|
||||
.into_iter()
|
||||
.map(Option::unwrap_or_default)
|
||||
.filter(|count| conn.next_batch.ge(count))
|
||||
.max()
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(rooms = rooms.clone().count())
|
||||
)]
|
||||
async fn window<'a, Rooms>(
|
||||
sync_info: SyncInfo<'_>,
|
||||
conn: &Connection,
|
||||
rooms: Rooms,
|
||||
lists: &ResponseLists,
|
||||
) -> Window
|
||||
where
|
||||
Rooms: Iterator<Item = &'a WindowRoom> + Clone + Send + Sync,
|
||||
{
|
||||
static FULL_RANGE: (UInt, UInt) = (UInt::MIN, UInt::MAX);
|
||||
|
||||
let SyncInfo { services, sender_user, .. } = sync_info;
|
||||
|
||||
let selections = lists
|
||||
.keys()
|
||||
.cloned()
|
||||
.filter_map(|id| conn.lists.get(&id).map(|list| (id, list)))
|
||||
.flat_map(|(id, list)| {
|
||||
let full_range = list
|
||||
.ranges
|
||||
.is_empty()
|
||||
.then_some(&FULL_RANGE)
|
||||
.into_iter();
|
||||
|
||||
list.ranges
|
||||
.iter()
|
||||
.chain(full_range)
|
||||
.map(apply!(2, usize_from_ruma))
|
||||
.map(move |range| (id.clone(), range))
|
||||
})
|
||||
.flat_map(|(id, (start, end))| {
|
||||
rooms
|
||||
.clone()
|
||||
.filter(move |&room| room.lists.contains(&id))
|
||||
.filter(|&room| {
|
||||
conn.rooms
|
||||
.get(&room.room_id)
|
||||
.is_some_and(|conn_room| {
|
||||
conn_room.roomsince == 0 || room.last_count > conn_room.roomsince
|
||||
})
|
||||
})
|
||||
.enumerate()
|
||||
.skip_while(move |&(i, _)| i < start)
|
||||
.take(end.saturating_add(1).saturating_sub(start))
|
||||
.map(|(_, room)| (room.room_id.clone(), room.clone()))
|
||||
})
|
||||
.stream();
|
||||
|
||||
let subscriptions = conn
|
||||
.subscriptions
|
||||
.iter()
|
||||
.stream()
|
||||
.broad_filter_map(async |(room_id, _)| {
|
||||
filter_room_meta(sync_info, room_id)
|
||||
.await
|
||||
.into_option()?;
|
||||
|
||||
Some(WindowRoom {
|
||||
room_id: room_id.clone(),
|
||||
lists: Default::default(),
|
||||
ranked: usize::MAX,
|
||||
last_count: 0,
|
||||
membership: services
|
||||
.state_cache
|
||||
.user_membership(sender_user, room_id)
|
||||
.await,
|
||||
})
|
||||
})
|
||||
.map(|room| (room.room_id.clone(), room));
|
||||
|
||||
subscriptions.chain(selections).collect().await
|
||||
}
|
||||
|
||||
fn response_lists<'a, Rooms>(rooms: Rooms) -> ResponseLists
|
||||
where
|
||||
Rooms: Iterator<Item = &'a WindowRoom>,
|
||||
{
|
||||
rooms
|
||||
.flat_map(|room| room.lists.iter())
|
||||
.fold(ResponseLists::default(), |mut lists, id| {
|
||||
let list = lists.entry(id.clone()).or_default();
|
||||
list.count = list
|
||||
.count
|
||||
.checked_add(uint!(1))
|
||||
.expect("list count must not overflow JsInt");
|
||||
|
||||
lists
|
||||
})
|
||||
}
|
||||
|
||||
fn room_sort(a: &WindowRoom, b: &WindowRoom) -> Ordering { b.last_count.cmp(&a.last_count) }
|
||||
@@ -28,7 +28,7 @@ pub(crate) async fn create_typing_event_route(
|
||||
|
||||
match body.state {
|
||||
| Typing::Yes(duration) => {
|
||||
let duration = utils::clamp(
|
||||
let duration = Ord::clamp(
|
||||
duration
|
||||
.as_millis()
|
||||
.try_into()
|
||||
@@ -64,12 +64,14 @@ pub(crate) async fn create_typing_event_route(
|
||||
}
|
||||
|
||||
// ping presence
|
||||
if services.config.allow_local_presence {
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &ruma::presence::PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
services
|
||||
.presence
|
||||
.maybe_ping_presence(
|
||||
&body.user_id,
|
||||
body.sender_device.as_deref(),
|
||||
&ruma::presence::PresenceState::Online,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(create_typing_event::v3::Response {})
|
||||
}
|
||||
|
||||
+20
-28
@@ -73,13 +73,11 @@ pub(crate) async fn delete_timezone_key_route(
|
||||
|
||||
services.users.set_timezone(&body.user_id, None);
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.maybe_ping_presence(&body.user_id, body.sender_device.as_deref(), &PresenceState::Online)
|
||||
.await?;
|
||||
|
||||
Ok(delete_timezone_key::unstable::Response {})
|
||||
}
|
||||
@@ -103,13 +101,11 @@ pub(crate) async fn set_timezone_key_route(
|
||||
.users
|
||||
.set_timezone(&body.user_id, body.tz.clone());
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.maybe_ping_presence(&body.user_id, body.sender_device.as_deref(), &PresenceState::Online)
|
||||
.await?;
|
||||
|
||||
Ok(set_timezone_key::unstable::Response {})
|
||||
}
|
||||
@@ -171,13 +167,11 @@ pub(crate) async fn set_profile_field_route(
|
||||
);
|
||||
}
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.maybe_ping_presence(&body.user_id, body.sender_device.as_deref(), &PresenceState::Online)
|
||||
.await?;
|
||||
|
||||
Ok(set_profile_field::v3::Response {})
|
||||
}
|
||||
@@ -227,13 +221,11 @@ pub(crate) async fn delete_profile_field_route(
|
||||
.set_profile_key(&body.user_id, body.field.as_str(), None);
|
||||
}
|
||||
|
||||
if services.config.allow_local_presence {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)
|
||||
.await?;
|
||||
}
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.maybe_ping_presence(&body.user_id, body.sender_device.as_deref(), &PresenceState::Online)
|
||||
.await?;
|
||||
|
||||
Ok(delete_profile_field::v3::Response {})
|
||||
}
|
||||
|
||||
@@ -42,17 +42,19 @@ pub(crate) async fn get_supported_versions_route(
|
||||
unstable_features: BTreeMap::from_iter([
|
||||
("org.matrix.e2e_cross_signing".to_owned(), true),
|
||||
("org.matrix.msc2285.stable".to_owned(), true), /* private read receipts (https://github.com/matrix-org/matrix-spec-proposals/pull/2285) */
|
||||
("fi.mau.msc2659.stable".to_owned(), true), /* appservice ping https://github.com/matrix-org/matrix-spec-proposals/pull/2659) */
|
||||
("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true), /* query mutual rooms (https://github.com/matrix-org/matrix-spec-proposals/pull/2666) */
|
||||
("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */
|
||||
("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */
|
||||
("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */
|
||||
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
|
||||
("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */
|
||||
("org.matrix.msc3575".to_owned(), true), /* sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1588877046) */
|
||||
("org.matrix.msc3814".to_owned(), true), /* dehydrated devices */
|
||||
("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */
|
||||
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
|
||||
("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */
|
||||
("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */
|
||||
("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */
|
||||
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
|
||||
("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */
|
||||
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -25,12 +25,12 @@ pub(crate) async fn turn_server_route(
|
||||
return Err!(Request(NotFound("Not Found")));
|
||||
}
|
||||
|
||||
let turn_secret = services.globals.turn_secret.clone();
|
||||
let turn_secret = &services.globals.turn_secret;
|
||||
|
||||
let (username, password) = if !turn_secret.is_empty() {
|
||||
let expiry = SecondsSinceUnixEpoch::from_system_time(
|
||||
SystemTime::now()
|
||||
.checked_add(Duration::from_secs(services.globals.turn_ttl()))
|
||||
.checked_add(Duration::from_secs(services.config.turn_ttl))
|
||||
.expect("TURN TTL should not get this high"),
|
||||
)
|
||||
.expect("time is valid");
|
||||
@@ -53,16 +53,13 @@ pub(crate) async fn turn_server_route(
|
||||
|
||||
(username, password)
|
||||
} else {
|
||||
(
|
||||
services.globals.turn_username().clone(),
|
||||
services.globals.turn_password().clone(),
|
||||
)
|
||||
(services.config.turn_username.clone(), services.config.turn_password.clone())
|
||||
};
|
||||
|
||||
Ok(get_turn_server_info::v3::Response {
|
||||
username,
|
||||
password,
|
||||
uris: services.globals.turn_uris().to_vec(),
|
||||
ttl: Duration::from_secs(services.globals.turn_ttl()),
|
||||
uris: services.config.turn_uris.clone(),
|
||||
ttl: Duration::from_secs(services.config.turn_ttl),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,28 +1,59 @@
|
||||
use axum::{Json, extract::State, response::IntoResponse};
|
||||
use ruma::api::client::discovery::{
|
||||
discover_homeserver::{self, HomeserverInfo},
|
||||
discover_homeserver::{self, HomeserverInfo, RtcFocusInfo},
|
||||
discover_support::{self, Contact},
|
||||
};
|
||||
use tuwunel_core::{Err, Result};
|
||||
use serde_json::Value as JsonValue;
|
||||
use tuwunel_core::{Err, Result, err, error::inspect_log};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /.well-known/matrix/client`
|
||||
///
|
||||
/// Returns the .well-known URL if it is configured, otherwise returns 404.
|
||||
/// Also includes RTC transport configuration for Element Call (MSC4143).
|
||||
pub(crate) async fn well_known_client(
|
||||
State(services): State<crate::State>,
|
||||
_body: Ruma<discover_homeserver::Request>,
|
||||
) -> Result<discover_homeserver::Response> {
|
||||
let client_url = match services.server.config.well_known.client.as_ref() {
|
||||
| Some(url) => url.to_string(),
|
||||
| None => return Err!(Request(NotFound("Not found."))),
|
||||
let homeserver = HomeserverInfo {
|
||||
base_url: match services.server.config.well_known.client.as_ref() {
|
||||
| Some(url) => url.to_string(),
|
||||
| None => return Err!(Request(NotFound("Not found."))),
|
||||
},
|
||||
};
|
||||
|
||||
// Add RTC transport configuration if available (MSC4143 / Element Call)
|
||||
// Element Call has evolved through several versions with different field
|
||||
// expectations
|
||||
let rtc_foci = services
|
||||
.server
|
||||
.config
|
||||
.well_known
|
||||
.rtc_transports
|
||||
.iter()
|
||||
.map(|transport| {
|
||||
let focus_type = transport
|
||||
.get("type")
|
||||
.and_then(JsonValue::as_str)
|
||||
.ok_or_else(|| err!("`type` is not a valid string"))?;
|
||||
|
||||
let transport = transport
|
||||
.as_object()
|
||||
.cloned()
|
||||
.ok_or_else(|| err!("`rtc_transport` is not a valid object"))?;
|
||||
|
||||
RtcFocusInfo::new(focus_type, transport).map_err(Into::into)
|
||||
})
|
||||
.collect::<Result<_>>()
|
||||
.map_err(|e| {
|
||||
err!(Config("global.well_known.rtc_transports", "Malformed value(s): {e:?}"))
|
||||
})
|
||||
.inspect_err(inspect_log)?;
|
||||
|
||||
Ok(discover_homeserver::Response {
|
||||
homeserver: HomeserverInfo { base_url: client_url },
|
||||
identity_server: None,
|
||||
tile_server: None,
|
||||
rtc_foci,
|
||||
..discover_homeserver::Response::new(homeserver)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#![type_length_limit = "163840"] //TODO: REDUCE ME
|
||||
#![type_length_limit = "262144"] //TODO: REDUCE ME
|
||||
#![allow(clippy::toplevel_ref_arg)]
|
||||
|
||||
pub mod client;
|
||||
|
||||
+9
-1
@@ -16,7 +16,9 @@
|
||||
use tuwunel_core::{Server, err};
|
||||
|
||||
use self::handler::RouterExt;
|
||||
pub(super) use self::{args::Args as Ruma, response::RumaResponse, state::State};
|
||||
pub(super) use self::{
|
||||
args::Args as Ruma, auth::auth_uiaa, response::RumaResponse, state::State,
|
||||
};
|
||||
use crate::{client, server};
|
||||
|
||||
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
@@ -45,6 +47,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.ruma_route(&client::request_3pid_management_token_via_email_route)
|
||||
.ruma_route(&client::request_3pid_management_token_via_msisdn_route)
|
||||
.ruma_route(&client::check_registration_token_validity)
|
||||
.ruma_route(&client::get_notifications_route)
|
||||
.ruma_route(&client::get_capabilities_route)
|
||||
.ruma_route(&client::get_pushrules_all_route)
|
||||
.ruma_route(&client::get_pushrules_global_route)
|
||||
@@ -143,6 +146,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
get(client::get_state_events_for_empty_key_route)
|
||||
.put(client::send_state_event_for_empty_key_route),
|
||||
)
|
||||
.ruma_route(&client::events_route)
|
||||
.ruma_route(&client::sync_events_route)
|
||||
.ruma_route(&client::sync_events_v5_route)
|
||||
.ruma_route(&client::get_context_route)
|
||||
@@ -161,6 +165,10 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.ruma_route(&client::update_device_route)
|
||||
.ruma_route(&client::delete_device_route)
|
||||
.ruma_route(&client::delete_devices_route)
|
||||
.ruma_route(&client::put_dehydrated_device_route)
|
||||
.ruma_route(&client::delete_dehydrated_device_route)
|
||||
.ruma_route(&client::get_dehydrated_device_route)
|
||||
.ruma_route(&client::get_dehydrated_events_route)
|
||||
.ruma_route(&client::get_tags_route)
|
||||
.ruma_route(&client::update_tag_route)
|
||||
.ruma_route(&client::delete_tag_route)
|
||||
|
||||
+5
-11
@@ -6,7 +6,7 @@
|
||||
CanonicalJsonObject, CanonicalJsonValue, DeviceId, OwnedDeviceId, OwnedServerName,
|
||||
OwnedUserId, ServerName, UserId, api::IncomingRequest,
|
||||
};
|
||||
use tuwunel_core::{Error, Result, debug, debug_warn, err, trace, utils::string::EMPTY};
|
||||
use tuwunel_core::{Error, Result, debug_warn, err, trace, utils::string::EMPTY};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
||||
|
||||
use super::{auth, auth::Auth, request, request::Request};
|
||||
@@ -95,6 +95,7 @@ async fn from_request(
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
let mut request = request::from(services, request).await?;
|
||||
let mut json_body = serde_json::from_slice::<CanonicalJsonValue>(&request.body).ok();
|
||||
trace!(?request);
|
||||
|
||||
// while very unusual and really shouldn't be recommended, Synapse accepts POST
|
||||
// requests with a completely empty body. very old clients, libraries, and some
|
||||
@@ -104,13 +105,13 @@ async fn from_request(
|
||||
&& request.parts.method == http::Method::POST
|
||||
&& !request.parts.uri.path().contains("/media/")
|
||||
{
|
||||
trace!("json_body from_request: {:?}", json_body.clone());
|
||||
debug_warn!(
|
||||
"received a POST request with an empty body, defaulting/assuming to {{}} like \
|
||||
Synapse does"
|
||||
);
|
||||
json_body = Some(CanonicalJsonValue::Object(CanonicalJsonObject::new()));
|
||||
}
|
||||
|
||||
let auth = auth::auth(services, &mut request, json_body.as_ref(), &T::METADATA).await?;
|
||||
Ok(Self {
|
||||
body: make_body::<T>(services, &mut request, json_body.as_mut(), &auth)?,
|
||||
@@ -147,16 +148,9 @@ fn into_http_request(request: &Request, body: Bytes) -> hyper::Request<Bytes> {
|
||||
.headers_mut()
|
||||
.expect("mutable http headers") = request.parts.headers.clone();
|
||||
|
||||
let http_request = http_request
|
||||
.body(body)
|
||||
.expect("http request body");
|
||||
|
||||
let headers = http_request.headers();
|
||||
let method = http_request.method();
|
||||
let uri = http_request.uri();
|
||||
debug!("{method:?} {uri:?} {headers:?}");
|
||||
|
||||
http_request
|
||||
.body(body)
|
||||
.expect("http request body")
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
|
||||
+10
-162
@@ -1,10 +1,13 @@
|
||||
mod appservice;
|
||||
mod server;
|
||||
mod uiaa;
|
||||
|
||||
use std::{fmt::Debug, time::SystemTime};
|
||||
|
||||
use axum::RequestPartsExt;
|
||||
use axum_extra::{
|
||||
TypedHeader,
|
||||
headers::{Authorization, authorization::Bearer},
|
||||
typed_header::TypedHeaderRejectionReason,
|
||||
};
|
||||
use futures::{
|
||||
TryFutureExt,
|
||||
@@ -15,7 +18,7 @@
|
||||
pin_mut,
|
||||
};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
|
||||
CanonicalJsonValue, OwnedDeviceId, OwnedServerName, OwnedUserId,
|
||||
api::{
|
||||
AuthScheme, IncomingRequest, Metadata,
|
||||
client::{
|
||||
@@ -27,18 +30,14 @@
|
||||
},
|
||||
voip::get_turn_server_info,
|
||||
},
|
||||
federation::{authentication::XMatrix, openid::get_openid_userinfo},
|
||||
federation::openid::get_openid_userinfo,
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Error, Result, debug_error, err, is_less_than, utils::result::LogDebugErr, warn,
|
||||
};
|
||||
use tuwunel_service::{
|
||||
Services,
|
||||
appservice::RegistrationInfo,
|
||||
server_keys::{PubKeyMap, PubKeys},
|
||||
};
|
||||
use tuwunel_core::{Err, Error, Result, is_less_than, utils::result::LogDebugErr};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
||||
|
||||
pub(crate) use self::uiaa::auth_uiaa;
|
||||
use self::{appservice::auth_appservice, server::auth_server};
|
||||
use super::request::Request;
|
||||
|
||||
enum Token {
|
||||
@@ -223,154 +222,3 @@ async fn find_token(services: &Services, token: Option<&str>) -> Result<Token> {
|
||||
| _ => Ok(Token::Invalid),
|
||||
}
|
||||
}
|
||||
|
||||
async fn auth_appservice(
|
||||
services: &Services,
|
||||
request: &Request,
|
||||
info: Box<RegistrationInfo>,
|
||||
) -> Result<Auth> {
|
||||
let user_id_default = || {
|
||||
UserId::parse_with_server_name(
|
||||
info.registration.sender_localpart.as_str(),
|
||||
services.globals.server_name(),
|
||||
)
|
||||
};
|
||||
|
||||
let Ok(user_id) = request
|
||||
.query
|
||||
.user_id
|
||||
.clone()
|
||||
.map_or_else(user_id_default, OwnedUserId::parse)
|
||||
else {
|
||||
return Err!(Request(InvalidUsername("Username is invalid.")));
|
||||
};
|
||||
|
||||
if !info.is_user_match(&user_id) {
|
||||
return Err!(Request(Exclusive("User is not in namespace.")));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
sender_user: Some(user_id),
|
||||
appservice_info: Some(*info),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
|
||||
async fn auth_server(
|
||||
services: &Services,
|
||||
request: &mut Request,
|
||||
body: Option<&CanonicalJsonValue>,
|
||||
) -> Result<Auth> {
|
||||
type Member = (String, CanonicalJsonValue);
|
||||
type Object = CanonicalJsonObject;
|
||||
type Value = CanonicalJsonValue;
|
||||
|
||||
let x_matrix = parse_x_matrix(request).await?;
|
||||
auth_server_checks(services, &x_matrix)?;
|
||||
|
||||
let destination = services.globals.server_name();
|
||||
let origin = &x_matrix.origin;
|
||||
let signature_uri = request
|
||||
.parts
|
||||
.uri
|
||||
.path_and_query()
|
||||
.expect("all requests have a path")
|
||||
.to_string();
|
||||
|
||||
let signature: [Member; 1] =
|
||||
[(x_matrix.key.as_str().into(), Value::String(x_matrix.sig.to_string()))];
|
||||
|
||||
let signatures: [Member; 1] = [(origin.as_str().into(), Value::Object(signature.into()))];
|
||||
|
||||
let authorization: Object = if let Some(body) = body.cloned() {
|
||||
let authorization: [Member; 6] = [
|
||||
("content".into(), body),
|
||||
("destination".into(), Value::String(destination.into())),
|
||||
("method".into(), Value::String(request.parts.method.as_str().into())),
|
||||
("origin".into(), Value::String(origin.as_str().into())),
|
||||
("signatures".into(), Value::Object(signatures.into())),
|
||||
("uri".into(), Value::String(signature_uri)),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
} else {
|
||||
let authorization: [Member; 5] = [
|
||||
("destination".into(), Value::String(destination.into())),
|
||||
("method".into(), Value::String(request.parts.method.as_str().into())),
|
||||
("origin".into(), Value::String(origin.as_str().into())),
|
||||
("signatures".into(), Value::Object(signatures.into())),
|
||||
("uri".into(), Value::String(signature_uri)),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
};
|
||||
|
||||
let key = services
|
||||
.server_keys
|
||||
.get_verify_key(origin, &x_matrix.key)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(Forbidden(debug_warn!("Failed to fetch signing keys: {e}"))))
|
||||
})?;
|
||||
|
||||
let keys: PubKeys = [(x_matrix.key.to_string(), key.key)].into();
|
||||
let keys: PubKeyMap = [(origin.as_str().into(), keys)].into();
|
||||
if let Err(e) = ruma::signatures::verify_json(&keys, &authorization) {
|
||||
debug_error!("Failed to verify federation request from {origin}: {e}");
|
||||
if request.parts.uri.to_string().contains('@') {
|
||||
warn!(
|
||||
"Request uri contained '@' character. Make sure your reverse proxy gives \
|
||||
tuwunel the raw uri (apache: use nocanon)"
|
||||
);
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Failed to verify X-Matrix signatures.")));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
origin: origin.to_owned().into(),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn auth_server_checks(services: &Services, x_matrix: &XMatrix) -> Result {
|
||||
if !services.server.config.allow_federation {
|
||||
return Err!(Config("allow_federation", "Federation is disabled."));
|
||||
}
|
||||
|
||||
let destination = services.globals.server_name();
|
||||
if x_matrix.destination.as_deref() != Some(destination) {
|
||||
return Err!(Request(Forbidden("Invalid destination.")));
|
||||
}
|
||||
|
||||
let origin = &x_matrix.origin;
|
||||
if services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(origin.host())
|
||||
{
|
||||
return Err!(Request(Forbidden(debug_warn!(
|
||||
"Federation requests from {origin} denied."
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn parse_x_matrix(request: &mut Request) -> Result<XMatrix> {
|
||||
let TypedHeader(Authorization(x_matrix)) = request
|
||||
.parts
|
||||
.extract::<TypedHeader<Authorization<XMatrix>>>()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let msg = match e.reason() {
|
||||
| TypedHeaderRejectionReason::Missing => "Missing Authorization header.",
|
||||
| TypedHeaderRejectionReason::Error(_) => "Invalid X-Matrix signatures.",
|
||||
| _ => "Unknown header-related error",
|
||||
};
|
||||
|
||||
err!(Request(Forbidden(debug_warn!("{msg}: {e}"))))
|
||||
})?;
|
||||
|
||||
Ok(x_matrix)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
use ruma::{OwnedUserId, UserId};
|
||||
use tuwunel_core::{Err, Result};
|
||||
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
||||
|
||||
use super::{Auth, Request};
|
||||
|
||||
pub(super) async fn auth_appservice(
|
||||
services: &Services,
|
||||
request: &Request,
|
||||
info: Box<RegistrationInfo>,
|
||||
) -> Result<Auth> {
|
||||
let user_id_default = || {
|
||||
UserId::parse_with_server_name(
|
||||
info.registration.sender_localpart.as_str(),
|
||||
services.globals.server_name(),
|
||||
)
|
||||
};
|
||||
|
||||
let Ok(user_id) = request
|
||||
.query
|
||||
.user_id
|
||||
.as_deref()
|
||||
.map_or_else(user_id_default, OwnedUserId::parse)
|
||||
else {
|
||||
return Err!(Request(InvalidUsername("Username is invalid.")));
|
||||
};
|
||||
|
||||
if !info.is_user_match(&user_id) {
|
||||
return Err!(Request(Exclusive("User is not in namespace.")));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
sender_user: Some(user_id),
|
||||
appservice_info: Some(*info),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
use axum::RequestPartsExt;
|
||||
use axum_extra::{TypedHeader, headers::Authorization, typed_header::TypedHeaderRejectionReason};
|
||||
use ruma::{
|
||||
CanonicalJsonName, CanonicalJsonObject, CanonicalJsonValue,
|
||||
api::federation::authentication::XMatrix,
|
||||
};
|
||||
use tuwunel_core::{Err, Result, debug_error, err, warn};
|
||||
use tuwunel_service::{
|
||||
Services,
|
||||
server_keys::{PubKeyMap, PubKeys},
|
||||
};
|
||||
|
||||
use super::{Auth, Request};
|
||||
|
||||
pub(super) async fn auth_server(
|
||||
services: &Services,
|
||||
request: &mut Request,
|
||||
body: Option<&CanonicalJsonValue>,
|
||||
) -> Result<Auth> {
|
||||
type Member = (CanonicalJsonName, CanonicalJsonValue);
|
||||
type Object = CanonicalJsonObject;
|
||||
type Value = CanonicalJsonValue;
|
||||
|
||||
let x_matrix = parse_x_matrix(request).await?;
|
||||
auth_server_checks(services, &x_matrix)?;
|
||||
|
||||
let destination = services.globals.server_name();
|
||||
let origin = &x_matrix.origin;
|
||||
let signature_uri = request
|
||||
.parts
|
||||
.uri
|
||||
.path_and_query()
|
||||
.expect("all requests have a path")
|
||||
.to_string();
|
||||
|
||||
let signature: [Member; 1] =
|
||||
[(x_matrix.key.as_str().into(), Value::String(x_matrix.sig.to_string()))];
|
||||
|
||||
let signatures: [Member; 1] = [(origin.as_str().into(), Value::Object(signature.into()))];
|
||||
|
||||
let authorization: Object = if let Some(body) = body.cloned() {
|
||||
let authorization: [Member; 6] = [
|
||||
("content".into(), body),
|
||||
("destination".into(), Value::String(destination.into())),
|
||||
("method".into(), Value::String(request.parts.method.as_str().into())),
|
||||
("origin".into(), Value::String(origin.as_str().into())),
|
||||
("signatures".into(), Value::Object(signatures.into())),
|
||||
("uri".into(), Value::String(signature_uri)),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
} else {
|
||||
let authorization: [Member; 5] = [
|
||||
("destination".into(), Value::String(destination.into())),
|
||||
("method".into(), Value::String(request.parts.method.as_str().into())),
|
||||
("origin".into(), Value::String(origin.as_str().into())),
|
||||
("signatures".into(), Value::Object(signatures.into())),
|
||||
("uri".into(), Value::String(signature_uri)),
|
||||
];
|
||||
|
||||
authorization.into()
|
||||
};
|
||||
|
||||
let key = services
|
||||
.server_keys
|
||||
.get_verify_key(origin, &x_matrix.key)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(Forbidden(debug_warn!("Failed to fetch signing keys: {e}"))))
|
||||
})?;
|
||||
|
||||
let keys: PubKeys = [(x_matrix.key.as_str().into(), key.key)].into();
|
||||
let keys: PubKeyMap = [(origin.as_str().into(), keys)].into();
|
||||
if let Err(e) = ruma::signatures::verify_json(&keys, &authorization) {
|
||||
debug_error!("Failed to verify federation request from {origin}: {e}");
|
||||
if request.parts.uri.to_string().contains('@') {
|
||||
warn!(
|
||||
"Request uri contained '@' character. Make sure your reverse proxy gives \
|
||||
tuwunel the raw uri (apache: use nocanon)"
|
||||
);
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Failed to verify X-Matrix signatures.")));
|
||||
}
|
||||
|
||||
Ok(Auth {
|
||||
origin: origin.to_owned().into(),
|
||||
..Auth::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn auth_server_checks(services: &Services, x_matrix: &XMatrix) -> Result {
|
||||
if !services.server.config.allow_federation {
|
||||
return Err!(Config("allow_federation", "Federation is disabled."));
|
||||
}
|
||||
|
||||
let destination = services.globals.server_name();
|
||||
if x_matrix.destination.as_deref() != Some(destination) {
|
||||
return Err!(Request(Forbidden("Invalid destination.")));
|
||||
}
|
||||
|
||||
let origin = &x_matrix.origin;
|
||||
if services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(origin.host())
|
||||
{
|
||||
return Err!(Request(Forbidden(debug_warn!(
|
||||
"Federation requests from {origin} denied."
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn parse_x_matrix(request: &mut Request) -> Result<XMatrix> {
|
||||
let TypedHeader(Authorization(x_matrix)) = request
|
||||
.parts
|
||||
.extract::<TypedHeader<Authorization<XMatrix>>>()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let msg = match e.reason() {
|
||||
| TypedHeaderRejectionReason::Missing => "Missing Authorization header.",
|
||||
| TypedHeaderRejectionReason::Error(_) => "Invalid X-Matrix signatures.",
|
||||
| _ => "Unknown header-related error",
|
||||
};
|
||||
|
||||
err!(Request(Forbidden(debug_warn!("{msg}: {e}"))))
|
||||
})?;
|
||||
|
||||
Ok(x_matrix)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user