mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-08-13 16:19:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af570c481e | ||
|
|
dc9314de1f | ||
|
|
cf005ee537 | ||
|
|
6455ef72cd | ||
|
|
7115fb2796 | ||
|
|
959c559bd8 | ||
|
|
a1bf188504 | ||
|
|
87770fefeb | ||
|
|
0c7ba1dd5a | ||
|
|
7b2079f714 | ||
|
|
c5508bba58 | ||
|
|
88a6b72f0f | ||
|
|
22c5f0207d | ||
|
|
3af78ac851 | ||
|
|
b5f50c3fda | ||
|
|
c9a26a0280 | ||
|
|
417e9ba052 | ||
|
|
085cdb30f4 | ||
|
|
77474479b5 | ||
|
|
d244e8027c | ||
|
|
4fea0abac4 | ||
|
|
cab0b3fd9f | ||
|
|
f311332bad | ||
|
|
fb0c2a2832 | ||
|
|
3403943880 | ||
|
|
7e69e9b051 | ||
|
|
27ed9b88f1 | ||
|
|
10edc3bd5e | ||
|
|
6553ba829f | ||
|
|
1ce3d2b01f | ||
|
|
50bfb0fe5e | ||
|
|
74f8cd3708 | ||
|
|
bcc2be7661 | ||
|
|
4f9b1d6dbd | ||
|
|
c9362b8605 | ||
|
|
e84d6666c0 | ||
|
|
7666bb63d8 | ||
|
|
3125b7e291 | ||
|
|
aebe2d72de | ||
|
|
8f54d9dc09 | ||
|
|
52b156e034 | ||
|
|
d2d6a98180 | ||
|
|
0bbc228f7a | ||
|
|
0d782095ad | ||
|
|
f6b95ff1c4 | ||
|
|
347298d7d6 | ||
|
|
442a5aafeb | ||
|
|
8bb0d02619 | ||
|
|
71f3ccf140 | ||
|
|
98affbdeaf | ||
|
|
e5073165f0 | ||
|
|
6705efc760 | ||
|
|
61085f4707 | ||
|
|
deb5c65885 | ||
|
|
11c4cbf54e | ||
|
|
a748edd621 | ||
|
|
9e539d0a22 | ||
|
|
5260912c3b | ||
|
|
b924412efb | ||
|
|
120ab1d068 | ||
|
|
e60e86e9ed | ||
|
|
5147b541b5 | ||
|
|
f4eeaaf167 | ||
|
|
54fe4bdf56 | ||
|
|
fe12daead9 | ||
|
|
6f29a34ffb | ||
|
|
dafbe59d00 | ||
|
|
0746f4b1ad | ||
|
|
90228e4865 | ||
|
|
53b5eb4ba6 | ||
|
|
20f080fc49 | ||
|
|
424ed3d7ad | ||
|
|
728085bd1b | ||
|
|
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 | ||
|
|
a1677f05d3 | ||
|
|
1f77200299 | ||
|
|
888b93df91 |
+1
-1
@@ -1,5 +1,5 @@
|
||||
[advisories]
|
||||
ignore = ["RUSTSEC-2024-0436", "RUSTSEC-2025-0014"] # advisory IDs to ignore e.g. ["RUSTSEC-2019-0001", ...]
|
||||
ignore = ["RUSTSEC-2024-0436", "RUSTSEC-2025-0014", "RUSTSEC-2025-0134"] # advisory IDs to ignore e.g. ["RUSTSEC-2019-0001", ...]
|
||||
informational_warnings = [] # warn for categories of informational advisories
|
||||
severity_threshold = "none" # CVSS severity ("none", "low", "medium", "high", "critical")
|
||||
|
||||
|
||||
@@ -106,7 +106,6 @@ jobs:
|
||||
excludes: ${{inputs.excludes}}
|
||||
includes: ${{inputs.includes}}
|
||||
|
||||
# disabled due to excessive build time issue installing cargo lychee
|
||||
lychee:
|
||||
if: >
|
||||
!failure() && !cancelled()
|
||||
@@ -114,7 +113,6 @@ jobs:
|
||||
&& 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])
|
||||
&& false
|
||||
|
||||
name: Lychee
|
||||
uses: ./.github/workflows/bake.yml
|
||||
|
||||
+57
-18
@@ -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')}}
|
||||
check_pkgs: ${{vars.CHECK_PKGS || 'false'}}
|
||||
complement: ${{vars.COMPLEMENT || 'true'}}
|
||||
package: ${{vars.PACKAGE != 'false'}}
|
||||
publish: ${{vars.PUBLISH != 'false'}}
|
||||
build_nix: ${{vars.BUILD_NIX != 'false'}}
|
||||
build_pkgs: ${{vars.BUILD_PKGS || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test' || contains(github.ref, 'tags/v')}}
|
||||
check_pkgs: ${{vars.CHECK_PKGS || github.ref == 'refs/heads/test'}}
|
||||
complement: ${{vars.COMPLEMENT != 'false'}}
|
||||
complement_runner: 'het'
|
||||
docker_repo: ${{vars.DOCKER_REPO}}
|
||||
release_url: ${{steps.release.outputs.upload_url}}
|
||||
pages_url: 'https://matrix-construct.github.io/tuwunel/'
|
||||
rust_sdk_integ: ${{vars.RUST_SDK_INTEGRATION != 'false'}}
|
||||
head_msg: ${{github.event.head_commit.message || github.event.workflow_run.head_commit.message}}
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@@ -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
|
||||
|
||||
+189
-23
@@ -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
|
||||
@@ -229,7 +395,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_output-${{matrix.feat_set}}-${{matrix.sys_name}}-${{matrix.sys_target}}.jsonl
|
||||
path: ./complement.jsonl
|
||||
path: ./tests/complement/logs.jsonl
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Accept
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ public/
|
||||
rustc-ice-*
|
||||
|
||||
# complement test logs are huge
|
||||
tests/test_results/complement/test_logs.jsonl
|
||||
tests/complement/logs.jsonl
|
||||
|
||||
# cargo profiles from -Z self-profile
|
||||
*.mm_profdata
|
||||
|
||||
Generated
+1147
-1015
File diff suppressed because it is too large
Load Diff
+63
-66
@@ -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.0"
|
||||
rust-version = "1.89.0"
|
||||
version = "1.4.8"
|
||||
|
||||
[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",
|
||||
]
|
||||
@@ -231,6 +231,10 @@ version = "0.1"
|
||||
[workspace.dependencies.itertools]
|
||||
version = "0.14"
|
||||
|
||||
[workspace.dependencies.jevmalloc]
|
||||
git = "https://github.com/matrix-construct/jevmalloc"
|
||||
rev = "93795449913f65ab533b7fa482333eef63fc3ae0"
|
||||
|
||||
[workspace.dependencies.jsonwebtoken]
|
||||
version = "9.3"
|
||||
default-features = false
|
||||
@@ -238,7 +242,7 @@ features = ["use_pem"]
|
||||
|
||||
[workspace.dependencies.ldap3]
|
||||
git = "https://github.com/matrix-construct/ldap3"
|
||||
rev = "7d423314b9dbc66347284e38fc2b78c3d8f3d494"
|
||||
rev = "fdfbba2bf916b53e5f73cdb1a495ebb649978079"
|
||||
default-features = false
|
||||
features = ["sync", "tls-rustls"]
|
||||
|
||||
@@ -262,7 +266,7 @@ version = "0.1"
|
||||
version = "1.0"
|
||||
|
||||
[workspace.dependencies.minicbor]
|
||||
version = "2.0"
|
||||
version = "2.1"
|
||||
features = ["std"]
|
||||
|
||||
[workspace.dependencies.minicbor-serde]
|
||||
@@ -272,13 +276,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 +293,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 +306,7 @@ version = "1.0"
|
||||
version = "0.8"
|
||||
|
||||
[workspace.dependencies.regex]
|
||||
version = "1.11"
|
||||
version = "1.12"
|
||||
|
||||
[workspace.dependencies.reqwest]
|
||||
version = "0.12"
|
||||
@@ -317,7 +324,7 @@ default-features = false
|
||||
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://github.com/matrix-construct/ruma"
|
||||
rev = "8bc15ba4f145e7b995d36e82e8624c3ac3ce0ef6"
|
||||
rev = "214ab27fdc3d7004f6d46e0aa89fba573b59c66f"
|
||||
features = [
|
||||
"__compat",
|
||||
"appservice-api-c",
|
||||
@@ -336,6 +343,7 @@ features = [
|
||||
"unstable-msc2870",
|
||||
"unstable-msc3026",
|
||||
"unstable-msc3061",
|
||||
"unstable-msc3814",
|
||||
"unstable-msc3245",
|
||||
"unstable-msc3381", # polls
|
||||
"unstable-msc3489", # beacon / live location
|
||||
@@ -345,17 +353,18 @@ 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]
|
||||
version = "0.23"
|
||||
default-features = false
|
||||
features = ["aws_lc_rs"]
|
||||
features = ["aws_lc_rs", "logging", "tls12", "prefer-post-quantum"]
|
||||
|
||||
[workspace.dependencies.rustyline-async]
|
||||
version = "0.4.6"
|
||||
@@ -363,14 +372,16 @@ default-features = false
|
||||
|
||||
[workspace.dependencies.rust-rocksdb]
|
||||
git = "https://github.com/matrix-construct/rust-rocksdb"
|
||||
rev = "225a42519276e502205bdc845cebdb22d70ee245"
|
||||
rev = "c11395350bc1f2090a0152f2d15c8c5847821eba"
|
||||
default-features = false
|
||||
features = [
|
||||
"bzip2",
|
||||
"lto",
|
||||
"lz4",
|
||||
"multi-threaded-cf",
|
||||
"mt_static",
|
||||
"serde1",
|
||||
"zstd",
|
||||
"zstd-static-linking-only",
|
||||
]
|
||||
|
||||
[workspace.dependencies.sanitize-filename]
|
||||
@@ -381,7 +392,7 @@ version = "0.4"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.sentry]
|
||||
version = "0.42"
|
||||
version = "0.45"
|
||||
default-features = false
|
||||
features = [
|
||||
"backtrace",
|
||||
@@ -397,10 +408,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 +441,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,39 +474,15 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.termimad]
|
||||
version = "0.33"
|
||||
version = "0.34"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.thiserror]
|
||||
version = "2.0"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.tikv-jemallocator]
|
||||
git = "https://github.com/matrix-construct/jemallocator"
|
||||
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
|
||||
default-features = false
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
]
|
||||
|
||||
[workspace.dependencies.tikv-jemalloc-ctl]
|
||||
git = "https://github.com/matrix-construct/jemallocator"
|
||||
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
|
||||
default-features = false
|
||||
features = ["use_std"]
|
||||
|
||||
[workspace.dependencies.tikv-jemalloc-sys]
|
||||
git = "https://github.com/matrix-construct/jemallocator"
|
||||
rev = "03bed96afbbc898bef4d4f7d335c0519e3d1afad"
|
||||
default-features = false
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
]
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
version = "1.47"
|
||||
version = "1.48"
|
||||
default-features = false
|
||||
features = [
|
||||
"fs",
|
||||
@@ -536,21 +524,21 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.tracing]
|
||||
version = "0.1.41"
|
||||
version = "0.1"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.tracing-core]
|
||||
version = "0.1.33"
|
||||
version = "0.1"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.tracing-flame]
|
||||
version = "0.2.0"
|
||||
version = "0.2"
|
||||
|
||||
[workspace.dependencies.tracing-opentelemetry]
|
||||
version = "0.31.0"
|
||||
version = "0.32"
|
||||
|
||||
[workspace.dependencies.tracing-subscriber]
|
||||
version = "0.3.20"
|
||||
version = "0.3"
|
||||
default-features = false
|
||||
features = [
|
||||
"ansi",
|
||||
@@ -699,7 +687,7 @@ inherits = "release-native.build-override"
|
||||
|
||||
[profile.bench]
|
||||
debug = "limited"
|
||||
strip = false
|
||||
strip = "none"
|
||||
#rustflags = [
|
||||
# "-Cremark=all",
|
||||
# '-Ztime-passes',
|
||||
@@ -719,7 +707,7 @@ strip = false
|
||||
# and can be raised if build times are tolerable.
|
||||
|
||||
[profile.dev]
|
||||
debug = "full"
|
||||
debug = 0
|
||||
#rustflags = [
|
||||
# '--cfg', 'tuwunel_mods',
|
||||
# '-Ztime-passes',
|
||||
@@ -779,7 +767,7 @@ inherits = "dev"
|
||||
|
||||
[profile.dev.package.'*']
|
||||
inherits = "dev"
|
||||
debug = "limited"
|
||||
debug = 0
|
||||
incremental = false
|
||||
codegen-units = 1
|
||||
opt-level = 'z'
|
||||
@@ -796,6 +784,11 @@ opt-level = 'z'
|
||||
# '-Clink-arg=-Wl,-z,nodelete',
|
||||
#]
|
||||
|
||||
# same as dev but slower.
|
||||
[profile.dbg]
|
||||
inherits = "dev"
|
||||
debug = "full"
|
||||
|
||||
# primarily used for CI
|
||||
[profile.test]
|
||||
debug = "limited"
|
||||
@@ -887,6 +880,9 @@ multiple_crate_versions = { level = "allow", priority = 1 }
|
||||
###################
|
||||
complexity = { level = "warn", priority = -1 }
|
||||
|
||||
# promotes forward-compat for literal ..default() construction
|
||||
needless_update = { level = "allow", priority = 1 }
|
||||
|
||||
###################
|
||||
correctness = { level = "warn", priority = -1 }
|
||||
|
||||
@@ -899,6 +895,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 +910,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 +956,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"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Tuwunel<sup>💕</sup>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/matrix-construct/tuwunel/actions/workflows/main.yml)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/matrix-construct/tuwunel/actions/workflows/main.yml)
|
||||
|
||||
<!-- ANCHOR: catchphrase -->
|
||||
|
||||
@@ -15,19 +15,20 @@ ## High Performance Matrix Homeserver in Rust!
|
||||
|
||||
<!-- ANCHOR: body -->
|
||||
|
||||
[](https://matrix-construct.github.io/tuwunel/)
|
||||
[](https://matrix.to/#/#tuwunel:grin.hu)
|
||||
|
||||
Tuwunel is a featureful [Matrix](https://matrix.org/) homeserver you can use instead of Synapse
|
||||
with your favorite [client](https://matrix.org/ecosystem/clients/),
|
||||
[bridge](https://matrix.org/ecosystem/bridges/) or
|
||||
[bot](https://matrix.org/ecosystem/integrations/). It is written entirely in Rust to be a scalable,
|
||||
lightweight, low-cost, community-driven alternative covering all but the most niche uses.
|
||||
low-cost, enterprise-ready, community-driven alternative, fully implementing the
|
||||
[Matrix Specification](https://spec.matrix.org/latest/) for all but the most niche uses.
|
||||
|
||||
This project is the official successor to conduwuit, which
|
||||
was a featureful and high-performance fork of [Conduit](https://gitlab.com/famedly/conduit), all
|
||||
community-lead homeservers implementing the compatible
|
||||
[Matrix Specification](https://spec.matrix.org/latest/).
|
||||
|
||||
Tuwunel is operated by enterprise users with a vested interest in sponsoring its continued
|
||||
development. It is now maintained by full-time staff.
|
||||
This project is the official successor to [conduwuit](https://github.com/x86pup/conduwuit) after it
|
||||
reached stability. Tuwunel is now used by many companies with a vested interest in its continued
|
||||
development by full-time staff. It is primarily sponsored by the government of
|
||||
Switzerland 🇨🇭 where it is currently deployed for citizens.
|
||||
|
||||
### Getting Started
|
||||
|
||||
@@ -38,7 +39,7 @@ ### Getting Started
|
||||
- Static binaries available as [releases](https://github.com/matrix-construct/tuwunel/releases) or [build artifacts](https://github.com/matrix-construct/tuwunel/actions?query=branch%3Amain).
|
||||
- Deb and RPM packages available as [releases](https://github.com/matrix-construct/tuwunel/releases) or [build artifacts](https://github.com/matrix-construct/tuwunel/actions?query=branch%3Amain).
|
||||
- Arch package available as [tuwunel](https://aur.archlinux.org/packages/tuwunel) or [tuwunel-git](https://aur.archlinux.org/packages/tuwunel-git).
|
||||
- Nix package still [needs some love](https://github.com/NixOS/nixpkgs/issues/415469).
|
||||
- Nix package available as [`matrix-tuwunel`](https://search.nixos.org/packages?query=matrix-tuwunel) and NixOS module available as [`services.matrix-tuwunel`](https://search.nixos.org/options?query=services.matrix-tuwunel).
|
||||
|
||||
**1.** [Configure](https://matrix-construct.github.io/tuwunel/configuration.html) by
|
||||
copying and editing the `tuwunel-example.toml`. The `server_name` and `database_path` must be
|
||||
@@ -110,22 +111,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
|
||||
|
||||
|
||||
+5
-34
@@ -1,38 +1,9 @@
|
||||
# Tuwunel 1.4.0
|
||||
# Tuwunel 1.4.8
|
||||
|
||||
September 1, 2025
|
||||
December 21, 2025
|
||||
|
||||
#### Room Version 12 is now stable.
|
||||
All federating deployments must upgrade to this patch for mitigations to severe vulnerabilities in Matrix protocol implementation logic. This is an off-schedule coordinated security release. Full release notes will be included with the next scheduled release.
|
||||
|
||||
You can freely create these rooms and join them over the federation. Administrators should take note of the following:
|
||||
- The default room version is still 11. This is due to Matrix compliance tests not yet providing total coverage to make formal assurances about the new version.
|
||||
- Hydra-backports are not enabled by default. Administrators of high risk servers can enable `hydra_backports` to continue using their pre-v12 rooms with increased security. This will be enabled by default very shortly.
|
||||
- The ability to upgrade from existing rooms to version 12 is not yet complete as of this release. This decision was purely economical: we provide `hydra_backports` in the interim.
|
||||
### Security Fixes
|
||||
|
||||
### New Features
|
||||
|
||||
- **Deleting Rooms** is now possible thanks to a generous effort by @dasha_uwu. Admins can use the `!admin rooms delete-room` command to force all local users to leave and erase the room from the database.
|
||||
|
||||
- An idea by @korewaChino (#136) taken up by serial contribtor @dasha_uwu now grants admins the power to access rooms using `!admin users force-promote` if at least one user on the server has federation-level access to the room. This feature is a major Trust & Safety enhancement.
|
||||
|
||||
- Thanks to an idea by @obioma (#118) with an implementation contributed by @dasha_uwu the admin room can be de-federated by setting `federate_admin_room = false` when first setting up a new Tuwunel server. This option is only available for fresh installs.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Based on a help request by @mageslayer (#138) the docs for configuring Caddy have been graciously improved by @itzk0tlin (#139).
|
||||
|
||||
- Spaces pageloads have been optimized. This primarily affects large and multi-level spaces such as the Matrix Community.
|
||||
|
||||
- Building on the room deletion infrastructure contributed by @dasha_uwu an experimental addon can automatically delete empty rooms after the last local user leaves. This is not enabled by default and highly experimental and will not be considered stable until the next release.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Thanks to a concise report by @alaviss the `/joined_members` and `/members` endpoints now return consistent profile data for each user. Previously the former returned "global profile" data rather than room membership. (#121)
|
||||
|
||||
- After a diagnosis by @gardiol the pushers set by a client are now deleted when the associated device logs out. (#120)
|
||||
|
||||
- Sync longpoll loop properly terminates for server shutdown thanks to @dasha_uwu.
|
||||
|
||||
- Joining restricted rooms with an invite has been fixed by @dasha_uwu.
|
||||
|
||||
- Thanks @obioma for making corrections to documentation.
|
||||
- Requests to the [Federation Invite API](https://spec.matrix.org/v1.17/server-server-api/#put_matrixfederationv2inviteroomideventid) lacked sufficient validation on all input fields. An attacker can use this route to process other kinds of events: upon acceptance, they are signed by the victim's server as specified by the Matrix protocol. The attacker can therefore forge events on behalf of the victim's authority to gain control of a room. This vulnerability was present in all versions and derivatives of Conduit.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,6 @@ authors = [
|
||||
]
|
||||
description = "Tuwunel, a high performance successor to Conduit and Conduwuit"
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "docs"
|
||||
title = "Tuwunel One"
|
||||
text-direction = "ltr"
|
||||
@@ -15,7 +14,9 @@ build-dir = "public"
|
||||
create-missing = true
|
||||
extra-watch-dirs = [
|
||||
"debian",
|
||||
"docker",
|
||||
"docs",
|
||||
"rpm",
|
||||
]
|
||||
|
||||
[rust]
|
||||
@@ -24,7 +25,7 @@ edition = "2024"
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/matrix-construct/tuwunel"
|
||||
edit-url-template = "https://github.com/matrix-construct/tuwunel/edit/main/{path}"
|
||||
git-repository-icon = "fa-github-square"
|
||||
git-repository-icon = "fab-github"
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 15
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
stack-size-threshold = 393216
|
||||
future-size-threshold = 24576
|
||||
array-size-threshold = 4096
|
||||
cognitive-complexity-threshold = 100 # TODO reduce me ALARA
|
||||
excessive-nesting-threshold = 8
|
||||
future-size-threshold = 8192
|
||||
stack-size-threshold = 196608 # TODO reduce me ALARA
|
||||
too-many-lines-threshold = 780 # TODO reduce me to <= 100
|
||||
type-complexity-threshold = 250 # reduce me to ~200
|
||||
large-error-threshold = 256 # TODO reduce me ALARA
|
||||
too-many-lines-threshold = 780 # TODO reduce me to <= 100
|
||||
excessive-nesting-threshold = 8
|
||||
type-complexity-threshold = 250 # reduce me to ~200
|
||||
cognitive-complexity-threshold = 100 # TODO reduce me ALARA
|
||||
|
||||
#disallowed-macros = [
|
||||
# { path = "log::error", reason = "use tuwunel_core::error" },
|
||||
|
||||
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="4d3130f06d0dc3f794b5d48fbdba0b466792b52b"
|
||||
ARG complement_tags="conduwuit_blacklist"
|
||||
ARG complement_tests="./tests/..."
|
||||
ARG complement_run=".*"
|
||||
|
||||
WORKDIR /
|
||||
RUN \
|
||||
--mount=type=cache,dst=/var/cache,id=${var_cache},sharing=locked \
|
||||
--mount=type=cache,dst=/var/lib/apt,id=${var_lib_apt},sharing=locked \
|
||||
--mount=type=cache,dst=/go/pkg/mod/cache,sharing=locked \
|
||||
<<EOF
|
||||
set -eux
|
||||
apt-get -y -U install --no-install-recommends golang-go jq
|
||||
EOF
|
||||
|
||||
WORKDIR /usr/src
|
||||
ADD https://github.com/matrix-construct/complement.git#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"
|
||||
|
||||
@@ -15,15 +15,17 @@ ENV src_path="${CARGO_TARGET_DIR}/${rust_target}/${cargo_target_profile}/tuwunel
|
||||
ENV dst_path="${install_prefix}/bin/tuwunel"
|
||||
COPY --from=bins $src_path $dst_path
|
||||
RUN <<EOF
|
||||
ret=$(ldd "${dst_path}")
|
||||
ldd -v "${dst_path}"
|
||||
ret=$?
|
||||
if [ "$ret" = "0" ] && [ "$assert_linkage" = "static" ]; then
|
||||
echo "($ret) expected a static binary"
|
||||
exit 1
|
||||
elif [ "$ret" != "0" ] && [ "$assert_linkage" = "dynamic" ]; then
|
||||
echo "($ret) expected a dynamic binary"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -eux
|
||||
ldd -v ${dst_path} || true
|
||||
du -h ${dst_path}
|
||||
sha1sum ${dst_path}
|
||||
du -h "${dst_path}"
|
||||
sha1sum "${dst_path}"
|
||||
EOF
|
||||
|
||||
@@ -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
|
||||
@@ -28,10 +28,12 @@ FROM input AS rocksdb-build
|
||||
ARG rocksdb_shared=0
|
||||
ARG rocksdb_portable="1"
|
||||
ARG rocksdb_opt_level="3"
|
||||
ARG rocksdb_lto="-flto -ffat-lto-objects"
|
||||
ARG rocksdb_build_type="Release"
|
||||
ARG rocksdb_cxx_flags="-ftls-model=initial-exec"
|
||||
ARG rocksdb_make_verbose="ON"
|
||||
ARG rocksdb_make_rule_messages="OFF"
|
||||
ARG rocksdb_numa=0
|
||||
ARG rocksdb_jemalloc=1
|
||||
ARG rocksdb_iouring=1
|
||||
ARG rocksdb_zstd=1
|
||||
@@ -59,7 +61,7 @@ RUN <<EOF
|
||||
"-DBUILD_SHARED_LIBS=${rocksdb_shared}" \
|
||||
"-DROCKSDB_BUILD_SHARED=${rocksdb_shared}" \
|
||||
"-DCMAKE_CXX_FLAGS:STRING=${rocksdb_cxx_flags}" \
|
||||
"-DCMAKE_CXX_FLAGS_RELEASE:STRING=-g0 -O${rocksdb_opt_level} -DNDEBUG" \
|
||||
"-DCMAKE_CXX_FLAGS_RELEASE:STRING=-g0 -O${rocksdb_opt_level} -DNDEBUG ${rocksdb_lto}" \
|
||||
"-DPORTABLE=${rocksdb_portable}" \
|
||||
"-DFAIL_ON_WARNINGS=0" \
|
||||
"-DUSE_RTTI=0" \
|
||||
@@ -73,6 +75,7 @@ RUN <<EOF
|
||||
"-DWITH_TOOLS=0" \
|
||||
"-DWITH_TESTS=0" \
|
||||
"-DWITH_GFLAGS=0" \
|
||||
"-DWITH_NUMA=${rocksdb_numa}" \
|
||||
"-DWITH_LIBURING=${rocksdb_iouring}" \
|
||||
"-DWITH_JEMALLOC=${rocksdb_jemalloc}" \
|
||||
"-DWITH_ZSTD=${rocksdb_zstd}" \
|
||||
|
||||
@@ -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 \
|
||||
|
||||
+270
-40
@@ -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"
|
||||
@@ -80,6 +84,9 @@ variable "rocksdb_build_type" {
|
||||
variable "rocksdb_make_verbose" {
|
||||
default = "ON"
|
||||
}
|
||||
variable "rocksdb_numa" {
|
||||
default = "0"
|
||||
}
|
||||
|
||||
# Complement options
|
||||
variable "complement_count" {
|
||||
@@ -180,7 +187,6 @@ dynamic_libs = [
|
||||
|
||||
nightly_rustflags = [
|
||||
"--cfg tokio_unstable",
|
||||
"--cfg tuwunel_bench",
|
||||
"--allow=unstable-features",
|
||||
"-Z crate-attr=feature(test)",
|
||||
"-Z enforce-type-length-limit",
|
||||
@@ -233,9 +239,18 @@ group "lints" {
|
||||
|
||||
group "tests" {
|
||||
targets = [
|
||||
"docs",
|
||||
"unit",
|
||||
"smoke",
|
||||
"integration",
|
||||
"matrix-compliance",
|
||||
]
|
||||
}
|
||||
|
||||
group "matrix-compliance" {
|
||||
targets = [
|
||||
"complement",
|
||||
"rust-sdk-integ",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -403,7 +418,7 @@ target "complement-base" {
|
||||
elem("complement-config", [sys_name, sys_version, sys_target])
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:base", [sys_name, sys_version, sys_target])
|
||||
input = elem("target:builder", [sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = complement_args
|
||||
}
|
||||
@@ -424,6 +439,116 @@ target "complement-config" {
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Integration tests
|
||||
#
|
||||
|
||||
group "integration" {
|
||||
targets = [
|
||||
"integ",
|
||||
"rust-sdk-integ",
|
||||
]
|
||||
}
|
||||
|
||||
variable "valgrind_flags" {
|
||||
default = "--error-exitcode=1 --exit-on-first-error=yes --undef-value-errors=no --leak-check=no"
|
||||
}
|
||||
|
||||
target "rust-sdk-valgrind" {
|
||||
name = elem("rust-sdk-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("rust-sdk-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("rust-sdk-integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:rust", [rust_toolchain, rust_target, sys_name, sys_version, sys_target])
|
||||
install = elem("target:install", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
VALGRINDFLAGS = "${valgrind_flags}"
|
||||
mrsdk_testee = "valgrind ${valgrind_flags} /usr/bin/tuwunel"
|
||||
mrsdk_test_args = ""
|
||||
mrsdk_startup_delay = "30s"
|
||||
mrsdk_skip_list =<<EOF
|
||||
--skip test_delayed_invite_response_and_sent_message_decryption
|
||||
--skip test_history_share_on_invite_pin_violation
|
||||
EOF
|
||||
}
|
||||
}
|
||||
|
||||
target "rust-sdk-integ" {
|
||||
name = elem("rust-sdk-integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("rust-sdk-integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
output = ["type=docker,compression=zstd,mode=max,compression-level=${zstd_image_compress_level}"]
|
||||
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
|
||||
target = "rust-sdk-integration"
|
||||
dockerfile = "${docker_dir}/Dockerfile.matrix-rust-sdk"
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("rust", [rust_toolchain, rust_target, sys_name, sys_version, sys_target]),
|
||||
elem("integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:rust", [rust_toolchain, rust_target, sys_name, sys_version, sys_target])
|
||||
install = elem("target:install", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
mrsdk_target_share = "/usr/src/matrix-rust-sdk/target/${sys_name}/${sys_version}/${rust_target}/${rust_toolchain}/_shared_cache"
|
||||
|
||||
mrsdk_testee = "/usr/bin/tuwunel"
|
||||
mrsdk_test_args = "--no-fail-fast"
|
||||
|
||||
mrsdk_skip_list =<<EOF
|
||||
--skip test_delayed_invite_response_and_sent_message_decryption
|
||||
EOF
|
||||
}
|
||||
}
|
||||
|
||||
target "integ-valgrind" {
|
||||
name = elem("integ-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("integ-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
VALGRINDFLAGS = "${valgrind_flags}"
|
||||
cargo_cmd = "valgrind test"
|
||||
cargo_args = "--test=*"
|
||||
}
|
||||
}
|
||||
|
||||
target "integ" {
|
||||
name = elem("integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("integ", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
TUWUNEL_DATABASE_PATH = "/tmp/integration.test.db"
|
||||
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
|
||||
cargo_args = (cargo_profile == "bench"?
|
||||
"--no-fail-fast --bench=*": "--no-fail-fast --test=*"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Smoke tests
|
||||
#
|
||||
@@ -432,11 +557,26 @@ group "smoke" {
|
||||
targets = [
|
||||
"smoke-version",
|
||||
"smoke-startup",
|
||||
#"smoke-nix",
|
||||
#"smoke-valgrind",
|
||||
#"smoke-perf",
|
||||
]
|
||||
}
|
||||
|
||||
target "smoke-nix" {
|
||||
name = elem("smoke-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("smoke-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
output = ["type=cacheonly,compression=zstd,mode=min,compression-level=${cache_compress_level}"]
|
||||
dockerfile = "${docker_dir}/Dockerfile.nix"
|
||||
target = "smoke-nix"
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("build-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
}
|
||||
|
||||
target "smoke-valgrind" {
|
||||
name = elem("smoke-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
@@ -509,6 +649,70 @@ target "tests-smoke" {
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Unit tests
|
||||
#
|
||||
|
||||
target "unit-valgrind" {
|
||||
name = elem("unit-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("unit-valgrind", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
target = "cargo"
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
VALGRINDFLAGS = "${valgrind_flags}"
|
||||
cargo_cmd = "valgrind test"
|
||||
cargo_args = "--lib --bins"
|
||||
}
|
||||
}
|
||||
|
||||
target "unit" {
|
||||
name = elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
target = "cargo"
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
|
||||
cargo_args = (cargo_profile == "bench"?
|
||||
"--no-fail-fast --lib": "--no-fail-fast --lib --bins"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
target "docs" {
|
||||
name = elem("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
target = "cargo"
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
cargo_cmd = "test"
|
||||
cargo_args = "--doc --no-fail-fast"
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Installation
|
||||
#
|
||||
@@ -664,6 +868,12 @@ target "install" {
|
||||
}
|
||||
args = {
|
||||
install_prefix = install_prefix
|
||||
assert_linkage = (
|
||||
substr(cargo_profile, 0, 5) == "bench"? "static":
|
||||
substr(cargo_profile, 0, 7) == "release"? "static":
|
||||
substr(rust_toolchain, 0, 6) == "stable"? "static":
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,6 +883,7 @@ target "install" {
|
||||
|
||||
group "pkg" {
|
||||
targets = [
|
||||
"nix",
|
||||
"deb",
|
||||
"rpm",
|
||||
"deb-install",
|
||||
@@ -784,28 +995,36 @@ target "build-deb" {
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Unit tests
|
||||
#
|
||||
|
||||
target "unit" {
|
||||
name = elem("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
target "nix" {
|
||||
name = elem("nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("unit", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
elem_tag("nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
target = "cargo"
|
||||
output = ["type=docker,compression=zstd,mode=min,compression-level=${zstd_image_compress_level}"]
|
||||
target = "nix-pkg"
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
elem("build-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target]),
|
||||
]
|
||||
}
|
||||
|
||||
target "build-nix" {
|
||||
name = elem("build-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("build-nix", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
output = ["type=cacheonly,compression=zstd,mode=min,compression-level=${cache_compress_level}"]
|
||||
cache_to = ["type=local,compression=zstd,mode=max,compression-level=${cache_compress_level}"]
|
||||
dockerfile = "${docker_dir}/Dockerfile.nix"
|
||||
target = "build-nix"
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
elem("builder", [sys_name, sys_version, sys_target]),
|
||||
elem("source", [sys_name, sys_version, sys_target]),
|
||||
]
|
||||
contexts = {
|
||||
input = elem("target:build-tests", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
|
||||
cargo_args = (rust_toolchain == "nightly"?
|
||||
"--no-fail-fast --all-targets": "--no-fail-fast --bins --tests"
|
||||
)
|
||||
input = elem("target:builder", [sys_name, sys_version, sys_target]),
|
||||
source = elem("target:source", [sys_name, sys_version, sys_target]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,15 +1049,14 @@ target "book" {
|
||||
}
|
||||
dockerfile-inline =<<EOF
|
||||
FROM input AS book
|
||||
COPY --link --from=input . .
|
||||
RUN ["mdbook", "build", "-d", "/book", "/usr/src/tuwunel"]
|
||||
EOF
|
||||
}
|
||||
|
||||
target "docs" {
|
||||
name = elem("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
target "build-docs" {
|
||||
name = elem("build-docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
tags = [
|
||||
elem_tag("docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
elem_tag("build-docs", [cargo_profile, rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target], "latest"),
|
||||
]
|
||||
matrix = cargo_rust_feat_sys
|
||||
inherits = [
|
||||
@@ -898,7 +1116,7 @@ target "build-tests" {
|
||||
}
|
||||
args = {
|
||||
cargo_cmd = (cargo_profile == "bench"? "bench": "test")
|
||||
cargo_args = "--no-run"
|
||||
cargo_args = (cargo_profile == "bench"? "--no-run --benches": "--no-run --tests")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,7 +1253,7 @@ target "fmt" {
|
||||
input = elem("target:ingredients", [rust_toolchain, rust_target, feat_set, sys_name, sys_version, sys_target])
|
||||
}
|
||||
args = {
|
||||
fmt_args = "-- --color always"
|
||||
fmt_args = "-- --color=always"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1176,6 +1394,8 @@ target "deps-base" {
|
||||
# cache key for unique artifact area
|
||||
cargo_target_artifact = "${cargo_tgt_dir_base}/${sys_name}/${sys_version}/${rust_target}/${rust_toolchain}/${cargo_profile}/${feat_set}/${git_ref_sha}"
|
||||
# cache key for hashed subdirs
|
||||
cargo_share = "${cargo_tgt_dir_base}/${sys_name}/${sys_version}/${rust_toolchain}/${cargo_profile}/_shared_cache"
|
||||
# cache key for hashed subdirs
|
||||
cargo_target_share = "${cargo_tgt_dir_base}/${sys_name}/${sys_version}/${rust_target}/${rust_toolchain}/${cargo_profile}/_shared_cache"
|
||||
# cased name of profile subdir within target complex
|
||||
cargo_target_profile = (
|
||||
@@ -1186,8 +1406,8 @@ target "deps-base" {
|
||||
|
||||
CARGO_PROFILE_TEST_DEBUG = "false"
|
||||
CARGO_PROFILE_TEST_INCREMENTAL = "false"
|
||||
CARGO_PROFILE_BENCH_DEBUG = "limited"
|
||||
CARGO_PROFILE_BENCH_LTO = "false"
|
||||
CARGO_PROFILE_BENCH_DEBUG = "false"
|
||||
CARGO_PROFILE_BENCH_LTO = "thin"
|
||||
CARGO_PROFILE_RELEASE_LTO = "thin"
|
||||
CARGO_PROFILE_RELEASE_DEBUGINFO_DEBUG = "limited"
|
||||
CARGO_PROFILE_RELEASE_DEBUGINFO_LTO = "off"
|
||||
@@ -1200,7 +1420,7 @@ target "deps-base" {
|
||||
join(" ", static_rustflags),
|
||||
join(" ", static_nightly_rustflags),
|
||||
join(" ", native_rustflags),
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-l:libbz2.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
@@ -1214,7 +1434,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),
|
||||
@@ -1222,7 +1442,7 @@ target "deps-base" {
|
||||
join(" ", static_nightly_rustflags),
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
"-C target-cpu=${sys_target_isa(sys_target)}": "",
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-l:libbz2.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
@@ -1236,13 +1456,13 @@ 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),
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
"-C target-cpu=${sys_target_isa(sys_target)}": "",
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-l:libbz2.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
@@ -1256,13 +1476,13 @@ target "deps-base" {
|
||||
"-C link-arg=-l:libgcc.a": "",
|
||||
]):
|
||||
|
||||
rust_toolchain == "stable"?
|
||||
substr(rust_toolchain, 0, 6) == "stable"?
|
||||
join(" ", [
|
||||
join(" ", rustflags),
|
||||
join(" ", static_rustflags),
|
||||
sys_target_triple(sys_target) == "x86_64-linux-gnu"?
|
||||
"-C target-cpu=${sys_target_isa(sys_target)}": "",
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/14", #FIXME
|
||||
"-C link-arg=-L/usr/lib/gcc/${sys_target_triple(sys_target)}/15", #FIXME
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "bzip2_compression")?
|
||||
"-C link-arg=-l:libbz2.a": "",
|
||||
contains(split(",", cargo_feat_sets[feat_set]), "lz4_compression")?
|
||||
@@ -1276,7 +1496,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),
|
||||
@@ -1341,6 +1561,7 @@ target "rocksdb-build" {
|
||||
rocksdb_zstd = contains(split(",", cargo_feat_sets[feat_set]), "zstd_compression")? 1: 0
|
||||
rocksdb_jemalloc = contains(split(",", cargo_feat_sets[feat_set]), "jemalloc")? 1: 0
|
||||
rocksdb_iouring = contains(split(",", cargo_feat_sets[feat_set]), "io_uring")? 1: 0
|
||||
rocksdb_numa = rocksdb_numa
|
||||
rocksdb_shared = 0
|
||||
rocksdb_opt_level = rocksdb_opt_level
|
||||
rocksdb_build_type = rocksdb_build_type
|
||||
@@ -1499,12 +1720,13 @@ rustup_components = [
|
||||
]
|
||||
|
||||
cargo_installs = [
|
||||
"cargo-chef",
|
||||
"cargo-audit",
|
||||
"cargo-deb",
|
||||
#"cargo-arch",
|
||||
"cargo-chef",
|
||||
"cargo-deb",
|
||||
"cargo-generate-rpm",
|
||||
#"lychee",
|
||||
"cargo-valgrind",
|
||||
"lychee",
|
||||
"mdbook",
|
||||
"typos-cli",
|
||||
]
|
||||
@@ -1587,13 +1809,22 @@ kitchen_packages = [
|
||||
"clang",
|
||||
"cmake",
|
||||
"curl",
|
||||
"gawk",
|
||||
"git",
|
||||
"golang-go",
|
||||
"gzip",
|
||||
"jq",
|
||||
"libc6-dev",
|
||||
"libclang-dev",
|
||||
"libnuma-dev",
|
||||
"libssl-dev",
|
||||
"libsqlite3-dev",
|
||||
"make",
|
||||
"nix-bin",
|
||||
"openssl",
|
||||
"pkg-config",
|
||||
"pkgconf",
|
||||
"valgrind",
|
||||
"xz-utils",
|
||||
]
|
||||
|
||||
@@ -1613,7 +1844,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,6 +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"
|
||||
skip="${skip}|TestThreadReceiptsInSyncMSC4102"
|
||||
|
||||
set -a
|
||||
cargo_profile="${cargo_profile:-$default_cargo_profile}"
|
||||
@@ -50,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}"
|
||||
@@ -79,13 +82,13 @@ if test "$CI" = "true"; then
|
||||
fi
|
||||
|
||||
output_src="$cid:/usr/src/complement/full_output.jsonl"
|
||||
output_dst="complement.jsonl"
|
||||
output_dst="tests/complement/logs.jsonl"
|
||||
extract_output() {
|
||||
docker cp "$output_src" "$output_dst"
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -20,7 +20,6 @@ services:
|
||||
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
|
||||
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
|
||||
TUWUNEL_ALLOW_FEDERATION: 'true'
|
||||
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#TUWUNEL_LOG: warn,state_res=warn
|
||||
TUWUNEL_ADDRESS: 0.0.0.0
|
||||
|
||||
@@ -36,7 +36,6 @@ services:
|
||||
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
|
||||
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
|
||||
TUWUNEL_ALLOW_FEDERATION: 'true'
|
||||
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#TUWUNEL_LOG: warn,state_res=warn
|
||||
TUWUNEL_ADDRESS: 0.0.0.0
|
||||
|
||||
@@ -26,7 +26,6 @@ services:
|
||||
# TUWUNEL_LOG: info # default is: "warn,state_res=warn"
|
||||
# TUWUNEL_ALLOW_ENCRYPTION: 'true'
|
||||
# TUWUNEL_ALLOW_FEDERATION: 'true'
|
||||
# TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# TUWUNEL_ALLOW_INCOMING_PRESENCE: true
|
||||
# TUWUNEL_ALLOW_OUTGOING_PRESENCE: true
|
||||
# TUWUNEL_ALLOW_LOCAL_PRESENCE: true
|
||||
|
||||
@@ -20,7 +20,6 @@ services:
|
||||
TUWUNEL_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
|
||||
#TUWUNEL_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
|
||||
TUWUNEL_ALLOW_FEDERATION: 'true'
|
||||
TUWUNEL_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
TUWUNEL_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#TUWUNEL_LOG: warn,state_res=warn
|
||||
TUWUNEL_ADDRESS: 0.0.0.0
|
||||
|
||||
+42
-64
@@ -1,9 +1,9 @@
|
||||
# Generic deployment documentation
|
||||
|
||||
> ### Getting help
|
||||
> [!TIP]
|
||||
>
|
||||
> If you run into any problems while setting up Tuwunel [open an issue on
|
||||
> GitHub](https://github.com/matrix-construct/tuwunel/issues/new).
|
||||
> Getting help: If you run into any problems while setting up Tuwunel
|
||||
> [open an issue on GitHub](https://github.com/matrix-construct/tuwunel/issues/new).
|
||||
|
||||
## Installing Tuwunel
|
||||
|
||||
@@ -12,24 +12,23 @@ ### Static prebuilt binary
|
||||
You may simply download the binary that fits your machine architecture (x86_64
|
||||
or aarch64). Run `uname -m` to see what you need.
|
||||
|
||||
Prebuilt fully static musl binaries can be downloaded from the latest tagged
|
||||
Prebuilt fully static binaries can be downloaded from the latest tagged
|
||||
release [here](https://github.com/matrix-construct/tuwunel/releases/latest) or
|
||||
`main` CI branch workflow artifact output. These also include Debian/Ubuntu
|
||||
packages.
|
||||
`main` CI branch workflow artifact output. These also include `.deb` packages
|
||||
for Debian or Ubuntu and `.rpm` packages for Red Hat or Fedora.
|
||||
|
||||
These can be curl'd directly from. `ci-bins` are CI workflow binaries by commit
|
||||
hash/revision, and `releases` are tagged releases. Sort by descending last
|
||||
modified for the latest.
|
||||
For the **best** performance; if using an `x86_64` CPU made in the last ~10 years,
|
||||
we recommend using the `-v3-` optimised packages. See below for a command to check
|
||||
what your system supports. If the server refuses to start or exits with an "Illegal
|
||||
Instruction" error you will need `-v2-` or `-v1-` packages instead. The database
|
||||
backend, RocksDB, benefits from `-v2-` or greater as it features performance
|
||||
critical hardware accelerated CRC32 hashing/checksumming.
|
||||
|
||||
These binaries have jemalloc and io_uring statically linked and included with
|
||||
them, so no additional dynamic dependencies need to be installed.
|
||||
|
||||
For the **best** performance; if using an `x86_64` CPU made in the last ~15 years,
|
||||
we recommend using the `-haswell-` optimised binaries. This sets
|
||||
`-march=haswell` which is the most compatible and highest performance with
|
||||
optimised binaries. The database backend, RocksDB, most benefits from this as it
|
||||
will then use hardware accelerated CRC32 hashing/checksumming which is critical
|
||||
for performance.
|
||||
Linux users can run this script to display which optimization levels they may
|
||||
choose:
|
||||
```
|
||||
cat /proc/cpuinfo | grep -Po '(avx|sse)[235]' | sort -u | sed 's/avx5/v4/;s/avx2/v3/;s/sse3/v2/;s/sse2/v1/' | sort
|
||||
```
|
||||
|
||||
### Compiling
|
||||
|
||||
@@ -142,69 +141,48 @@ ## Setting the correct file permissions
|
||||
|
||||
## Setting up the Reverse Proxy
|
||||
|
||||
We recommend Caddy as a reverse proxy, as it is trivial to use, handling TLS certificates, reverse proxy headers, etc transparently with proper defaults.
|
||||
For other software, please refer to their respective documentation or online guides.
|
||||
We recommend Caddy as a reverse proxy, as it is trivial to use, handling TLS certificates, reverse proxy headers, etc. transparently with proper defaults. However, Nginx is also well-supported and widely used.
|
||||
|
||||
### Caddy
|
||||
**Choose your reverse proxy:**
|
||||
|
||||
After installing Caddy via your preferred method, create `/etc/caddy/conf.d/tuwunel_caddyfile`
|
||||
and enter this (substitute for your server name).
|
||||
- **[Caddy Setup Guide](reverse-proxy-caddy.md)** - Recommended for ease of use and automatic TLS
|
||||
- **[Nginx Setup Guide](reverse-proxy-nginx.md)** - Popular choice with extensive documentation
|
||||
|
||||
```caddyfile
|
||||
your.server.name, your.server.name:8448 {
|
||||
# TCP reverse_proxy
|
||||
reverse_proxy 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)
|
||||
@@ -1,5 +1,5 @@
|
||||
[Container]
|
||||
Environment=TUWUNEL_SERVER_NAME=your.domain.here TUWUNEL_DATABASE_PATH=/var/lib/tuwunel TUWUNEL_PORT=6167 TUWUNEL_MAX_REQUEST_SIZE=20000000 TUWUNEL_ALLOW_REGISTRATION=true TUWUNEL_REGISTRATION_TOKEN=YOUR_TOKEN TUWUNEL_ALLOW_FEDERATION=true TUWUNEL_ALLOW_CHECK_FOR_UPDATES=true TUWUNEL_TRUSTED_SERVERS=["matrix.org"] TUWUNEL_ADDRESS=0.0.0.0 # Add TUWUNEL_CONFIG: '/etc/tuwunel.toml' if the config is mapped
|
||||
Environment=TUWUNEL_SERVER_NAME=your.domain.here TUWUNEL_DATABASE_PATH=/var/lib/tuwunel TUWUNEL_PORT=6167 TUWUNEL_MAX_REQUEST_SIZE=20000000 TUWUNEL_ALLOW_REGISTRATION=true TUWUNEL_REGISTRATION_TOKEN=YOUR_TOKEN TUWUNEL_ALLOW_FEDERATION=true TUWUNEL_TRUSTED_SERVERS=["matrix.org"] TUWUNEL_ADDRESS=0.0.0.0 # Add TUWUNEL_CONFIG: '/etc/tuwunel.toml' if the config is mapped
|
||||
Image=docker.io/jevolk/tuwunel:latest
|
||||
PublishPort=8448:6167
|
||||
Volume=/path/to/db:/var/lib/tuwunel
|
||||
|
||||
+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
+100
-140
@@ -10,11 +10,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748532342,
|
||||
"narHash": "sha256-CvaKOUq8G10sghKpZhEB2UYjJoWhEkrDFggDgi7piUI=",
|
||||
"lastModified": 1758711588,
|
||||
"narHash": "sha256-0nZlCCDC5PfndsQJXXtcyrtrfW49I3KadGMDlutzaGU=",
|
||||
"owner": "zhaofengli",
|
||||
"repo": "attic",
|
||||
"rev": "ce9373715fe3fac7a174a65a7e6d6baeba8cb4f9",
|
||||
"rev": "12cbeca141f46e1ade76728bce8adc447f2166c6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -29,14 +29,14 @@
|
||||
"devenv": "devenv",
|
||||
"flake-compat": "flake-compat_2",
|
||||
"git-hooks": "git-hooks",
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748883665,
|
||||
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
|
||||
"lastModified": 1763236786,
|
||||
"narHash": "sha256-JB19RGXDr6loKSdqwvA15jhRHwf6+9Crq2glqqVar84=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
|
||||
"rev": "938a275857047c300596092beaabaee6d892e243",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -58,16 +58,21 @@
|
||||
],
|
||||
"git-hooks": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
"devenv",
|
||||
"git-hooks"
|
||||
],
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1744206633,
|
||||
"narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=",
|
||||
"lastModified": 1752264895,
|
||||
"narHash": "sha256-1zBPE/PNAkPNUsOWFET4J0cjlvziH8DOekesDmjND+w=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "8a60090640b96f9df95d1ab99e5763a586be1404",
|
||||
"rev": "47053aef762f452e816e44eb9a23fbc3827b241a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -80,11 +85,11 @@
|
||||
"complement": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1741891349,
|
||||
"narHash": "sha256-YvrzOWcX7DH1drp5SGa+E/fc7wN3hqFtPbqPjZpOu1Q=",
|
||||
"lastModified": 1761739261,
|
||||
"narHash": "sha256-XdzSBbJIYG6thrHbo44/qBiMu5R4bayfy/dlWo9AXBA=",
|
||||
"owner": "matrix-construct",
|
||||
"repo": "complement",
|
||||
"rev": "e587b3df569cba411aeac7c20b6366d03c143745",
|
||||
"rev": "350d7666cab14cb0051ef53da7a1b0b3216d7269",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -95,18 +100,12 @@
|
||||
}
|
||||
},
|
||||
"crane": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"attic",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1722960479,
|
||||
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
|
||||
"lastModified": 1751562746,
|
||||
"narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
|
||||
"rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -117,11 +116,11 @@
|
||||
},
|
||||
"crane_2": {
|
||||
"locked": {
|
||||
"lastModified": 1748970125,
|
||||
"narHash": "sha256-UDyigbDGv8fvs9aS95yzFfOKkEjx1LO3PL3DsKopohA=",
|
||||
"lastModified": 1763511871,
|
||||
"narHash": "sha256-KKZWi+ij7oT0Ag8yC6MQkzfHGcytyjMJDD+47ZV1YNU=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "323b5746d89e04b22554b061522dfce9e4c49b18",
|
||||
"rev": "099f9014bc8d0cd6e445470ea1df0fd691d5a548",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -138,6 +137,7 @@
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-parts": "flake-parts_2",
|
||||
"git-hooks": [
|
||||
"cachix",
|
||||
"git-hooks"
|
||||
@@ -149,11 +149,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748273445,
|
||||
"narHash": "sha256-5V0dzpNgQM0CHDsMzh+ludYeu1S+Y+IMjbaskSSdFh0=",
|
||||
"lastModified": 1760560333,
|
||||
"narHash": "sha256-goJQdVl9oDgCxF9CggPUw1DvB4gsot1jzMmz9px8Du8=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb",
|
||||
"rev": "0a4043938f540027e562c5a0feebbe6be872c3ea",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -170,11 +170,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1749883145,
|
||||
"narHash": "sha256-RlcGw3vAnbI3cfZn8aFaovNUd7312VZh+/FDWkqdA7E=",
|
||||
"lastModified": 1763707297,
|
||||
"narHash": "sha256-Bd9VGavwFBLpyU4pjiWfv73gUibNj8dc3xmOW8ff3bI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "a804172f150bcf81262655324e583bb0cd0f28dd",
|
||||
"rev": "7c2d3a165a4a080fdcb6c191d8f9768281c99f75",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -187,11 +187,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"lastModified": 1747046372,
|
||||
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -219,11 +219,11 @@
|
||||
"flake-compat_3": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1747046372,
|
||||
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
|
||||
"lastModified": 1761588595,
|
||||
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
|
||||
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -241,11 +241,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1722555600,
|
||||
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
|
||||
"lastModified": 1751413152,
|
||||
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
|
||||
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -259,16 +259,15 @@
|
||||
"nixpkgs-lib": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"lastModified": 1756770412,
|
||||
"narHash": "sha256-+uWLQZccFHwqpGqr2Yt5VsW/PbeJVTn9Dk6SHWhNRPw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"rev": "4524271976b625a4a605beefd893f270620fd751",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -309,11 +308,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1747372754,
|
||||
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=",
|
||||
"lastModified": 1760392170,
|
||||
"narHash": "sha256-WftxJgr2MeDDFK47fQKywzC72L2jRc/PWcyGdjaDzkw=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
|
||||
"rev": "46d55f0aeb1d567a78223e69729734f3dca25a85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -344,30 +343,14 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"libgit2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1697646580,
|
||||
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"liburing": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1749816983,
|
||||
"narHash": "sha256-p5hXfDe53Y4MVwL2+wKZYpy4OPGvqFFnOEvkMsFAO6c=",
|
||||
"lastModified": 1763758538,
|
||||
"narHash": "sha256-cDsxLOqeC7imBcArolTHvejSnWoadgpvDy1DJ2/3MOw=",
|
||||
"owner": "axboe",
|
||||
"repo": "liburing",
|
||||
"rev": "ad83d3ab64894c16eaf21ef869656a5bddb93ca4",
|
||||
"rev": "e1ef1e680ee38ed9116989155fca47921698c25f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -381,11 +364,24 @@
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-parts": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-parts"
|
||||
],
|
||||
"git-hooks-nix": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"git-hooks"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"flake-parts": "flake-parts_2",
|
||||
"libgit2": "libgit2",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"nixpkgs-23-11": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
@@ -393,34 +389,30 @@
|
||||
"nixpkgs-regression": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1745930071,
|
||||
"narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=",
|
||||
"owner": "domenkozar",
|
||||
"lastModified": 1758763079,
|
||||
"narHash": "sha256-Bx1A+lShhOWwMuy3uDzZQvYiBKBFcKwy6G6NEohhv6A=",
|
||||
"owner": "cachix",
|
||||
"repo": "nix",
|
||||
"rev": "b455edf3505f1bf0172b39a735caef94687d0d9c",
|
||||
"rev": "6f0140527c2b0346df4afad7497baa08decb929f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.24",
|
||||
"owner": "cachix",
|
||||
"ref": "devenv-2.30.5",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1731533336,
|
||||
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
|
||||
"lastModified": 1757882181,
|
||||
"narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
|
||||
"rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -438,11 +430,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729742964,
|
||||
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
|
||||
"lastModified": 1737420293,
|
||||
"narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
|
||||
"rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -453,11 +445,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1726042813,
|
||||
"narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=",
|
||||
"lastModified": 1751949589,
|
||||
"narHash": "sha256-mgFxAPLWw0Kq+C8P3dRrZrOYEQXOtKuYVlo9xvPntt8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "159be5db480d1df880a0135ca0bfed84c2f88353",
|
||||
"rev": "9b008d60392981ad674e04016d25619281550a9d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -469,27 +461,27 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1724316499,
|
||||
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
|
||||
"lastModified": 1751741127,
|
||||
"narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841",
|
||||
"rev": "29e290002bfff26af1db6f64d070698019460302",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1733212471,
|
||||
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
|
||||
"lastModified": 1760524057,
|
||||
"narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
|
||||
"rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -501,43 +493,11 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1717432640,
|
||||
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
|
||||
"lastModified": 1763618868,
|
||||
"narHash": "sha256-v5afmLjn/uyD9EQuPBn7nZuaZVV9r+JerayK/4wvdWA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1748190013,
|
||||
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_5": {
|
||||
"locked": {
|
||||
"lastModified": 1749871736,
|
||||
"narHash": "sha256-K9yBph93OLTNw02Q6e9CYFGrUhvEXnh45vrZqIRWfvQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6afe187897bef7933475e6af374c893f4c84a293",
|
||||
"rev": "a8d610af3f1a5fb71e23e08434d8d61a466fc942",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -550,11 +510,11 @@
|
||||
"rocksdb": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1749358049,
|
||||
"narHash": "sha256-ZSjvAZBfZkJrBIpw8ANZMbJVb8AeuogvuAipGVE4Qe4=",
|
||||
"lastModified": 1763593074,
|
||||
"narHash": "sha256-aOV/jJjRjNJ3hrRqhCsXlIz05NvEhDF/j5Q5UOQuvp8=",
|
||||
"owner": "matrix-construct",
|
||||
"repo": "rocksdb",
|
||||
"rev": "cf7f65d0b377af019661c240f9165b3ef60640c3",
|
||||
"rev": "9a3a213b55df0b11408102c899a940675c0d90e4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -575,18 +535,18 @@
|
||||
"flake-utils": "flake-utils",
|
||||
"liburing": "liburing",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs_5",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"rocksdb": "rocksdb"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1749829309,
|
||||
"narHash": "sha256-t6x6/PKg8Shnkd3htrxf3WMgycfRLRWvN9JHAmGWf+s=",
|
||||
"lastModified": 1763648203,
|
||||
"narHash": "sha256-/WJdebbRD+m5vr2xy/bJdCpqd7YHSMapjuXAM/0lvtA=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "a497f4114ccf24978accb56190e60d1e1659e0c7",
|
||||
"rev": "eaaa2da9fbbfd7a79ff501e0563351cb2004574a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -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"
|
||||
];
|
||||
|
||||
@@ -21,7 +21,6 @@ media_startup_check = true
|
||||
prune_missing_media = true
|
||||
log_colors = true
|
||||
admin_room_notices = false
|
||||
allow_check_for_updates = false
|
||||
intentionally_unknown_config_option_for_testing = true
|
||||
rocksdb_log_level = "info"
|
||||
rocksdb_max_log_files = 1
|
||||
|
||||
@@ -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",
|
||||
|
||||
+20
-36
@@ -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
|
||||
@@ -247,8 +236,8 @@ pub(super) async fn get_remote_pdu(
|
||||
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(&server, ruma::api::federation::event::get_event::v1::Request {
|
||||
.federation
|
||||
.execute(&server, ruma::api::federation::event::get_event::v1::Request {
|
||||
event_id: event_id.clone(),
|
||||
})
|
||||
.await
|
||||
@@ -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
|
||||
@@ -338,11 +327,8 @@ pub(super) async fn ping(&self, server: OwnedServerName) -> Result {
|
||||
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&server,
|
||||
ruma::api::federation::discovery::get_server_version::v1::Request {},
|
||||
)
|
||||
.federation
|
||||
.execute(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
|
||||
.await
|
||||
{
|
||||
| Err(e) => {
|
||||
@@ -582,8 +568,8 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
|
||||
let remote_state_response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(&server_name, get_room_state::v1::Request {
|
||||
.federation
|
||||
.execute(&server_name, get_room_state::v1::Request {
|
||||
room_id: room_id.clone(),
|
||||
event_id: first_pdu.event_id().to_owned(),
|
||||
})
|
||||
@@ -918,7 +904,7 @@ pub(super) async fn database_files(&self, map: Option<String>, level: Option<i32
|
||||
let mut files: Vec<_> = self
|
||||
.services
|
||||
.db
|
||||
.db
|
||||
.engine
|
||||
.file_list()
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
@@ -974,10 +960,10 @@ pub(super) async fn create_jwt(
|
||||
#[derive(Serialize)]
|
||||
struct Claim {
|
||||
sub: String,
|
||||
iss: String,
|
||||
aud: String,
|
||||
exp: usize,
|
||||
nbf: usize,
|
||||
iss: Option<String>,
|
||||
aud: Option<String>,
|
||||
exp: Option<usize>,
|
||||
nbf: Option<usize>,
|
||||
}
|
||||
|
||||
let config = &self.services.config.jwt;
|
||||
@@ -994,21 +980,19 @@ struct Claim {
|
||||
let claim = Claim {
|
||||
sub: user,
|
||||
|
||||
iss: issuer.unwrap_or_default(),
|
||||
iss: issuer,
|
||||
|
||||
aud: audience.unwrap_or_default(),
|
||||
aud: audience,
|
||||
|
||||
exp: exp_from_now
|
||||
.and_then(|val| now_secs().checked_add(val))
|
||||
.map(TryInto::try_into)
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or(usize::MAX),
|
||||
.and_then(Result::ok),
|
||||
|
||||
nbf: nbf_from_now
|
||||
.and_then(|val| now_secs().checked_add(val))
|
||||
.map(TryInto::try_into)
|
||||
.and_then(Result::ok)
|
||||
.unwrap_or(0),
|
||||
.and_then(Result::ok),
|
||||
};
|
||||
|
||||
encode(&header, &claim, &key)
|
||||
@@ -1025,7 +1009,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: Option<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: Option<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
|
||||
}
|
||||
|
||||
+51
-160
@@ -1,8 +1,8 @@
|
||||
use std::{collections::BTreeMap, fmt::Write as _};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
Int, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
|
||||
Int, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
|
||||
events::{
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
room::{
|
||||
@@ -13,10 +13,9 @@
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug, debug_warn, error, info, is_equal_to,
|
||||
Err, Result, debug_warn, info,
|
||||
matrix::{Event, pdu::PduBuilder},
|
||||
utils::{self, ReadyExt},
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
@@ -62,148 +61,11 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
|
||||
let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
||||
|
||||
// Create user
|
||||
self.services
|
||||
.users
|
||||
.create(&user_id, Some(password.as_str()), None)
|
||||
.full_register(&user_id, Some(&password), None, None, false, true)
|
||||
.await?;
|
||||
|
||||
// Default to pretty displayname
|
||||
let mut displayname = user_id.localpart().to_owned();
|
||||
|
||||
// If `new_user_displayname_suffix` is set, registration will push whatever
|
||||
// content is set to the user's display name with a space before it
|
||||
if !self
|
||||
.services
|
||||
.server
|
||||
.config
|
||||
.new_user_displayname_suffix
|
||||
.is_empty()
|
||||
{
|
||||
write!(
|
||||
displayname,
|
||||
" {}",
|
||||
self.services
|
||||
.server
|
||||
.config
|
||||
.new_user_displayname_suffix
|
||||
)?;
|
||||
}
|
||||
|
||||
self.services
|
||||
.users
|
||||
.set_displayname(&user_id, Some(displayname));
|
||||
|
||||
// Initial account data
|
||||
self.services
|
||||
.account_data
|
||||
.update(
|
||||
None,
|
||||
&user_id,
|
||||
ruma::events::GlobalAccountDataEventType::PushRules
|
||||
.to_string()
|
||||
.into(),
|
||||
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
|
||||
content: ruma::events::push_rules::PushRulesEventContent {
|
||||
global: ruma::push::Ruleset::server_default(&user_id),
|
||||
},
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !self
|
||||
.services
|
||||
.server
|
||||
.config
|
||||
.auto_join_rooms
|
||||
.is_empty()
|
||||
{
|
||||
for room in &self.services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = self.services.alias.resolve(room).await else {
|
||||
error!(
|
||||
%user_id,
|
||||
"Failed to resolve room alias to room ID when attempting to auto join {room}, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
if !self
|
||||
.services
|
||||
.state_cache
|
||||
.server_in_room(self.services.globals.server_name(), &room_id)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Skipping room {room} to automatically join as we have never joined before."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let state_lock = self.services.state.mutex.lock(&room_id).await;
|
||||
|
||||
if let Some(room_server_name) = room.server_name() {
|
||||
match self
|
||||
.services
|
||||
.membership
|
||||
.join(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[
|
||||
self.services.globals.server_name().to_owned(),
|
||||
room_server_name.to_owned(),
|
||||
],
|
||||
&None,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(_response) => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
| Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
error!(
|
||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||
);
|
||||
self.services
|
||||
.admin
|
||||
.send_text(&format!(
|
||||
"Failed to automatically join room {room} for user {user_id}: \
|
||||
{e}"
|
||||
))
|
||||
.await;
|
||||
},
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we dont add a device since we're not the user, just the creator
|
||||
|
||||
// if this account creation is from the CLI / --execute, invite the first user
|
||||
// to admin room
|
||||
if let Ok(admin_room) = self.services.admin.get_admin_room().await {
|
||||
if self
|
||||
.services
|
||||
.state_cache
|
||||
.room_joined_count(&admin_room)
|
||||
.await
|
||||
.is_ok_and(is_equal_to!(1))
|
||||
{
|
||||
self.services
|
||||
.admin
|
||||
.make_user_admin(&user_id)
|
||||
.boxed()
|
||||
.await?;
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
} else {
|
||||
debug!("create_user admin command called without an admin room being available");
|
||||
}
|
||||
|
||||
self.write_str(&format!("Created user with user_id: {user_id} and password: `{password}`"))
|
||||
.await
|
||||
}
|
||||
@@ -224,6 +86,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)?;
|
||||
@@ -384,7 +265,7 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result {
|
||||
#[admin_command]
|
||||
pub(super) async fn force_join_list_of_local_users(
|
||||
&self,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
room: OwnedRoomOrAliasId,
|
||||
yes_i_want_to_do_this: bool,
|
||||
) -> Result {
|
||||
if self.body.len() < 2
|
||||
@@ -396,7 +277,7 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
|
||||
if !yes_i_want_to_do_this {
|
||||
return Err!(
|
||||
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force \
|
||||
"You must pass the --yes-i-want-to-do-this flag to ensure you really want to force \
|
||||
bulk join all specified local users.",
|
||||
);
|
||||
}
|
||||
@@ -408,7 +289,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, None)
|
||||
.await?;
|
||||
|
||||
if !self
|
||||
@@ -486,9 +367,10 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
.join(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some(&room),
|
||||
Some(String::from(BULK_JOIN_REASON)),
|
||||
&servers,
|
||||
&None,
|
||||
false,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
@@ -515,7 +397,7 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
#[admin_command]
|
||||
pub(super) async fn force_join_all_local_users(
|
||||
&self,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
room: OwnedRoomOrAliasId,
|
||||
yes_i_want_to_do_this: bool,
|
||||
) -> Result {
|
||||
if !yes_i_want_to_do_this {
|
||||
@@ -532,7 +414,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, None)
|
||||
.await?;
|
||||
|
||||
if !self
|
||||
@@ -581,9 +463,10 @@ pub(super) async fn force_join_all_local_users(
|
||||
.join(
|
||||
user_id,
|
||||
&room_id,
|
||||
Some(&room),
|
||||
Some(String::from(BULK_JOIN_REASON)),
|
||||
&servers,
|
||||
&None,
|
||||
false,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
@@ -608,16 +491,12 @@ pub(super) async fn force_join_all_local_users(
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_join_room(
|
||||
&self,
|
||||
user_id: String,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
) -> Result {
|
||||
pub(super) async fn force_join_room(&self, user_id: String, room: OwnedRoomOrAliasId) -> Result {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let (room_id, servers) = self
|
||||
.services
|
||||
.alias
|
||||
.resolve_with_servers(&room_id, None)
|
||||
.maybe_resolve_with_servers(&room, None)
|
||||
.await?;
|
||||
|
||||
assert!(
|
||||
@@ -629,7 +508,7 @@ pub(super) async fn force_join_room(
|
||||
|
||||
self.services
|
||||
.membership
|
||||
.join(&user_id, &room_id, None, &servers, &None, &state_lock)
|
||||
.join(&user_id, &room_id, Some(&room), None, &servers, false, &state_lock)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
@@ -645,7 +524,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 +548,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 +561,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 +631,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;
|
||||
|
||||
|
||||
+10
-4
@@ -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,
|
||||
@@ -72,7 +78,7 @@ pub(super) enum UserCommand {
|
||||
/// - Manually join a local user to a room.
|
||||
ForceJoinRoom {
|
||||
user_id: String,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
room: OwnedRoomOrAliasId,
|
||||
},
|
||||
|
||||
/// - Manually leave a local user from a room.
|
||||
@@ -142,7 +148,7 @@ pub(super) enum UserCommand {
|
||||
///
|
||||
/// Requires the `--yes-i-want-to-do-this` flag.
|
||||
ForceJoinListOfLocalUsers {
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
room: OwnedRoomOrAliasId,
|
||||
|
||||
#[arg(long)]
|
||||
yes_i_want_to_do_this: bool,
|
||||
@@ -154,7 +160,7 @@ pub(super) enum UserCommand {
|
||||
///
|
||||
/// Requires the `--yes-i-want-to-do-this` flag.
|
||||
ForceJoinAllLocalUsers {
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
room: OwnedRoomOrAliasId,
|
||||
|
||||
#[arg(long)]
|
||||
yes_i_want_to_do_this: bool,
|
||||
|
||||
@@ -11,6 +11,7 @@ version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "mod.rs"
|
||||
bench = false
|
||||
crate-type = [
|
||||
"rlib",
|
||||
# "dylib",
|
||||
|
||||
+14
-93
@@ -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, err, 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;
|
||||
}
|
||||
@@ -116,10 +73,12 @@ pub(crate) async fn whoami_route(
|
||||
Ok(whoami::v3::Response {
|
||||
user_id: body.sender_user().to_owned(),
|
||||
device_id: body.sender_device.clone(),
|
||||
is_guest: services
|
||||
.users
|
||||
.is_deactivated(body.sender_user())
|
||||
.await? && body.appservice_info.is_none(),
|
||||
is_guest: body.appservice_info.is_none()
|
||||
&& services
|
||||
.users
|
||||
.is_deactivated(body.sender_user())
|
||||
.await
|
||||
.map_err(|_| err!(Request(Forbidden("User does not exist."))))?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -140,44 +99,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 +108,6 @@ pub(crate) async fn deactivate_route(
|
||||
.await?;
|
||||
|
||||
info!("User {sender_user} deactivated their account.");
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
|
||||
+13
-25
@@ -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:?}");
|
||||
@@ -123,26 +121,16 @@ async fn room_available_servers(
|
||||
|
||||
// insert our server as the very first choice if in list, else check if we can
|
||||
// prefer the room alias server first
|
||||
match servers
|
||||
if let Some(server_index) = servers
|
||||
.iter()
|
||||
.position(|server_name| services.globals.server_is_ours(server_name))
|
||||
{
|
||||
| Some(server_index) => {
|
||||
servers.swap_remove(server_index);
|
||||
servers.insert(0, services.globals.server_name().to_owned());
|
||||
},
|
||||
| _ => {
|
||||
match servers
|
||||
.iter()
|
||||
.position(|server| server == room_alias.server_name())
|
||||
{
|
||||
| Some(alias_server_index) => {
|
||||
servers.swap_remove(alias_server_index);
|
||||
servers.insert(0, room_alias.server_name().into());
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
},
|
||||
servers.swap(0, server_index);
|
||||
} else if let Some(alias_server_index) = servers
|
||||
.iter()
|
||||
.position(|server| server == room_alias.server_name())
|
||||
{
|
||||
servers.swap(0, alias_server_index);
|
||||
}
|
||||
|
||||
servers
|
||||
|
||||
@@ -37,13 +37,10 @@ pub(crate) async fn appservice_ping(
|
||||
let timer = tokio::time::Instant::now();
|
||||
|
||||
let _response = services
|
||||
.sending
|
||||
.send_appservice_request(
|
||||
appservice_info.registration.clone(),
|
||||
ping::send_ping::v1::Request {
|
||||
transaction_id: body.transaction_id.clone(),
|
||||
},
|
||||
)
|
||||
.appservice
|
||||
.send_request(appservice_info.registration.clone(), ping::send_ping::v1::Request {
|
||||
transaction_id: body.transaction_id.clone(),
|
||||
})
|
||||
.await?
|
||||
.expect("We already validated if an appservice URL exists above");
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ pub(crate) async fn get_context_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_context::v3::Request>,
|
||||
) -> Result<get_context::v3::Response> {
|
||||
let sender = body.sender();
|
||||
let (sender_user, sender_device) = sender;
|
||||
let sender_user = body.sender_user();
|
||||
let sender_device = body.sender_device.as_deref();
|
||||
let room_id = &body.room_id;
|
||||
let event_id = &body.event_id;
|
||||
let filter = &body.filter;
|
||||
@@ -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<_>) =
|
||||
@@ -110,7 +110,7 @@ pub(crate) async fn get_context_route(
|
||||
|
||||
let lazy_loading_context = lazy_loading::Context {
|
||||
user_id: sender_user,
|
||||
device_id: Some(sender_device),
|
||||
device_id: sender_device,
|
||||
room_id,
|
||||
token: Some(base_count.into_unsigned()),
|
||||
options: Some(&filter.lazy_load_options),
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
+18
-81
@@ -2,17 +2,14 @@
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
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},
|
||||
MilliSecondsSinceUnixEpoch,
|
||||
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::string::to_small_string};
|
||||
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::{Ruma, client::DEVICE_ID_LENGTH};
|
||||
use crate::{Ruma, 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.")));
|
||||
}
|
||||
@@ -93,14 +94,12 @@ pub(crate) async fn update_device_route(
|
||||
appservice.registration.id
|
||||
);
|
||||
|
||||
let device_id = OwnedDeviceId::from(utils::random_string(DEVICE_ID_LENGTH));
|
||||
|
||||
services
|
||||
.users
|
||||
.create_device(
|
||||
sender_user,
|
||||
&device_id,
|
||||
(&appservice.registration.as_token, None),
|
||||
None,
|
||||
(Some(&appservice.registration.as_token), None),
|
||||
None,
|
||||
None,
|
||||
Some(client.to_string()),
|
||||
@@ -126,10 +125,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 +141,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 +168,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 +186,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
|
||||
|
||||
@@ -220,8 +220,8 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
server.filter(|server_name| !services.globals.server_is_ours(server_name))
|
||||
{
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
.federation
|
||||
.execute(
|
||||
other_server,
|
||||
federation::directory::get_public_rooms_filtered::v1::Request {
|
||||
limit,
|
||||
@@ -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,118 @@
|
||||
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 = body.sender_user();
|
||||
|
||||
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,
|
||||
body.sender_device.as_deref(),
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
-89
@@ -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`
|
||||
///
|
||||
@@ -38,28 +35,19 @@ pub(crate) async fn upload_keys_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<upload_keys::v3::Request>,
|
||||
) -> Result<upload_keys::v3::Response> {
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
let sender_user = body.sender_user();
|
||||
let sender_device = body.sender_device()?;
|
||||
|
||||
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 +72,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 +149,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 +167,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 +211,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 +224,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 +232,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 +242,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 +265,7 @@ async fn check_for_new_keys(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if empty {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -377,12 +349,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
|
||||
@@ -532,10 +504,7 @@ pub(crate) async fn get_keys_helper<F>(
|
||||
let request =
|
||||
federation::keys::get_keys::v1::Request { device_keys: device_keys_input_fed };
|
||||
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(server, request)
|
||||
.await;
|
||||
let response = services.federation.execute(server, request).await;
|
||||
|
||||
(server, response)
|
||||
})
|
||||
@@ -557,16 +526,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 +572,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())
|
||||
};
|
||||
@@ -660,8 +629,8 @@ pub(crate) async fn claim_keys_helper(
|
||||
(
|
||||
server,
|
||||
services
|
||||
.sending
|
||||
.send_federation_request(server, federation::keys::claim_keys::v1::Request {
|
||||
.federation
|
||||
.execute(server, federation::keys::claim_keys::v1::Request {
|
||||
one_time_keys: one_time_keys_input_fed,
|
||||
})
|
||||
.await,
|
||||
|
||||
@@ -22,8 +22,7 @@ pub(crate) async fn invite_user_route(
|
||||
|
||||
invite_check(&services, sender_user, room_id).await?;
|
||||
|
||||
banned_room_check(&services, sender_user, Some(room_id), room_id.server_name(), client)
|
||||
.await?;
|
||||
banned_room_check(&services, sender_user, room_id, None, client).await?;
|
||||
|
||||
let invite_user::v3::InvitationRecipient::UserId { user_id } = &body.recipient else {
|
||||
return Err!(Request(ThreepidDenied("Third party identifiers are not implemented")));
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
RoomId, RoomOrAliasId,
|
||||
RoomId,
|
||||
api::client::membership::{join_room_by_id, join_room_by_id_or_alias},
|
||||
};
|
||||
use tuwunel_core::Result;
|
||||
use tuwunel_core::{Result, warn};
|
||||
|
||||
use super::banned_room_check;
|
||||
use crate::{Ruma, client::membership::get_join_params};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
|
||||
///
|
||||
@@ -28,30 +28,38 @@ pub(crate) async fn join_room_by_id_route(
|
||||
|
||||
let room_id: &RoomId = &body.room_id;
|
||||
|
||||
banned_room_check(&services, sender_user, Some(room_id), room_id.server_name(), client)
|
||||
.await?;
|
||||
banned_room_check(&services, sender_user, room_id, None, client).await?;
|
||||
|
||||
let (room_id, servers) =
|
||||
get_join_params(&services, sender_user, <&RoomOrAliasId>::from(room_id), &[]).await?;
|
||||
let state_lock = services.state.mutex.lock(room_id).await;
|
||||
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
|
||||
services
|
||||
let mut errors = 0_usize;
|
||||
while let Err(e) = services
|
||||
.membership
|
||||
.join(
|
||||
sender_user,
|
||||
&room_id,
|
||||
room_id,
|
||||
None,
|
||||
body.reason.clone(),
|
||||
&servers,
|
||||
&body.appservice_info,
|
||||
&[],
|
||||
body.appservice_info.is_some(),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
errors = errors.saturating_add(1);
|
||||
if errors >= services.config.max_join_attempts_per_join_request {
|
||||
warn!(
|
||||
"Several servers failed. Giving up for this request. Try again for different \
|
||||
server selection."
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
Ok(join_room_by_id::v3::Response { room_id })
|
||||
Ok(join_room_by_id::v3::Response { room_id: room_id.to_owned() })
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/join/{roomIdOrAlias}`
|
||||
@@ -72,28 +80,42 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
let sender_user = body.sender_user();
|
||||
let appservice_info = &body.appservice_info;
|
||||
|
||||
let (room_id, servers) =
|
||||
get_join_params(&services, sender_user, &body.room_id_or_alias, &body.via).await?;
|
||||
let (room_id, servers) = services
|
||||
.alias
|
||||
.maybe_resolve_with_servers(&body.room_id_or_alias, Some(&body.via))
|
||||
.await?;
|
||||
|
||||
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client)
|
||||
banned_room_check(&services, sender_user, &room_id, Some(&body.room_id_or_alias), client)
|
||||
.await?;
|
||||
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
|
||||
services
|
||||
let mut errors = 0_usize;
|
||||
while let Err(e) = services
|
||||
.membership
|
||||
.join(
|
||||
sender_user,
|
||||
&room_id,
|
||||
Some(&body.room_id_or_alias),
|
||||
body.reason.clone(),
|
||||
&servers,
|
||||
appservice_info,
|
||||
appservice_info.is_some(),
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
errors = errors.saturating_add(1);
|
||||
if errors >= services.config.max_join_attempts_per_join_request {
|
||||
warn!(
|
||||
"Several servers failed. Giving up for this request. Try again for different \
|
||||
server selection."
|
||||
);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
Ok(join_room_by_id_or_alias::v3::Response { room_id })
|
||||
Ok(join_room_by_id_or_alias::v3::Response { room_id: room_id.clone() })
|
||||
}
|
||||
|
||||
@@ -1,44 +1,10 @@
|
||||
use std::{borrow::Borrow, collections::HashMap, iter::once, sync::Arc};
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedServerName, RoomId,
|
||||
RoomVersionId, UserId,
|
||||
api::{
|
||||
client::knock::knock_room,
|
||||
federation::{
|
||||
membership::RawStrippedState,
|
||||
{self},
|
||||
},
|
||||
},
|
||||
canonical_json::to_canonical_value,
|
||||
events::{
|
||||
StateEventType,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Err, Result, debug, debug_info, debug_warn, err, extract_variant, info,
|
||||
matrix::{
|
||||
event::{Event, gen_event_id},
|
||||
pdu::{PduBuilder, PduEvent},
|
||||
},
|
||||
trace,
|
||||
utils::{self},
|
||||
warn,
|
||||
};
|
||||
use tuwunel_service::{
|
||||
Services,
|
||||
rooms::{
|
||||
state::RoomMutexGuard,
|
||||
state_compressor::{CompressedState, HashSetCompressStateEvent},
|
||||
},
|
||||
};
|
||||
use ruma::api::client::knock::knock_room;
|
||||
use tuwunel_core::Result;
|
||||
|
||||
use super::banned_room_check;
|
||||
use crate::{Ruma, client::membership::get_join_params};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/*/knock/{roomIdOrAlias}`
|
||||
///
|
||||
@@ -50,541 +16,30 @@ pub(crate) async fn knock_room_route(
|
||||
body: Ruma<knock_room::v3::Request>,
|
||||
) -> Result<knock_room::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
let body = &body.body;
|
||||
|
||||
let (room_id, servers) =
|
||||
get_join_params(&services, sender_user, &body.room_id_or_alias, &body.via).await?;
|
||||
|
||||
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client)
|
||||
let (room_id, servers) = services
|
||||
.alias
|
||||
.maybe_resolve_with_servers(&body.room_id_or_alias, Some(&body.via))
|
||||
.await?;
|
||||
|
||||
knock_room_by_id_helper(&services, sender_user, &room_id, body.reason.clone(), &servers).await
|
||||
}
|
||||
|
||||
async fn knock_room_by_id_helper(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
) -> Result<knock_room::v3::Response> {
|
||||
let state_lock = services.state.mutex.lock(room_id).await;
|
||||
|
||||
if services
|
||||
.state_cache
|
||||
.is_invited(sender_user, room_id)
|
||||
.await
|
||||
{
|
||||
debug_warn!("{sender_user} is already invited in {room_id} but attempted to knock");
|
||||
return Err!(Request(Forbidden(
|
||||
"You cannot knock on a room you are already invited/accepted to."
|
||||
)));
|
||||
}
|
||||
|
||||
if services
|
||||
.state_cache
|
||||
.is_joined(sender_user, room_id)
|
||||
.await
|
||||
{
|
||||
debug_warn!("{sender_user} is already joined in {room_id} but attempted to knock");
|
||||
return Err!(Request(Forbidden("You cannot knock on a room you are already joined in.")));
|
||||
}
|
||||
|
||||
if services
|
||||
.state_cache
|
||||
.is_knocked(sender_user, room_id)
|
||||
.await
|
||||
{
|
||||
debug_warn!("{sender_user} is already knocked in {room_id}");
|
||||
return Ok(knock_room::v3::Response { room_id: room_id.into() });
|
||||
}
|
||||
|
||||
if let Ok(membership) = services
|
||||
.state_accessor
|
||||
.get_member(room_id, sender_user)
|
||||
.await
|
||||
{
|
||||
if membership.membership == MembershipState::Ban {
|
||||
debug_warn!("{sender_user} is banned from {room_id} but attempted to knock");
|
||||
return Err!(Request(Forbidden("You cannot knock on a room you are banned from.")));
|
||||
}
|
||||
}
|
||||
|
||||
let server_in_room = services
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), room_id)
|
||||
.await;
|
||||
|
||||
let local_knock = server_in_room
|
||||
|| servers.is_empty()
|
||||
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
|
||||
|
||||
if local_knock {
|
||||
knock_room_helper_local(services, sender_user, room_id, reason, servers, state_lock)
|
||||
.boxed()
|
||||
.await?;
|
||||
} else {
|
||||
knock_room_helper_remote(services, sender_user, room_id, reason, servers, state_lock)
|
||||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(knock_room::v3::Response::new(room_id.to_owned()))
|
||||
}
|
||||
|
||||
async fn knock_room_helper_local(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
state_lock: RoomMutexGuard,
|
||||
) -> Result {
|
||||
debug_info!("We can knock locally");
|
||||
|
||||
let room_version_id = services.state.get_room_version(room_id).await?;
|
||||
|
||||
if matches!(
|
||||
room_version_id,
|
||||
RoomVersionId::V1
|
||||
| RoomVersionId::V2
|
||||
| RoomVersionId::V3
|
||||
| RoomVersionId::V4
|
||||
| RoomVersionId::V5
|
||||
| RoomVersionId::V6
|
||||
) {
|
||||
return Err!(Request(Forbidden("This room does not support knocking.")));
|
||||
}
|
||||
|
||||
let content = RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason: reason.clone(),
|
||||
..RoomMemberEventContent::new(MembershipState::Knock)
|
||||
};
|
||||
|
||||
// Try normal knock first
|
||||
let Err(error) = services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(sender_user.to_string(), &content),
|
||||
sender_user,
|
||||
room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if servers.is_empty() || (servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
|
||||
{
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
warn!("We couldn't do the knock locally, maybe federation can help to satisfy the knock");
|
||||
|
||||
let (make_knock_response, remote_server) =
|
||||
make_knock_request(services, sender_user, room_id, servers).await?;
|
||||
|
||||
info!("make_knock finished");
|
||||
|
||||
let room_version_id = make_knock_response.room_version;
|
||||
|
||||
if !services
|
||||
.server
|
||||
.supported_room_version(&room_version_id)
|
||||
{
|
||||
return Err!(BadServerResponse(
|
||||
"Remote room version {room_version_id} is not supported by tuwunel"
|
||||
));
|
||||
}
|
||||
|
||||
let mut knock_event_stub = serde_json::from_str::<CanonicalJsonObject>(
|
||||
make_knock_response.event.get(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
err!(BadServerResponse("Invalid make_knock event json received from server: {e:?}"))
|
||||
})?;
|
||||
|
||||
knock_event_stub.insert(
|
||||
"origin".to_owned(),
|
||||
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
|
||||
);
|
||||
knock_event_stub.insert(
|
||||
"origin_server_ts".to_owned(),
|
||||
CanonicalJsonValue::Integer(
|
||||
utils::millis_since_unix_epoch()
|
||||
.try_into()
|
||||
.expect("Timestamp is valid js_int value"),
|
||||
),
|
||||
);
|
||||
knock_event_stub.insert(
|
||||
"content".to_owned(),
|
||||
to_canonical_value(RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Knock)
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
);
|
||||
|
||||
// In order to create a compatible ref hash (EventID) the `hashes` field needs
|
||||
// to be present
|
||||
services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut knock_event_stub, &room_version_id)?;
|
||||
|
||||
// Generate event id
|
||||
let event_id = gen_event_id(&knock_event_stub, &room_version_id)?;
|
||||
|
||||
// Add event_id
|
||||
knock_event_stub
|
||||
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
|
||||
|
||||
// It has enough fields to be called a proper event now
|
||||
let knock_event = knock_event_stub;
|
||||
|
||||
info!("Asking {remote_server} for send_knock in room {room_id}");
|
||||
let send_knock_request = federation::membership::create_knock_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.clone(),
|
||||
pdu: services
|
||||
.federation
|
||||
.format_pdu_into(knock_event.clone(), Some(&room_version_id))
|
||||
.await,
|
||||
};
|
||||
|
||||
let send_knock_response = services
|
||||
.sending
|
||||
.send_federation_request(&remote_server, send_knock_request)
|
||||
banned_room_check(&services, sender_user, &room_id, Some(&body.room_id_or_alias), client)
|
||||
.await?;
|
||||
|
||||
info!("send_knock finished");
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
|
||||
services
|
||||
.short
|
||||
.get_or_create_shortroomid(room_id)
|
||||
.await;
|
||||
|
||||
info!("Parsing knock event");
|
||||
|
||||
let parsed_knock_pdu = PduEvent::from_id_val(&event_id, knock_event.clone())
|
||||
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
|
||||
|
||||
info!("Updating membership locally to knock state with provided stripped state events");
|
||||
services
|
||||
.state_cache
|
||||
.update_membership(
|
||||
room_id,
|
||||
.membership
|
||||
.knock(
|
||||
sender_user,
|
||||
parsed_knock_pdu
|
||||
.get_content::<RoomMemberEventContent>()
|
||||
.expect("we just created this"),
|
||||
sender_user,
|
||||
Some(
|
||||
send_knock_response
|
||||
.knock_room_state
|
||||
.into_iter()
|
||||
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
|
||||
.collect(),
|
||||
),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Appending room knock event locally");
|
||||
services
|
||||
.timeline
|
||||
.append_pdu(
|
||||
&parsed_knock_pdu,
|
||||
knock_event,
|
||||
once(parsed_knock_pdu.event_id.borrow()),
|
||||
&room_id,
|
||||
Some(&body.room_id_or_alias),
|
||||
body.reason.clone(),
|
||||
&servers,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn knock_room_helper_remote(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
reason: Option<String>,
|
||||
servers: &[OwnedServerName],
|
||||
state_lock: RoomMutexGuard,
|
||||
) -> Result {
|
||||
info!("Knocking {room_id} over federation.");
|
||||
|
||||
let (make_knock_response, remote_server) =
|
||||
make_knock_request(services, sender_user, room_id, servers).await?;
|
||||
|
||||
info!("make_knock finished");
|
||||
|
||||
let room_version_id = make_knock_response.room_version;
|
||||
|
||||
if !services
|
||||
.server
|
||||
.supported_room_version(&room_version_id)
|
||||
{
|
||||
return Err!(BadServerResponse(
|
||||
"Remote room version {room_version_id} is not supported by tuwunel"
|
||||
));
|
||||
}
|
||||
|
||||
let mut knock_event_stub: CanonicalJsonObject =
|
||||
serde_json::from_str(make_knock_response.event.get()).map_err(|e| {
|
||||
err!(BadServerResponse("Invalid make_knock event json received from server: {e:?}"))
|
||||
})?;
|
||||
|
||||
knock_event_stub.insert(
|
||||
"origin".to_owned(),
|
||||
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
|
||||
);
|
||||
knock_event_stub.insert(
|
||||
"origin_server_ts".to_owned(),
|
||||
CanonicalJsonValue::Integer(
|
||||
utils::millis_since_unix_epoch()
|
||||
.try_into()
|
||||
.expect("Timestamp is valid js_int value"),
|
||||
),
|
||||
);
|
||||
knock_event_stub.insert(
|
||||
"content".to_owned(),
|
||||
to_canonical_value(RoomMemberEventContent {
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason,
|
||||
..RoomMemberEventContent::new(MembershipState::Knock)
|
||||
})
|
||||
.expect("event is valid, we just created it"),
|
||||
);
|
||||
|
||||
// In order to create a compatible ref hash (EventID) the `hashes` field needs
|
||||
// to be present
|
||||
services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut knock_event_stub, &room_version_id)?;
|
||||
|
||||
// Generate event id
|
||||
let event_id = gen_event_id(&knock_event_stub, &room_version_id)?;
|
||||
|
||||
// Add event_id
|
||||
knock_event_stub
|
||||
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
|
||||
|
||||
// It has enough fields to be called a proper event now
|
||||
let knock_event = knock_event_stub;
|
||||
|
||||
info!("Asking {remote_server} for send_knock in room {room_id}");
|
||||
let send_knock_request = federation::membership::create_knock_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
event_id: event_id.clone(),
|
||||
pdu: services
|
||||
.federation
|
||||
.format_pdu_into(knock_event.clone(), Some(&room_version_id))
|
||||
.await,
|
||||
};
|
||||
|
||||
let send_knock_response = services
|
||||
.sending
|
||||
.send_federation_request(&remote_server, send_knock_request)
|
||||
.await?;
|
||||
|
||||
info!("send_knock finished");
|
||||
|
||||
services
|
||||
.short
|
||||
.get_or_create_shortroomid(room_id)
|
||||
.await;
|
||||
|
||||
info!("Parsing knock event");
|
||||
let parsed_knock_pdu = PduEvent::from_id_val(&event_id, knock_event.clone())
|
||||
.map_err(|e| err!(BadServerResponse("Invalid knock event PDU: {e:?}")))?;
|
||||
|
||||
info!("Going through send_knock response knock state events");
|
||||
let state = send_knock_response
|
||||
.knock_room_state
|
||||
.iter()
|
||||
.map(|event| {
|
||||
serde_json::from_str::<CanonicalJsonObject>(
|
||||
extract_variant!(event.clone(), RawStrippedState::Stripped)
|
||||
.expect("Raw<AnyStrippedStateEvent>")
|
||||
.json()
|
||||
.get(),
|
||||
)
|
||||
})
|
||||
.filter_map(Result::ok);
|
||||
|
||||
let mut state_map: HashMap<u64, OwnedEventId> = HashMap::new();
|
||||
|
||||
for event in state {
|
||||
let Some(state_key) = event.get("state_key") else {
|
||||
debug_warn!("send_knock stripped state event missing state_key: {event:?}");
|
||||
continue;
|
||||
};
|
||||
let Some(event_type) = event.get("type") else {
|
||||
debug_warn!("send_knock stripped state event missing event type: {event:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(state_key) = serde_json::from_value::<String>(state_key.clone().into()) else {
|
||||
debug_warn!("send_knock stripped state event has invalid state_key: {event:?}");
|
||||
continue;
|
||||
};
|
||||
let Ok(event_type) = serde_json::from_value::<StateEventType>(event_type.clone().into())
|
||||
else {
|
||||
debug_warn!("send_knock stripped state event has invalid event type: {event:?}");
|
||||
continue;
|
||||
};
|
||||
|
||||
let event_id = gen_event_id(&event, &room_version_id)?;
|
||||
let shortstatekey = services
|
||||
.short
|
||||
.get_or_create_shortstatekey(&event_type, &state_key)
|
||||
.await;
|
||||
|
||||
services
|
||||
.timeline
|
||||
.add_pdu_outlier(&event_id, &event);
|
||||
|
||||
state_map.insert(shortstatekey, event_id.clone());
|
||||
}
|
||||
|
||||
info!("Compressing state from send_knock");
|
||||
let compressed: CompressedState = services
|
||||
.state_compressor
|
||||
.compress_state_events(
|
||||
state_map
|
||||
.iter()
|
||||
.map(|(ssk, eid)| (ssk, eid.borrow())),
|
||||
)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
debug!("Saving compressed state");
|
||||
let HashSetCompressStateEvent {
|
||||
shortstatehash: statehash_before_knock,
|
||||
added,
|
||||
removed,
|
||||
} = services
|
||||
.state_compressor
|
||||
.save_state(room_id, Arc::new(compressed))
|
||||
.await?;
|
||||
|
||||
debug!("Forcing state for new room");
|
||||
services
|
||||
.state
|
||||
.force_state(room_id, statehash_before_knock, added, removed, &state_lock)
|
||||
.await?;
|
||||
|
||||
let statehash_after_knock = services
|
||||
.state
|
||||
.append_to_state(&parsed_knock_pdu)
|
||||
.await?;
|
||||
|
||||
info!("Updating membership locally to knock state with provided stripped state events");
|
||||
services
|
||||
.state_cache
|
||||
.update_membership(
|
||||
room_id,
|
||||
sender_user,
|
||||
parsed_knock_pdu
|
||||
.get_content::<RoomMemberEventContent>()
|
||||
.expect("we just created this"),
|
||||
sender_user,
|
||||
Some(
|
||||
send_knock_response
|
||||
.knock_room_state
|
||||
.into_iter()
|
||||
.filter_map(|s| extract_variant!(s, RawStrippedState::Stripped))
|
||||
.collect(),
|
||||
),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Appending room knock event locally");
|
||||
services
|
||||
.timeline
|
||||
.append_pdu(
|
||||
&parsed_knock_pdu,
|
||||
knock_event,
|
||||
once(parsed_knock_pdu.event_id.borrow()),
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("Setting final room state for new room");
|
||||
// We set the room state after inserting the pdu, so that we never have a moment
|
||||
// in time where events in the current room state do not exist
|
||||
services
|
||||
.state
|
||||
.set_room_state(room_id, statehash_after_knock, &state_lock);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn make_knock_request(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
room_id: &RoomId,
|
||||
servers: &[OwnedServerName],
|
||||
) -> Result<(federation::membership::prepare_knock_event::v1::Response, OwnedServerName)> {
|
||||
let mut make_knock_response_and_server =
|
||||
Err!(BadServerResponse("No server available to assist in knocking."));
|
||||
|
||||
let mut make_knock_counter: usize = 0;
|
||||
|
||||
for remote_server in servers {
|
||||
if services.globals.server_is_ours(remote_server) {
|
||||
continue;
|
||||
}
|
||||
|
||||
info!("Asking {remote_server} for make_knock ({make_knock_counter})");
|
||||
|
||||
let make_knock_response = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
remote_server,
|
||||
federation::membership::prepare_knock_event::v1::Request {
|
||||
room_id: room_id.to_owned(),
|
||||
user_id: sender_user.to_owned(),
|
||||
ver: services
|
||||
.server
|
||||
.supported_room_versions()
|
||||
.collect(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
trace!("make_knock response: {make_knock_response:?}");
|
||||
make_knock_counter = make_knock_counter.saturating_add(1);
|
||||
|
||||
make_knock_response_and_server = make_knock_response.map(|r| (r, remote_server.clone()));
|
||||
|
||||
if make_knock_response_and_server.is_ok() {
|
||||
break;
|
||||
}
|
||||
|
||||
if make_knock_counter > 40 {
|
||||
warn!(
|
||||
"50 servers failed to provide valid make_knock response, assuming no server can \
|
||||
assist in knocking."
|
||||
);
|
||||
make_knock_response_and_server =
|
||||
Err!(BadServerResponse("No server available to assist in knocking."));
|
||||
|
||||
return make_knock_response_and_server;
|
||||
}
|
||||
}
|
||||
|
||||
make_knock_response_and_server
|
||||
drop(state_lock);
|
||||
|
||||
Ok(knock_room::v3::Response::new(room_id.clone()))
|
||||
}
|
||||
|
||||
@@ -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,6 +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(&MembershipState::Join), None))
|
||||
.ready_filter_map(|pdu| {
|
||||
let content = pdu.get_content::<RoomMemberEventContent>().ok()?;
|
||||
let sender = pdu.sender().to_owned();
|
||||
@@ -92,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,
|
||||
};
|
||||
|
||||
|
||||
@@ -8,15 +8,12 @@
|
||||
mod members;
|
||||
mod unban;
|
||||
|
||||
use std::{cmp::Ordering, net::IpAddr};
|
||||
use std::net::IpAddr;
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedServerName, RoomId, RoomOrAliasId, ServerName, UserId,
|
||||
api::client::membership::joined_rooms,
|
||||
};
|
||||
use tuwunel_core::{Err, Result, result::LogErr, utils::shuffle, warn};
|
||||
use ruma::{RoomId, RoomOrAliasId, UserId, api::client::membership::joined_rooms};
|
||||
use tuwunel_core::{Err, Result, result::LogErr, warn};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
pub(crate) use self::{
|
||||
@@ -58,57 +55,42 @@ pub(crate) async fn joined_rooms_route(
|
||||
pub(crate) async fn banned_room_check(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
room_id: Option<&RoomId>,
|
||||
server_name: Option<&ServerName>,
|
||||
room_id: &RoomId,
|
||||
orig_room_id: Option<&RoomOrAliasId>,
|
||||
client_ip: IpAddr,
|
||||
) -> Result {
|
||||
if services.users.is_admin(user_id).await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// TODO: weird condition
|
||||
if let Some(room_id) = room_id {
|
||||
if services.metadata.is_banned(room_id).await
|
||||
|| (room_id.server_name().is_some()
|
||||
&& services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(
|
||||
room_id
|
||||
.server_name()
|
||||
.expect("legacy room mxid")
|
||||
.host(),
|
||||
)) {
|
||||
warn!(
|
||||
"User {user_id} who is not an admin attempted to send an invite for or \
|
||||
attempted to join a banned room or banned room server name: {room_id}"
|
||||
);
|
||||
// room id is banned ...
|
||||
if services.metadata.is_banned(room_id).await
|
||||
// ... or legacy room id server is banned ...
|
||||
|| room_id.server_name().is_some_and(|server_name| {
|
||||
services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(server_name.host())
|
||||
})
|
||||
// ... or alias server is banned
|
||||
|| orig_room_id.is_some_and(|orig_room_id| {
|
||||
orig_room_id.server_name().is_some_and(|orig_server_name|
|
||||
services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(orig_server_name.host()))
|
||||
}) {
|
||||
warn!(
|
||||
"User {user_id} who is not an admin attempted to send an invite for or attempted to \
|
||||
join a banned room or banned room server name: {room_id}"
|
||||
);
|
||||
|
||||
maybe_deactivate(services, user_id, client_ip)
|
||||
.await
|
||||
.log_err()
|
||||
.ok();
|
||||
maybe_deactivate(services, user_id, client_ip)
|
||||
.await
|
||||
.log_err()
|
||||
.ok();
|
||||
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
} else if let Some(server_name) = server_name {
|
||||
if services
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.is_match(server_name.host())
|
||||
{
|
||||
warn!(
|
||||
"User {user_id} who is not an admin tried joining a room which has the server \
|
||||
name {server_name} that is globally forbidden. Rejecting.",
|
||||
);
|
||||
|
||||
maybe_deactivate(services, user_id, client_ip)
|
||||
.await
|
||||
.log_err()
|
||||
.ok();
|
||||
|
||||
return Err!(Request(Forbidden("This remote server is banned on this homeserver.")));
|
||||
}
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -120,16 +102,15 @@ async fn maybe_deactivate(services: &Services, user_id: &UserId, client_ip: IpAd
|
||||
.config
|
||||
.auto_deactivate_banned_room_attempts
|
||||
{
|
||||
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
|
||||
let notice = format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room join from \
|
||||
IP {client_ip}"
|
||||
);
|
||||
|
||||
warn!("{notice}");
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room \
|
||||
join from IP {client_ip}"
|
||||
))
|
||||
.await;
|
||||
services.admin.send_text(¬ice).await;
|
||||
}
|
||||
|
||||
services
|
||||
@@ -141,91 +122,3 @@ async fn maybe_deactivate(services: &Services, user_id: &UserId, client_ip: IpAd
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: should this be in services? banned check would have to resolve again if
|
||||
// room_id is not available at callsite
|
||||
async fn get_join_params(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
room_id_or_alias: &RoomOrAliasId,
|
||||
via: &[OwnedServerName],
|
||||
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
|
||||
// servers tried first, additional_servers shuffled then tried after
|
||||
let (room_id, mut servers, mut additional_servers) =
|
||||
match OwnedRoomId::try_from(room_id_or_alias.to_owned()) {
|
||||
// if room id, shuffle via + room_id server_name ...
|
||||
| Ok(room_id) => {
|
||||
let mut additional_servers = via.to_vec();
|
||||
|
||||
if let Some(server) = room_id.server_name() {
|
||||
additional_servers.push(server.to_owned());
|
||||
}
|
||||
|
||||
(room_id, Vec::new(), additional_servers)
|
||||
},
|
||||
// ... 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?;
|
||||
|
||||
(room_id, servers, Vec::new())
|
||||
},
|
||||
};
|
||||
|
||||
// either way, add invited vias
|
||||
additional_servers.extend(
|
||||
services
|
||||
.state_cache
|
||||
.servers_invite_via(&room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
.await,
|
||||
);
|
||||
|
||||
// either way, add invite senders' servers
|
||||
additional_servers.extend(
|
||||
services
|
||||
.state_cache
|
||||
.invite_state(user_id, &room_id)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter_map(|event| event.get_field("sender").ok().flatten())
|
||||
.filter_map(|sender: &str| UserId::parse(sender).ok())
|
||||
.map(|user| user.server_name().to_owned()),
|
||||
);
|
||||
|
||||
// shuffle additionals, append to base servers
|
||||
additional_servers.sort_unstable();
|
||||
additional_servers.dedup();
|
||||
shuffle(&mut additional_servers);
|
||||
servers.sort_unstable();
|
||||
servers.dedup();
|
||||
servers.append(&mut additional_servers);
|
||||
|
||||
// sort deprioritized servers last
|
||||
servers.sort_by(|a, b| {
|
||||
let a_matches = services
|
||||
.server
|
||||
.config
|
||||
.deprioritize_joins_through_servers
|
||||
.is_match(a.host());
|
||||
let b_matches = services
|
||||
.server
|
||||
.config
|
||||
.deprioritize_joins_through_servers
|
||||
.is_match(b.host());
|
||||
|
||||
if a_matches && !b_matches {
|
||||
Ordering::Greater
|
||||
} else if !a_matches && b_matches {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
});
|
||||
|
||||
Ok((room_id, servers))
|
||||
}
|
||||
|
||||
+17
-12
@@ -1,5 +1,9 @@
|
||||
use axum::extract::State;
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture, pin_mut};
|
||||
use futures::{
|
||||
FutureExt, StreamExt, TryFutureExt,
|
||||
future::{Either, OptionFuture},
|
||||
pin_mut,
|
||||
};
|
||||
use ruma::{
|
||||
RoomId, UserId,
|
||||
api::{
|
||||
@@ -105,17 +109,18 @@ pub(crate) async fn get_message_events_route(
|
||||
}
|
||||
|
||||
let it = match body.dir {
|
||||
| Direction::Forward => services
|
||||
.timeline
|
||||
.pdus(Some(sender_user), room_id, Some(from))
|
||||
.ignore_err()
|
||||
.boxed(),
|
||||
|
||||
| Direction::Backward => services
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(from))
|
||||
.ignore_err()
|
||||
.boxed(),
|
||||
| Direction::Forward => Either::Left(
|
||||
services
|
||||
.timeline
|
||||
.pdus(Some(sender_user), room_id, Some(from))
|
||||
.ignore_err(),
|
||||
),
|
||||
| Direction::Backward => Either::Right(
|
||||
services
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(from))
|
||||
.ignore_err(),
|
||||
),
|
||||
};
|
||||
|
||||
let events: Vec<_> = it
|
||||
|
||||
@@ -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::*;
|
||||
@@ -84,9 +88,6 @@
|
||||
pub(super) use voip::*;
|
||||
pub(super) use well_known::*;
|
||||
|
||||
/// generated device ID length
|
||||
const DEVICE_ID_LENGTH: usize = 10;
|
||||
|
||||
/// generated user access token length
|
||||
const TOKEN_LENGTH: usize = tuwunel_service::users::device::TOKEN_LENGTH;
|
||||
|
||||
|
||||
+17
-21
@@ -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 {})
|
||||
}
|
||||
@@ -70,8 +68,8 @@ pub(crate) async fn get_displayname_route(
|
||||
if !services.globals.user_is_local(&body.user_id) {
|
||||
// Create and update our local copy of the user
|
||||
if let Ok(response) = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
.federation
|
||||
.execute(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
@@ -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 {})
|
||||
}
|
||||
@@ -173,8 +169,8 @@ pub(crate) async fn get_avatar_url_route(
|
||||
if !services.globals.user_is_local(&body.user_id) {
|
||||
// Create and update our local copy of the user
|
||||
if let Ok(response) = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
.federation
|
||||
.execute(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
@@ -235,8 +231,8 @@ pub(crate) async fn get_profile_route(
|
||||
if !services.globals.user_is_local(&body.user_id) {
|
||||
// Create and update our local copy of the user
|
||||
if let Ok(response) = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
.federation
|
||||
.execute(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
|
||||
+109
-6
@@ -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.")));
|
||||
@@ -486,7 +589,7 @@ pub(crate) async fn set_pushers_route(
|
||||
|
||||
services
|
||||
.pusher
|
||||
.set_pusher(sender_user, body.sender_device(), &body.action)
|
||||
.set_pusher(sender_user, body.sender_device()?, &body.action)
|
||||
.await?;
|
||||
|
||||
Ok(set_pusher::v3::Response::new())
|
||||
|
||||
@@ -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
|
||||
|
||||
+62
-244
@@ -2,7 +2,6 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use futures::FutureExt;
|
||||
use register::RegistrationKind;
|
||||
use ruma::{
|
||||
UserId,
|
||||
@@ -13,13 +12,11 @@
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
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, info, utils};
|
||||
use tuwunel_service::users::device::generate_refresh_token;
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH};
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::Ruma;
|
||||
|
||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
@@ -46,20 +43,15 @@ pub(crate) async fn get_register_available_route(
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix-appservice-irc")
|
||||
|| appservice
|
||||
.registration
|
||||
.id
|
||||
.contains("matrix_appservice_irc")
|
||||
let id = &appservice.registration.id;
|
||||
id == "irc"
|
||||
|| id.contains("matrix-appservice-irc")
|
||||
|| id.contains("matrix_appservice_irc")
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.config
|
||||
.forbidden_usernames
|
||||
.is_match(&body.username)
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
@@ -146,67 +138,30 @@ pub(crate) async fn register_route(
|
||||
let is_guest = body.kind == RegistrationKind::Guest;
|
||||
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
||||
|
||||
let user = body.username.as_deref().unwrap_or("");
|
||||
let device_name = body
|
||||
.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
|
||||
if !services.config.allow_registration && body.appservice_info.is_none() {
|
||||
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
|
||||
| (Some(username), Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (Some(username), _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (_, Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (None, _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
}
|
||||
info!(
|
||||
%is_guest,
|
||||
%user,
|
||||
%device_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
|
||||
return Err!(Request(Forbidden("Registration has been disabled.")));
|
||||
}
|
||||
|
||||
if is_guest
|
||||
&& (!services.config.allow_guest_registration
|
||||
|| (services.config.allow_registration
|
||||
&& services.globals.registration_token.is_some()))
|
||||
{
|
||||
info!(
|
||||
"Guest registration disabled / registration enabled with token configured, \
|
||||
rejecting guest registration attempt, initial device name: \"{}\"",
|
||||
body.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
if is_guest && !services.config.allow_guest_registration {
|
||||
debug_warn!(
|
||||
%device_name,
|
||||
"Guest registration disabled, rejecting guest registration attempt"
|
||||
);
|
||||
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
||||
}
|
||||
|
||||
// forbid guests from registering if there is not a real admin user yet. give
|
||||
// generic user error.
|
||||
if is_guest && services.users.count().await < 2 {
|
||||
warn!(
|
||||
"Guest account attempted to register before a real admin user has been registered, \
|
||||
rejecting registration. Guest's initial device name: \"{}\"",
|
||||
body.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
);
|
||||
return Err!(Request(Forbidden("Registration is temporarily disabled.")));
|
||||
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
||||
}
|
||||
|
||||
let user_id = match (body.username.as_ref(), is_guest) {
|
||||
@@ -228,8 +183,8 @@ pub(crate) async fn register_route(
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.config
|
||||
.forbidden_usernames
|
||||
.is_match(username)
|
||||
&& !emergency_mode_enabled
|
||||
{
|
||||
@@ -309,7 +264,13 @@ 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
|
||||
.get_registration_tokens()
|
||||
.await
|
||||
.is_empty()
|
||||
&& !is_guest
|
||||
{
|
||||
// Registration token required
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
@@ -320,6 +281,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 +292,7 @@ pub(crate) async fn register_route(
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
body.appservice_info.is_some() || is_guest
|
||||
};
|
||||
|
||||
@@ -372,45 +335,9 @@ pub(crate) async fn register_route(
|
||||
|
||||
let password = if is_guest { None } else { body.password.as_deref() };
|
||||
|
||||
// Create user
|
||||
services
|
||||
.users
|
||||
.create(&user_id, password, None)
|
||||
.await?;
|
||||
|
||||
// Default to pretty displayname
|
||||
let mut displayname = user_id.localpart().to_owned();
|
||||
|
||||
// If `new_user_displayname_suffix` is set, registration will push whatever
|
||||
// content is set to the user's display name with a space before it
|
||||
if !services
|
||||
.globals
|
||||
.new_user_displayname_suffix()
|
||||
.is_empty()
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(&user_id, Some(displayname.clone()));
|
||||
|
||||
// Initial account data
|
||||
services
|
||||
.account_data
|
||||
.update(
|
||||
None,
|
||||
&user_id,
|
||||
GlobalAccountDataEventType::PushRules
|
||||
.to_string()
|
||||
.into(),
|
||||
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
|
||||
content: ruma::events::push_rules::PushRulesEventContent {
|
||||
global: push::Ruleset::server_default(&user_id),
|
||||
},
|
||||
})?,
|
||||
)
|
||||
.full_register(&user_id, password, None, body.appservice_info.as_ref(), is_guest, true)
|
||||
.await?;
|
||||
|
||||
if (!is_guest && body.inhibit_login)
|
||||
@@ -420,169 +347,56 @@ pub(crate) async fn register_route(
|
||||
.is_some_and(|appservice| appservice.registration.device_management)
|
||||
{
|
||||
return Ok(register::v3::Response {
|
||||
access_token: None,
|
||||
user_id,
|
||||
device_id: None,
|
||||
access_token: None,
|
||||
refresh_token: None,
|
||||
expires_in: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = if is_guest { None } else { body.device_id.clone() }
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
let device_id = if is_guest { None } else { body.device_id.as_deref() };
|
||||
|
||||
// Generate new token for the device
|
||||
let (access_token, expires_in) = services
|
||||
.users
|
||||
.generate_access_token(body.body.refresh_token);
|
||||
.generate_access_token(body.refresh_token);
|
||||
|
||||
// Generate a new refresh_token if requested by client
|
||||
let refresh_token = expires_in.is_some().then(generate_refresh_token);
|
||||
|
||||
// Create device for this account
|
||||
services
|
||||
let device_id = services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
(&access_token, expires_in),
|
||||
device_id,
|
||||
(Some(&access_token), expires_in),
|
||||
refresh_token.as_deref(),
|
||||
body.initial_device_display_name.clone(),
|
||||
body.initial_device_display_name.as_deref(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
|
||||
let device_display_name = body
|
||||
.initial_device_display_name
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
if body.appservice_info.is_none() && (!is_guest || services.config.log_guest_registrations) {
|
||||
let mut notice = String::from(if is_guest { "New guest user" } else { "New user" });
|
||||
|
||||
// log in conduit admin channel if a non-guest user registered
|
||||
if body.appservice_info.is_none() && !is_guest {
|
||||
if !device_display_name.is_empty() {
|
||||
let notice = format!(
|
||||
"New user \"{user_id}\" registered on this server from IP {client} and device \
|
||||
display name \"{device_display_name}\""
|
||||
);
|
||||
write!(notice, " registered on this server from IP {client}")?;
|
||||
|
||||
info!("{notice}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
} else {
|
||||
let notice = format!("New user \"{user_id}\" registered on this server.");
|
||||
|
||||
info!("{notice}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
if let Some(device_name) = body.initial_device_display_name.as_deref() {
|
||||
write!(notice, " with device name {device_name}")?;
|
||||
}
|
||||
}
|
||||
|
||||
// log in conduit admin channel if a guest registered
|
||||
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
|
||||
debug_info!("New guest user \"{user_id}\" registered on this server.");
|
||||
|
||||
if !device_display_name.is_empty() {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with device display name \
|
||||
\"{device_display_name}\" registered on this server from IP {client}"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
if !is_guest {
|
||||
info!("{notice}");
|
||||
} else {
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on \
|
||||
this server from IP {client}",
|
||||
))
|
||||
.await;
|
||||
}
|
||||
debug_info!("{notice}");
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the first real user, grant them admin privileges except for guest
|
||||
// users
|
||||
// Note: the server user is generated first
|
||||
if !is_guest
|
||||
&& services.config.grant_admin_to_first_user
|
||||
&& let Ok(admin_room) = services.admin.get_admin_room().await
|
||||
&& services
|
||||
.state_cache
|
||||
.room_joined_count(&admin_room)
|
||||
.await
|
||||
.is_ok_and(is_equal_to!(1))
|
||||
{
|
||||
services
|
||||
.admin
|
||||
.make_user_admin(&user_id)
|
||||
.boxed()
|
||||
.await?;
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
|
||||
if body.appservice_info.is_none()
|
||||
&& !services.server.config.auto_join_rooms.is_empty()
|
||||
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
||||
{
|
||||
for room in &services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = services.alias.resolve(room).await else {
|
||||
error!(
|
||||
"Failed to resolve room alias to room ID when attempting to auto join \
|
||||
{room}, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
if !services
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), &room_id)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Skipping room {room} to automatically join as we have never joined before."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(room_server_name) = room.server_name() {
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
|
||||
match services
|
||||
.membership
|
||||
.join(
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
&body.appservice_info,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
| Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
error!(
|
||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
}
|
||||
|
||||
drop(state_lock);
|
||||
}
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,9 +419,13 @@ pub(crate) async fn check_registration_token_validity(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||
) -> Result<check_registration_token_validity::v1::Response> {
|
||||
let Some(reg_token) = services.globals.registration_token.clone() else {
|
||||
return Err!(Request(Forbidden("Server does not allow token registration")));
|
||||
};
|
||||
let tokens = services.globals.get_registration_tokens().await;
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response { valid: reg_token == body.token })
|
||||
if tokens.is_empty() {
|
||||
return Err!(Request(Forbidden("Server does not allow token registration")));
|
||||
}
|
||||
|
||||
let valid = tokens.contains(&body.token);
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response { valid })
|
||||
}
|
||||
|
||||
+112
-59
@@ -1,5 +1,11 @@
|
||||
use std::iter::once;
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::StreamExt;
|
||||
use futures::{
|
||||
FutureExt, StreamExt, TryFutureExt,
|
||||
future::try_join3,
|
||||
stream::{select_all, unfold},
|
||||
};
|
||||
use ruma::{
|
||||
EventId, RoomId, UInt, UserId,
|
||||
api::{
|
||||
@@ -12,12 +18,16 @@
|
||||
events::{TimelineEventType, relation::RelationType},
|
||||
};
|
||||
use tuwunel_core::{
|
||||
Result, at,
|
||||
Err, Error, Result, at, err,
|
||||
matrix::{
|
||||
event::{Event, RelationTypeEqual},
|
||||
pdu::PduCount,
|
||||
pdu::{PduCount, PduId},
|
||||
},
|
||||
utils::{
|
||||
BoolExt,
|
||||
result::FlatOk,
|
||||
stream::{ReadyExt, WidebandExt},
|
||||
},
|
||||
utils::{IterStream, ReadyExt, result::FlatOk, stream::WidebandExt},
|
||||
};
|
||||
use tuwunel_service::Services;
|
||||
|
||||
@@ -99,6 +109,12 @@ pub(crate) async fn get_relating_events_route(
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[tracing::instrument(
|
||||
name = "relations",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(room_id, target, from, to, dir, limit, recurse)
|
||||
)]
|
||||
async fn paginate_relations_with_filter(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
@@ -112,78 +128,115 @@ async fn paginate_relations_with_filter(
|
||||
recurse: bool,
|
||||
dir: Direction,
|
||||
) -> Result<get_relating_events::v1::Response> {
|
||||
let start: PduCount = from
|
||||
.map(str::parse)
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| match dir {
|
||||
| Direction::Forward => PduCount::min(),
|
||||
| Direction::Backward => PduCount::max(),
|
||||
});
|
||||
let from: Option<PduCount> = from.map(str::parse).transpose()?;
|
||||
|
||||
let to: Option<PduCount> = to.map(str::parse).flat_ok();
|
||||
|
||||
// Use limit or else 30, with maximum 100
|
||||
// Spec (v1.10) recommends depth of at least 3
|
||||
let max_depth: usize = if recurse { 3 } else { 0 };
|
||||
|
||||
let limit: usize = limit
|
||||
.map(TryInto::try_into)
|
||||
.flat_ok()
|
||||
.unwrap_or(30)
|
||||
.min(100);
|
||||
|
||||
// Spec (v1.10) recommends depth of at least 3
|
||||
let depth: u8 = if recurse { 3 } else { 1 };
|
||||
let target = services
|
||||
.timeline
|
||||
.get_pdu_id(target)
|
||||
.map_ok(PduId::from)
|
||||
.map_ok(Ok::<_, Error>);
|
||||
|
||||
let events: Vec<_> = services
|
||||
.pdu_metadata
|
||||
.get_relations(sender_user, room_id, target, start, limit, depth, dir)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter(|(_, pdu)| {
|
||||
filter_event_type
|
||||
.as_ref()
|
||||
.is_none_or(|kind| kind == pdu.kind())
|
||||
})
|
||||
.filter(|(_, pdu)| {
|
||||
filter_rel_type
|
||||
.as_ref()
|
||||
.is_none_or(|rel_type| rel_type.relation_type_equal(pdu))
|
||||
})
|
||||
.stream()
|
||||
.ready_take_while(|(count, _)| Some(*count) != to)
|
||||
.wide_filter_map(|item| visibility_filter(services, sender_user, item))
|
||||
.take(limit)
|
||||
.collect()
|
||||
.await;
|
||||
let visible = services
|
||||
.state_accessor
|
||||
.user_can_see_state_events(sender_user, room_id)
|
||||
.map(|visible| {
|
||||
visible.ok_or_else(|| err!(Request(Forbidden("You cannot view this room."))))
|
||||
});
|
||||
|
||||
let next_batch = match dir {
|
||||
| Direction::Forward => events.last(),
|
||||
| Direction::Backward => events.first(),
|
||||
let shortroomid = services.short.get_shortroomid(room_id);
|
||||
|
||||
let (shortroomid, target, ()) = try_join3(shortroomid, target, visible).await?;
|
||||
|
||||
let Ok(target) = target else {
|
||||
return Ok(get_relating_events::v1::Response::new(Vec::new()));
|
||||
};
|
||||
|
||||
if shortroomid != target.shortroomid {
|
||||
return Err!(Request(NotFound("Event not found in room.")));
|
||||
}
|
||||
.map(at!(0))
|
||||
.as_ref()
|
||||
.map(ToString::to_string);
|
||||
|
||||
if let PduCount::Backfilled(_) = target.count {
|
||||
return Ok(get_relating_events::v1::Response::new(Vec::new()));
|
||||
}
|
||||
|
||||
let fetch = |depth: usize, count: PduCount| {
|
||||
services
|
||||
.pdu_metadata
|
||||
.get_relations(shortroomid, count, from, dir, Some(sender_user))
|
||||
.map(move |(count, pdu)| (depth, count, pdu))
|
||||
.ready_filter(|(_, count, _)| matches!(count, PduCount::Normal(_)))
|
||||
.boxed()
|
||||
};
|
||||
|
||||
let events = unfold(select_all(once(fetch(0, target.count))), async |mut relations| {
|
||||
let (depth, count, pdu) = relations.next().await?;
|
||||
|
||||
if depth < max_depth {
|
||||
relations.push(fetch(depth.saturating_add(1), count));
|
||||
}
|
||||
|
||||
Some(((depth, count, pdu), relations))
|
||||
})
|
||||
.ready_take_while(|&(_, count, _)| Some(count) != to)
|
||||
.ready_filter(|(_, _, pdu)| {
|
||||
filter_event_type
|
||||
.as_ref()
|
||||
.is_none_or(|kind| kind == pdu.kind())
|
||||
})
|
||||
.ready_filter(|(_, _, pdu)| {
|
||||
filter_rel_type
|
||||
.as_ref()
|
||||
.is_none_or(|rel_type| rel_type.relation_type_equal(pdu))
|
||||
})
|
||||
.wide_filter_map(async |(depth, count, pdu)| {
|
||||
services
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
|
||||
.await
|
||||
.then_some((depth, count, pdu))
|
||||
})
|
||||
.take(limit)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
Ok(get_relating_events::v1::Response {
|
||||
next_batch,
|
||||
prev_batch: from.map(Into::into),
|
||||
recursion_depth: recurse.then_some(depth.into()),
|
||||
recursion_depth: max_depth
|
||||
.gt(&0)
|
||||
.then(|| events.iter().map(at!(0)))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.max()
|
||||
.map(TryInto::try_into)
|
||||
.transpose()?,
|
||||
|
||||
next_batch: events
|
||||
.last()
|
||||
.map(at!(1))
|
||||
.as_ref()
|
||||
.map(ToString::to_string),
|
||||
|
||||
prev_batch: events
|
||||
.first()
|
||||
.map(at!(1))
|
||||
.or(from)
|
||||
.as_ref()
|
||||
.map(ToString::to_string),
|
||||
|
||||
chunk: events
|
||||
.into_iter()
|
||||
.map(at!(1))
|
||||
.map(at!(2))
|
||||
.map(Event::into_format)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn visibility_filter<Pdu: Event>(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
item: (PduCount, Pdu),
|
||||
) -> Option<(PduCount, Pdu)> {
|
||||
let (_, pdu) = &item;
|
||||
|
||||
services
|
||||
.state_accessor
|
||||
.user_can_see_event(sender_user, pdu.room_id(), pdu.event_id())
|
||||
.await
|
||||
.then_some(item)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
.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();
|
||||
|
||||
@@ -230,8 +232,8 @@ async fn remote_room_summary_hierarchy_response(
|
||||
.iter()
|
||||
.map(|server| {
|
||||
services
|
||||
.sending
|
||||
.send_federation_request(server, request.clone())
|
||||
.federation
|
||||
.execute(server, request.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
+420
-185
@@ -1,42 +1,63 @@
|
||||
use std::cmp::max;
|
||||
|
||||
use axum::extract::State;
|
||||
use futures::StreamExt;
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, RoomId, RoomVersionId,
|
||||
api::client::room::upgrade_room,
|
||||
CanonicalJsonObject, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, UserId,
|
||||
api::client::room::upgrade_room::v3,
|
||||
events::{
|
||||
StateEventType, TimelineEventType,
|
||||
room::{
|
||||
create::PreviousRoom,
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
tombstone::RoomTombstoneEventContent,
|
||||
},
|
||||
},
|
||||
int,
|
||||
room_version_rules::RoomIdFormatVersion,
|
||||
room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
|
||||
};
|
||||
use serde_json::{
|
||||
Value as JsonValue, json,
|
||||
value::{to_raw_value, to_value},
|
||||
};
|
||||
use serde_json::{json, value::to_raw_value};
|
||||
use tuwunel_core::{
|
||||
Err, Result, err,
|
||||
Err, Result, debug_info, err, error, implement, info, is_equal_to, is_less_than,
|
||||
matrix::{Event, StateKey, pdu::PduBuilder, room_version},
|
||||
utils::{
|
||||
future::TryExtExt,
|
||||
stream::{IterStream, ReadyExt, WidebandExt},
|
||||
},
|
||||
};
|
||||
use tuwunel_service::{Services, rooms::timeline::RoomMutexGuard};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// Recommended transferable state events list from the spec
|
||||
const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
|
||||
StateEventType::RoomAvatar,
|
||||
//TODO: Upgrade Ruma
|
||||
const RECOMMENDED_TRANSFERABLE_STATE_EVENT_TYPES: &[StateEventType; 9] = &[
|
||||
StateEventType::RoomServerAcl,
|
||||
StateEventType::RoomEncryption,
|
||||
StateEventType::RoomName,
|
||||
StateEventType::RoomAvatar,
|
||||
StateEventType::RoomTopic,
|
||||
StateEventType::RoomGuestAccess,
|
||||
StateEventType::RoomHistoryVisibility,
|
||||
StateEventType::RoomJoinRules,
|
||||
StateEventType::RoomName,
|
||||
StateEventType::RoomPowerLevels,
|
||||
StateEventType::RoomServerAcl,
|
||||
StateEventType::RoomTopic,
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RoomUpgradeContext<'a> {
|
||||
services: &'a Services,
|
||||
sender_user: &'a UserId,
|
||||
old_room_id: &'a RoomId,
|
||||
old_state_lock: &'a RoomMutexGuard,
|
||||
new_room_id: &'a RoomId,
|
||||
new_state_lock: &'a RoomMutexGuard,
|
||||
new_version_rules: &'a RoomVersionRules,
|
||||
additional_creators: &'a [OwnedUserId],
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/rooms/{roomId}/upgrade`
|
||||
///
|
||||
/// Upgrades the room.
|
||||
@@ -47,117 +68,227 @@
|
||||
/// - Transfers some state events
|
||||
/// - Moves local aliases
|
||||
/// - Modifies old room power levels to prevent users from speaking
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub(crate) async fn upgrade_room_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<upgrade_room::v3::Request>,
|
||||
) -> Result<upgrade_room::v3::Response> {
|
||||
debug_assert!(
|
||||
TRANSFERABLE_STATE_EVENTS.is_sorted(),
|
||||
"TRANSFERABLE_STATE_EVENTS is not sorted"
|
||||
);
|
||||
|
||||
body: Ruma<v3::Request>,
|
||||
) -> Result<v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
let new_version = &body.new_version;
|
||||
let version_rules = room_version::rules(new_version)?;
|
||||
|
||||
if !services
|
||||
.server
|
||||
.supported_room_version(&body.new_version)
|
||||
.supported_room_version(new_version)
|
||||
{
|
||||
return Err!(Request(UnsupportedRoomVersion(
|
||||
"This server does not support that room version.",
|
||||
)));
|
||||
}
|
||||
|
||||
if matches!(body.new_version, RoomVersionId::V12) {
|
||||
return Err!(Request(UnsupportedRoomVersion(
|
||||
"Upgrading to version 12 is still under development.",
|
||||
)));
|
||||
let old_room_id = &body.room_id;
|
||||
let old_state_lock = services.state.mutex.lock(old_room_id).await;
|
||||
|
||||
if !services
|
||||
.state_accessor
|
||||
.user_can_tombstone(old_room_id, sender_user, &old_state_lock)
|
||||
.await
|
||||
{
|
||||
return Err!(Request(Forbidden("You are not permitted to upgrade the room.")));
|
||||
}
|
||||
|
||||
let room_version_rules = room_version::rules(&body.new_version)?;
|
||||
let room_id_format = &room_version_rules.room_id_format;
|
||||
assert!(*room_id_format == RoomIdFormatVersion::V1, "TODO");
|
||||
|
||||
// Create a replacement room
|
||||
let replacement_room = RoomId::new_v1(services.globals.server_name());
|
||||
|
||||
let _short_id = services
|
||||
.short
|
||||
.get_or_create_shortroomid(&replacement_room)
|
||||
.await;
|
||||
|
||||
let state_lock = services.state.mutex.lock(&body.room_id).await;
|
||||
|
||||
// Send a m.room.tombstone event to the old room to indicate that it is not
|
||||
// intended to be used any further Fail if the sender does not have the required
|
||||
// permissions
|
||||
let tombstone_event_id = services
|
||||
let latest_event = services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
|
||||
body: "This room has been replaced".to_owned(),
|
||||
replacement_room: replacement_room.clone(),
|
||||
}),
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
.latest_pdu_in_room(old_room_id)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
// Change lock to replacement room
|
||||
drop(state_lock);
|
||||
let state_lock = services.state.mutex.lock(&replacement_room).await;
|
||||
let predecessor = PreviousRoom {
|
||||
room_id: old_room_id.to_owned(),
|
||||
event_id: latest_event
|
||||
.as_ref()
|
||||
.map(Event::event_id)
|
||||
.map(ToOwned::to_owned),
|
||||
};
|
||||
|
||||
debug_info!(
|
||||
%sender_user,
|
||||
%old_room_id,
|
||||
last_event = ?predecessor.event_id,
|
||||
?new_version,
|
||||
"Attempting upgrade of room..."
|
||||
);
|
||||
|
||||
let id_format = version_rules.room_id_format;
|
||||
let (replacement_room, state_lock) = match id_format {
|
||||
| RoomIdFormatVersion::V2 =>
|
||||
upgrade_room_create(
|
||||
&services,
|
||||
sender_user,
|
||||
old_room_id,
|
||||
new_version,
|
||||
&version_rules,
|
||||
predecessor,
|
||||
body.additional_creators.clone(),
|
||||
)
|
||||
.await,
|
||||
|
||||
| RoomIdFormatVersion::V1 =>
|
||||
upgrade_room_create_legacy(
|
||||
&services,
|
||||
sender_user,
|
||||
old_room_id,
|
||||
new_version,
|
||||
&version_rules,
|
||||
predecessor,
|
||||
)
|
||||
.await,
|
||||
}
|
||||
.inspect_err(|e| error!(?body, "Upgrade m.room.create event failed: {e}"))?;
|
||||
|
||||
let context = RoomUpgradeContext {
|
||||
services: &services,
|
||||
sender_user,
|
||||
old_room_id: &body.room_id,
|
||||
old_state_lock: &old_state_lock,
|
||||
new_room_id: &replacement_room,
|
||||
new_state_lock: &state_lock,
|
||||
new_version_rules: &version_rules,
|
||||
additional_creators: &body.additional_creators,
|
||||
};
|
||||
|
||||
if let Err(e) = context.transfer_room().await {
|
||||
error!(?e, ?context, "Room upgrade failed. Cleaning up incomplete room...");
|
||||
|
||||
if let Err(e) = services
|
||||
.delete
|
||||
.delete_room(&replacement_room, false, state_lock)
|
||||
.await
|
||||
{
|
||||
error!("Additional errors while deleting incomplete room: {e}");
|
||||
}
|
||||
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
info!(
|
||||
old_room_id = %context.old_room_id,
|
||||
new_room_id = %context.new_room_id,
|
||||
upgraded_by = %sender_user,
|
||||
"Room upgraded",
|
||||
);
|
||||
|
||||
Ok(v3::Response { replacement_room })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info")]
|
||||
async fn upgrade_room_create(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
old_room_id: &RoomId,
|
||||
new_version: &RoomVersionId,
|
||||
version_rules: &RoomVersionRules,
|
||||
predecessor: PreviousRoom,
|
||||
mut additional_creators: Vec<OwnedUserId>,
|
||||
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
|
||||
// Get the old room creation event
|
||||
let mut create_event_content: CanonicalJsonObject = services
|
||||
let mut content: CanonicalJsonObject = services
|
||||
.state_accessor
|
||||
.room_state_get_content(&body.room_id, &StateEventType::RoomCreate, "")
|
||||
.room_state_get_content(old_room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.map_err(|_| err!(Database("Found room without m.room.create event.")))?;
|
||||
|
||||
// Use the m.room.tombstone event as the predecessor
|
||||
let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
|
||||
body.room_id.clone(),
|
||||
Some(tombstone_event_id),
|
||||
));
|
||||
content.remove("creator");
|
||||
content.insert("predecessor".into(), json!(predecessor).try_into()?);
|
||||
content.insert("room_version".into(), json!(new_version).try_into()?);
|
||||
|
||||
// Send a m.room.create event containing a predecessor field and the applicable
|
||||
// room_version
|
||||
if version_rules
|
||||
.authorization
|
||||
.additional_room_creators
|
||||
{
|
||||
use RoomVersionId::*;
|
||||
match body.new_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
|
||||
create_event_content.insert(
|
||||
"creator".into(),
|
||||
json!(&sender_user).try_into().map_err(|e| {
|
||||
err!(Request(BadJson(error!("Error forming creation event: {e}"))))
|
||||
})?,
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
// "creator" key no longer exists in V11+ rooms
|
||||
create_event_content.remove("creator");
|
||||
},
|
||||
additional_creators.sort();
|
||||
additional_creators.dedup();
|
||||
content.remove("additional_creators");
|
||||
if !additional_creators.is_empty() {
|
||||
content.insert("additional_creators".into(), json!(additional_creators).try_into()?);
|
||||
}
|
||||
}
|
||||
|
||||
create_event_content.insert(
|
||||
"room_version".into(),
|
||||
json!(&body.new_version)
|
||||
.try_into()
|
||||
.map_err(|_| err!(Request(BadJson("Error forming creation event"))))?,
|
||||
);
|
||||
create_event_content.insert(
|
||||
"predecessor".into(),
|
||||
json!(predecessor)
|
||||
.try_into()
|
||||
.map_err(|_| err!(Request(BadJson("Error forming creation event"))))?,
|
||||
);
|
||||
// Validate creation event content
|
||||
let raw_content = to_raw_value(&content)?;
|
||||
if let Err(e) = serde_json::from_str::<CanonicalJsonObject>(raw_content.get()) {
|
||||
return Err!(Request(BadJson("Error forming creation event: {e}")));
|
||||
}
|
||||
|
||||
let room_id = ruma::room_id!("!thiswillbereplaced").to_owned();
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
let create_event_id = services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&content)?,
|
||||
state_key: Some(StateKey::new()),
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
// The real room_id is now the event_id.
|
||||
let room_id = OwnedRoomId::from_parts('!', create_event_id.localpart(), None)?;
|
||||
let state_lock = services.state.mutex.lock(&room_id).await;
|
||||
|
||||
Ok((room_id, state_lock))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info")]
|
||||
async fn upgrade_room_create_legacy(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
old_room_id: &RoomId,
|
||||
new_version: &RoomVersionId,
|
||||
version_rules: &RoomVersionRules,
|
||||
predecessor: PreviousRoom,
|
||||
) -> Result<(OwnedRoomId, RoomMutexGuard)> {
|
||||
// Create a replacement room
|
||||
let new_room_id = RoomId::new_v1(services.globals.server_name());
|
||||
let state_lock = services.state.mutex.lock(&new_room_id).await;
|
||||
let _short_id = services
|
||||
.short
|
||||
.get_or_create_shortroomid(&new_room_id)
|
||||
.await;
|
||||
|
||||
// Get the old room creation event
|
||||
let mut content: CanonicalJsonObject = services
|
||||
.state_accessor
|
||||
.room_state_get_content(old_room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.map_err(|_| err!(Database("Found room without m.room.create event.")))?;
|
||||
|
||||
// Send a m.room.create event containing a predecessor field and the applicable
|
||||
// room_version. "creator" key no longer exists in V11+ rooms.
|
||||
{
|
||||
use RoomVersionId::*;
|
||||
match new_version {
|
||||
| V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
|
||||
content.insert("creator".into(), json!(&sender_user).try_into()?),
|
||||
| _ => content.remove("creator"),
|
||||
}
|
||||
};
|
||||
|
||||
content.insert("predecessor".into(), json!(predecessor).try_into()?);
|
||||
content.insert("room_version".into(), json!(new_version).try_into()?);
|
||||
|
||||
// Validate creation event content
|
||||
if serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&create_event_content)?.get())
|
||||
.is_err()
|
||||
{
|
||||
return Err!(Request(BadJson("Error forming creation event")));
|
||||
let raw_content = to_raw_value(&content)?;
|
||||
if let Err(e) = serde_json::from_str::<CanonicalJsonObject>(raw_content.get()) {
|
||||
return Err!(Request(BadJson("Error forming creation event: {e}")));
|
||||
}
|
||||
|
||||
services
|
||||
@@ -165,125 +296,229 @@ pub(crate) async fn upgrade_room_route(
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomCreate,
|
||||
content: to_raw_value(&create_event_content)?,
|
||||
unsigned: None,
|
||||
content: to_raw_value(&content)?,
|
||||
state_key: Some(StateKey::new()),
|
||||
redacts: None,
|
||||
timestamp: None,
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&new_room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Join the new room
|
||||
services
|
||||
Ok((new_room_id, state_lock))
|
||||
}
|
||||
|
||||
#[implement(RoomUpgradeContext, params = "<'_>")]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
async fn transfer_room(&self) -> Result {
|
||||
self.move_joined_member().await?;
|
||||
|
||||
self.move_state_events().await?;
|
||||
|
||||
self.move_local_aliases().await?;
|
||||
|
||||
self.tombstone_old_room().await?;
|
||||
|
||||
// After commitment to the tombstone above no more errors can propagate.
|
||||
self.lockdown_old_room()
|
||||
.await
|
||||
.inspect_err(|e| error!(?self, "Failed to lockdown old room: {e}"))
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Join the new room
|
||||
#[implement(RoomUpgradeContext, params = "<'_>")]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
async fn move_joined_member(&self) -> Result<OwnedEventId> {
|
||||
let old_content: RoomMemberEventContent = self
|
||||
.services
|
||||
.state_accessor
|
||||
.room_state_get_content(
|
||||
self.old_room_id,
|
||||
&StateEventType::RoomMember,
|
||||
self.sender_user.as_str(),
|
||||
)
|
||||
.inspect_err(|e| error!(?self, "Missing room member event: {e}"))
|
||||
.await?;
|
||||
|
||||
self.services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: services.users.displayname(sender_user).await.ok(),
|
||||
avatar_url: services.users.avatar_url(sender_user).await.ok(),
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
blurhash: services.users.blurhash(sender_user).await.ok(),
|
||||
reason: None,
|
||||
join_authorized_via_users_server: None,
|
||||
})?,
|
||||
unsigned: None,
|
||||
state_key: Some(sender_user.as_str().into()),
|
||||
redacts: None,
|
||||
timestamp: None,
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&state_lock,
|
||||
PduBuilder::state(self.sender_user.as_str(), &RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
..old_content
|
||||
}),
|
||||
self.sender_user,
|
||||
self.new_room_id,
|
||||
self.new_state_lock,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
|
||||
// Replicate transferable state events to the new room
|
||||
for event_type in TRANSFERABLE_STATE_EVENTS {
|
||||
let event_content = match services
|
||||
.state_accessor
|
||||
.room_state_get(&body.room_id, event_type, "")
|
||||
.await
|
||||
// Replicate transferable state events to the new room
|
||||
#[implement(RoomUpgradeContext, params = "<'_>")]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
async fn move_state_events(&self) -> Result {
|
||||
RECOMMENDED_TRANSFERABLE_STATE_EVENT_TYPES
|
||||
.iter()
|
||||
.rev()
|
||||
.stream()
|
||||
.wide_filter_map(|event_type| {
|
||||
self.services
|
||||
.state_accessor
|
||||
.room_state_get(self.old_room_id, event_type, "")
|
||||
.ok()
|
||||
})
|
||||
.map(Ok)
|
||||
.try_for_each(async |event| {
|
||||
self.services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
self.rebuild_state_event(&event)?,
|
||||
self.sender_user,
|
||||
self.new_room_id,
|
||||
self.new_state_lock,
|
||||
)
|
||||
.inspect_err(|e| {
|
||||
error!(?event, ?self, "Failed to transfer state on upgrade: {e}");
|
||||
})
|
||||
.map_ok(|_| ())
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[implement(RoomUpgradeContext, params = "<'_>")]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn rebuild_state_event<Pdu: Event>(&self, event: &Pdu) -> Result<PduBuilder> {
|
||||
let content = match event.kind() {
|
||||
| TimelineEventType::RoomPowerLevels
|
||||
if self
|
||||
.new_version_rules
|
||||
.authorization
|
||||
.explicitly_privilege_room_creators =>
|
||||
{
|
||||
| Ok(v) => v.content().to_owned(),
|
||||
| Err(_) => continue, // Skipping missing events.
|
||||
};
|
||||
let mut content = event.get_content_as_value();
|
||||
|
||||
services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: event_type.to_string().into(),
|
||||
content: event_content,
|
||||
state_key: Some(StateKey::new()),
|
||||
..Default::default()
|
||||
},
|
||||
sender_user,
|
||||
&replacement_room,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if let Some(users) = content
|
||||
.get_mut("users")
|
||||
.and_then(JsonValue::as_object_mut)
|
||||
{
|
||||
users.retain(|user_id, _pl| {
|
||||
!self
|
||||
.additional_creators
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.map(UserId::as_str)
|
||||
.any(is_equal_to!(user_id.as_str()))
|
||||
&& self.sender_user.as_str() != user_id.as_str()
|
||||
});
|
||||
}
|
||||
|
||||
// Moves any local aliases to the new room
|
||||
let mut local_aliases = services
|
||||
if content["events"]["m.room.tombstone"]
|
||||
.as_i64()
|
||||
.is_none_or(is_less_than!(150))
|
||||
{
|
||||
content["events"]["m.room.tombstone"] = to_value(150)?;
|
||||
}
|
||||
|
||||
to_raw_value(&content)?
|
||||
},
|
||||
| _ => to_raw_value(event.content())?,
|
||||
};
|
||||
|
||||
Ok(PduBuilder {
|
||||
content,
|
||||
event_type: event.kind().clone(),
|
||||
state_key: event.state_key().map(Into::into),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
// Moves any local aliases to the new room
|
||||
#[implement(RoomUpgradeContext, params = "<'_>")]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
async fn move_local_aliases(&self) -> Result {
|
||||
self.services
|
||||
.alias
|
||||
.local_aliases_for_room(&body.room_id)
|
||||
.boxed();
|
||||
.local_aliases_for_room(self.old_room_id)
|
||||
.filter_map(|alias| {
|
||||
self.services
|
||||
.alias
|
||||
.remove_alias(alias, self.sender_user)
|
||||
.inspect_err(move |e| error!(?alias, ?self, "Failed to remove alias: {e}"))
|
||||
.map_ok(move |()| alias)
|
||||
.ok()
|
||||
})
|
||||
.ready_for_each(|alias| {
|
||||
self.services
|
||||
.alias
|
||||
.set_alias(alias, self.new_room_id, self.sender_user)
|
||||
.inspect_err(|e| error!(?self, "Failed to add alias: {e}"))
|
||||
.ok();
|
||||
})
|
||||
.map(Ok)
|
||||
.await
|
||||
}
|
||||
|
||||
while let Some(alias) = local_aliases.next().await {
|
||||
services
|
||||
.alias
|
||||
.remove_alias(alias, sender_user)
|
||||
.await?;
|
||||
|
||||
services
|
||||
.alias
|
||||
.set_alias(alias, &replacement_room, sender_user)?;
|
||||
}
|
||||
// Send a m.room.tombstone event to the old room to indicate that it is not
|
||||
// intended to be used any further Fail if the sender does not have the required
|
||||
// permissions.
|
||||
#[implement(RoomUpgradeContext, params = "<'_>")]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
async fn tombstone_old_room(&self) -> Result<OwnedEventId> {
|
||||
self.services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(StateKey::new(), &RoomTombstoneEventContent {
|
||||
body: "This room has been upgraded.".to_owned(),
|
||||
replacement_room: self.new_room_id.to_owned(),
|
||||
}),
|
||||
self.sender_user,
|
||||
self.old_room_id,
|
||||
self.old_state_lock,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Modify the power levels in the old room to prevent sending of events and
|
||||
// inviting new users. Though a Result is returned, the callsite above treats it
|
||||
// as infallible because the tombstone represents the commitment.
|
||||
#[implement(RoomUpgradeContext, params = "<'_>")]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
async fn lockdown_old_room(&self) -> Result<OwnedEventId> {
|
||||
// Get the old room power levels
|
||||
let power_levels_event_content: RoomPowerLevelsEventContent = services
|
||||
let old_content: RoomPowerLevelsEventContent = self
|
||||
.services
|
||||
.state_accessor
|
||||
.room_state_get_content(&body.room_id, &StateEventType::RoomPowerLevels, "")
|
||||
.room_state_get_content(self.old_room_id, &StateEventType::RoomPowerLevels, "")
|
||||
.await
|
||||
.map_err(|_| err!(Database("Found room without m.room.power_levels event.")))?;
|
||||
|
||||
// Setting events_default and invite to the greater of 50 and users_default + 1
|
||||
let new_level = max(
|
||||
int!(50),
|
||||
power_levels_event_content
|
||||
.users_default
|
||||
.checked_add(int!(1))
|
||||
.ok_or_else(|| {
|
||||
err!(Request(BadJson("users_default power levels event content is not valid")))
|
||||
})?,
|
||||
);
|
||||
let old_users_default = old_content
|
||||
.users_default
|
||||
.checked_add(int!(1))
|
||||
.ok_or_else(|| {
|
||||
err!(Request(BadJson("users_default power levels event content is not valid")))
|
||||
})?;
|
||||
|
||||
// Modify the power levels in the old room to prevent sending of events and
|
||||
// inviting new users
|
||||
services
|
||||
// Setting events_default and invite to the greater of 50 and users_default + 1
|
||||
let new_level = max(int!(50), old_users_default);
|
||||
|
||||
self.services
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder::state(StateKey::new(), &RoomPowerLevelsEventContent {
|
||||
events_default: new_level,
|
||||
invite: new_level,
|
||||
..power_levels_event_content
|
||||
..old_content
|
||||
}),
|
||||
sender_user,
|
||||
&body.room_id,
|
||||
&state_lock,
|
||||
self.sender_user,
|
||||
self.old_room_id,
|
||||
self.old_state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
// Return the replacement room id
|
||||
Ok(upgrade_room::v3::Response { replacement_room })
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -51,7 +51,7 @@ pub(super) async fn ldap_login(
|
||||
if !services.users.exists(lowercased_user_id).await {
|
||||
services
|
||||
.users
|
||||
.create(lowercased_user_id, Some("*"), Some("ldap"))
|
||||
.full_register(lowercased_user_id, Some("*"), Some("ldap"), None, false, false)
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ pub(crate) async fn logout_route(
|
||||
) -> Result<logout::v3::Response> {
|
||||
services
|
||||
.users
|
||||
.remove_device(body.sender_user(), body.sender_device())
|
||||
.remove_device(body.sender_user(), body.sender_device()?)
|
||||
.await;
|
||||
|
||||
Ok(logout::v3::Response::new())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod appservice;
|
||||
mod jwt;
|
||||
pub(crate) mod jwt;
|
||||
mod ldap;
|
||||
mod logout;
|
||||
mod password;
|
||||
@@ -21,7 +21,7 @@
|
||||
v3::{DiscoveryInfo, HomeserverInfo, LoginInfo},
|
||||
},
|
||||
};
|
||||
use tuwunel_core::{Err, Result, info, utils, utils::stream::ReadyExt};
|
||||
use tuwunel_core::{Err, Result, info, utils::stream::ReadyExt};
|
||||
use tuwunel_service::users::device::generate_refresh_token;
|
||||
|
||||
use self::{ldap::ldap_login, password::password_login};
|
||||
@@ -30,7 +30,7 @@
|
||||
refresh::refresh_token_route,
|
||||
token::login_token_route,
|
||||
};
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
|
||||
use super::TOKEN_LENGTH;
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /_matrix/client/v3/login`
|
||||
@@ -97,43 +97,39 @@ pub(crate) async fn login_route(
|
||||
// Generate a new refresh_token if requested by client
|
||||
let refresh_token = expires_in.is_some().then(generate_refresh_token);
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = body
|
||||
.device_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Determine if device_id was provided and exists in the db for this user
|
||||
let device_exists = services
|
||||
.users
|
||||
.all_device_ids(&user_id)
|
||||
.ready_any(|v| v == device_id)
|
||||
.await;
|
||||
|
||||
if !device_exists {
|
||||
services
|
||||
let device_id = if let Some(device_id) = &body.device_id
|
||||
&& services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
(&access_token, expires_in),
|
||||
refresh_token.as_deref(),
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
.all_device_ids(&user_id)
|
||||
.ready_any(|v| v == device_id)
|
||||
.await
|
||||
{
|
||||
services
|
||||
.users
|
||||
.set_access_token(
|
||||
&user_id,
|
||||
&device_id,
|
||||
device_id,
|
||||
&access_token,
|
||||
expires_in,
|
||||
refresh_token.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
device_id.clone()
|
||||
} else {
|
||||
services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
body.device_id.as_deref(),
|
||||
(Some(&access_token), expires_in),
|
||||
refresh_token.as_deref(),
|
||||
body.initial_device_display_name.as_deref(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
info!("{user_id} logged in");
|
||||
|
||||
|
||||
@@ -50,7 +50,8 @@ pub(crate) async fn login_token_route(
|
||||
|
||||
// This route SHOULD have UIA
|
||||
// TODO: How do we make only UIA sessions that have not been used before valid?
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
let sender_user = body.sender_user();
|
||||
let sender_device = body.sender_device()?;
|
||||
|
||||
let password_flow = uiaa::AuthFlow { stages: vec![uiaa::AuthType::Password] };
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
+507
-358
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user