mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-10 09:45:39 +00:00
Compare commits
387 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0069cc100 | ||
|
|
556e78214a | ||
|
|
8fff7ea706 | ||
|
|
f712c0cefb | ||
|
|
26d103d314 | ||
|
|
0688a96c37 | ||
|
|
eb73d8c669 | ||
|
|
20a54aacd6 | ||
|
|
81cd677b4e | ||
|
|
73da353e52 | ||
|
|
d5677b6ae7 | ||
|
|
01a77f8a71 | ||
|
|
ea03a50e21 | ||
|
|
25d44cad31 | ||
|
|
91519959ed | ||
|
|
2e31bcc213 | ||
|
|
305dfc3b42 | ||
|
|
65fbb80145 | ||
|
|
f1d90e5df6 | ||
|
|
74b29ce067 | ||
|
|
0e7c3cb338 | ||
|
|
14a3471fcb | ||
|
|
c834e86e67 | ||
|
|
b4f0a8a8b5 | ||
|
|
9bb90213e1 | ||
|
|
fcdf1463ef | ||
|
|
88d038ffec | ||
|
|
4b4c0952a2 | ||
|
|
016270b33b | ||
|
|
d2063013b4 | ||
|
|
03ba9bde29 | ||
|
|
1287a86c05 | ||
|
|
8210e8c42e | ||
|
|
adf0bfd894 | ||
|
|
6b843ec4dd | ||
|
|
ac02078395 | ||
|
|
b9d38fd3ba | ||
|
|
1b2c8236fb | ||
|
|
d7b8af627c | ||
|
|
130aae8758 | ||
|
|
4741a76896 | ||
|
|
5bfb62e979 | ||
|
|
cb03654dc1 | ||
|
|
f0557e3303 | ||
|
|
f52acd9cdf | ||
|
|
eae41fc411 | ||
|
|
c3c91e9d80 | ||
|
|
a8de5d1e60 | ||
|
|
7688d67870 | ||
|
|
89d7d48324 | ||
|
|
b525031a25 | ||
|
|
67f4285504 | ||
|
|
b65f05ce19 | ||
|
|
db2c9f28b6 | ||
|
|
fc1b8326e6 | ||
|
|
6e50b07bf5 | ||
|
|
9a3c52aa75 | ||
|
|
ccf9f95cc9 | ||
|
|
0524e6ed52 | ||
|
|
7f5b59afbb | ||
|
|
ab5db37851 | ||
|
|
c0c7f23a05 | ||
|
|
14ec41c211 | ||
|
|
2230bc7339 | ||
|
|
0ebabba971 | ||
|
|
3ed561cb31 | ||
|
|
a061644b2d | ||
|
|
82ac6b01b2 | ||
|
|
97ddb2ce87 | ||
|
|
a04ff7d4af | ||
|
|
10dfbf6420 | ||
|
|
d10bc67c9d | ||
|
|
197a02bf8d | ||
|
|
8103bd7310 | ||
|
|
81487e3f07 | ||
|
|
bfbb29dded | ||
|
|
1cc7cf54a7 | ||
|
|
40e4019f7f | ||
|
|
176d95c2a8 | ||
|
|
8d32fb1445 | ||
|
|
82a3b73774 | ||
|
|
1f19356693 | ||
|
|
3ada847570 | ||
|
|
0bade5317f | ||
|
|
c2267d4c03 | ||
|
|
aebae11c82 | ||
|
|
f871d8fd4e | ||
|
|
4a68e28c71 | ||
|
|
6e59135a7d | ||
|
|
0e74ade7d7 | ||
|
|
e4aa20ebeb | ||
|
|
427aa4645c | ||
|
|
73718a1208 | ||
|
|
0e3d192ad2 | ||
|
|
76a4d8aa4c | ||
|
|
9bb52cb3ec | ||
|
|
dd49b3c3a1 | ||
|
|
b2e56777af | ||
|
|
f32380772f | ||
|
|
8428f43c78 | ||
|
|
3af153f5ae | ||
|
|
38238c309f | ||
|
|
0857fe7907 | ||
|
|
c738c119f8 | ||
|
|
c1227340b3 | ||
|
|
bf10ff65a4 | ||
|
|
b781771a9b | ||
|
|
df8ba04e31 | ||
|
|
19926ba00d | ||
|
|
893cc50570 | ||
|
|
c9fbbdce1c | ||
|
|
732e8b82aa | ||
|
|
282c2feca8 | ||
|
|
919735b4ce | ||
|
|
2e83e56a07 | ||
|
|
ff7dfec74c | ||
|
|
84290bd668 | ||
|
|
b29a8791de | ||
|
|
83220b43a2 | ||
|
|
4ea7af5780 | ||
|
|
79fb8091dc | ||
|
|
f6fa2a4f65 | ||
|
|
b63937af0b | ||
|
|
3c4e325036 | ||
|
|
023fb41c49 | ||
|
|
9a5f1dac57 | ||
|
|
173ff26eb6 | ||
|
|
45e3fdba69 | ||
|
|
9f359e0550 | ||
|
|
ffdf47d1ea | ||
|
|
1af65e695d | ||
|
|
b1886583d9 | ||
|
|
f11103b43b | ||
|
|
9b096cc67b | ||
|
|
1ac72ab914 | ||
|
|
f0533e07ef | ||
|
|
68f42f5a2f | ||
|
|
884cbab135 | ||
|
|
4aead5de7a | ||
|
|
aef25ea1f7 | ||
|
|
1a4736d40b | ||
|
|
f09e0dc137 | ||
|
|
de79b66cea | ||
|
|
95ca9d00a2 | ||
|
|
887496d040 | ||
|
|
c2586737ae | ||
|
|
7d2f510cc3 | ||
|
|
102bd1b4a6 | ||
|
|
89ab687f16 | ||
|
|
1108235c63 | ||
|
|
90d9a997a5 | ||
|
|
5fe5ab279c | ||
|
|
f1d1366129 | ||
|
|
ba48758b89 | ||
|
|
9df5265c00 | ||
|
|
ee52d2f751 | ||
|
|
53fe2362fc | ||
|
|
38ab1083e3 | ||
|
|
050841a871 | ||
|
|
4521e93d04 | ||
|
|
0f3d43153b | ||
|
|
e5eccb3a0c | ||
|
|
68cbf19154 | ||
|
|
2ab427fe99 | ||
|
|
02081b66c4 | ||
|
|
b3fc8516ed | ||
|
|
9e51525c25 | ||
|
|
14039d9df4 | ||
|
|
eed8a2a801 | ||
|
|
c3a0d28309 | ||
|
|
6d1144bb69 | ||
|
|
2e45cb281a | ||
|
|
0baa57f5d9 | ||
|
|
faa2b95c84 | ||
|
|
dd1d8fa760 | ||
|
|
f4cfc77a57 | ||
|
|
b8b93a2e86 | ||
|
|
29d69b7688 | ||
|
|
bd07fb61e0 | ||
|
|
a41a60ef07 | ||
|
|
ec7a9ab726 | ||
|
|
25f598ce6c | ||
|
|
dbcb3be0ab | ||
|
|
a537462d51 | ||
|
|
d2aef071bc | ||
|
|
d68b11e8ff | ||
|
|
9cf5b0926e | ||
|
|
ff0b57c89c | ||
|
|
b94045a468 | ||
|
|
3122648767 | ||
|
|
3f5349ad76 | ||
|
|
27dcf213f1 | ||
|
|
a1b526b3b7 | ||
|
|
dc614e11d6 | ||
|
|
c5569b4c6e | ||
|
|
71a1285c7b | ||
|
|
abdda6cf32 | ||
|
|
4d21f9d962 | ||
|
|
1013fe5a42 | ||
|
|
f31b7b9420 | ||
|
|
e5e358cc68 | ||
|
|
50bc7cc005 | ||
|
|
445015e9ea | ||
|
|
7a38c12e5d | ||
|
|
2a77951152 | ||
|
|
0256c27363 | ||
|
|
826edc0a3a | ||
|
|
a5043a38e1 | ||
|
|
bfd471a863 | ||
|
|
3981e77ec6 | ||
|
|
81bf4b7150 | ||
|
|
b8ec763a7c | ||
|
|
003d4edbfa | ||
|
|
4f0006d18a | ||
|
|
b822e3a94c | ||
|
|
68fffe8e96 | ||
|
|
7328ed7509 | ||
|
|
6ccf578437 | ||
|
|
8a1848a814 | ||
|
|
b4cd8e9140 | ||
|
|
a08f90b161 | ||
|
|
207979579c | ||
|
|
68b96026ec | ||
|
|
30beb20230 | ||
|
|
19e7779693 | ||
|
|
6269822613 | ||
|
|
0877ee6191 | ||
|
|
a37b2b9e64 | ||
|
|
29fe960efa | ||
|
|
6bf2e73830 | ||
|
|
630760b5da | ||
|
|
61e7f1e614 | ||
|
|
7ebed7aa3e | ||
|
|
ad3eeaf4c1 | ||
|
|
5215fbe695 | ||
|
|
dc9fe657d5 | ||
|
|
1c7c5bc09c | ||
|
|
32161801ed | ||
|
|
71bdcb958a | ||
|
|
d3db0ad4e2 | ||
|
|
e098448b9d | ||
|
|
d49507bc21 | ||
|
|
cb73ae3732 | ||
|
|
06bec40591 | ||
|
|
9a7ba94ccf | ||
|
|
2990c30ac9 | ||
|
|
d9c575d96f | ||
|
|
c32406aa0e | ||
|
|
03d12cb44e | ||
|
|
bef7dbd1cb | ||
|
|
08577873b4 | ||
|
|
a3931b0f1f | ||
|
|
ba2f22b5d3 | ||
|
|
0914aaa1b6 | ||
|
|
f3427afc7f | ||
|
|
9aa372d83b | ||
|
|
5893901a75 | ||
|
|
8ba9b33a95 | ||
|
|
70047ff26d | ||
|
|
1d57e14dc0 | ||
|
|
5d81203277 | ||
|
|
ad39a34c16 | ||
|
|
a007338b34 | ||
|
|
3d1507e6dd | ||
|
|
4cb7c0b982 | ||
|
|
0c34cf95ce | ||
|
|
17cc02ff99 | ||
|
|
c0f8253fc5 | ||
|
|
0fd0a5d73c | ||
|
|
4e6fc2f2df | ||
|
|
a6742ce8a7 | ||
|
|
188dea13e0 | ||
|
|
a7fe434086 | ||
|
|
eb8dd9cb44 | ||
|
|
474d50d10c | ||
|
|
2e732c711c | ||
|
|
981ec51ec0 | ||
|
|
2dd5cf8c68 | ||
|
|
74832bdc47 | ||
|
|
fdc9a9a1b8 | ||
|
|
1f3a9a40e5 | ||
|
|
362649ff87 | ||
|
|
4aeec78ab4 | ||
|
|
9bfa89a555 | ||
|
|
6c1434c165 | ||
|
|
ae1a4fd283 | ||
|
|
9eb0784f6f | ||
|
|
8bffcfe82b | ||
|
|
6ef4781050 | ||
|
|
302592f219 | ||
|
|
7cd72d8447 | ||
|
|
4389e08686 | ||
|
|
91064fe873 | ||
|
|
004354353a | ||
|
|
c64a507691 | ||
|
|
81d2078cdb | ||
|
|
f5864afb52 | ||
|
|
9a63e7cc9b | ||
|
|
296d7c58ee | ||
|
|
a8446f910a | ||
|
|
a063a6d088 | ||
|
|
5069c88f77 | ||
|
|
53974320e5 | ||
|
|
1c6ef66e3e | ||
|
|
ffb63c9c8d | ||
|
|
de6b296eb5 | ||
|
|
4c11c9f048 | ||
|
|
6074298426 | ||
|
|
6e9f68bf81 | ||
|
|
edd67a102a | ||
|
|
434b5118cc | ||
|
|
4185a33747 | ||
|
|
829307c83b | ||
|
|
2bd7a92256 | ||
|
|
bfa33f8713 | ||
|
|
040cf29051 | ||
|
|
80bc1cd78a | ||
|
|
78994deb1e | ||
|
|
714b3e7144 | ||
|
|
1cd57f40f6 | ||
|
|
da9a0eb77b | ||
|
|
37b2c90e62 | ||
|
|
ba150a1185 | ||
|
|
ddce9496f2 | ||
|
|
fe637f481d | ||
|
|
18e43e1d35 | ||
|
|
09fca89ac5 | ||
|
|
9f19a2025d | ||
|
|
6b918966d4 | ||
|
|
328502c1cd | ||
|
|
d15e461303 | ||
|
|
6946eead28 | ||
|
|
09d3240365 | ||
|
|
653ec3799e | ||
|
|
6de9f52d5a | ||
|
|
484e7d1d2a | ||
|
|
dfa01541b3 | ||
|
|
adbe9268ce | ||
|
|
3504e6e724 | ||
|
|
154b2ab490 | ||
|
|
2231ccf118 | ||
|
|
d4d9f92ade | ||
|
|
e4e1636da8 | ||
|
|
e99aac9550 | ||
|
|
ddb87168ed | ||
|
|
245c34e659 | ||
|
|
43b07be3fc | ||
|
|
99d98efeb1 | ||
|
|
7b25ef2e6c | ||
|
|
1f8a7a707c | ||
|
|
86ec20e787 | ||
|
|
8c21388f01 | ||
|
|
d657fa32e9 | ||
|
|
321e197d8c | ||
|
|
16a98b0683 | ||
|
|
9e1bbc1650 | ||
|
|
91ff6a36a4 | ||
|
|
56f1d8be1f | ||
|
|
ed60f189cc | ||
|
|
cabf4362be | ||
|
|
2472c7c47a | ||
|
|
136cb038cf | ||
|
|
8f89be0fbd | ||
|
|
bbdced9c90 | ||
|
|
a6f4dc2b74 | ||
|
|
df203fa244 | ||
|
|
c6e6eb0af3 | ||
|
|
29babebc4d | ||
|
|
2f3194840c | ||
|
|
0ebb323490 | ||
|
|
f8e1255994 | ||
|
|
b5c0c30a5e | ||
|
|
ac4590952b | ||
|
|
67569cb9c8 | ||
|
|
11ec0dff4f | ||
|
|
a198f0481a | ||
|
|
6266e0ab5e | ||
|
|
9ee1485960 | ||
|
|
05314ec46c | ||
|
|
b66d2d44d0 | ||
|
|
3b2db9027a | ||
|
|
97e81885db | ||
|
|
706c1c993b | ||
|
|
cb70d51e2b | ||
|
|
bfb827a418 | ||
|
|
e2fb588a8c | ||
|
|
43c4dfc5df |
4
.envrc
4
.envrc
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
use flake
|
||||
dotenv_if_exists
|
||||
|
||||
use flake ".#${DIRENV_DEVSHELL:-default}"
|
||||
|
||||
PATH_add bin
|
||||
|
||||
103
.github/workflows/ci.yml
vendored
103
.github/workflows/ci.yml
vendored
@@ -14,10 +14,10 @@ on:
|
||||
- 'docs/**'
|
||||
- 'debian/**'
|
||||
- 'docker/**'
|
||||
- 'test_results/**'
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
tags:
|
||||
- '*'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -35,6 +35,11 @@ env:
|
||||
# Custom nix binary cache if fork is being used
|
||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||
# Use the all-features devshell instead of default, to ensure that features
|
||||
# match between nix and cargo
|
||||
DIRENV_DEVSHELL: all-features
|
||||
# Get error output from nix that we can actually use
|
||||
NIX_CONFIG: show-trace = true
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
@@ -48,6 +53,18 @@ jobs:
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Tag comparison check
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
run: |
|
||||
# Tag mismatch with latest repo tag check to prevent potential downgrades
|
||||
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
if [ $LATEST_TAG != ${{ github.ref_name }} ]; then
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.'
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
|
||||
@@ -63,8 +80,8 @@ jobs:
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit
|
||||
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg= conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
@@ -80,7 +97,11 @@ jobs:
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
direnv allow
|
||||
nix develop --command true
|
||||
nix develop .#all-features --command true
|
||||
|
||||
- name: Cache CI dependencies
|
||||
run: |
|
||||
bin/nix-build-and-cache ci
|
||||
|
||||
- name: Run CI tests
|
||||
run: |
|
||||
@@ -95,6 +116,14 @@ jobs:
|
||||
- name: Run Complement tests
|
||||
run: |
|
||||
direnv exec . bin/complement 'complement_src' 'complement_test_logs.jsonl' 'complement_test_results.jsonl'
|
||||
cp -v -f result complement_oci_image.tar.gz
|
||||
|
||||
- name: Upload Complement OCI image
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_oci_image.tar.gz
|
||||
path: complement_oci_image.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Complement logs
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -111,16 +140,14 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Diff Complement results with checked-in repo results
|
||||
# TODO: figure out why our complement results are not 100% consistent so we don't need to allow failures
|
||||
continue-on-error: true
|
||||
run: |
|
||||
diff -u --color=always complement_test_results.jsonl tests/test_results/complement/test_results.jsonl > >(tee -a complement_test_output.log)
|
||||
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_test_output.log)
|
||||
|
||||
- name: Add Complement diff result to Job Summary
|
||||
run: |
|
||||
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 50 complement_test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 100 complement_test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Update Job Summary
|
||||
@@ -138,14 +165,12 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: tests
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
- target: aarch64-unknown-linux-musl-jemalloc
|
||||
- target: x86_64-unknown-linux-musl
|
||||
- target: x86_64-unknown-linux-musl-jemalloc
|
||||
steps:
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -165,8 +190,8 @@ jobs:
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit
|
||||
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg= conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
@@ -182,15 +207,21 @@ jobs:
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
direnv allow
|
||||
nix develop --command true
|
||||
nix develop .#all-features --command true
|
||||
|
||||
- name: Build static ${{ matrix.target }}
|
||||
run: |
|
||||
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}
|
||||
mkdir -p target/release
|
||||
cp -v -f result/bin/conduit target/release/
|
||||
direnv exec . cargo deb --no-build --no-strip --output target/debian/${{ matrix.target }}.deb
|
||||
mv target/release/conduit static-${{ matrix.target }}
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
cp -v -f result/bin/conduit target/release/conduwuit
|
||||
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
# -p conduit is the main crate name
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}.deb
|
||||
mv -v target/release/conduwuit static-${{ matrix.target }}
|
||||
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
|
||||
|
||||
- name: Upload static-${{ matrix.target }}
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -203,8 +234,9 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deb-${{ matrix.target }}
|
||||
path: target/debian/${{ matrix.target }}.deb
|
||||
path: ${{ matrix.target }}.deb
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Build OCI image ${{ matrix.target }}
|
||||
run: |
|
||||
@@ -223,20 +255,21 @@ jobs:
|
||||
name: Docker publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && github.event_name != 'pull_request'
|
||||
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '')
|
||||
env:
|
||||
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
|
||||
DOCKER_TAG: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
|
||||
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
|
||||
GHCR_ARM64: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GHCR_AMD64: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
|
||||
GHCR_TAG: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
|
||||
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
|
||||
GLCR_ARM64: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GLCR_AMD64: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
|
||||
GLCR_TAG: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
|
||||
GLCR_BRANCH: registry.gitlab.com/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
|
||||
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
DOCKER_TAG: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
GHCR_ARM64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GHCR_AMD64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
GHCR_TAG: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
GLCR_ARM64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GLCR_AMD64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
GLCR_TAG: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
GLCR_BRANCH: registry.gitlab.com/conduwuit/conduwuit:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
|
||||
steps:
|
||||
@@ -268,8 +301,8 @@ jobs:
|
||||
|
||||
- name: Move OCI images into position
|
||||
run: |
|
||||
mv oci-image-x86_64-*-jemalloc/*.tar.gz oci-image-amd64.tar.gz
|
||||
mv oci-image-aarch64-*-jemalloc/*.tar.gz oci-image-arm64v8.tar.gz
|
||||
mv -v oci-image-x86_64-*/*.tar.gz oci-image-amd64.tar.gz
|
||||
mv -v oci-image-aarch64-*/*.tar.gz oci-image-arm64v8.tar.gz
|
||||
|
||||
- name: Load and push amd64 image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
|
||||
8
.github/workflows/documentation.yml
vendored
8
.github/workflows/documentation.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
@@ -47,7 +49,7 @@ jobs:
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Install Nix (with flakes and nix-command enabled)
|
||||
uses: cachix/install-nix-action@v26
|
||||
uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
@@ -59,9 +61,9 @@ jobs:
|
||||
extra-substituters = https://crane.cachix.org
|
||||
extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit
|
||||
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit
|
||||
extra-trusted-public-keys = conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
extra-trusted-public-keys = conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=
|
||||
|
||||
- name: Add alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
|
||||
8
.github/workflows/trivy.yml
vendored
8
.github/workflows/trivy.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
schedule:
|
||||
- cron: '00 12 * * *'
|
||||
|
||||
@@ -24,7 +26,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy code and vulnerability scanner on repo
|
||||
uses: aquasecurity/trivy-action@0.19.0
|
||||
uses: aquasecurity/trivy-action@0.22.0
|
||||
with:
|
||||
scan-type: repo
|
||||
format: sarif
|
||||
@@ -32,9 +34,9 @@ jobs:
|
||||
severity: CRITICAL,HIGH,MEDIUM,LOW
|
||||
|
||||
- name: Run Trivy code and vulnerability scanner on filesystem
|
||||
uses: aquasecurity/trivy-action@0.19.0
|
||||
uses: aquasecurity/trivy-action@0.22.0
|
||||
with:
|
||||
scan-type: fs
|
||||
format: sarif
|
||||
output: trivy-results.sarif
|
||||
severity: CRITICAL,HIGH,MEDIUM,LOW
|
||||
severity: CRITICAL,HIGH,MEDIUM,LOW
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
# Local environment overrides
|
||||
/.env
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
|
||||
@@ -26,15 +26,19 @@ before_script:
|
||||
|
||||
# Add conduwuit binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduwuit" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduit" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add alternate binary cache
|
||||
- if command -v nix > /dev/null && [ -n "$ATTIC_ENDPOINT" ]; then echo "extra-substituters = $ATTIC_ENDPOINT" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null && [ -n "$ATTIC_PUBLIC_KEY" ]; then echo "extra-trusted-public-keys = $ATTIC_PUBLIC_KEY" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add Lix binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://cache.lix.systems" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add crane binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=" >> /etc/nix/nix.conf; fi
|
||||
@@ -54,7 +58,7 @@ before_script:
|
||||
|
||||
ci:
|
||||
stage: ci
|
||||
image: nixos/nix:2.22.0
|
||||
image: nixos/nix:2.22.1
|
||||
script:
|
||||
# Cache CI dependencies
|
||||
- ./bin/nix-build-and-cache ci
|
||||
@@ -79,7 +83,7 @@ ci:
|
||||
|
||||
artifacts:
|
||||
stage: artifacts
|
||||
image: nixos/nix:2.22.0
|
||||
image: nixos/nix:2.22.1
|
||||
script:
|
||||
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl
|
||||
- cp result/bin/conduit x86_64-unknown-linux-musl
|
||||
|
||||
92
CONTRIBUTING.md
Normal file
92
CONTRIBUTING.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Contributing guide
|
||||
|
||||
This page is for about contributing to conduwuit. The [development](docs/development.md) page may be of interest for you as well.
|
||||
|
||||
If you would like to work on an [issue][issues] that is not assigned, preferably ask in the Matrix room first at [#conduwuit:puppygock.gay][conduwuit-matrix], and comment on it.
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
It is mandatory all your changes satisfy the lints (clippy, rustc, rustdoc, etc) and your code is formatted via the **nightly** `cargo fmt`. A lot of the `rustfmt.toml` features depend on nightly toolchain. It would be ideal if they weren't nightly-exclusive features, but they currently still are. CI's rustfmt uses nightly.
|
||||
|
||||
If you need to allow a lint, please make sure it's either obvious as to why (e.g. clippy saying redundant clone but it's actually required) or it has a comment saying why. Do not write inefficient code for the sake of satisfying lints. If a lint is wrong and provides a more inefficient solution or suggestion, allow the lint and mention that in a comment.
|
||||
|
||||
### Running CI tests locally
|
||||
|
||||
conduwuit's CI for tests, linting, formatting, audit, etc use [`engage`][engage]. engage can be installed from nixpkgs or `cargo install engage`. conduwuit's Nix flake devshell has the nixpkgs engage with `direnv`. Use `engage --help` for more usage details.
|
||||
|
||||
To test, format, lint, etc that CI would do, install engage, allow the `.envrc` file using `direnv allow`, and run `engage`.
|
||||
|
||||
All of the tasks are defined at the [engage.toml][engage.toml] file. You can view all of them neatly by running `engage list`
|
||||
|
||||
If you would like to run only a specific engage task group, use `just`:
|
||||
- `engage just <group>`
|
||||
- Example: `engage just lints`
|
||||
|
||||
If you would like to run a specific engage task in a specific group, use `just <GROUP> [TASK]`: `engage just lints cargo-fmt`
|
||||
|
||||
The following binaries are used in [`engage.toml`][engage.toml]:
|
||||
|
||||
- [`engage`][engage]
|
||||
- `nix`
|
||||
- [`direnv`][direnv]
|
||||
- `rustc`
|
||||
- `cargo`
|
||||
- `cargo-fmt`
|
||||
- `rustdoc`
|
||||
- `cargo-clippy`
|
||||
- [`cargo-audit`][cargo-audit]
|
||||
- [`cargo-deb`][cargo-deb]
|
||||
- [`lychee`][lychee]
|
||||
|
||||
### Matrix tests
|
||||
|
||||
CI runs [Complement][complement], but currently does not fail if results from the checked-in results differ with the new results. If your changes are done to fix Matrix tests, note that in your pull request. If more Complement tests start failing from your changes, please review the logs (they are uploaded as artifacts) and determine if they're intended or not.
|
||||
|
||||
If you'd like to run Complement locally using Nix, see the [testing](docs/development/testing.md) page.
|
||||
|
||||
[Sytest][sytest] support will come soon.
|
||||
|
||||
### Writing documentation
|
||||
|
||||
conduwuit's website uses [`mdbook`][mdbook] and deployed via CI using GitHub Pages in the [`documentation.yml`][documentation.yml] workflow file with Nix's mdbook in the devshell. All documentation is in the `docs/` directory at the top level. The compiled mdbook website is also uploaded as an artifact.
|
||||
|
||||
To build the documentation using Nix, run: `bin/nix-build-and-cache just .#book`
|
||||
|
||||
The output of the mdbook generation is in `result/`. mdbooks can be opened in your browser from the individual HTML files without any web server needed.
|
||||
|
||||
### Inclusivity and Diversity
|
||||
|
||||
All **MUST** code and write with inclusivity and diversity in mind. See the [following page by Google on writing inclusive code and documentation](https://developers.google.com/style/inclusive-documentation).
|
||||
|
||||
This **EXPLICITLY** forbids usage of terms like "blacklist"/"whitelist" and "master"/"slave", [forbids gender-specific words and phrases](https://developers.google.com/style/pronouns#gender-neutral-pronouns), forbids ableist language like "sanity-check", "cripple", or "insane", and forbids culture-specific language (e.g. US-only holidays or cultures).
|
||||
|
||||
No exceptions are allowed. Dependencies that may use these terms are allowed but [do not replicate the name in your functions or variables](https://developers.google.com/style/inclusive-documentation#write-around).
|
||||
|
||||
In addition to language, write and code with the user experience in mind. This is software that intends to be used by everyone, so make it easy and comfortable for everyone to use. 🏳️⚧️
|
||||
|
||||
### Variable, comment, function, etc standards
|
||||
|
||||
Rust's default style and standards with regards to [function names, variable names, comments](https://rust-lang.github.io/api-guidelines/naming.html), etc applies here.
|
||||
|
||||
### Creating pull requests
|
||||
|
||||
Please try to keep contributions to the GitHub. While the mirrors of conduwuit allow for pull/merge requests, there is no guarantee I will see them in a timely manner. Additionally, please mark WIP or unfinished or incomplete PRs as drafts. This prevents me from having to ping once in a while to double check the status of it, especially when the CI completed successfully and everything so it *looks* done.
|
||||
|
||||
If you open a pull request on one of the mirrors, it is your responsibility to inform me about its existence. In the future I may try to solve this with more repo bots in the conduwuit Matrix room. There is no mailing list or email-patch support on the sr.ht mirror, but if you'd like to email me a git patch you can do so at `strawberry@puppygock.gay`.
|
||||
|
||||
Direct all PRs/MRs to the `main` branch.
|
||||
|
||||
By sending a pull request or patch, you are agreeing that your changes are allowed to be licenced under the Apache-2.0 licence and all of your conduct is in line with the Contributor's Covenant.
|
||||
|
||||
[issues]: https://github.com/girlbossceo/conduwuit/issues
|
||||
[conduwuit-matrix]: https://matrix.to/#/#conduwuit:puppygock.gay
|
||||
[complement]: https://github.com/matrix-org/complement/
|
||||
[engage.toml]: https://github.com/girlbossceo/conduwuit/blob/main/engage.toml
|
||||
[engage]: https://charles.page.computer.surgery/engage/
|
||||
[sytest]: https://github.com/matrix-org/sytest/
|
||||
[cargo-deb]: https://github.com/kornelski/cargo-deb
|
||||
[lychee]: https://github.com/lycheeverse/lychee
|
||||
[cargo-audit]: https://github.com/RustSec/rustsec/tree/main/cargo-audit
|
||||
[direnv]: https://direnv.net/
|
||||
[mdbook]: https://rust-lang.github.io/mdBook/
|
||||
[documentation.yml]: https://github.com/girlbossceo/conduwuit/blob/main/.github/workflows/documentation.yml
|
||||
1313
Cargo.lock
generated
1313
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
1073
Cargo.toml
1073
Cargo.toml
File diff suppressed because it is too large
Load Diff
17
README.md
17
README.md
@@ -2,8 +2,6 @@ # conduwuit
|
||||
|
||||
`main` / stable: [](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
|
||||
|
||||
`dev`: [](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
|
||||
|
||||
<!-- ANCHOR: catchphrase -->
|
||||
### a very cool, featureful fork of [Conduit](https://conduit.rs/)
|
||||
<!-- ANCHOR_END: catchphrase -->
|
||||
@@ -28,6 +26,10 @@ #### Can I try it out?
|
||||
|
||||
An official conduwuit server ran by me is available at transfem.dev ([element.transfem.dev](https://element.transfem.dev) / [cinny.transfem.dev](https://cinny.transfem.dev))
|
||||
|
||||
transfem.dev is a public homeserver that can be used, it is not a "test only homeserver". This means there are rules, so please read the rules: [https://transfem.dev/homeserver_rules.txt](https://transfem.dev/homeserver_rules.txt)
|
||||
|
||||
transfem.dev is also listed at [servers.joinmatrix.org](https://servers.joinmatrix.org/)
|
||||
|
||||
#### What is the current status?
|
||||
|
||||
conduwuit is a hard fork of Conduit which is in beta, meaning you can join and participate in most
|
||||
@@ -37,14 +39,6 @@ #### What is the current status?
|
||||
<!-- ANCHOR_END: body -->
|
||||
|
||||
<!-- ANCHOR: footer -->
|
||||
#### How can I contribute?
|
||||
|
||||
1. Look for an issue you would like to work on and make sure it's not assigned
|
||||
to other users
|
||||
2. Ask someone to assign the issue to you (comment on the issue or chat in
|
||||
[#conduwuit:puppygock.gay](https://matrix.to/#/#conduwuit:puppygock.gay))
|
||||
3. Fork the repo and work on the issue.
|
||||
4. Submit a PR (please keep contributions to the GitHub repo, main development is done here, not the GitLab repo which exists just as a mirror. If you are avoiding GitHub, feel free to join our Matrix chat to get your patch in.)
|
||||
|
||||
#### Contact
|
||||
|
||||
@@ -69,7 +63,8 @@ #### Is it conduwuit or Conduwuit?
|
||||
#### Mirrors of conduwuit
|
||||
|
||||
- GitHub: <https://github.com/girlbossceo/conduwuit>
|
||||
- GitLab: <https://gitlab.com/girlbossceo/conduwuit>
|
||||
- GitLab: <https://gitlab.com/conduwuit/conduwuit>
|
||||
- git.girlcock.ceo: <https://git.girlcock.ceo/strawberry/conduwuit>
|
||||
- git.gay: <https://git.gay/june/conduwuit>
|
||||
- Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
|
||||
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[advisories]
|
||||
ignore = ["RUSTSEC-2020-0016"]
|
||||
@@ -15,17 +15,20 @@ LOG_FILE="$2"
|
||||
# A `.jsonl` file to write test results to
|
||||
RESULTS_FILE="$3"
|
||||
|
||||
OCI_IMAGE="complement-conduit:dev"
|
||||
OCI_IMAGE="complement-conduit:main"
|
||||
|
||||
# Complement tests that are skipped due to flakiness/reliability issues (likely
|
||||
# Complement itself induced based on various open issues)
|
||||
#
|
||||
# According to Go docs, these are separated by forward slashes and not pipes (why)
|
||||
SKIPPED_COMPLEMENT_TESTS='-skip=TestJumpToDateEndpoint.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestFederationRoomsInvite.*|TestClientSpacesSummary.*'
|
||||
|
||||
toplevel="$(git rev-parse --show-toplevel)"
|
||||
|
||||
pushd "$toplevel" > /dev/null
|
||||
# uses nix-output-monitor (nom) if available
|
||||
if command -v nom &> /dev/null; then
|
||||
nom build .#complement
|
||||
else
|
||||
nix build -L .#complement
|
||||
fi
|
||||
|
||||
bin/nix-build-and-cache just .#static-complement
|
||||
|
||||
docker load < result
|
||||
popd > /dev/null
|
||||
|
||||
@@ -34,7 +37,7 @@ set +o pipefail
|
||||
env \
|
||||
-C "$COMPLEMENT_SRC" \
|
||||
COMPLEMENT_BASE_IMAGE="$OCI_IMAGE" \
|
||||
go test -vet=off -timeout 1h -json ./tests | tee "$LOG_FILE"
|
||||
go test -tags="conduwuit_blacklist" "$SKIPPED_COMPLEMENT_TESTS" -v -timeout 1h -json ./tests | tee "$LOG_FILE"
|
||||
set -o pipefail
|
||||
|
||||
# Post-process the results into an easy-to-compare format, sorted by Test name for reproducible results
|
||||
|
||||
@@ -13,41 +13,55 @@ just() {
|
||||
nix build -L "$@"
|
||||
fi
|
||||
|
||||
if [ ! -z "$ATTIC_TOKEN" ]; then
|
||||
# historical "conduit" store for compatibility purposes, same as conduwuit
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
login \
|
||||
conduit \
|
||||
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
readarray -t outputs < <(nix path-info "$@")
|
||||
readarray -t derivations < <(nix path-info "$@" --derivation)
|
||||
|
||||
# Push the target installable and its build dependencies
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
push \
|
||||
conduit \
|
||||
"${outputs[@]}" \
|
||||
"${derivations[@]}"
|
||||
|
||||
# main "conduwuit" store
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
login \
|
||||
conduwuit \
|
||||
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduwuit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
# Push the target installable and its build dependencies
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
push \
|
||||
conduwuit \
|
||||
"${outputs[@]}" \
|
||||
"${derivations[@]}"
|
||||
else
|
||||
if [ -z "$ATTIC_TOKEN" ]; then
|
||||
echo "\$ATTIC_TOKEN is unset, skipping uploading to the binary cache"
|
||||
return
|
||||
fi
|
||||
|
||||
# historical "conduit" store for compatibility purposes, same as conduwuit
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
login \
|
||||
conduit \
|
||||
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
# Find all output paths of the installables and their build dependencies
|
||||
readarray -t derivations < <(nix path-info --derivation "$@")
|
||||
cache=()
|
||||
for derivation in "${derivations[@]}"; do
|
||||
cache+=(
|
||||
"$(nix-store --query --requisites --include-outputs "$derivation")"
|
||||
)
|
||||
done
|
||||
|
||||
# Upload them to Attic (conduit store)
|
||||
#
|
||||
# Use `xargs` and a here-string because something would probably explode if
|
||||
# several thousand arguments got passed to a command at once. Hopefully no
|
||||
# store paths include a newline in them.
|
||||
(
|
||||
IFS=$'\n'
|
||||
nix shell --inputs-from "$toplevel" attic -c xargs \
|
||||
attic push conduit <<< "${cache[*]}"
|
||||
)
|
||||
|
||||
# main "conduwuit" store
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
login \
|
||||
conduwuit \
|
||||
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduwuit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
# Upload them to Attic (conduwuit store)
|
||||
#
|
||||
# Use `xargs` and a here-string because something would probably explode if
|
||||
# several thousand arguments got passed to a command at once. Hopefully no
|
||||
# store paths include a newline in them.
|
||||
(
|
||||
IFS=$'\n'
|
||||
nix shell --inputs-from "$toplevel" attic -c xargs \
|
||||
attic push conduwuit <<< "${cache[*]}"
|
||||
)
|
||||
}
|
||||
|
||||
# Build and cache things needed for CI
|
||||
@@ -56,7 +70,8 @@ ci() {
|
||||
--inputs-from "$toplevel"
|
||||
|
||||
# Keep sorted
|
||||
"$toplevel#devShells.x86_64-linux.default.inputDerivation"
|
||||
"$toplevel#devShells.x86_64-linux.default"
|
||||
"$toplevel#devShells.x86_64-linux.all-features"
|
||||
attic#default
|
||||
nixpkgs#direnv
|
||||
nixpkgs#jq
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
too-many-lines-threshold = 700
|
||||
array-size-threshold = 4096
|
||||
cognitive-complexity-threshold = 94 # TODO reduce me ALARA
|
||||
excessive-nesting-threshold = 11 # TODO reduce me to 4 or 5
|
||||
future-size-threshold = 7745 # TODO reduce me ALARA
|
||||
stack-size-threshold = 144000 # reduce me ALARA
|
||||
too-many-lines-threshold = 700 # TODO reduce me to <= 100
|
||||
type-complexity-threshold = 250 # reduce me to ~200
|
||||
|
||||
@@ -60,8 +60,9 @@
|
||||
|
||||
### Database configuration
|
||||
|
||||
# This is the only directory where conduwuit will save its data, including media
|
||||
database_path = "/var/lib/matrix-conduit/"
|
||||
# This is the only directory where conduwuit will save its data, including media.
|
||||
# Note: this was previously "/var/lib/matrix-conduit"
|
||||
database_path = "/var/lib/conduwuit"
|
||||
|
||||
# Database backend: Only rocksdb and sqlite are supported. Please note that sqlite
|
||||
# will perform significantly worse than rocksdb as it is not intended to be used the
|
||||
@@ -76,11 +77,16 @@ database_backend = "rocksdb"
|
||||
# forwarded to the conduwuit instance running on this port
|
||||
# Docker users: Don't change this, you'll need to map an external port to this.
|
||||
# To listen on multiple ports, specify a vector e.g. [8080, 8448]
|
||||
#
|
||||
# default if unspecified is 8008
|
||||
port = 6167
|
||||
|
||||
# default address (IPv4 or IPv6) conduwuit will listen on. Generally you want this to be
|
||||
# localhost (127.0.0.1 / ::1). If you are using Docker or a container NAT networking setup, you
|
||||
# likely need this to be 0.0.0.0.
|
||||
# To listen multiple addresses, specify a vector e.g. ["127.0.0.1", "::1"]
|
||||
#
|
||||
# default if unspecified is both IPv4 and IPv6 localhost: ["127.0.0.1", "::1"]
|
||||
address = "127.0.0.1"
|
||||
|
||||
# Max request size for file uploads
|
||||
@@ -269,6 +275,19 @@ url_preview_check_root_domain = false
|
||||
# Defaults to true
|
||||
allow_profile_lookup_federation_requests = true
|
||||
|
||||
# Config option to automatically deactivate the account of any user who attempts to join a:
|
||||
# - banned room
|
||||
# - forbidden room alias
|
||||
# - room alias or ID with a forbidden server name
|
||||
#
|
||||
# This may be useful if all your banned lists consist of toxic rooms or servers that no good faith user would ever attempt to join, and
|
||||
# to automatically remediate the problem without any admin user intervention.
|
||||
#
|
||||
# This will also make the user leave all rooms. Federation (e.g. remote room invites) are ignored here.
|
||||
#
|
||||
# Defaults to false as rooms can be banned for non-moderation-related reasons
|
||||
#auto_deactivate_banned_room_attempts = false
|
||||
|
||||
|
||||
### Misc
|
||||
|
||||
@@ -361,15 +380,6 @@ allow_profile_lookup_federation_requests = true
|
||||
# Defaults to 256.0
|
||||
#db_cache_capacity_mb = 256.0
|
||||
|
||||
# Interval in seconds when conduwuit will run database cleanup operations.
|
||||
#
|
||||
# For SQLite: this will flush the WAL by executing `PRAGMA wal_checkpoint(RESTART)` (https://www.sqlite.org/pragma.html#pragma_wal_checkpoint)
|
||||
# For RocksDB: this will run `flush_opt` to flush database memtables to SST files on disk (https://docs.rs/rocksdb/latest/rocksdb/struct.DBCommon.html#method.flush_opt)
|
||||
# These operations always run on shutdown.
|
||||
#
|
||||
# Defaults to 30 minutes (1800 seconds) to avoid IO amplification from too frequent cleanups
|
||||
#cleanup_second_interval = 1800
|
||||
|
||||
|
||||
### RocksDB options
|
||||
|
||||
@@ -478,11 +488,6 @@ allow_profile_lookup_federation_requests = true
|
||||
# Defaults to 1 (TolerateCorruptedTailRecords)
|
||||
#rocksdb_recovery_mode = 1
|
||||
|
||||
# Controls whether memory buffers are written to storage at the fixed interval set by `cleanup_period_interval`
|
||||
# even when they are not full. Setting this will increase load on the storage backplane and is never advised
|
||||
# under normal circumstances.
|
||||
#rocksdb_periodic_cleanup = false
|
||||
|
||||
|
||||
### Domain Name Resolution and Caching
|
||||
|
||||
@@ -697,6 +702,44 @@ allow_profile_lookup_federation_requests = true
|
||||
#typing_client_timeout_max_s = 45
|
||||
|
||||
|
||||
### TURN / VoIP
|
||||
|
||||
# vector list of TURN URIs/servers to use
|
||||
#
|
||||
# No default
|
||||
#turn_uris = ["turn:example.turn.uri?transport=udp", "turn:example.turn.uri?transport=tcp"]
|
||||
|
||||
# TURN secret to use for generating the HMAC-SHA1 hash apart of username and password generation
|
||||
#
|
||||
# this is more secure, but if needed you can use traditional username/password below.
|
||||
#
|
||||
# no default
|
||||
#turn_secret = ""
|
||||
|
||||
# TURN username to provide the client
|
||||
#
|
||||
# no default
|
||||
#turn_username = ""
|
||||
|
||||
# TURN password to provide the client
|
||||
#
|
||||
# no default
|
||||
#turn_password = ""
|
||||
|
||||
# TURN TTL
|
||||
#
|
||||
# Default is 86400 seconds
|
||||
#turn_ttl = 86400
|
||||
|
||||
# allow guests/unauthenticated users to access TURN credentials
|
||||
#
|
||||
# this is the equivalent of Synapse's `turn_allow_guests` config option. this allows
|
||||
# any unauthenticated user to call `/_matrix/client/v3/voip/turnServer`.
|
||||
#
|
||||
# defaults to false
|
||||
#turn_allow_guests = false
|
||||
|
||||
|
||||
# Other options not in [global]:
|
||||
#
|
||||
#
|
||||
|
||||
40
debian/README.md
vendored
40
debian/README.md
vendored
@@ -1,34 +1,22 @@
|
||||
conduwuit for Debian
|
||||
==================
|
||||
# conduwuit for Debian
|
||||
|
||||
Installation
|
||||
------------
|
||||
Information about downloading and deploying the Debian package. This may also be referenced for other `apt`-based distros such as Ubuntu.
|
||||
|
||||
Information about downloading, building and deploying the Debian package, see
|
||||
the "Installing conduwuit" section in the Deploying docs.
|
||||
All following sections until "Setting up the Reverse Proxy" be ignored because
|
||||
this is handled automatically by the packaging.
|
||||
### Installation
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
It is recommended to see the [generic deployment guide](../deploying/generic.md) for further information if needed as usage of the Debian package is generally related.
|
||||
|
||||
When installed, Debconf generates the configuration of the homeserver
|
||||
(host)name, the address and port it listens on. This configuration ends up in
|
||||
`/etc/conduwuit/conduwuit.toml`.
|
||||
### Configuration
|
||||
|
||||
You can tweak more detailed settings by uncommenting and setting the variables
|
||||
in `/etc/conduwuit/conduwuit.toml`. This involves settings such as the maximum
|
||||
file size for download/upload, enabling federation, etc.
|
||||
When installed, the example config is placed at `/etc/conduwuit/conduwuit.toml` as the default config. At the minimum, you will need to change your `server_name` here.
|
||||
|
||||
Running
|
||||
-------
|
||||
You can tweak more detailed settings by uncommenting and setting the config options
|
||||
in `/etc/conduwuit/conduwuit.toml`.
|
||||
|
||||
The package uses the `conduwuit.service` systemd unit file to start and
|
||||
stop conduwuit. It loads the configuration file mentioned above to set up the
|
||||
environment before running the server.
|
||||
### Running
|
||||
|
||||
This package assumes by default that conduwuit will be placed behind a reverse
|
||||
proxy. This default deployment entails just listening
|
||||
on `127.0.0.1` and the free port `6167` and is reachable via a client using the URL
|
||||
<http://localhost:6167>. Matrix federation requires TLS, so you will need to set up
|
||||
some certificates and renewal, for it to work properly.
|
||||
The package uses the [`conduwuit.service`](../configuration.md#example-systemd-unit-file) systemd unit file to start and stop conduwuit. The binary is installed at `/usr/sbin/conduwuit`.
|
||||
|
||||
This package assumes by default that conduwuit will be placed behind a reverse proxy. The default config options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS, so you will need to set up TLS certificates and renewal for it to work properly if you intend to federate.
|
||||
|
||||
Consult various online documentation and guides on setting up a reverse proxy and TLS. Caddy is documented at the [generic deployment guide](../deploying/generic.md#setting-up-the-reverse-proxy) as it's the easiest and most user friendly.
|
||||
|
||||
22
debian/conduwuit.service
vendored
22
debian/conduwuit.service
vendored
@@ -1,13 +1,20 @@
|
||||
[Unit]
|
||||
Description=conduwuit Matrix homeserver
|
||||
Documentation=https://conduwuit.puppyirl.gay/
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
DynamicUser=yes
|
||||
User=_conduwuit
|
||||
Group=_conduwuit
|
||||
User=conduwuit
|
||||
Group=conduwuit
|
||||
Type=notify
|
||||
|
||||
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||
|
||||
ExecStart=/usr/sbin/conduwuit
|
||||
|
||||
ReadWritePaths=/var/lib/conduwuit /etc/conduwuit
|
||||
|
||||
AmbientCapabilities=
|
||||
CapabilityBoundingSet=
|
||||
|
||||
@@ -39,19 +46,16 @@ SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service @resources
|
||||
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
|
||||
SystemCallErrorNumber=EPERM
|
||||
StateDirectory=matrix-conduit
|
||||
#StateDirectory=conduwuit
|
||||
|
||||
RuntimeDirectory=conduit
|
||||
RuntimeDirectory=conduwuit
|
||||
RuntimeDirectoryMode=0750
|
||||
|
||||
Environment="CONDUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||
|
||||
ExecStart=/usr/sbin/conduwuit
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
TimeoutStopSec=4m
|
||||
TimeoutStartSec=4m
|
||||
TimeoutStopSec=2m
|
||||
TimeoutStartSec=2m
|
||||
|
||||
StartLimitInterval=1m
|
||||
StartLimitBurst=5
|
||||
|
||||
23
debian/config
vendored
23
debian/config
vendored
@@ -1,17 +1,18 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# TODO: implement debconf support that is maintainable without duplicating the config
|
||||
# Source debconf library.
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
# Ask for the Matrix homeserver name, address and port.
|
||||
db_input high conduwuit/hostname || true
|
||||
db_go
|
||||
|
||||
db_input low conduwuit/address || true
|
||||
db_go
|
||||
|
||||
db_input medium conduwuit/port || true
|
||||
db_go
|
||||
#. /usr/share/debconf/confmodule
|
||||
#
|
||||
## Ask for the Matrix homeserver name, address and port.
|
||||
#db_input high conduwuit/hostname || true
|
||||
#db_go
|
||||
#
|
||||
#db_input low conduwuit/address || true
|
||||
#db_go
|
||||
#
|
||||
#db_input medium conduwuit/port || true
|
||||
#db_go
|
||||
|
||||
exit 0
|
||||
|
||||
37
debian/postinst
vendored
37
debian/postinst
vendored
@@ -1,28 +1,45 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
# TODO: implement debconf support that is maintainable without duplicating the config
|
||||
#. /usr/share/debconf/confmodule
|
||||
|
||||
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit/
|
||||
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
|
||||
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
|
||||
CONDUWUIT_CONFIG_FILE="${CONDUWUIT_CONFIG_PATH}/conduwuit.toml"
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
# Create the `_conduwuit` user if it does not exist yet.
|
||||
if ! getent passwd _conduwuit > /dev/null ; then
|
||||
# Create the `conduwuit` user if it does not exist yet.
|
||||
if ! getent passwd conduwuit > /dev/null ; then
|
||||
echo 'Adding system user for the conduwuit Matrix homeserver' 1>&2
|
||||
adduser --system --group --quiet \
|
||||
--home "$CONDUWUIT_DATABASE_PATH" \
|
||||
--disabled-login \
|
||||
--shell "/usr/sbin/nologin" \
|
||||
--force-badname \
|
||||
_conduwuit
|
||||
--verbose \
|
||||
conduwuit
|
||||
fi
|
||||
|
||||
# Create the database path if it does not exist yet and fix up ownership
|
||||
# and permissions.
|
||||
mkdir -p "$CONDUWUIT_DATABASE_PATH"
|
||||
chown _conduwuit:_conduwuit -R "$CONDUWUIT_DATABASE_PATH"
|
||||
chmod 700 "$CONDUWUIT_DATABASE_PATH"
|
||||
# and permissions for the config.
|
||||
mkdir -v -p "$CONDUWUIT_DATABASE_PATH"
|
||||
|
||||
# symlink the previous location for compatibility if it does not exist yet.
|
||||
if ! test -L "/var/lib/matrix-conduit" ; then
|
||||
ln -s -v "$CONDUWUIT_DATABASE_PATH" "/var/lib/matrix-conduit"
|
||||
fi
|
||||
|
||||
chown -v conduwuit:conduwuit -R "$CONDUWUIT_DATABASE_PATH"
|
||||
chown -v conduwuit:conduwuit -R "$CONDUWUIT_CONFIG_PATH"
|
||||
|
||||
chmod -v 740 "$CONDUWUIT_DATABASE_PATH"
|
||||
|
||||
echo ''
|
||||
echo 'Make sure you edit the example config at /etc/conduwuit/conduwuit.toml before starting!'
|
||||
echo 'To start the server, run: systemctl start conduwuit.service'
|
||||
echo ''
|
||||
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
11
debian/postrm
vendored
11
debian/postrm
vendored
@@ -1,10 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
#. /usr/share/debconf/confmodule
|
||||
|
||||
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
|
||||
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_PATH_SYMLINK=/var/lib/matrix-conduit
|
||||
|
||||
case $1 in
|
||||
purge)
|
||||
@@ -15,11 +16,15 @@ case $1 in
|
||||
# "configuration files must be preserved when the package is removed, and
|
||||
# only deleted when the package is purged."
|
||||
if [ -d "$CONDUWUIT_CONFIG_PATH" ]; then
|
||||
rm -r "$CONDUWUIT_CONFIG_PATH"
|
||||
rm -v -r "$CONDUWUIT_CONFIG_PATH"
|
||||
fi
|
||||
|
||||
if [ -d "$CONDUWUIT_DATABASE_PATH" ]; then
|
||||
rm -r "$CONDUWUIT_DATABASE_PATH"
|
||||
rm -v -r "$CONDUWUIT_DATABASE_PATH"
|
||||
fi
|
||||
|
||||
if [ -d "$CONDUWUIT_DATABASE_PATH_SYMLINK" ]; then
|
||||
rm -v -r "$CONDUWUIT_DATABASE_PATH_SYMLINK"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
21
debian/templates
vendored
21
debian/templates
vendored
@@ -1,21 +0,0 @@
|
||||
Template: conduwuit/hostname
|
||||
Type: string
|
||||
Default: localhost
|
||||
Description: The server (host)name of the Matrix homeserver
|
||||
This is the hostname the homeserver will be reachable at via a client.
|
||||
.
|
||||
If set to "localhost", you can connect with a client locally and clients
|
||||
from other hosts and also other homeservers will not be able to reach you!
|
||||
|
||||
Template: conduwuit/address
|
||||
Type: string
|
||||
Default: 127.0.0.1
|
||||
Description: The listen address of the Matrix homeserver
|
||||
This is the address the homeserver will listen on. Leave it set to 127.0.0.1
|
||||
when using a reverse proxy.
|
||||
|
||||
Template: conduwuit/port
|
||||
Type: string
|
||||
Default: 6167
|
||||
Description: The port of the Matrix homeserver
|
||||
This port is most often just accessed by a reverse proxy.
|
||||
42
deps/rust-rocksdb/Cargo.toml
vendored
Normal file
42
deps/rust-rocksdb/Cargo.toml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "rust-rocksdb-uwu"
|
||||
categories.workspace = true
|
||||
description = "dylib wrapper for rust-rocksdb"
|
||||
edition = "2021"
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version = "0.0.1"
|
||||
|
||||
[features]
|
||||
default = ["snappy", "lz4", "zstd", "zlib", "bzip2"]
|
||||
jemalloc = ["rust-rocksdb/jemalloc"]
|
||||
io-uring = ["rust-rocksdb/io-uring"]
|
||||
valgrind = ["rust-rocksdb/valgrind"]
|
||||
snappy = ["rust-rocksdb/snappy"]
|
||||
lz4 = ["rust-rocksdb/lz4"]
|
||||
zstd = ["rust-rocksdb/zstd"]
|
||||
zlib = ["rust-rocksdb/zlib"]
|
||||
bzip2 = ["rust-rocksdb/bzip2"]
|
||||
rtti = ["rust-rocksdb/rtti"]
|
||||
mt_static = ["rust-rocksdb/mt_static"]
|
||||
multi-threaded-cf = ["rust-rocksdb/multi-threaded-cf"]
|
||||
serde1 = ["rust-rocksdb/serde1"]
|
||||
malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
|
||||
|
||||
[dependencies.rust-rocksdb]
|
||||
git = "https://github.com/zaidoon1/rust-rocksdb"
|
||||
rev = "e9e1cb5ba92a44ea225fe8d13b31aa23621b9035"
|
||||
#branch = "master"
|
||||
default-features = false
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
crate-type = [
|
||||
"rlib",
|
||||
# "dylib"
|
||||
]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
61
deps/rust-rocksdb/lib.rs
vendored
Normal file
61
deps/rust-rocksdb/lib.rs
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
pub use rust_rocksdb::*;
|
||||
|
||||
#[cfg_attr(not(conduit_mods), link(name = "rocksdb"))]
|
||||
#[cfg_attr(conduit_mods, link(name = "rocksdb", kind = "static"))]
|
||||
extern "C" {
|
||||
pub fn rocksdb_list_column_families();
|
||||
pub fn rocksdb_logger_create_stderr_logger();
|
||||
pub fn rocksdb_options_set_info_log();
|
||||
pub fn rocksdb_get_options_from_string();
|
||||
pub fn rocksdb_writebatch_create();
|
||||
pub fn rocksdb_writebatch_destroy();
|
||||
pub fn rocksdb_writebatch_put_cf();
|
||||
pub fn rocksdb_writebatch_delete_cf();
|
||||
pub fn rocksdb_iter_value();
|
||||
pub fn rocksdb_iter_seek_to_last();
|
||||
pub fn rocksdb_iter_seek_for_prev();
|
||||
pub fn rocksdb_iter_seek_to_first();
|
||||
pub fn rocksdb_iter_next();
|
||||
pub fn rocksdb_iter_prev();
|
||||
pub fn rocksdb_iter_seek();
|
||||
pub fn rocksdb_iter_valid();
|
||||
pub fn rocksdb_iter_get_error();
|
||||
pub fn rocksdb_iter_key();
|
||||
pub fn rocksdb_iter_destroy();
|
||||
pub fn rocksdb_livefiles();
|
||||
pub fn rocksdb_livefiles_count();
|
||||
pub fn rocksdb_livefiles_destroy();
|
||||
pub fn rocksdb_livefiles_column_family_name();
|
||||
pub fn rocksdb_livefiles_name();
|
||||
pub fn rocksdb_livefiles_size();
|
||||
pub fn rocksdb_livefiles_level();
|
||||
pub fn rocksdb_livefiles_smallestkey();
|
||||
pub fn rocksdb_livefiles_largestkey();
|
||||
pub fn rocksdb_livefiles_entries();
|
||||
pub fn rocksdb_livefiles_deletions();
|
||||
pub fn rocksdb_put_cf();
|
||||
pub fn rocksdb_delete_cf();
|
||||
pub fn rocksdb_get_pinned_cf();
|
||||
pub fn rocksdb_create_column_family();
|
||||
pub fn rocksdb_get_latest_sequence_number();
|
||||
pub fn rocksdb_batched_multi_get_cf();
|
||||
pub fn rocksdb_cancel_all_background_work();
|
||||
pub fn rocksdb_repair_db();
|
||||
pub fn rocksdb_list_column_families_destroy();
|
||||
pub fn rocksdb_flush();
|
||||
pub fn rocksdb_flush_wal();
|
||||
pub fn rocksdb_open_column_families();
|
||||
pub fn rocksdb_open_for_read_only_column_families();
|
||||
pub fn rocksdb_open_as_secondary_column_families();
|
||||
pub fn rocksdb_open_column_families_with_ttl();
|
||||
pub fn rocksdb_open();
|
||||
pub fn rocksdb_open_for_read_only();
|
||||
pub fn rocksdb_open_with_ttl();
|
||||
pub fn rocksdb_open_as_secondary();
|
||||
pub fn rocksdb_write();
|
||||
pub fn rocksdb_create_iterator_cf();
|
||||
pub fn rocksdb_backup_engine_create_new_backup_flush();
|
||||
pub fn rocksdb_backup_engine_options_create();
|
||||
pub fn rocksdb_write_buffer_manager_destroy();
|
||||
pub fn rocksdb_options_set_ttl();
|
||||
}
|
||||
@@ -2,14 +2,19 @@ # Summary
|
||||
|
||||
- [Introduction](introduction.md)
|
||||
- [Differences from upstream Conduit](differences.md)
|
||||
|
||||
- [Example configuration](configuration.md)
|
||||
- [Deploying](deploying.md)
|
||||
- [Generic](deploying/generic.md)
|
||||
- [Debian](deploying/debian.md)
|
||||
- [Docker](deploying/docker.md)
|
||||
- [NixOS](deploying/nixos.md)
|
||||
- [Docker](deploying/docker.md)
|
||||
- [Arch Linux](deploying/arch-linux.md)
|
||||
- [Debian](deploying/debian.md)
|
||||
- [TURN](turn.md)
|
||||
- [Appservices](appservices.md)
|
||||
- [Maintenance](maintenance.md)
|
||||
- [Troubleshooting](troubleshooting.md)
|
||||
- [Development](development.md)
|
||||
- [Contributing](contributing.md)
|
||||
- [Testing](development/testing.md)
|
||||
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
|
||||
- [conduwuit Community Code of Conduct](conduwuit_coc.md)
|
||||
|
||||
@@ -12,9 +12,9 @@ ## Set up the appservice - general instructions
|
||||
|
||||
At some point the appservice guide should ask you to add a registration yaml
|
||||
file to the homeserver. In Synapse you would do this by adding the path to the
|
||||
homeserver.yaml, but in Conduit you can do this from within Matrix:
|
||||
homeserver.yaml, but in conduwuit you can do this from within Matrix:
|
||||
|
||||
First, go into the #admins room of your homeserver. The first person that
|
||||
First, go into the `#admins` room of your homeserver. The first person that
|
||||
registered on the homeserver automatically joins it. Then send a message into
|
||||
the room like this:
|
||||
|
||||
@@ -31,13 +31,13 @@ ## Set up the appservice - general instructions
|
||||
```
|
||||
|
||||
You can confirm it worked by sending a message like this:
|
||||
`@conduit:your.server.name: appservices list`
|
||||
`!admin appservices list`
|
||||
|
||||
The `@conduit` bot should answer with `Appservices (1): your-bridge`
|
||||
|
||||
Then you are done. Conduit will send messages to the appservices and the
|
||||
Then you are done. conduwuit will send messages to the appservices and the
|
||||
appservice can send requests to the homeserver. You don't need to restart
|
||||
Conduit, but if it doesn't work, restarting while the appservice is running
|
||||
conduwuit, but if it doesn't work, restarting while the appservice is running
|
||||
could help.
|
||||
|
||||
## Appservice-specific instructions
|
||||
@@ -46,16 +46,6 @@ ### Remove an appservice
|
||||
|
||||
To remove an appservice go to your admin room and execute
|
||||
|
||||
`@conduit:your.server.name: appservices unregister <name>`
|
||||
`!admin appservices unregister <name>`
|
||||
|
||||
where `<name>` one of the output of `appservices list`.
|
||||
|
||||
### Tested appservices
|
||||
|
||||
These appservices have been tested and work with Conduit without any extra steps:
|
||||
|
||||
- [matrix-appservice-discord](https://github.com/Half-Shot/matrix-appservice-discord)
|
||||
- [mautrix-hangouts](https://github.com/mautrix/hangouts/)
|
||||
- [mautrix-telegram](https://github.com/mautrix/telegram/)
|
||||
- [mautrix-signal](https://github.com/mautrix/signal/) from version `0.2.2` forward.
|
||||
- [heisenbridge](https://github.com/hifi/heisenbridge/)
|
||||
|
||||
77
docs/conduwuit_coc.md
Normal file
77
docs/conduwuit_coc.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Conduwuit Community Code of Conduct
|
||||
|
||||
Welcome to the conduwuit community! We’re excited to have you here. Conduwuit is a hard-fork of the Conduit homeserver,
|
||||
aimed at making Matrix more accessible and inclusive for everyone.
|
||||
|
||||
This space is dedicated to fostering a positive, supportive, and inclusive environment for everyone. This Code of
|
||||
Conduct applies to all conduwuit spaces, including any further community rooms that reference this CoC. Here are our
|
||||
guidelines to help maintain the welcoming atmosphere that sets conduwuit apart.
|
||||
|
||||
For the foundational rules, please refer to the [Matrix.org Code of Conduct](https://matrix.org/legal/code-of-conduct/)
|
||||
and the [Contributor's Covenant](https://github.com/girlbossceo/conduwuit/blob/main/CODE_OF_CONDUCT.md). Below are
|
||||
additional guidelines specific to the conduwuit community.
|
||||
|
||||
## Our Values and Guidelines
|
||||
|
||||
1. **Respect and Inclusivity**: We are committed to maintaining a community where everyone feels safe and respected.
|
||||
Discrimination, harassment, or hate speech of any kind will not be tolerated. Recognise that each community member
|
||||
experiences the world differently based on their past experiences, background, and identity. Share your own
|
||||
experiences and be open to learning about others' diverse perspectives.
|
||||
|
||||
2. **Positivity and Constructiveness**: Engage in constructive discussions and support each other. If you feel angry,
|
||||
negative, or aggressive, take a break until you can participate in a positive and constructive manner. Process
|
||||
intense feelings with a friend or in a private setting before engaging in community conversations to help maintain
|
||||
a supportive and focused environment.
|
||||
|
||||
3. **Clarity and Understanding**: Our community includes neurodivergent individuals and those who may not appreciate
|
||||
sarcasm or subtlety. Communicate clearly and kindly, avoiding sarcasm and ensuring your messages are easily
|
||||
understood by all. Additionally, avoid putting the burden of education on marginalized groups by doing your own
|
||||
research before asking for explanations.
|
||||
|
||||
4. **Be Open to Inclusivity**: Actively engage in conversations about making our community more inclusive. Report
|
||||
discriminatory behavior to the moderators and be open to constructive feedback that aims to improve our community.
|
||||
Understand that discussing discrimination and negative experiences can be emotionally taxing, so focus on the
|
||||
message rather than critiquing the tone used.
|
||||
|
||||
5. **Commit to Inclusivity**: Building an inclusive community requires time, energy, and resources. Recognise that
|
||||
addressing discrimination and bias is an ongoing process that necessitates commitment and action from all community
|
||||
members.
|
||||
|
||||
## Matrix Community
|
||||
|
||||
This Code of Conduct applies to the entire [Conduwuit Matrix Space](https://matrix.to/#/#conduwuit-space:puppygock.gay)
|
||||
and its rooms, including:
|
||||
|
||||
### [#conduwuit:puppygock.gay](https://matrix.to/#/#conduwuit:puppygock.gay)
|
||||
|
||||
This room is for support and discussions about conduwuit. Ask questions, share insights, and help each other out.
|
||||
|
||||
### [#conduwuit-offtopic:girlboss.ceo](https://matrix.to/#/#conduwuit-offtopic:girlboss.ceo)
|
||||
|
||||
For off-topic community conversations about any subject. While this room allows for a wide range of topics, the same
|
||||
CoC applies. Keep discussions respectful and inclusive, and avoid divisive subjects like country/world politics.
|
||||
General topics, such as world events, are welcome as long as they follow the CoC.
|
||||
|
||||
### [#conduwuit-dev:puppygock.gay](https://matrix.to/#/#conduwuit-dev:puppygock.gay)
|
||||
|
||||
This room is dedicated to discussing active development of conduwuit. Posting requires an elevated power level, which
|
||||
can be requested in one of the other rooms. Use this space to collaborate and innovate.
|
||||
|
||||
## Enforcement
|
||||
|
||||
We have a zero-tolerance policy for violations of this Code of Conduct. If someone’s behavior makes you uncomfortable,
|
||||
please report it to the moderators. Actions we may take include:
|
||||
|
||||
1. **Warning**: A warning given directly in the room or via a private message from the moderators, identifying
|
||||
the violation and requesting corrective action.
|
||||
2. **Temporary Mute**: Temporary restriction from participating in discussions for a specified period to allow for
|
||||
reflection and cooling off.
|
||||
3. **Kick or Ban**: Egregious behavior may result in an immediate kick or ban to protect other community members.
|
||||
Bans are considered permanent and will only be reversed in exceptional circumstances after proven good behavior.
|
||||
|
||||
Please highlight issues directly in rooms when possible, but if you don't feel comfortable doing that, then please send
|
||||
a DM to one of the moderators directly.
|
||||
|
||||
Together, let’s build a community where everyone feels valued and respected.
|
||||
|
||||
- The Conduwuit Moderation Team
|
||||
@@ -3,3 +3,9 @@ # Example configuration
|
||||
``` toml
|
||||
{{#include ../conduwuit-example.toml}}
|
||||
```
|
||||
|
||||
# Example systemd unit file
|
||||
|
||||
```
|
||||
{{#include ../debian/conduwuit.service}}
|
||||
```
|
||||
|
||||
1
docs/contributing.md
Normal file
1
docs/contributing.md
Normal file
@@ -0,0 +1 @@
|
||||
{{#include ../CONTRIBUTING.md}}
|
||||
8
docs/deploying/arch-linux.md
Normal file
8
docs/deploying/arch-linux.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# conduwuit for Arch Linux
|
||||
|
||||
Currently conduwuit is only on the Arch User Repository (AUR).
|
||||
|
||||
The conduwuit AUR packages are community maintained and are not maintained by conduwuit development team, but the AUR package maintainers are in the Matrix room. Please attempt to verify your AUR package's PKGBUILD file looks fine before asking for support.
|
||||
|
||||
- [conduwuit](https://aur.archlinux.org/packages/conduwuit) - latest tagged conduwuit
|
||||
- [conduwuit-git](https://aur.archlinux.org/packages/conduwuit-git) - latest git conduwuit from `main` branch
|
||||
@@ -1,40 +1,30 @@
|
||||
# Conduit - Behind Traefik Reverse Proxy
|
||||
# conduwuit - Behind Traefik Reverse Proxy
|
||||
version: '2.4' # uses '2.4' for cpuset
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
|
||||
### If you already built the conduduwit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
|
||||
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
|
||||
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
|
||||
# build:
|
||||
# context: .
|
||||
# args:
|
||||
# CREATED: '2021-03-16T08:18:27Z'
|
||||
# VERSION: '0.1.0'
|
||||
# LOCAL: 'false'
|
||||
# GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/var/lib/matrix-conduit
|
||||
#- ./conduwuit.toml:/etc/conduit.toml
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit
|
||||
CONDUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUIT_PORT: 6167
|
||||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUIT_LOG: warn,state_res=warn
|
||||
CONDUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUWUIT_LOG: warn,state_res=warn
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
|
||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||
@@ -48,7 +38,7 @@ services:
|
||||
- ./nginx/www:/var/www/ # location of the client and server .well-known-files
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and Conduit
|
||||
### Domain or Subdomain for the communication between Element and conduwuit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/element-web:latest
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Conduit - Traefik Reverse Proxy Labels
|
||||
# conduwuit - Traefik Reverse Proxy Labels
|
||||
version: '2.4' # uses '2.4' for cpuset
|
||||
|
||||
services:
|
||||
@@ -7,10 +7,10 @@ services:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
|
||||
|
||||
- "traefik.http.routers.to-conduit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which Conduit is hosted
|
||||
- "traefik.http.routers.to-conduit.tls=true"
|
||||
- "traefik.http.routers.to-conduit.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.to-conduit.middlewares=cors-headers@docker"
|
||||
- "traefik.http.routers.to-conduwuit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which conduwuit is hosted
|
||||
- "traefik.http.routers.to-conduwuit.tls=true"
|
||||
- "traefik.http.routers.to-conduwuit.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.to-conduwuit.middlewares=cors-headers@docker"
|
||||
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
|
||||
|
||||
@@ -1,44 +1,33 @@
|
||||
# Conduit - Behind Traefik Reverse Proxy
|
||||
# conduwuit - Behind Traefik Reverse Proxy
|
||||
version: '2.4' # uses '2.4' for cpuset
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
|
||||
### If you already built the conduwuit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
|
||||
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
|
||||
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
|
||||
# build:
|
||||
# context: .
|
||||
# args:
|
||||
# CREATED: '2021-03-16T08:18:27Z'
|
||||
# VERSION: '0.1.0'
|
||||
# LOCAL: 'false'
|
||||
# GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/srv/conduit/.local/share/conduit
|
||||
#- ./conduwuit.toml:/etc/conduit.toml
|
||||
- db:/srv/conduwuit/.local/share/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
CONDUIT_ALLOW_REGISTRATION : 'true'
|
||||
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
CONDUWUIT_ALLOW_REGISTRATION : 'true'
|
||||
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
### Uncomment and change values as desired
|
||||
# CONDUIT_ADDRESS: 0.0.0.0
|
||||
# CONDUIT_PORT: 6167
|
||||
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
|
||||
# CONDUIT_LOG: info # default is: "warn,state_res=warn"
|
||||
# CONDUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUIT_ALLOW_ENCRYPTION: 'true'
|
||||
# CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
# CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||
# CONDUIT_WORKERS: 10
|
||||
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
# CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
# CONDUWUIT_PORT: 6167
|
||||
# CONDUWUIT_LOG: info # default is: "warn,state_res=warn"
|
||||
# CONDUWUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUWUIT_ALLOW_ENCRYPTION: 'true'
|
||||
# CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
# CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# CONDUWUIT_DATABASE_PATH: /srv/conduwuit/.local/share/conduwuit
|
||||
# CONDUWUIT_WORKERS: 10
|
||||
# CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
|
||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||
@@ -53,7 +42,7 @@ services:
|
||||
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and Conduit
|
||||
### Domain or Subdomain for the communication between Element and conduwuit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/element-web:latest
|
||||
|
||||
@@ -1,45 +1,35 @@
|
||||
# Conduit
|
||||
# conduwuit
|
||||
version: '2.4' # uses '2.4' for cpuset
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
### If you already built the Conduit image with 'docker build' or want to use a registry image,
|
||||
### If you already built the conduwuit image with 'docker build' or want to use a registry image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
|
||||
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
|
||||
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
|
||||
# build:
|
||||
# context: .
|
||||
# args:
|
||||
# CREATED: '2021-03-16T08:18:27Z'
|
||||
# VERSION: '0.1.0'
|
||||
# LOCAL: 'false'
|
||||
# GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8448:6167
|
||||
volumes:
|
||||
- db:/var/lib/matrix-conduit
|
||||
#- ./conduwuit.toml:/etc/conduit.toml
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
environment:
|
||||
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit
|
||||
CONDUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUIT_PORT: 6167
|
||||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUIT_LOG: warn,state_res=warn
|
||||
CONDUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUWUIT_LOG: warn,state_res=warn
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
#
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and Conduit
|
||||
### Domain or Subdomain for the communication between Element and conduwuit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/element-web:latest
|
||||
|
||||
@@ -12,22 +12,17 @@ ### Use a registry
|
||||
| Registry | Image | Size | Notes |
|
||||
| --------------- | --------------------------------------------------------------- | ----------------------------- | ---------------------- |
|
||||
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:latest][gh] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:latest][gl] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| GitLab Registry | [registry.gitlab.com/conduwuit/conduwuit:latest][gl] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| Docker Hub | [docker.io/girlbossceo/conduwuit:latest][dh] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:main][gh] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:main][gl] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| GitLab Registry | [registry.gitlab.com/conduwuit/conduwuit:main][gl] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| Docker Hub | [docker.io/girlbossceo/conduwuit:main][dh] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:dev][gh] | ![Image Size][shield-dev] | Development version/branch. |
|
||||
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:dev][gl] | ![Image Size][shield-dev] | Development version/branch. |
|
||||
| Docker Hub | [docker.io/girlbossceo/conduwuit:dev][dh] | ![Image Size][shield-dev] | Development version/branch. |
|
||||
|
||||
|
||||
[dh]: https://hub.docker.com/repository/docker/girlbossceo/conduwuit
|
||||
[gh]: https://github.com/girlbossceo/conduwuit/pkgs/container/conduwuit
|
||||
[gl]: https://gitlab.com/girlbossceo/conduwuit/container_registry/6351657
|
||||
[gl]: https://gitlab.com/conduwuit/conduwuit/container_registry/6351657
|
||||
[shield-latest]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/latest
|
||||
[shield-main]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/main
|
||||
[shield-dev]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/dev
|
||||
|
||||
|
||||
Use
|
||||
@@ -56,9 +51,9 @@ ### Run
|
||||
|
||||
or you can use [docker compose](#docker-compose).
|
||||
|
||||
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, an example can be found [here](../configuration.md).
|
||||
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, the example config can be found [here](../configuration.md).
|
||||
You can pass in different env vars to change config values on the fly. You can even configure conduwuit completely by using env vars. For an overview of possible
|
||||
values, please take a look at the `docker-compose.yml` file.
|
||||
values, please take a look at the [`docker-compose.yml`](docker-compose.yml) file.
|
||||
|
||||
If you just want to test conduwuit for a short time, you can use the `--rm` flag, which will clean up everything related to your container after you stop it.
|
||||
|
||||
@@ -112,92 +107,7 @@ ### Use Traefik as Proxy
|
||||
|
||||
With the service `well-known` we use a single `nginx` container that will serve those two files.
|
||||
|
||||
So...step by step:
|
||||
|
||||
1. Copy [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
||||
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and [`docker-compose.override.yml`](docker-compose.override.yml) from the repository and remove `.for-traefik` (or `.with-traefik`) from the filename.
|
||||
2. Open both files and modify/adjust them to your needs. Meaning, change the `CONDUIT_SERVER_NAME` and the volume host mappings according to your needs.
|
||||
3. Create the `conduwuit.toml` config file, an example can be found [here](../configuration.md), or set `CONDUIT_CONFIG=""` and configure conduwuit per env vars.
|
||||
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
|
||||
5. Create the files needed by the `well-known` service.
|
||||
|
||||
- `./nginx/matrix.conf` (relative to the compose file, you can change this, but then also need to change the volume mapping)
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name <SUBDOMAIN>.<DOMAIN>;
|
||||
listen 80 default_server;
|
||||
|
||||
location /.well-known/matrix/server {
|
||||
return 200 '{"m.server": "<SUBDOMAIN>.<DOMAIN>:443"}';
|
||||
types { } default_type "application/json; charset=utf-8";
|
||||
}
|
||||
|
||||
location /.well-known/matrix/client {
|
||||
return 200 '{"m.homeserver": {"base_url": "https://<SUBDOMAIN>.<DOMAIN>"}}';
|
||||
types { } default_type "application/json; charset=utf-8";
|
||||
add_header "Access-Control-Allow-Origin" *;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 404;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. Run `docker compose up -d`
|
||||
7. Connect to your homeserver with your preferred client and create a user. You should do this immediately after starting Conduit, because the first created user is the admin.
|
||||
|
||||
|
||||
|
||||
|
||||
## Voice communication
|
||||
|
||||
In order to make or receive calls, a TURN server is required. conduwuit suggests using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also available as a Docker image. Before proceeding with the software installation, it is essential to have the necessary configurations in place.
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a configuration file called `coturn.conf` containing:
|
||||
|
||||
```conf
|
||||
use-auth-secret
|
||||
static-auth-secret=<a secret key>
|
||||
realm=<your server domain>
|
||||
```
|
||||
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
|
||||
|
||||
These same values need to be set in conduwuit. You can either modify conduwuit.toml to include these lines:
|
||||
```
|
||||
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
|
||||
turn_secret = "<secret key from coturn configuration>"
|
||||
```
|
||||
or append the following to the docker environment variables dependig on which configuration method you used earlier:
|
||||
```yml
|
||||
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
|
||||
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
|
||||
```
|
||||
Restart Conduit to apply these changes.
|
||||
|
||||
### Run
|
||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||
```bash
|
||||
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
||||
```
|
||||
|
||||
or docker-compose. For the latter, paste the following section into a file called `docker-compose.yml`
|
||||
and run `docker compose up -d` in the same directory.
|
||||
|
||||
```yml
|
||||
version: 3
|
||||
services:
|
||||
turn:
|
||||
container_name: coturn-server
|
||||
image: docker.io/coturn/coturn
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- ./coturn.conf:/etc/coturn/turnserver.conf
|
||||
```
|
||||
|
||||
To understand why the host networking mode is used and explore alternative configuration options, please visit the following link: https://github.com/coturn/coturn/blob/master/docker/coturn/README.md.
|
||||
For security recommendations see Synapse's [Coturn documentation](https://github.com/matrix-org/synapse/blob/develop/docs/setup/turn/coturn.md#configuration).
|
||||
See the [TURN](../turn.md) page.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Generic deployment documentation
|
||||
|
||||
### Please note that this documentation is not fully representative of conduwuit at the moment. Assume majority of it is outdated.
|
||||
|
||||
> ## Getting help
|
||||
>
|
||||
> If you run into any problems while setting up conduwuit, ask us
|
||||
@@ -13,23 +11,16 @@ ## Installing conduwuit
|
||||
|
||||
Prebuilt binaries can be downloaded from the latest tagged release [here](https://github.com/girlbossceo/conduwuit/releases/latest).
|
||||
|
||||
Alternatively, you may compile the binary yourself. First, install any dependencies:
|
||||
The latest tagged release also includes the Debian packages.
|
||||
|
||||
```bash
|
||||
# Debian
|
||||
$ sudo apt install libclang-dev build-essential
|
||||
Alternatively, you may compile the binary yourself. We recommend using [Lix](https://lix.systems) to build conduwuit as this has the most guaranteed
|
||||
reproducibiltiy and easiest to get a build environment and output going.
|
||||
|
||||
# RHEL
|
||||
$ sudo dnf install clang
|
||||
```
|
||||
Then, `cd` into the source tree of conduwuit and run:
|
||||
```bash
|
||||
$ cargo build --release
|
||||
```
|
||||
Otherwise, follow standard Rust project build guides (installing git and cloning the repo, getting the Rust toolchain via rustup, installing LLVM toolchain + libclang, installing liburing for io_uring and RocksDB, etc).
|
||||
|
||||
## Adding a conduwuit user
|
||||
|
||||
While conduwuit can run as any user it is usually better to use dedicated users for different services. This also allows
|
||||
While conduwuit can run as any user it is better to use dedicated users for different services. This also allows
|
||||
you to make sure that the file permissions are correctly set up.
|
||||
|
||||
In Debian or RHEL, you can use this command to create a conduwuit user:
|
||||
@@ -38,6 +29,12 @@ ## Adding a conduwuit user
|
||||
sudo adduser --system conduwuit --group --disabled-login --no-create-home
|
||||
```
|
||||
|
||||
For distros without `adduser`:
|
||||
|
||||
```bash
|
||||
sudo useradd -r --shell /usr/bin/nologin --no-create-home conduwuit
|
||||
```
|
||||
|
||||
## Forwarding ports in the firewall or the router
|
||||
|
||||
conduwuit uses the ports 443 and 8448 both of which need to be open in the firewall.
|
||||
@@ -46,45 +43,18 @@ ## Forwarding ports in the firewall or the router
|
||||
|
||||
## Setting up a systemd service
|
||||
|
||||
Now we'll set up a systemd service for conduwuit, so it's easy to start/stop conduwuit and set it to autostart when your
|
||||
server reboots. Simply paste the default systemd service you can find below into
|
||||
`/etc/systemd/system/conduwuit.service`.
|
||||
|
||||
```systemd
|
||||
[Unit]
|
||||
Description=conduwuit Matrix Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||
User=conduwuit
|
||||
Group=conduwuit
|
||||
RuntimeDirectory=conduwuit
|
||||
RuntimeDirectoryMode=0750
|
||||
Restart=always
|
||||
ExecStart=/usr/local/bin/conduwuit
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Finally, run
|
||||
|
||||
```bash
|
||||
$ sudo systemctl daemon-reload
|
||||
```
|
||||
The systemd unit for conduwuit can be found [here](../configuration.md#example-systemd-unit-file). You may need to change the `ExecStart=` path to where you placed the conduwuit binary.
|
||||
|
||||
## Creating the conduwuit configuration file
|
||||
|
||||
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. Paste this in **and take a moment
|
||||
to read it. You need to change at least the server name.**
|
||||
RocksDB (`rocksdb`) is the only supported database backend. SQLite only exists for historical reasons and is not recommended. Any performance issues, storage issues, database issues, etc will not be assisted if using SQLite and you will be asked to migrate to RocksDB first.
|
||||
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. The example config can be found at [conduwuit-example.toml](../configuration.md).**Please take a moment to read it. You need to change at least the server name.**
|
||||
|
||||
See the following example config at [conduwuit-example.toml](../configuration.md)
|
||||
RocksDB is the only supported database backend. SQLite only exists for historical reasons, is not recommended, and will be removed soon (likely in v0.5.0). Any performance issues, storage issues, database issues, etc will not be assisted if using SQLite and you will be asked to migrate to RocksDB first.
|
||||
|
||||
## Setting the correct file permissions
|
||||
|
||||
As we are using a conduwuit specific user we need to allow it to read the config. To do that you can run this command on
|
||||
If you are using a dedicated user for conduwuit, you will need to allow it to read the config. To do that you can run this command on
|
||||
|
||||
Debian or RHEL:
|
||||
|
||||
```bash
|
||||
@@ -102,7 +72,9 @@ ## Setting the correct file permissions
|
||||
|
||||
## Setting up the Reverse Proxy
|
||||
|
||||
Refer to the documentation or various guides online of your chosen reverse proxy software. A Caddy example will be provided as this is the recommended reverse proxy for new users and is very trivial.
|
||||
Refer to the documentation or various guides online of your chosen reverse proxy software. A [Caddy](https://caddyserver.com/) example will be provided as this is the recommended reverse proxy for new users and is very trivial to use (handles TLS, reverse proxy headers, etc transparently with proper defaults).
|
||||
|
||||
Lighttpd is not supported as it seems to mess with the `X-Matrix` Authorization header, making federation non-functional. If using Apache, you need to use `nocanon` to prevent this.
|
||||
|
||||
### Caddy
|
||||
|
||||
@@ -118,10 +90,10 @@ ### Caddy
|
||||
}
|
||||
```
|
||||
|
||||
That's it! Just start or enable the service and you're set.
|
||||
That's it! Just start and enable the service and you're set.
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable caddy
|
||||
$ sudo systemctl enable --now caddy
|
||||
```
|
||||
|
||||
## You're done!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# conduwuit for NixOS
|
||||
|
||||
conduwuit can be acquired by Nix from various places:
|
||||
conduwuit can be acquired by [Lix][lix] from various places:
|
||||
|
||||
* The `flake.nix` at the root of the repo
|
||||
* The `default.nix` at the root of the repo
|
||||
@@ -10,9 +10,17 @@ # conduwuit for NixOS
|
||||
following places (both are the same just different names):
|
||||
```
|
||||
https://attic.kennel.juneis.dog/conduit
|
||||
conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
|
||||
|
||||
https://attic.kennel.juneis.dog/conduwuit
|
||||
conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=
|
||||
```
|
||||
|
||||
The binary caches have been recreated recently due to attic issues. The old public keys were:
|
||||
|
||||
```
|
||||
conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
|
||||
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
```
|
||||
|
||||
@@ -26,5 +34,6 @@ # conduwuit for NixOS
|
||||
or `default.nix` and set [`services.matrix-conduit.package`][package]
|
||||
appropriately.
|
||||
|
||||
[lix]: https://lix.systems/
|
||||
[module]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit
|
||||
[package]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit.package
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Development
|
||||
|
||||
Information about developing the project. If you are only interested in using
|
||||
it, you can safely ignore this section.
|
||||
it, you can safely ignore this section. If you plan on contributing, see the
|
||||
[contributor's guide](contributing.md).
|
||||
|
||||
## Debugging with `tokio-console`
|
||||
|
||||
@@ -15,8 +16,7 @@ ## Debugging with `tokio-console`
|
||||
RUSTFLAGS="--cfg tokio_unstable" cargo build \
|
||||
--release \
|
||||
--no-default-features \
|
||||
--features
|
||||
backend_rocksdb,systemd,element_hacks,sentry_telemetry,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
||||
--features=rocksdb,systemd,element_hacks,sentry_telemetry,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
||||
```
|
||||
|
||||
[1]: https://docs.rs/tokio-console/latest/tokio_console/
|
||||
|
||||
BIN
docs/development/assets/libraries.png
Normal file
BIN
docs/development/assets/libraries.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/development/assets/reload_order.png
Normal file
BIN
docs/development/assets/reload_order.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
93
docs/development/hot_reload.md
Normal file
93
docs/development/hot_reload.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Hot Reloading ("Live" Development)
|
||||
|
||||
### Summary
|
||||
|
||||
When developing in debug-builds with the nightly toolchain, conduwuit is modular using dynamic libraries and various parts of the application are hot-reloadable while the server is running: http api handlers, admin commands, services, database, etc. These are all split up into individual workspace crates as seen in the `src/` directory. Changes to sourcecode in a crate rebuild that crate and subsequent crates depending on it. Reloading then occurs for the changed crates.
|
||||
|
||||
Release builds still produce static binaries which are unaffected. Rust's soundness guarantees are in full force. Thus you cannot hot-reload release binaries.
|
||||
|
||||
### Requirements
|
||||
|
||||
Currently, this development setup only works on x86_64 and aarch64 Linux glibc. [musl explicitly does not support hot reloadable libraries, and does not implement `dlclose`][2]. macOS does not fully support our usage of `RTLD_GLOBAL` possibly due to some thread-local issues. [This Rust issue][3] may be of relevance, specifically [this comment][4]. It may be possible to get it working on only very modern macOS versions such as at least Sonoma, as currently loading dylibs is supported, but not unloading them in our setup, and the cited comment mentions an Apple WWDC confirming there have been TLS changes to somewhat make this possible.
|
||||
|
||||
As mentioned above this requires the nightly toolchain. This is due to reliance on various Cargo.toml features that are only available on nightly, most specifically `RUSTFLAGS` in Cargo.toml. Some of the implementation could also be simpler based on other various nightly features. We hope lots of nightly features start making it out of nightly sooner as there have been dozens of very helpful features that have been stuck in nightly ("unstable") for at least 5+ years that would make this simpler. We encourage greater community consensus to move these features into stability.
|
||||
|
||||
This currently only works on x86_64/aarch64 Linux with a glibc C library. musl C library, macOS, and likely other host architectures are not supported (if other architectures work, feel free to let us know and/or make a PR updating this). This should work on GNU ld and lld (rust-lld) and gcc/clang, however if you happen to have linker issues it's recommended to try using `mold` or `gold` linkers, and please let us know in the [conduwuit Matrix room][7] the linker error and what linker solved this issue so we can figure out a solution. Ideally there should be minimal friction to using this, and in the future a build script (`build.rs`) may be suitable to making this easier to use if the capabilities allow us.
|
||||
|
||||
### Usage
|
||||
|
||||
As of 19 May 2024, the instructions for using this are:
|
||||
|
||||
0. Have patience. Don't hesitate to join the [conduwuit Matrix room][7] to receive help using this. As indicated by the various rustflags used and some of the interesting issues linked at the bottom, this is definitely not something the Rust ecosystem or toolchain is used to doing.
|
||||
|
||||
1. Install the nightly toolchain using rustup. You may need to use `rustup override set nightly` in your local conduwuit directory, or use `cargo +nightly` for all actions.
|
||||
|
||||
2. Uncomment `cargo-features` at the top level / root Cargo.toml
|
||||
|
||||
3. Scroll down to the `# Developer profile` section and uncomment ALL the rustflags for each dev profile and their respective packages.
|
||||
|
||||
4. In each workspace crate's Cargo.toml (everything under `src/*` AND `deps/rust-rocksdb/Cargo.toml`), uncomment the `dylib` crate type under `[lib]`.
|
||||
|
||||
5. Due to [this rpath issue][5], you must export the `LD_LIBRARY_PATH` environment variable to your nightly Rust toolchain library directory. If using rustup (hopefully), use this: `export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/`
|
||||
|
||||
6. Start the server. You can use `cargo +nightly run` for this along with the standard.
|
||||
|
||||
7. Make some changes where you need to.
|
||||
|
||||
8. In a separate terminal window in the same directory (or using a terminal multiplexer like tmux), run the *build* Cargo command `cargo +nightly build`. Cargo should only rebuild what was changed / what's necessary, so it should not be rebuilding all the crates.
|
||||
|
||||
9. In your conduwuit server terminal, hit/send `CTRL+C` signal. This will tell conduwuit to find which libraries need to be reloaded, and reloads them as necessary.
|
||||
|
||||
10. If there were no errors, it will tell you it successfully reloaded `#` modules, and your changes should now be visible. Repeat 7 - 9 as needed.
|
||||
|
||||
To shutdown conduwuit in this setup, hit/send `CTRL+\`. Normal builds still shutdown with `CTRL+C` as usual.
|
||||
|
||||
Steps 1 - 5 are the initial first-time steps for using this. To remove the hot reload setup, revert/comment all the Cargo.toml changes.
|
||||
|
||||
As mentioned in the requirements section, if you happen to have some linker issues, try using the `-fuse-ld=` rustflag and specify mold or gold in all the `rustflags` definitions in the top level Cargo.toml, and please let us know in the [conduwuit Matrix room][7] the problem. mold can be installed typically through your distro, and gold is provided by the binutils package.
|
||||
|
||||
It's possible a helper script can be made to do all of this, or most preferably a specially made build script (build.rs). `cargo watch` support will be implemented soon which will eliminate the need to manually run `cargo build` all together.
|
||||
|
||||
### Addendum
|
||||
|
||||
Conduit was inherited as a single crate without modularity or reloading in its design. Reasonable partitioning and abstraction allowed a split into several crates, though many circular dependencies had to be corrected. The resulting crates now form a directed graph as depicted in figures below. The interfacing between these crates is still extremely broad which is not mitigable.
|
||||
|
||||
Initially [hot_lib_reload][6] was investigated but found appropriate for a project designed with modularity through limited interfaces, not a large and complex existing codebase. Instead a bespoke solution built directly on [libloading][8] satisfied our constraints. This required relatively minimal modifications and zero maintenance burden compared to what would be required otherwise. The technical difference lies with relocation processing: we leverage global bindings (`RTLD_GLOBAL`) in a very intentional way. Most libraries and off-the-shelf module systems (such as [hot_lib_reload][6]) restrict themselves to local bindings (`RTLD_LOCAL`). This allows them to release software to multiple platforms with much greater consistency, but at the cost of burdening applications to explicitly manage these bindings. In our case with an optional feature for developers, we shrug any such requirement to enjoy the cost/benefit on platforms where global relocations are properly cooperative.
|
||||
|
||||
To make use of `RTLD_GLOBAL` the application has to be oriented as a directed acyclic graph. The primary rule is simple and illustrated in the figure below: **no crate is allowed to call a function or use a variable from a crate below it.**
|
||||
|
||||

|
||||
|
||||
When a symbol is referenced between crates they become bound: **crates cannot be unloaded until their calling crates are first unloaded.** Thus we start the reloading process from the crate which has no callers. There is a small problem though: the first crate is called by the base executable itself! This is solved by using an `RTLD_LOCAL` binding for just one link between the main executable and the first crate, freeing the executable from all modules as no global binding ever occurs between them.
|
||||
|
||||

|
||||
|
||||
Proper resource management is essential for reliable reloading to occur. This is a very basic ask in RAII-idiomatic Rust and the exposure to reloading hazards is remarkably low, generally stemming from poor patterns and practices. Unfortunately static analysis doesn't enforce reload-safety programmatically (though it could one day), for now hazards can be avoided by knowing a few basic do's and dont's:
|
||||
|
||||
1. Understand that code is memory. Just like one is forbidden from referencing free'd memory, one must not transfer control to free'd code. Exposure to this is primarily from two things:
|
||||
- Callbacks, which this project makes very little use of.
|
||||
- Async tasks, which are addressed below.
|
||||
|
||||
2. Tie all resources to a scope or object lifetime with greatest possible symmetry (locality). For our purposes this applies to code resources, which means async blocks and tokio tasks.
|
||||
- **Never spawn a task without receiving and storing its JoinHandle**.
|
||||
- **Always wait on join handles** before leaving a scope or in another cleanup function called by an owning scope.
|
||||
|
||||
3. Know any minor specific quirks documented in code or here:
|
||||
- Don't use `tokio::spawn`, instead use our `Handle` in `core/server.rs`, which is reachable in most of the codebase via `services()` or other state. This is due to some bugs or assumptions made in tokio, as it happens in `unsafe {}` blocks, which are mitigated by circumventing some thread-local variables. Using runtime handles is good practice in any case.
|
||||
|
||||
The initial implementation PR is available [here][1].
|
||||
|
||||
### Interesting related issues/bugs
|
||||
|
||||
- [DT_RUNPATH produced in binary with rpath = true is wrong (cargo)][5]
|
||||
- [Disabling MIR Optimization in Rust Compilation (cargo)](https://internals.rust-lang.org/t/disabling-mir-optimization-in-rust-compilation/19066/5)
|
||||
- [Workspace-level metadata (cargo-deb)](https://github.com/kornelski/cargo-deb/issues/68)
|
||||
|
||||
[1]: https://github.com/girlbossceo/conduwuit/pull/387
|
||||
[2]: https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading-libraries
|
||||
[3]: https://github.com/rust-lang/rust/issues/28794
|
||||
[4]: https://github.com/rust-lang/rust/issues/28794#issuecomment-368693049
|
||||
[5]: https://github.com/rust-lang/cargo/issues/12746
|
||||
[6]: https://crates.io/crates/hot-lib-reloader/
|
||||
[7]: https://matrix.to/#/#conduwuit:puppygock.gay
|
||||
[8]: https://crates.io/crates/libloading
|
||||
@@ -5,13 +5,16 @@ ## Complement
|
||||
Have a look at [Complement's repository][complement] for an explanation of what
|
||||
it is.
|
||||
|
||||
To test against Complement, with Nix and direnv installed and set up, you can
|
||||
either:
|
||||
To test against Complement, with [Lix][lix] and direnv installed and set up, you can:
|
||||
|
||||
* Run `./bin/complement "$COMPLEMENT_SRC" ./path/to/logs.jsonl ./path/to/results.jsonl`
|
||||
to build a Complement image, run the tests, and output the logs and results
|
||||
to the specified paths
|
||||
to the specified paths. This will also output the OCI image at `result`
|
||||
* Run `nix build .#complement` from the root of the repository to just build a
|
||||
Complement image
|
||||
Complement OCI image outputted to `result` (it's a `.tar.gz` file)
|
||||
* Or download the latest Complement OCI image from the CI workflow artifacts output
|
||||
from the commit/revision you want to test (e.g. from main) [here][ci-workflows]
|
||||
|
||||
[lix]: https://lix.systems/
|
||||
[ci-workflows]: https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml?query=event%3Apush+is%3Asuccess+actor%3Agirlbossceo
|
||||
[complement]: https://github.com/matrix-org/complement
|
||||
|
||||
@@ -6,7 +6,7 @@ ### list of features, bug fixes, etc that conduwuit does that Conduit does not:
|
||||
|
||||
## Performance:
|
||||
- Concurrency support for key fetching for faster remote room joins and room joins that will error less frequently
|
||||
- Send `Cache-Control` response header with `immutable` and 1 year cache length for all media requests to instruct clients to cache media, and reduce server load from media requests that could be otherwise cached
|
||||
- Send `Cache-Control` response header with `immutable` and 1 year cache length for all media requests (download and thumbnail) to instruct clients to cache media, and reduce server load from media requests that could be otherwise cached
|
||||
- Add feature flags and config options to enable/build with zstd, brotli, and/or gzip HTTP body compression (response and request)
|
||||
- Eliminate all usage of the thread-blocking `getaddrinfo(3)` call upon DNS queries, significantly improving federation latency/ping and cache DNS results (NXDOMAINs, successful queries, etc) using hickory-dns / hickory-resolver
|
||||
- Vastly improve RocksDB default settings to use new features that help with performance significantly, uses settings tailored to SSDs, various ways to tweak RocksDB, and a conduwuit setting to tell RocksDB to use settings that are tailored to HDDs or slow spinning rust storage or buggy filesystems.
|
||||
@@ -22,6 +22,7 @@ ## Performance:
|
||||
- Add config options for RocksDB compression and bottommost compression, including choosing the algorithm and compression level
|
||||
- Use [loole](https://github.com/mahdi-shojaee/loole) MPSC channels instead of tokio MPSC channels for huge performance boosts in sending channels (mainly relevant for federation) and presence channels
|
||||
- Use `tracing`/`log`'s `release_max_level_info` feature to improve performance, build speeds, binary size, and CPU usage in release builds by avoid compiling debug/trace log level macros that users will generally never use (can be disabled with a build-time feature flag)
|
||||
- Enable RocksDB async read I/O via `io_uring` by default
|
||||
|
||||
|
||||
## General Fixes:
|
||||
@@ -35,7 +36,7 @@ ## General Fixes:
|
||||
- Increased graceful shutdown timeout from a low 60 seconds to 180 seconds to avoid killing connections and let the remaining ones finish processing
|
||||
- Return joined member count of rooms for push rules/conditions instead of a hardcoded value of 10
|
||||
- Make `CONDUIT_CONFIG` optional, relevant for container users that configure only by environment variables and no longer need to set `CONDUIT_CONFIG` to an empty string.
|
||||
- Allow HEAD HTTP requests in CORS for clients (despite not being explicity mentioned in Matrix spec, HTTP spec says all HEAD requests need to behave the same as GET requests, Synapse supports HEAD requests)
|
||||
- Allow HEAD and PATCH (MSC4138) HTTP requests in CORS for clients (despite not being explicity mentioned in Matrix spec, HTTP spec says all HEAD requests need to behave the same as GET requests, Synapse supports HEAD requests)
|
||||
- Resolve and remove some "features" from upstream that result in concurrency hazards, exponential backoff issues, or arbitrary performance limiters
|
||||
- Find more servers for outbound federation `/hierarchy` requests instead of just the room ID server name
|
||||
- Support for suggesting servers to join through at `/_matrix/client/v3/directory/room/{roomAlias}`
|
||||
@@ -55,6 +56,7 @@ ## Moderation:
|
||||
- On new public room creations, only allow moderators to send `m.call.invite`, `org.matrix.msc3401.call`, and `org.matrix.msc3401.call.member` events
|
||||
- Add support for a "global ACLs" feature (`forbidden_remote_server_names`) that blocks inbound remote room invites, room joins by room ID on server name, room joins by room alias on server name, incoming federated joins, and incoming federated room directory requests. This is very helpful for blocking servers that are purely toxic/bad and serve no value in allowing our users to suffer from things like room invite spam or such. Please note that this is not a substitute for room ACLs.
|
||||
- Add support for a config option to forbid our local users from sending federated room directory requests for (`forbidden_remote_room_directory_server_names`). Similar to above, useful for blocking servers that help prevent our users from wandering into bad areas of Matrix via room directories of those malicious servers.
|
||||
- Add config option for auto remediating/deactivating local non-admin users who attempt to join bad/forbidden rooms (`auto_deactivate_banned_room_attempts`)
|
||||
|
||||
|
||||
## Privacy/Security:
|
||||
@@ -68,6 +70,9 @@ ## Privacy/Security:
|
||||
- Config option to disable incoming and/or outgoing remote read receipts
|
||||
- Config option to disable incoming and/or outgoing remote typing indicators
|
||||
- Config option to disable incoming, outgoing, and/or local presence
|
||||
- Sanitise file names for the `Content-Disposition` header for all media requests (thumbnails, downloads, uploads)
|
||||
- Return `inline` or `attachment` based on the detected file MIME type for the `Content-Disposition` and only allow images/videos/text/audio to be `inline`
|
||||
- Send secure default HTTP headers such as a strong restrictive CSP, deny iframes, disable `X-XSS-Protection`, disable interest cohort in `Permission-Policy`, etc to mitigate any potential attack surface such as from untrusted media
|
||||
|
||||
|
||||
## Administration/Logging:
|
||||
@@ -81,7 +86,7 @@ ## Administration/Logging:
|
||||
- Warn on unknown config options specified
|
||||
- Add `/_conduwuit/server_version` route to return the version of conduwuit without relying on the federation API `/_matrix/federation/v1/version`
|
||||
- Add configurable RocksDB recovery modes to aid in recovering corrupted RocksDB databases
|
||||
- Support config options via `CONDUWUIT_` prefix
|
||||
- Support config options via `CONDUWUIT_` prefix and accessing non-global struct config options with the `__` split (e.g. `CONDUWUIT_WELL_KNOWN__SERVER`)
|
||||
- Add support for listening on multiple TCP ports
|
||||
- Disable update check by default as it's not useful for conduwuit
|
||||
- **Opt-in** Sentry.io telemetry and metrics, mainly used for crash reporting
|
||||
@@ -89,7 +94,8 @@ ## Administration/Logging:
|
||||
|
||||
## Maintenance/Stability:
|
||||
- GitLab CI ported to GitHub Actions
|
||||
- Repo is mirrored to GitHub, GitLab, git.gay, sourcehut, and Codeberg (see README.md for their links)
|
||||
- Repo is mirrored to GitHub, GitLab, git.gay, git.girlcock.ceo, sourcehut, and Codeberg (see README.md for their links)
|
||||
- Docker container images published to GitLab Container Registry, GitHub Container Registry, and Dockerhub
|
||||
- Extensively revamp the example config to be extremely helpful and useful to both new users and power users
|
||||
- Fixed every single clippy (default lints) and rustc warnings, including some that were performance related or potential safety issues / unsoundness
|
||||
- Add a **lot** of other clippy and rustc lints and a rustfmt.toml file
|
||||
@@ -146,6 +152,8 @@ ## Misc:
|
||||
- Implement running and diff'ing Complement results in CI
|
||||
- Interest in supporting other operating systems such as macOS, BSDs, and Windows, and getting them added into CI and doing builds for them
|
||||
- Add config option for disabling RocksDB Direct IO if needed
|
||||
- Add various documentation on maintaining conduwuit, using RocksDB online backups, some troubleshooting, using admin commands, etc
|
||||
- (Developers): Add support for [hot reloadable/"live" modular development](development/hot_reload.md)
|
||||
- (Developers): Add support for tokio-console
|
||||
- (Developers): Add support for tracing flame graphs
|
||||
- Add `release-debuginfo` Cargo build profile
|
||||
|
||||
@@ -6,7 +6,7 @@ # Conduwuit
|
||||
|
||||
#### What's different about your fork than upstream Conduit?
|
||||
|
||||
See [differences.md](differences.md)
|
||||
See the [differences](differences.md) page
|
||||
|
||||
#### How can I deploy my own?
|
||||
|
||||
@@ -14,4 +14,8 @@ #### How can I deploy my own?
|
||||
|
||||
If you want to connect an Appservice to Conduwuit, take a look at the [appservices documentation](appservices.md).
|
||||
|
||||
#### How can I contribute?
|
||||
|
||||
See the [contributor's guide](contributing.md)
|
||||
|
||||
{{#include ../README.md:footer}}
|
||||
|
||||
63
docs/maintenance.md
Normal file
63
docs/maintenance.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Maintaining your conduwuit setup
|
||||
|
||||
## Moderation
|
||||
|
||||
conduwuit has moderation through admin room commands. "binary commands" (medium priority) and an admin API (low priority) is planned. Some moderation-related config options are available in the example config such as "global ACLs" and blocking media requests to certain servers. See the example config for the moderation config options under the "Moderation / Privacy / Security" section.
|
||||
|
||||
conduwuit has moderation admin commands for:
|
||||
- managing room aliases (`!admin rooms alias`)
|
||||
- managing room directory (`!admin rooms directory`)
|
||||
- managing room banning/blocking and user removal (`!admin rooms moderation`)
|
||||
- managing user accounts (`!admin users`)
|
||||
- fetching `/.well-known/matrix/support` from servers (`!admin federation`)
|
||||
- blocking incoming federation for certain rooms (not the same as room banning) (`!admin federation`)
|
||||
- deleting media (see [the media section](#media))
|
||||
|
||||
Any commands with `-list` in them will require a codeblock in the message with each object being newline delimited. An example of doing this is:
|
||||
|
||||
````
|
||||
!admin rooms moderation ban-list-of-rooms
|
||||
```
|
||||
!roomid1:server.name
|
||||
!roomid2:server.name
|
||||
!roomid3:server.name
|
||||
```
|
||||
````
|
||||
|
||||
## Database
|
||||
|
||||
If using RocksDB, there's very little you need to do. Compaction is ran automatically based on various defined thresholds tuned for conduwuit to be high performance with the least I/O amplifcation or overhead. Manually running compaction is not recommended, or compaction via a timer. RocksDB is built with io_uring support via liburing for async read I/O.
|
||||
|
||||
Some RocksDB settings can be adjusted such as the compression method chosen. See the RocksDB section in the [example config](configuration.md). btrfs users may benefit from disabling compression on RocksDB if CoW is in use.
|
||||
|
||||
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](troubleshooting.md).
|
||||
|
||||
## Backups
|
||||
|
||||
Currently only RocksDB supports online backups. If you'd like to backup your database online without any downtime, see the `!admin server` command for the backup commands and the `database_backup_path` config options in the example config. Please note that the format of the database backup is not the exact same. This is unfortunately a bad design choice by Facebook as we are using the database backup engine API from RocksDB, however the data is still there and can still be joined together.
|
||||
|
||||
To restore a backup from an online RocksDB backup:
|
||||
- shutdown conduwuit
|
||||
- create a new directory for merging together the data
|
||||
- in the online backup created, copy all `.sst` files in `$DATABASE_BACKUP_PATH/shared_checksum` to your new directory
|
||||
- trim all the strings so instead of `######_sxxxxxxxxx.sst`, it reads `######.sst`. A way of doing this with sed and bash is `for file in *.sst; do mv "$file" "$(echo "$file" | sed 's/_s.*/.sst/')"; done`
|
||||
- copy all the files in `$DATABASE_BACKUP_PATH/1` to your new directory
|
||||
- set your `database_path` config option to your new directory, or replace your old one with the new one you crafted
|
||||
- start up conduwuit again and it should open as normal
|
||||
|
||||
If you'd like to do an offline backup, shutdown conduwuit and copy your `database_path` directory elsewhere. This can be restored with no modifications needed.
|
||||
|
||||
Backing up media is also just copying the `media/` directory from your database directory.
|
||||
|
||||
## Media
|
||||
|
||||
Media still needs various work, however conduwuit implements media deletion via:
|
||||
- MXC URI
|
||||
- Delete list of MXC URIs
|
||||
- Delete remote media in the past `N` seconds/minutes
|
||||
|
||||
See the `!admin media` command for further information. All media in conduwuit is stored at `$DATABASE_DIR/media`. This will be configurable soon.
|
||||
|
||||
If you are finding yourself needing extensive granular control over media, we recommend looking into [Matrix Media Repo](https://github.com/t2bot/matrix-media-repo). conduwuit intends to implement various utilities for media, but MMR is dedicated to extensive media management.
|
||||
|
||||
Built-in S3 support is also planned, but for now using a "S3 filesystem" on `media/` works. conduwuit also sends a `Cache-Control` header of 1 year and immutable for all media requests (download and thumbnail) to reduce unnecessary media requests from browsers.
|
||||
62
docs/troubleshooting.md
Normal file
62
docs/troubleshooting.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Troubleshooting conduwuit
|
||||
|
||||
> ## Docker users ⚠️
|
||||
>
|
||||
> Docker is extremely UX unfriendly. Because of this, a ton of issues or support is actually Docker support, not conduwuit support. We also cannot document the ever-growing list of Docker issues here.
|
||||
>
|
||||
> 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.
|
||||
|
||||
## Rocksdb / database issues
|
||||
|
||||
#### Direct IO
|
||||
|
||||
Some filesystems may not like RocksDB using [Direct IO](https://github.com/facebook/rocksdb/wiki/Direct-IO). Direct IO is for non-buffered I/O which improves conduwuit performance, but at least FUSE is a filesystem potentially known to not like this. See the [example config](configuration.md) for disabling it if needed. Issues from Direct IO on unsupported filesystems are usually shown as startup errors.
|
||||
|
||||
#### Database corruption
|
||||
|
||||
If your database is corrupted and is failing to start (e.g. checksum mismatch), it may be recoverable but careful steps must be taken, and there is no guarantee it may be recoverable.
|
||||
|
||||
RocksDB has the following recovery modes:
|
||||
|
||||
- `TolerateCorruptedTailRecords`
|
||||
- `AbsoluteConsistency`
|
||||
- `PointInTime`
|
||||
- `SkipAnyCorruptedRecord`
|
||||
|
||||
By default, conduwuit uses `TolerateCorruptedTailRecords` as generally these may be due to bad federation and we can re-fetch the correct data over federation. The RocksDB default is `PointInTime` which will attempt to restore a "snapshot" of the data when it was last known to be good. This data can be either a few seconds old, or multiple minutes prior. `PointInTime` may not be suitable for default usage due to clients and servers possibly not being able to handle sudden "backwards time travels", and `AbsoluteConsistency` may be too strict.
|
||||
|
||||
`AbsoluteConsistency` will fail to start the database if any sign of corruption is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless it forbids the database from opening (e.g. too severe). Usage of `SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or leave your database in a permanently inconsistent state, but it may do something if `PointInTime` does not work as a last ditch effort.
|
||||
|
||||
With this in mind:
|
||||
- First start conduwuit with the `PointInTime` recovery method. See the [example config](configuration.md) for how to do this using `rocksdb_recovery_mode`
|
||||
- If your database successfully opens, clients are recommended to clear their client cache to account for the rollback
|
||||
- Leave your conduwuit running in `PointInTime` for at least 30-60 minutes so as much possible corruption is restored
|
||||
- If all goes will, you should be able to restore back to using `TolerateCorruptedTailRecords` and you have successfully recovered your database
|
||||
|
||||
## Media
|
||||
|
||||
#### "File name too long"
|
||||
|
||||
If you are running into the "file name is too long" OS error for media requests, your filesystem cannot handle file name lengths >=255 characters. This is unfortuntely due to Conduit (upstream) using base64 for file name keys which is very problematic for some filesystems as the base64 input is untrusted and long file names or specific inputs can cause this. If you would like to avoid this, you may build conduwuit yourself with the `sha256_media` feature. **This will lose database compatibility with upstream**.
|
||||
|
||||
## Debugging
|
||||
|
||||
Note that users should not really be debugging things. If you find yourself debugging and find the issue, please let us know and/or how we can fix it. Various debug commands can be found in `!admin debug`.
|
||||
|
||||
#### Debug/Trace log level
|
||||
|
||||
conduwuit builds without debug or trace log levels by default for at least performance reasons. This may change in the future and/or binaries providing such configurations may be provided. If you need to access debug/trace log levels, you will need to build without the `release_max_log_level` feature.
|
||||
|
||||
#### Changing log level dynamically
|
||||
|
||||
conduwuit supports changing the tracing log environment filter on-the-fly using the admin command `!admin debug change-log-level`. This accepts a string **without quotes** the same format as the `log` config option.
|
||||
|
||||
#### Pinging servers
|
||||
|
||||
conduwuit can ping other servers using `!admin debug ping`. This takes a server name and goes through the server discovery process and queries `/_matrix/federation/v1/version`. Errors are outputted.
|
||||
|
||||
#### Allocator memory stats
|
||||
|
||||
When using jemalloc with jemallocator's `stats` feature, you can see conduwuit's jemalloc memory stats by using `!admin debug memory-stats`
|
||||
62
docs/turn.md
62
docs/turn.md
@@ -1,25 +1,55 @@
|
||||
# Setting up TURN/STURN
|
||||
|
||||
## General instructions
|
||||
In order to make or receive calls, a TURN server is required. conduwuit suggests using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also available as a Docker image.
|
||||
|
||||
* It is assumed you have a [Coturn server](https://github.com/coturn/coturn) up and running. See [Synapse reference implementation](https://github.com/matrix-org/synapse/blob/develop/docs/turn-howto.md).
|
||||
### Configuration
|
||||
|
||||
## Edit/Add a few settings to your existing conduit.toml
|
||||
Create a configuration file called `coturn.conf` containing:
|
||||
|
||||
```conf
|
||||
use-auth-secret
|
||||
static-auth-secret=<a secret key>
|
||||
realm=<your server domain>
|
||||
```
|
||||
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
|
||||
|
||||
These same values need to be set in conduwuit. You can either modify conduwuit.toml to include these lines:
|
||||
|
||||
```
|
||||
# Refer to your Coturn settings.
|
||||
# `your.turn.url` has to match the REALM setting of your Coturn as well as `transport`.
|
||||
turn_uris = ["turn:your.turn.url?transport=udp", "turn:your.turn.url?transport=tcp"]
|
||||
|
||||
# static-auth-secret of your turnserver
|
||||
turn_secret = "ADD SECRET HERE"
|
||||
|
||||
# If you have your TURN server configured to use a username and password
|
||||
# you can provide these information too. In this case comment out `turn_secret above`!
|
||||
#turn_username = ""
|
||||
#turn_password = ""
|
||||
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
|
||||
turn_secret = "<secret key from coturn configuration>"
|
||||
```
|
||||
|
||||
## Apply settings
|
||||
or append the following to the docker environment variables dependig on which configuration method you used earlier:
|
||||
|
||||
Restart Conduit.
|
||||
```yml
|
||||
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
|
||||
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
|
||||
```
|
||||
|
||||
Restart conduwuit to apply these changes.
|
||||
|
||||
### Run
|
||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||
```bash
|
||||
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
||||
```
|
||||
|
||||
or docker-compose. For the latter, paste the following section into a file called `docker-compose.yml`
|
||||
and run `docker compose up -d` in the same directory.
|
||||
|
||||
```yml
|
||||
version: 3
|
||||
services:
|
||||
turn:
|
||||
container_name: coturn-server
|
||||
image: docker.io/coturn/coturn
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- ./coturn.conf:/etc/coturn/turnserver.conf
|
||||
```
|
||||
|
||||
To understand why the host networking mode is used and explore alternative configuration options, please visit [Coturn's Docker documentation](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md).
|
||||
|
||||
For security recommendations see Synapse's [Coturn documentation](https://element-hq.github.io/synapse/latest/turn-howto.html).
|
||||
|
||||
89
engage.toml
89
engage.toml
@@ -58,7 +58,7 @@ script = "lychee --version"
|
||||
[[task]]
|
||||
name = "cargo-audit"
|
||||
group = "security"
|
||||
script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked --ignore RUSTSEC-2020-0016"
|
||||
script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked"
|
||||
|
||||
[[task]]
|
||||
name = "cargo-fmt"
|
||||
@@ -69,12 +69,15 @@ script = "cargo fmt --check -- --color=always"
|
||||
name = "cargo-doc"
|
||||
group = "lints"
|
||||
script = """
|
||||
RUSTDOCFLAGS="-D warnings" cargo doc \
|
||||
--workspace \
|
||||
--all-features \
|
||||
--no-deps \
|
||||
--document-private-items \
|
||||
--color always
|
||||
env DIRENV_DEVSHELL=all-features \
|
||||
RUSTDOCFLAGS="-D warnings" \
|
||||
direnv exec . \
|
||||
cargo doc \
|
||||
--workspace \
|
||||
--all-features \
|
||||
--no-deps \
|
||||
--document-private-items \
|
||||
--color always
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
@@ -93,13 +96,15 @@ cargo clippy \
|
||||
name = "clippy/all"
|
||||
group = "lints"
|
||||
script = """
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
env DIRENV_DEVSHELL=all-features \
|
||||
direnv exec . \
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
@@ -115,33 +120,59 @@ cargo clippy \
|
||||
-D warnings
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "clippy/hardened_malloc"
|
||||
group = "lints"
|
||||
script = """
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--features hardened_malloc \
|
||||
--all-targets \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
"""
|
||||
#[[task]]
|
||||
#name = "clippy/hardened_malloc"
|
||||
#group = "lints"
|
||||
#script = """
|
||||
#cargo clippy \
|
||||
# --workspace \
|
||||
# --features hardened_malloc \
|
||||
# --all-targets \
|
||||
# --color=always \
|
||||
# -- \
|
||||
# -D warnings
|
||||
#"""
|
||||
|
||||
[[task]]
|
||||
name = "lychee"
|
||||
group = "lints"
|
||||
script = "lychee --offline docs"
|
||||
script = "lychee --verbose --offline docs *.md"
|
||||
|
||||
[[task]]
|
||||
name = "cargo"
|
||||
name = "cargo/all"
|
||||
group = "tests"
|
||||
script = """
|
||||
env DIRENV_DEVSHELL=all-features \
|
||||
direnv exec . \
|
||||
cargo test \
|
||||
--workspace \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "cargo/default"
|
||||
group = "tests"
|
||||
script = """
|
||||
cargo test \
|
||||
--workspace \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
"""
|
||||
|
||||
# Ensure that the flake's default output can build and run without crashing
|
||||
#
|
||||
# This is a dynamically-linked jemalloc build, which is a case not covered by
|
||||
# our other tests. We've had linking problems in the past with dynamic
|
||||
# jemalloc builds that usually show up as an immediate segfault or "invalid free"
|
||||
[[task]]
|
||||
name = "nix-default"
|
||||
group = "tests"
|
||||
script = """
|
||||
nix run .#default -- --help
|
||||
"""
|
||||
|
||||
60
flake.lock
generated
60
flake.lock
generated
@@ -26,11 +26,11 @@
|
||||
"complement": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1714472853,
|
||||
"narHash": "sha256-CNRHSZe3TE+3tFj2dHNyxTMjDqL0MKY3P/3jqUgA7YE=",
|
||||
"lastModified": 1715700731,
|
||||
"narHash": "sha256-cie+b5N/TQAFD8vF/XbqfyFJkFU0qUPDbtJQDm/TfQc=",
|
||||
"owner": "matrix-org",
|
||||
"repo": "complement",
|
||||
"rev": "891d18872c153d39a9ce63b545045efddb845738",
|
||||
"rev": "8587fb3cbe746754b2c883ff6c818ca4d987d0a5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -68,11 +68,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1713738183,
|
||||
"narHash": "sha256-qd/MuLm7OfKQKyd4FAMqV4H6zYyOfef5lLzRrmXwKJM=",
|
||||
"lastModified": 1716569590,
|
||||
"narHash": "sha256-5eDbq8TuXFGGO3mqJFzhUbt5zHVTf5zilQoyW5jnJwo=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "f6c6a2fb1b8bd9b65d65ca9342dd0eb180a63f11",
|
||||
"rev": "109987da061a1bf452f435f1653c47511587d919",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -90,11 +90,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1714544767,
|
||||
"narHash": "sha256-kF1bX+YFMedf1g0PAJYwGUkzh22JmULtj8Rm4IXAQKs=",
|
||||
"lastModified": 1716359173,
|
||||
"narHash": "sha256-pYcjP6Gy7i6jPWrjiWAVV0BCQp+DdmGaI/k65lBb/kM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "73124e1356bde9411b163d636b39fe4804b7ca45",
|
||||
"rev": "b6fc5035b28e36a98370d0eac44f4ef3fd323df6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -171,6 +171,23 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"liburing": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1716565485,
|
||||
"narHash": "sha256-4R19aJNQYs6vb0/Hz4bWT56YN1P1DkFL/sxdE4Yj0CE=",
|
||||
"owner": "axboe",
|
||||
"repo": "liburing",
|
||||
"rev": "b90c0e670a93caabbebe2d9e24ff85cece4cfe0e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "axboe",
|
||||
"ref": "master",
|
||||
"repo": "liburing",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1710156097,
|
||||
@@ -221,11 +238,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1713537308,
|
||||
"narHash": "sha256-XtTSSIB2DA6tOv+l0FhvfDMiyCmhoRbNB+0SeInZkbk=",
|
||||
"lastModified": 1716330097,
|
||||
"narHash": "sha256-8BO3B7e3BiyIDsaKA0tY8O88rClYRTjvAp66y+VBUeU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5c24cf2f0a12ad855f444c30b2421d044120c66f",
|
||||
"rev": "5710852ba686cc1fd0d3b8e22b3117d43ba374c2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -238,16 +255,16 @@
|
||||
"rocksdb": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1713810944,
|
||||
"narHash": "sha256-/Xf0bzNJPclH9IP80QNaABfhj4IAR5LycYET18VFCXc=",
|
||||
"owner": "facebook",
|
||||
"lastModified": 1716773462,
|
||||
"narHash": "sha256-5kUH+XK+2lbFfUgbxuNy3YMLHbp6scfWPdtc8za1wDM=",
|
||||
"owner": "girlbossceo",
|
||||
"repo": "rocksdb",
|
||||
"rev": "6f7cabeac80a3a6150be2c8a8369fcecb107bf43",
|
||||
"rev": "c8a1450231e9c608edf535538dbe8ca1a8d2f3bc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "facebook",
|
||||
"ref": "v9.1.1",
|
||||
"owner": "girlbossceo",
|
||||
"ref": "v9.2.1",
|
||||
"repo": "rocksdb",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -260,6 +277,7 @@
|
||||
"fenix": "fenix",
|
||||
"flake-compat": "flake-compat_2",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"liburing": "liburing",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"rocksdb": "rocksdb"
|
||||
@@ -268,11 +286,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1713628977,
|
||||
"narHash": "sha256-iN5QUlUq527lswmBC+RopfXdu6Xx7mmTaBSH2l59FtM=",
|
||||
"lastModified": 1716107283,
|
||||
"narHash": "sha256-NJgrwLiLGHDrCia5AeIvZUHUY7xYGVryee0/9D3Ir1I=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "55d9a533b309119c8acd13061581b43ae8840823",
|
||||
"rev": "21ec8f523812b88418b2bfc64240c62b3dd967bd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
165
flake.nix
165
flake.nix
@@ -8,23 +8,27 @@
|
||||
flake-utils.url = "github:numtide/flake-utils?ref=main";
|
||||
nix-filter.url = "github:numtide/nix-filter?ref=main";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
||||
rocksdb = { url = "github:facebook/rocksdb?ref=v9.1.1"; flake = false; };
|
||||
# https://github.com/girlbossceo/rocksdb/commit/db6df0b185774778457dabfcbd822cb81760cade
|
||||
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.2.1"; flake = false; };
|
||||
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
|
||||
};
|
||||
|
||||
outputs = inputs:
|
||||
inputs.flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgsHost = inputs.nixpkgs.legacyPackages.${system};
|
||||
pkgsHostStatic = pkgsHost.pkgsStatic;
|
||||
|
||||
# The Rust toolchain to use
|
||||
toolchain = inputs.fenix.packages.${system}.fromToolchainFile {
|
||||
file = ./rust-toolchain.toml;
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-e4mlaJehWBymYxJGgnbuCObVlqMlQSilZ8FljG9zPHY=";
|
||||
sha256 = "sha256-+syqAd2kX8KVa8/U2gz3blIQTTsYYt3U63xBWaGOSc8";
|
||||
};
|
||||
|
||||
scope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
||||
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
||||
inherit pkgs;
|
||||
book = self.callPackage ./nix/pkgs/book {};
|
||||
complement = self.callPackage ./nix/pkgs/complement {};
|
||||
craneLib = ((inputs.crane.mkLib pkgs).overrideToolchain toolchain);
|
||||
@@ -38,22 +42,87 @@
|
||||
(builtins.fromJSON (builtins.readFile ./flake.lock))
|
||||
.nodes.rocksdb.original.ref;
|
||||
});
|
||||
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
|
||||
liburing = pkgs.liburing.overrideAttrs (old: {
|
||||
# the configure script doesn't support these, and unconditionally
|
||||
# builds both static and dynamic libraries.
|
||||
configureFlags = pkgs.lib.subtractLists
|
||||
[ "--enable-static" "--disable-shared" ]
|
||||
old.configureFlags;
|
||||
|
||||
postInstall = old.postInstall + ''
|
||||
# we remove the extra outputs
|
||||
#
|
||||
# we need to do this to prevent rocksdb from trying to link the
|
||||
# static library in a dynamic stdenv
|
||||
rm $out/lib/liburing*${
|
||||
if pkgs.stdenv.hostPlatform.isStatic then ".so*" else ".a"
|
||||
}
|
||||
'';
|
||||
});
|
||||
});
|
||||
|
||||
scopeHost = (scope pkgsHost);
|
||||
scopeHost = mkScope pkgsHost;
|
||||
scopeHostStatic = mkScope pkgsHostStatic;
|
||||
|
||||
mkDevShell = scope: scope.pkgs.mkShell {
|
||||
env = scope.main.env // {
|
||||
# Rust Analyzer needs to be able to find the path to default crate
|
||||
# sources, and it can read this environment variable to do so. The
|
||||
# `rust-src` component is required in order for this to work.
|
||||
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||
|
||||
# Convenient way to access a pinned version of Complement's source
|
||||
# code.
|
||||
COMPLEMENT_SRC = inputs.complement.outPath;
|
||||
|
||||
# Needed for Complement
|
||||
CGO_CFLAGS = "-I${scope.pkgs.olm}/include";
|
||||
CGO_LDFLAGS = "-L${scope.pkgs.olm}/lib";
|
||||
};
|
||||
|
||||
# Development tools
|
||||
packages = [
|
||||
# Always use nightly rustfmt because most of its options are unstable
|
||||
#
|
||||
# This needs to come before `toolchain` in this list, otherwise
|
||||
# `$PATH` will have stable rustfmt instead.
|
||||
inputs.fenix.packages.${system}.latest.rustfmt
|
||||
|
||||
toolchain
|
||||
]
|
||||
++ (with pkgsHost.pkgs; [
|
||||
engage
|
||||
cargo-audit
|
||||
|
||||
# Needed for producing Debian packages
|
||||
cargo-deb
|
||||
|
||||
# Needed for Complement
|
||||
go
|
||||
|
||||
# Needed for our script for Complement
|
||||
jq
|
||||
|
||||
# Needed for finding broken markdown links
|
||||
lychee
|
||||
|
||||
# Useful for editing the book locally
|
||||
mdbook
|
||||
])
|
||||
++ scope.main.buildInputs
|
||||
++ scope.main.propagatedBuildInputs
|
||||
++ scope.main.nativeBuildInputs;
|
||||
|
||||
meta.broken = scope.main.meta.broken;
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
default = scopeHost.main;
|
||||
jemalloc = scopeHost.main.override { features = ["jemalloc"]; };
|
||||
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
|
||||
|
||||
oci-image = scopeHost.oci-image;
|
||||
oci-image-jemalloc = scopeHost.oci-image.override {
|
||||
main = scopeHost.main.override {
|
||||
features = ["jemalloc"];
|
||||
};
|
||||
};
|
||||
oci-image-hmalloc = scopeHost.oci-image.override {
|
||||
main = scopeHost.main.override {
|
||||
features = ["hardened_malloc"];
|
||||
@@ -63,6 +132,7 @@
|
||||
book = scopeHost.book;
|
||||
|
||||
complement = scopeHost.complement;
|
||||
static-complement = scopeHostStatic.complement;
|
||||
}
|
||||
//
|
||||
builtins.listToAttrs
|
||||
@@ -78,7 +148,7 @@
|
||||
config = crossSystem;
|
||||
};
|
||||
}).pkgsStatic;
|
||||
scopeCrossStatic = scope pkgsCrossStatic;
|
||||
scopeCrossStatic = mkScope pkgsCrossStatic;
|
||||
in
|
||||
[
|
||||
# An output for a statically-linked binary
|
||||
@@ -87,14 +157,6 @@
|
||||
value = scopeCrossStatic.main;
|
||||
}
|
||||
|
||||
# An output for a statically-linked binary with jemalloc
|
||||
{
|
||||
name = "${binaryName}-jemalloc";
|
||||
value = scopeCrossStatic.main.override {
|
||||
features = ["jemalloc"];
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a statically-linked binary with hardened_malloc
|
||||
{
|
||||
name = "${binaryName}-hmalloc";
|
||||
@@ -109,16 +171,6 @@
|
||||
value = scopeCrossStatic.oci-image;
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary with jemalloc
|
||||
{
|
||||
name = "oci-image-${crossSystem}-jemalloc";
|
||||
value = scopeCrossStatic.oci-image.override {
|
||||
main = scopeCrossStatic.main.override {
|
||||
features = ["jemalloc"];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary with hardened_malloc
|
||||
{
|
||||
name = "oci-image-${crossSystem}-hmalloc";
|
||||
@@ -137,50 +189,15 @@
|
||||
)
|
||||
);
|
||||
|
||||
devShells.default = pkgsHost.mkShell {
|
||||
env = scopeHost.main.env // {
|
||||
# Rust Analyzer needs to be able to find the path to default crate
|
||||
# sources, and it can read this environment variable to do so. The
|
||||
# `rust-src` component is required in order for this to work.
|
||||
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||
|
||||
# Convenient way to access a pinned version of Complement's source
|
||||
# code.
|
||||
COMPLEMENT_SRC = inputs.complement.outPath;
|
||||
};
|
||||
|
||||
# Development tools
|
||||
packages = [
|
||||
# Always use nightly rustfmt because most of its options are unstable
|
||||
#
|
||||
# This needs to come before `toolchain` in this list, otherwise
|
||||
# `$PATH` will have stable rustfmt instead.
|
||||
inputs.fenix.packages.${system}.latest.rustfmt
|
||||
|
||||
toolchain
|
||||
]
|
||||
++ (with pkgsHost; [
|
||||
engage
|
||||
cargo-audit
|
||||
|
||||
# Needed for producing Debian packages
|
||||
cargo-deb
|
||||
|
||||
# Needed for Complement
|
||||
go
|
||||
olm
|
||||
|
||||
# Needed for our script for Complement
|
||||
jq
|
||||
|
||||
# Needed for finding broken markdown links
|
||||
lychee
|
||||
|
||||
# Useful for editing the book locally
|
||||
mdbook
|
||||
])
|
||||
++
|
||||
scopeHost.main.nativeBuildInputs;
|
||||
};
|
||||
devShells.default = mkDevShell scopeHostStatic;
|
||||
devShells.all-features = mkDevShell
|
||||
(scopeHostStatic.overrideScope (final: prev: {
|
||||
main = prev.main.override { all_features = true; };
|
||||
}));
|
||||
devShells.no-features = mkDevShell
|
||||
(scopeHostStatic.overrideScope (final: prev: {
|
||||
main = prev.main.override { default_features = false; };
|
||||
}));
|
||||
devShells.dynamic = mkDevShell scopeHost;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ stdenv.mkDerivation {
|
||||
include = [
|
||||
"book.toml"
|
||||
"conduwuit-example.toml"
|
||||
"CONTRIBUTING.md"
|
||||
"README.md"
|
||||
"debian/conduwuit.service"
|
||||
"debian/README.md"
|
||||
"docs"
|
||||
];
|
||||
|
||||
@@ -44,16 +44,14 @@ let
|
||||
-sha256
|
||||
|
||||
${lib.getExe' coreutils "env"} \
|
||||
CONDUIT_SERVER_NAME="$SERVER_NAME" \
|
||||
CONDUIT_WELL_KNOWN_SERVER="$SERVER_NAME:8448" \
|
||||
CONDUIT_WELL_KNOWN_SERVER="$SERVER_NAME:8008" \
|
||||
CONDUWUIT_SERVER_NAME="$SERVER_NAME" \
|
||||
${lib.getExe main'}
|
||||
'';
|
||||
in
|
||||
|
||||
dockerTools.buildImage {
|
||||
name = "complement-${main.pname}";
|
||||
tag = "dev";
|
||||
tag = "main";
|
||||
|
||||
copyToRoot = buildEnv {
|
||||
name = "root";
|
||||
@@ -81,7 +79,7 @@ dockerTools.buildImage {
|
||||
|
||||
Env = [
|
||||
"SSL_CERT_FILE=/complement/ca/ca.crt"
|
||||
"CONDUIT_CONFIG=${./config.toml}"
|
||||
"CONDUWUIT_CONFIG=${./config.toml}"
|
||||
];
|
||||
|
||||
ExposedPorts = {
|
||||
|
||||
@@ -1,27 +1,87 @@
|
||||
# Dependencies (keep sorted)
|
||||
{ craneLib
|
||||
, inputs
|
||||
, jq
|
||||
, lib
|
||||
, libiconv
|
||||
, liburing
|
||||
, pkgsBuildHost
|
||||
, rocksdb
|
||||
, rust
|
||||
, rust-jemalloc-sys
|
||||
, stdenv
|
||||
|
||||
# Options (keep sorted)
|
||||
, default_features ? true
|
||||
, disable_release_max_log_level ? false
|
||||
, all_features ? false
|
||||
, disable_features ? []
|
||||
, features ? []
|
||||
, profile ? "release"
|
||||
}:
|
||||
|
||||
let
|
||||
# We perform default-feature unification in nix, because some of the dependencies
|
||||
# on the nix side depend on feature values.
|
||||
crateFeatures = path:
|
||||
let manifest = lib.importTOML "${path}/Cargo.toml"; in
|
||||
lib.remove "default" (lib.attrNames manifest.features) ++
|
||||
lib.attrNames
|
||||
(lib.filterAttrs
|
||||
(_: dependency: dependency.optional or false)
|
||||
manifest.dependencies);
|
||||
crateDefaultFeatures = path:
|
||||
(lib.importTOML "${path}/Cargo.toml").features.default;
|
||||
allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main";
|
||||
allFeatures = crateFeatures "${inputs.self}/src/main";
|
||||
features' = lib.unique
|
||||
(features ++
|
||||
lib.optionals default_features allDefaultFeatures ++
|
||||
lib.optionals all_features allFeatures);
|
||||
disable_features' = disable_features ++ lib.optionals disable_release_max_log_level ["release_max_log_level"];
|
||||
features'' = lib.subtractLists disable_features' features';
|
||||
|
||||
featureEnabled = feature : builtins.elem feature features'';
|
||||
|
||||
enableLiburing = featureEnabled "io_uring" && stdenv.isLinux;
|
||||
|
||||
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
|
||||
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
|
||||
# own. In order for this to work, we need to set flags on the build that match
|
||||
# whatever flags tikv-jemalloc-sys was going to use. These are dependent on
|
||||
# which features we enable in tikv-jemalloc-sys.
|
||||
rust-jemalloc-sys' = (rust-jemalloc-sys.override {
|
||||
# tikv-jemalloc-sys/unprefixed_malloc_on_supported_platforms feature
|
||||
unprefixed = true;
|
||||
}).overrideAttrs (old: {
|
||||
configureFlags = old.configureFlags ++
|
||||
# tikv-jemalloc-sys/profiling feature
|
||||
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof";
|
||||
});
|
||||
|
||||
buildDepsOnlyEnv =
|
||||
let
|
||||
rocksdb' = rocksdb.override {
|
||||
enableJemalloc = builtins.elem "jemalloc" features;
|
||||
};
|
||||
rocksdb' = (rocksdb.override {
|
||||
jemalloc = rust-jemalloc-sys';
|
||||
# rocksdb fails to build with prefixed jemalloc, which is required on
|
||||
# darwin due to [1]. In this case, fall back to building rocksdb with
|
||||
# libc malloc. This should not cause conflicts, because all of the
|
||||
# jemalloc symbols are prefixed.
|
||||
#
|
||||
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
|
||||
enableJemalloc = featureEnabled "jemalloc" && !stdenv.isDarwin;
|
||||
}).overrideAttrs (old: {
|
||||
# TODO: static rocksdb fails to build on darwin
|
||||
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
|
||||
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
|
||||
# TODO: switch to enableUring option once https://github.com/NixOS/nixpkgs/pull/314945 is available
|
||||
buildInputs = old.buildInputs ++ lib.optional enableLiburing liburing;
|
||||
});
|
||||
in
|
||||
{
|
||||
# https://crane.dev/faq/rebuilds-bindgen.html
|
||||
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
||||
|
||||
CARGO_PROFILE = profile;
|
||||
ROCKSDB_INCLUDE_DIR = "${rocksdb'}/include";
|
||||
ROCKSDB_LIB_DIR = "${rocksdb'}/lib";
|
||||
@@ -37,8 +97,15 @@ buildDepsOnlyEnv =
|
||||
});
|
||||
|
||||
buildPackageEnv = {
|
||||
CONDUWUIT_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev;
|
||||
} // buildDepsOnlyEnv;
|
||||
CONDUWUIT_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
|
||||
} // buildDepsOnlyEnv // {
|
||||
# Only needed in static stdenv because these are transitive dependencies of rocksdb
|
||||
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
|
||||
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
|
||||
" -L${lib.getLib liburing}/lib -luring";
|
||||
};
|
||||
|
||||
|
||||
|
||||
commonAttrs = {
|
||||
inherit
|
||||
@@ -55,18 +122,33 @@ commonAttrs = {
|
||||
include = [
|
||||
"Cargo.lock"
|
||||
"Cargo.toml"
|
||||
"hot_lib"
|
||||
"deps"
|
||||
"src"
|
||||
];
|
||||
};
|
||||
|
||||
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
|
||||
|
||||
nativeBuildInputs = [
|
||||
# bindgen needs the build platform's libclang. Apparently due to "splicing
|
||||
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
|
||||
# right thing here.
|
||||
pkgsBuildHost.rustPlatform.bindgenHook
|
||||
|
||||
# We don't actually depend on `jq`, but crane's `buildPackage` does, but
|
||||
# its `buildDepsOnly` doesn't. This causes those two derivations to have
|
||||
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
|
||||
# rebuilds of bindgen and its depedents.
|
||||
jq
|
||||
]
|
||||
++ lib.optionals stdenv.isDarwin [ libiconv ];
|
||||
++ lib.optionals stdenv.isDarwin [
|
||||
# https://github.com/NixOS/nixpkgs/issues/206242
|
||||
libiconv
|
||||
|
||||
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
|
||||
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
|
||||
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
|
||||
];
|
||||
};
|
||||
in
|
||||
|
||||
@@ -75,15 +157,14 @@ craneLib.buildPackage ( commonAttrs // {
|
||||
env = buildDepsOnlyEnv;
|
||||
});
|
||||
|
||||
cargoExtraArgs = ""
|
||||
cargoExtraArgs = "--no-default-features "
|
||||
+ lib.optionalString
|
||||
(!default_features)
|
||||
"--no-default-features "
|
||||
+ lib.optionalString
|
||||
(features != [])
|
||||
"--features " + (builtins.concatStringsSep "," features);
|
||||
(features'' != [])
|
||||
"--features " + (builtins.concatStringsSep "," features'');
|
||||
|
||||
# This is redundant with CI
|
||||
cargoTestCommand = "";
|
||||
cargoCheckCommand = "";
|
||||
doCheck = false;
|
||||
|
||||
env = buildPackageEnv;
|
||||
|
||||
@@ -11,5 +11,6 @@
|
||||
},
|
||||
"nix": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"labels": ["dependencies", "github_actions"]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||
|
||||
[toolchain]
|
||||
channel = "1.76.0"
|
||||
channel = "1.77.0"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
"rust-src",
|
||||
|
||||
61
src/admin/Cargo.toml
Normal file
61
src/admin/Cargo.toml
Normal file
@@ -0,0 +1,61 @@
|
||||
[package]
|
||||
name = "conduit_admin"
|
||||
categories.workspace = true
|
||||
description.workspace = true
|
||||
edition.workspace = true
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "mod.rs"
|
||||
crate-type = [
|
||||
"rlib",
|
||||
# "dylib",
|
||||
]
|
||||
|
||||
[features]
|
||||
dev_release_log_level = []
|
||||
release_max_log_level = [
|
||||
"tracing/max_level_trace",
|
||||
"tracing/release_max_level_info",
|
||||
"log/max_level_trace",
|
||||
"log/release_max_level_info",
|
||||
]
|
||||
rocksdb = [
|
||||
"dep:rust-rocksdb",
|
||||
]
|
||||
jemalloc = [
|
||||
"rust-rocksdb/jemalloc",
|
||||
]
|
||||
io_uring = [
|
||||
"rust-rocksdb/io-uring",
|
||||
]
|
||||
zstd_compression = [
|
||||
"rust-rocksdb/zstd",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
conduit-api.workspace = true
|
||||
conduit-core.workspace = true
|
||||
conduit-database.workspace = true
|
||||
conduit-service.workspace = true
|
||||
futures-util.workspace = true
|
||||
log.workspace = true
|
||||
loole.workspace = true
|
||||
regex.workspace = true
|
||||
ruma.workspace = true
|
||||
rust-rocksdb.optional = true
|
||||
rust-rocksdb.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,28 +1,28 @@
|
||||
use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent};
|
||||
|
||||
use crate::{service::admin::escape_html, services, Result};
|
||||
use crate::{escape_html, services, Result};
|
||||
|
||||
pub(crate) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
|
||||
let appservice_config = body[1..body.len() - 1].join("\n");
|
||||
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
|
||||
match parsed_config {
|
||||
Ok(yaml) => match services().appservice.register_appservice(yaml).await {
|
||||
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Appservice registered with ID: {id}."
|
||||
))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to register appservice: {e}"
|
||||
))),
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse appservice config: {e}"
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n");
|
||||
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
|
||||
match parsed_config {
|
||||
Ok(yaml) => match services().appservice.register_appservice(yaml).await {
|
||||
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Appservice registered with ID: {id}."
|
||||
))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to register appservice: {e}"
|
||||
))),
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse appservice config: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ pub(crate) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Res
|
||||
{
|
||||
Some(config) => {
|
||||
let config_str = serde_yaml::to_string(&config).expect("config should've been validated on register");
|
||||
let output = format!("Config for {}:\n\n```yaml\n{}\n```", appservice_identifier, config_str,);
|
||||
let output = format!("Config for {appservice_identifier}:\n\n```yaml\n{config_str}\n```",);
|
||||
let output_html = format!(
|
||||
"Config for {}:\n\n<pre><code class=\"language-yaml\">{}</code></pre>",
|
||||
escape_html(&appservice_identifier),
|
||||
@@ -1,8 +1,8 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use self::appservice_command::{list, register, show, unregister};
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) mod appservice_command;
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Instant};
|
||||
|
||||
use ruma::{
|
||||
api::client::error::ErrorKind, events::room::message::RoomMessageEventContent, CanonicalJsonObject, EventId,
|
||||
RoomId, RoomVersionId, ServerName,
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use api::client::validate_and_add_event_id;
|
||||
use conduit::{utils::HtmlEscape, Error, Result};
|
||||
use ruma::{
|
||||
api::{client::error::ErrorKind, federation::event::get_room_state},
|
||||
events::room::message::RoomMessageEventContent,
|
||||
CanonicalJsonObject, EventId, RoomId, RoomVersionId, ServerName,
|
||||
};
|
||||
use service::{rooms::event_handler::parse_incoming_pdu, sending::resolve::resolve_actual_dest, services, PduEvent};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, info, warn};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::{
|
||||
api::server_server::parse_incoming_pdu, service::sending::send::resolve_actual_dest, services, utils::HtmlEscape,
|
||||
Error, PduEvent, Result,
|
||||
};
|
||||
|
||||
pub(crate) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
||||
let event_id = Arc::<EventId>::from(event_id);
|
||||
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? {
|
||||
@@ -40,28 +43,30 @@ pub(crate) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
|
||||
}
|
||||
|
||||
pub(crate) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||
Ok(hash) => {
|
||||
let event_id = EventId::parse(format!("${hash}"));
|
||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
match serde_json::from_value::<PduEvent>(serde_json::to_value(value).expect("value is json")) {
|
||||
Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\nCould not parse event: {e}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))),
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||
Ok(hash) => {
|
||||
let event_id = EventId::parse(format!("${hash}"));
|
||||
|
||||
match serde_json::from_value::<PduEvent>(serde_json::to_value(value).expect("value is json")) {
|
||||
Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\nCould not parse event: {e}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid json in command body: {e}"
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain("Expected code block in command body."))
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))),
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid json in command body: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,31 +119,40 @@ pub(crate) async fn get_remote_pdu_list(
|
||||
|
||||
if server == services().globals.server_name() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
|
||||
the database.",
|
||||
));
|
||||
}
|
||||
|
||||
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
|
||||
let list = body
|
||||
.clone()
|
||||
.drain(1..body.len() - 1)
|
||||
.filter_map(|pdu| EventId::parse(pdu).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for pdu in list {
|
||||
if force {
|
||||
_ = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await;
|
||||
} else {
|
||||
get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."));
|
||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
))
|
||||
let list = body
|
||||
.clone()
|
||||
.drain(1..body.len().checked_sub(1).unwrap())
|
||||
.filter_map(|pdu| EventId::parse(pdu).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for pdu in list {
|
||||
if force {
|
||||
if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to get remote PDU, ignoring error: {e}"
|
||||
)))
|
||||
.await;
|
||||
warn!(%e, "Failed to get remote PDU, ignoring error");
|
||||
}
|
||||
} else {
|
||||
get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."))
|
||||
}
|
||||
|
||||
pub(crate) async fn get_remote_pdu(
|
||||
@@ -295,8 +309,7 @@ pub(crate) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<Ro
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Got non-JSON response which took {ping_time:?} time:\n{0:?}",
|
||||
response
|
||||
"Got non-JSON response which took {ping_time:?} time:\n{response:?}"
|
||||
)))
|
||||
},
|
||||
Err(e) => {
|
||||
@@ -332,7 +345,7 @@ pub(crate) async fn change_log_level(
|
||||
};
|
||||
|
||||
match services()
|
||||
.globals
|
||||
.server
|
||||
.tracing_reload_handle
|
||||
.reload(&old_filter_layer)
|
||||
{
|
||||
@@ -361,7 +374,7 @@ pub(crate) async fn change_log_level(
|
||||
};
|
||||
|
||||
match services()
|
||||
.globals
|
||||
.server
|
||||
.tracing_reload_handle
|
||||
.reload(&new_filter_layer)
|
||||
{
|
||||
@@ -380,56 +393,239 @@ pub(crate) async fn change_log_level(
|
||||
}
|
||||
|
||||
pub(crate) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(mut value) => {
|
||||
ruma::signatures::sign_json(
|
||||
services().globals.server_name().as_str(),
|
||||
services().globals.keypair(),
|
||||
&mut value,
|
||||
)
|
||||
.expect("our request json is what ruma expects");
|
||||
let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
|
||||
Ok(RoomMessageEventContent::text_plain(json_text))
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
}
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(mut value) => {
|
||||
ruma::signatures::sign_json(
|
||||
services().globals.server_name().as_str(),
|
||||
services().globals.keypair(),
|
||||
&mut value,
|
||||
)
|
||||
.expect("our request json is what ruma expects");
|
||||
let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
|
||||
Ok(RoomMessageEventContent::text_plain(json_text))
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
|
||||
let string = body[1..body.len() - 1].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => {
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.fetch_required_signing_keys([&value], &pub_key_map)
|
||||
.await?;
|
||||
|
||||
let pub_key_map = pub_key_map.read().await;
|
||||
match ruma::signatures::verify_json(&pub_key_map, &value) {
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Signature verification failed: {e}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
}
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => {
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.fetch_required_signing_keys([&value], &pub_key_map)
|
||||
.await?;
|
||||
|
||||
let pub_key_map = pub_key_map.read().await;
|
||||
match ruma::signatures::verify_json(&pub_key_map, &value) {
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Signature verification failed: {e}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(_body))]
|
||||
pub(crate) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
if !services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&services().globals.config.server_name, &room_id)?
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"We are not participating in the room / we don't know about the room ID.",
|
||||
));
|
||||
}
|
||||
|
||||
let first_pdu = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.first_pdu_in_room(&room_id)?
|
||||
.ok_or_else(|| Error::bad_database("Failed to find the first PDU in database"))?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}")))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(_body))]
|
||||
pub(crate) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
if !services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&services().globals.config.server_name, &room_id)?
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"We are not participating in the room / we don't know about the room ID.",
|
||||
));
|
||||
}
|
||||
|
||||
let latest_pdu = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.latest_pdu_in_room(&room_id)?
|
||||
.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}")))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(_body))]
|
||||
pub(crate) async fn force_set_room_state_from_server(
|
||||
_body: Vec<&str>, server_name: Box<ServerName>, room_id: Box<RoomId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&services().globals.config.server_name, &room_id)?
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"We are not participating in the room / we don't know about the room ID.",
|
||||
));
|
||||
}
|
||||
|
||||
let first_pdu = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.latest_pdu_in_room(&room_id)?
|
||||
.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
|
||||
|
||||
let room_version = services().rooms.state.get_room_version(&room_id)?;
|
||||
|
||||
let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
|
||||
let remote_state_response = services()
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&server_name,
|
||||
get_room_state::v1::Request {
|
||||
room_id: room_id.clone().into(),
|
||||
event_id: first_pdu.event_id.clone().into(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut events = Vec::with_capacity(remote_state_response.pdus.len());
|
||||
|
||||
for pdu in remote_state_response.pdus.clone() {
|
||||
events.push(match parse_incoming_pdu(&pdu) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
warn!("Could not parse PDU, ignoring: {e}");
|
||||
continue;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
info!("Fetching required signing keys for all the state events we got");
|
||||
services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
|
||||
.await?;
|
||||
|
||||
info!("Going through room_state response PDUs");
|
||||
for result in remote_state_response
|
||||
.pdus
|
||||
.iter()
|
||||
.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map))
|
||||
{
|
||||
let Ok((event_id, value)) = result.await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
|
||||
warn!("Invalid PDU in fetching remote room state PDUs response: {} {:?}", e, value);
|
||||
Error::BadServerResponse("Invalid PDU in send_join response.")
|
||||
})?;
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.outlier
|
||||
.add_pdu_outlier(&event_id, &value)?;
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let shortstatekey = services()
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
|
||||
state.insert(shortstatekey, pdu.event_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
info!("Going through auth_chain response");
|
||||
for result in remote_state_response
|
||||
.auth_chain
|
||||
.iter()
|
||||
.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map))
|
||||
{
|
||||
let Ok((event_id, value)) = result.await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.outlier
|
||||
.add_pdu_outlier(&event_id, &value)?;
|
||||
}
|
||||
|
||||
let new_room_state = services()
|
||||
.rooms
|
||||
.event_handler
|
||||
.resolve_state(room_id.clone().as_ref(), &room_version, state)
|
||||
.await?;
|
||||
|
||||
info!("Forcing new room state");
|
||||
let (short_state_hash, new, removed) = services()
|
||||
.rooms
|
||||
.state_compressor
|
||||
.save_state(room_id.clone().as_ref(), new_room_state)?;
|
||||
|
||||
let mutex_state = Arc::clone(
|
||||
services()
|
||||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.await
|
||||
.entry(room_id.clone().into())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.state
|
||||
.force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock)
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
|
||||
m.room.member state"
|
||||
);
|
||||
services().rooms.state_cache.update_joined_count(&room_id)?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Successfully forced the room state from the requested remote server.",
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_true_destination(
|
||||
@@ -447,15 +643,16 @@ pub(crate) async fn resolve_true_destination(
|
||||
));
|
||||
}
|
||||
|
||||
let (actual_dest, hostname_uri) = resolve_actual_dest(&server_name, no_cache, true).await?;
|
||||
let (actual_dest, hostname_uri) = resolve_actual_dest(&server_name, !no_cache).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Actual destination: {actual_dest:?} | Hostname URI: {hostname_uri}"
|
||||
"Actual destination: {actual_dest} | Hostname URI: {hostname_uri}"
|
||||
)))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn memory_stats() -> RoomMessageEventContent {
|
||||
let html_body = crate::alloc::memory_stats();
|
||||
let html_body = conduit::alloc::memory_stats();
|
||||
|
||||
if html_body.is_empty() {
|
||||
return RoomMessageEventContent::text_plain("malloc stats are not supported on your compiled malloc.");
|
||||
@@ -1,4 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use debug_commands::{first_pdu_in_room, force_set_room_state_from_server, latest_pdu_in_room};
|
||||
use ruma::{events::room::message::RoomMessageEventContent, EventId, RoomId, ServerName};
|
||||
|
||||
use self::debug_commands::{
|
||||
@@ -45,7 +46,8 @@ pub(crate) enum DebugCommand {
|
||||
server: Box<ServerName>,
|
||||
},
|
||||
|
||||
/// Same as `get-remote-pdu` but accepts a codeblock newline delimited list
|
||||
/// - Same as `get-remote-pdu` but accepts a codeblock newline delimited
|
||||
/// list
|
||||
/// of PDUs and a single server to fetch from
|
||||
GetRemotePduList {
|
||||
/// Argument for us to attempt to fetch all the events from the
|
||||
@@ -107,6 +109,41 @@ pub(crate) enum DebugCommand {
|
||||
/// the command.
|
||||
VerifyJson,
|
||||
|
||||
/// - Prints the very first PDU in the specified room (typically
|
||||
/// m.room.create)
|
||||
FirstPduInRoom {
|
||||
/// The room ID
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Prints the latest ("last") PDU in the specified room (typically a
|
||||
/// message)
|
||||
LatestPduInRoom {
|
||||
/// The room ID
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Forcefully replaces the room state of our local copy of the specified
|
||||
/// room, with the copy (auth chain and room state events) the specified
|
||||
/// remote server says.
|
||||
///
|
||||
/// A common desire for room deletion is to simply "reset" our copy of the
|
||||
/// room. While this admin command is not a replacement for that, if you
|
||||
/// know you have split/broken room state and you know another server in the
|
||||
/// room that has the best/working room state, this command can let you use
|
||||
/// their room state. Such example is your server saying users are in a
|
||||
/// room, but other servers are saying they're not in the room in question.
|
||||
///
|
||||
/// This command will get the latest PDU in the room we know about, and
|
||||
/// request the room state at that point in time via
|
||||
/// `/_matrix/federation/v1/state/{roomId}`.
|
||||
ForceSetRoomStateFromServer {
|
||||
/// The impacted room ID
|
||||
room_id: Box<RoomId>,
|
||||
/// The server we will use to query the room state for
|
||||
server_name: Box<ServerName>,
|
||||
},
|
||||
|
||||
/// - Runs a server name through conduwuit's true destination resolution
|
||||
/// process
|
||||
///
|
||||
@@ -148,10 +185,20 @@ pub(crate) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<Ro
|
||||
} => change_log_level(body, filter, reset).await?,
|
||||
DebugCommand::SignJson => sign_json(body).await?,
|
||||
DebugCommand::VerifyJson => verify_json(body).await?,
|
||||
DebugCommand::FirstPduInRoom {
|
||||
room_id,
|
||||
} => first_pdu_in_room(body, room_id).await?,
|
||||
DebugCommand::LatestPduInRoom {
|
||||
room_id,
|
||||
} => latest_pdu_in_room(body, room_id).await?,
|
||||
DebugCommand::GetRemotePduList {
|
||||
server,
|
||||
force,
|
||||
} => get_remote_pdu_list(body, server, force).await?,
|
||||
DebugCommand::ForceSetRoomStateFromServer {
|
||||
room_id,
|
||||
server_name,
|
||||
} => force_set_room_state_from_server(body, server_name, room_id).await?,
|
||||
DebugCommand::ResolveTrueDestination {
|
||||
server_name,
|
||||
no_cache,
|
||||
@@ -1,13 +1,8 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
|
||||
|
||||
use crate::{
|
||||
service::admin::{escape_html, get_room_info},
|
||||
services,
|
||||
utils::HtmlEscape,
|
||||
Result,
|
||||
};
|
||||
use crate::{escape_html, get_room_info, services, utils::HtmlEscape, Result};
|
||||
|
||||
pub(crate) async fn disable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
||||
@@ -19,13 +14,14 @@ pub(crate) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Resul
|
||||
Ok(RoomMessageEventContent::text_plain("Room enabled."))
|
||||
}
|
||||
|
||||
pub(crate) async fn incoming_federeation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
pub(crate) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
let map = services().globals.roomid_federationhandletime.read().await;
|
||||
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
|
||||
|
||||
for (r, (e, i)) in map.iter() {
|
||||
let elapsed = i.elapsed();
|
||||
let _ = writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60);
|
||||
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60,)
|
||||
.expect("should be able to write to string buffer");
|
||||
}
|
||||
Ok(RoomMessageEventContent::text_plain(&msg))
|
||||
}
|
||||
@@ -105,7 +101,8 @@ pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>)
|
||||
rooms.reverse();
|
||||
|
||||
let output_plain = format!(
|
||||
"Rooms {user_id} shares with us:\n{}",
|
||||
"Rooms {user_id} shares with us ({}):\n{}",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.map(|(id, members, name)| format!("{id}\tMembers: {members}\tName: {name}"))
|
||||
@@ -113,19 +110,20 @@ pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>)
|
||||
.join("\n")
|
||||
);
|
||||
let output_html = format!(
|
||||
"<table><caption>Rooms {user_id} shares with \
|
||||
us</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
|
||||
"<table><caption>Rooms {user_id} shares with us \
|
||||
({})</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (id, members, name)| {
|
||||
writeln!(
|
||||
output,
|
||||
"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
|
||||
escape_html(id.as_ref()),
|
||||
id,
|
||||
members,
|
||||
escape_html(name)
|
||||
)
|
||||
.unwrap();
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
@@ -2,7 +2,7 @@
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
|
||||
|
||||
use self::federation_commands::{
|
||||
disable_room, enable_room, fetch_support_well_known, incoming_federeation, remote_user_in_rooms,
|
||||
disable_room, enable_room, fetch_support_well_known, incoming_federation, remote_user_in_rooms,
|
||||
};
|
||||
use crate::Result;
|
||||
|
||||
@@ -51,7 +51,7 @@ pub(crate) async fn process(command: FederationCommand, body: Vec<&str>) -> Resu
|
||||
FederationCommand::EnableRoom {
|
||||
room_id,
|
||||
} => enable_room(body, room_id).await?,
|
||||
FederationCommand::IncomingFederation => incoming_federeation(body).await?,
|
||||
FederationCommand::IncomingFederation => incoming_federation(body).await?,
|
||||
FederationCommand::FetchSupportWellKnown {
|
||||
server_name,
|
||||
} => fetch_support_well_known(body, server_name).await?,
|
||||
@@ -17,9 +17,8 @@ pub(crate) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEvent
|
||||
let ok_count = users.iter().filter(|user| user.is_ok()).count();
|
||||
|
||||
let message = format!(
|
||||
"Database query completed in {query_time:?}:\n\n```\nTotal entries: {:?}\nFailure/Invalid user count: \
|
||||
{:?}\nSuccess/Valid user count: {:?}```",
|
||||
total, err_count, ok_count
|
||||
"Database query completed in {query_time:?}:\n\n```\nTotal entries: {total:?}\nFailure/Invalid user count: \
|
||||
{err_count:?}\nSuccess/Valid user count: {ok_count:?}```"
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::notice_html(message, String::new()))
|
||||
306
src/admin/handler.rs
Normal file
306
src/admin/handler.rs
Normal file
@@ -0,0 +1,306 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
events::{
|
||||
relation::InReplyTo,
|
||||
room::message::{Relation::Reply, RoomMessageEventContent},
|
||||
TimelineEventType,
|
||||
},
|
||||
OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use tokio::sync::MutexGuard;
|
||||
use tracing::error;
|
||||
|
||||
extern crate conduit_service as service;
|
||||
|
||||
use conduit::{Error, Result};
|
||||
pub(crate) use service::admin::{AdminRoomEvent, Service};
|
||||
use service::{admin::HandlerResult, pdu::PduBuilder};
|
||||
|
||||
use self::{fsck::FsckCommand, tester::TesterCommands};
|
||||
use crate::{
|
||||
appservice, appservice::AppserviceCommand, debug, debug::DebugCommand, escape_html, federation,
|
||||
federation::FederationCommand, fsck, media, media::MediaCommand, query, query::QueryCommand, room,
|
||||
room::RoomCommand, server, server::ServerCommand, services, tester, user, user::UserCommand,
|
||||
};
|
||||
pub(crate) const PAGE_SIZE: usize = 100;
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
#[derive(Parser)]
|
||||
#[command(name = "@conduit:server.name:", version = env!("CARGO_PKG_VERSION"))]
|
||||
pub(crate) enum AdminCommand {
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing appservices
|
||||
Appservices(AppserviceCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing local users
|
||||
Users(UserCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing rooms
|
||||
Rooms(RoomCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing federation
|
||||
Federation(FederationCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing the server
|
||||
Server(ServerCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing media
|
||||
Media(MediaCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for debugging things
|
||||
Debug(DebugCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Query all the database getters and iterators
|
||||
Query(QueryCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Query all the database getters and iterators
|
||||
Fsck(FsckCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
Tester(TesterCommands),
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn handle(event: AdminRoomEvent, room: OwnedRoomId, user: OwnedUserId) -> HandlerResult {
|
||||
Box::pin(handle_event(event, room, user))
|
||||
}
|
||||
|
||||
async fn handle_event(event: AdminRoomEvent, admin_room: OwnedRoomId, server_user: OwnedUserId) -> Result<()> {
|
||||
let (mut message_content, reply) = match event {
|
||||
AdminRoomEvent::SendMessage(content) => (content, None),
|
||||
AdminRoomEvent::ProcessMessage(room_message, reply_id) => {
|
||||
// This future is ~8 KiB so it's better to start it off the stack.
|
||||
(Box::pin(process_admin_message(room_message)).await, Some(reply_id))
|
||||
},
|
||||
};
|
||||
|
||||
let mutex_state = Arc::clone(
|
||||
services()
|
||||
.globals
|
||||
.roomid_mutex_state
|
||||
.write()
|
||||
.await
|
||||
.entry(admin_room.clone())
|
||||
.or_default(),
|
||||
);
|
||||
let state_lock = mutex_state.lock().await;
|
||||
|
||||
if let Some(reply) = reply {
|
||||
message_content.relates_to = Some(Reply {
|
||||
in_reply_to: InReplyTo {
|
||||
event_id: reply.into(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let response_pdu = PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&message_content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
};
|
||||
|
||||
if let Err(e) = services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(response_pdu, &server_user, &admin_room, &state_lock)
|
||||
.await
|
||||
{
|
||||
handle_response_error(&e, &admin_room, &server_user, &state_lock).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_response_error(
|
||||
e: &Error, admin_room: &RoomId, server_user: &UserId, state_lock: &MutexGuard<'_, ()>,
|
||||
) -> Result<()> {
|
||||
error!("Failed to build and append admin room response PDU: \"{e}\"");
|
||||
let error_room_message = RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to build and append admin room PDU: \"{e}\"\n\nThe original admin command may have finished \
|
||||
successfully, but we could not return the output."
|
||||
));
|
||||
|
||||
let response_pdu = PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&error_room_message).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: None,
|
||||
redacts: None,
|
||||
};
|
||||
|
||||
services()
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(response_pdu, server_user, admin_room, state_lock)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Parse and process a message from the admin room
|
||||
async fn process_admin_message(room_message: String) -> RoomMessageEventContent {
|
||||
let mut lines = room_message.lines().filter(|l| !l.trim().is_empty());
|
||||
let command_line = lines.next().expect("each string has at least one line");
|
||||
let body = lines.collect::<Vec<_>>();
|
||||
|
||||
let admin_command = match parse_admin_command(command_line) {
|
||||
Ok(command) => command,
|
||||
Err(error) => {
|
||||
let server_name = services().globals.server_name();
|
||||
let message = error.replace("server.name", server_name.as_str());
|
||||
let html_message = usage_to_html(&message, server_name);
|
||||
|
||||
return RoomMessageEventContent::text_html(message, html_message);
|
||||
},
|
||||
};
|
||||
|
||||
match process_admin_command(admin_command, body).await {
|
||||
Ok(reply_message) => reply_message,
|
||||
Err(error) => {
|
||||
let markdown_message = format!("Encountered an error while handling the command:\n```\n{error}\n```",);
|
||||
let html_message = format!("Encountered an error while handling the command:\n<pre>\n{error}\n</pre>",);
|
||||
|
||||
RoomMessageEventContent::text_html(markdown_message, html_message)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Parse chat messages from the admin room into an AdminCommand object
|
||||
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> {
|
||||
// Note: argv[0] is `@conduit:servername:`, which is treated as the main command
|
||||
let mut argv = command_line.split_whitespace().collect::<Vec<_>>();
|
||||
|
||||
// Replace `help command` with `command --help`
|
||||
// Clap has a help subcommand, but it omits the long help description.
|
||||
if argv.len() > 1 && argv[1] == "help" {
|
||||
argv.remove(1);
|
||||
argv.push("--help");
|
||||
}
|
||||
|
||||
// Backwards compatibility with `register_appservice`-style commands
|
||||
let command_with_dashes_argv1;
|
||||
if argv.len() > 1 && argv[1].contains('_') {
|
||||
command_with_dashes_argv1 = argv[1].replace('_', "-");
|
||||
argv[1] = &command_with_dashes_argv1;
|
||||
}
|
||||
|
||||
// Backwards compatibility with `register_appservice`-style commands
|
||||
let command_with_dashes_argv2;
|
||||
if argv.len() > 2 && argv[2].contains('_') {
|
||||
command_with_dashes_argv2 = argv[2].replace('_', "-");
|
||||
argv[2] = &command_with_dashes_argv2;
|
||||
}
|
||||
|
||||
// if the user is using the `query` command (argv[1]), replace the database
|
||||
// function/table calls with underscores to match the codebase
|
||||
let command_with_dashes_argv3;
|
||||
if argv.len() > 3 && argv[1].eq("query") {
|
||||
command_with_dashes_argv3 = argv[3].replace('_', "-");
|
||||
argv[3] = &command_with_dashes_argv3;
|
||||
}
|
||||
|
||||
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
async fn process_admin_command(command: AdminCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
let reply_message_content = match command {
|
||||
AdminCommand::Appservices(command) => appservice::process(command, body).await?,
|
||||
AdminCommand::Media(command) => media::process(command, body).await?,
|
||||
AdminCommand::Users(command) => user::process(command, body).await?,
|
||||
AdminCommand::Rooms(command) => room::process(command, body).await?,
|
||||
AdminCommand::Federation(command) => federation::process(command, body).await?,
|
||||
AdminCommand::Server(command) => server::process(command, body).await?,
|
||||
AdminCommand::Debug(command) => debug::process(command, body).await?,
|
||||
AdminCommand::Query(command) => query::process(command, body).await?,
|
||||
AdminCommand::Fsck(command) => fsck::process(command, body).await?,
|
||||
AdminCommand::Tester(command) => tester::process(command, body).await?,
|
||||
};
|
||||
|
||||
Ok(reply_message_content)
|
||||
}
|
||||
|
||||
// Utility to turn clap's `--help` text to HTML.
|
||||
fn usage_to_html(text: &str, server_name: &ServerName) -> String {
|
||||
// Replace `@conduit:servername:-subcmdname` with `@conduit:servername:
|
||||
// subcmdname`
|
||||
let text = text.replace(&format!("@conduit:{server_name}:-"), &format!("@conduit:{server_name}: "));
|
||||
|
||||
// For the conduit admin room, subcommands become main commands
|
||||
let text = text.replace("SUBCOMMAND", "COMMAND");
|
||||
let text = text.replace("subcommand", "command");
|
||||
|
||||
// Escape option names (e.g. `<element-id>`) since they look like HTML tags
|
||||
let text = escape_html(&text);
|
||||
|
||||
// Italicize the first line (command name and version text)
|
||||
let re = Regex::new("^(.*?)\n").expect("Regex compilation should not fail");
|
||||
let text = re.replace_all(&text, "<em>$1</em>\n");
|
||||
|
||||
// Unmerge wrapped lines
|
||||
let text = text.replace("\n ", " ");
|
||||
|
||||
// Wrap option names in backticks. The lines look like:
|
||||
// -V, --version Prints version information
|
||||
// And are converted to:
|
||||
// <code>-V, --version</code>: Prints version information
|
||||
// (?m) enables multi-line mode for ^ and $
|
||||
let re = Regex::new("(?m)^ {4}(([a-zA-Z_&;-]+(, )?)+) +(.*)$").expect("Regex compilation should not fail");
|
||||
let text = re.replace_all(&text, "<code>$1</code>: $4");
|
||||
|
||||
// Look for a `[commandbody]` tag. If it exists, use all lines below it that
|
||||
// start with a `#` in the USAGE section.
|
||||
let mut text_lines = text.lines().collect::<Vec<&str>>();
|
||||
let mut command_body = String::new();
|
||||
|
||||
if let Some(line_index) = text_lines.iter().position(|line| *line == "[commandbody]") {
|
||||
text_lines.remove(line_index);
|
||||
|
||||
while text_lines
|
||||
.get(line_index)
|
||||
.is_some_and(|line| line.starts_with('#'))
|
||||
{
|
||||
command_body += if text_lines[line_index].starts_with("# ") {
|
||||
&text_lines[line_index][2..]
|
||||
} else {
|
||||
&text_lines[line_index][1..]
|
||||
};
|
||||
command_body += "[nobr]\n";
|
||||
text_lines.remove(line_index);
|
||||
}
|
||||
}
|
||||
|
||||
let text = text_lines.join("\n");
|
||||
|
||||
// Improve the usage section
|
||||
let text = if command_body.is_empty() {
|
||||
// Wrap the usage line in code tags
|
||||
let re = Regex::new("(?m)^USAGE:\n {4}(@conduit:.*)$").expect("Regex compilation should not fail");
|
||||
re.replace_all(&text, "USAGE:\n<code>$1</code>").to_string()
|
||||
} else {
|
||||
// Wrap the usage line in a code block, and add a yaml block example
|
||||
// This makes the usage of e.g. `register-appservice` more accurate
|
||||
let re = Regex::new("(?m)^USAGE:\n {4}(.*?)\n\n").expect("Regex compilation should not fail");
|
||||
re.replace_all(&text, "USAGE:\n<pre>$1[nobr]\n[commandbodyblock]</pre>")
|
||||
.replace("[commandbodyblock]", &command_body)
|
||||
};
|
||||
|
||||
// Add HTML line-breaks
|
||||
|
||||
text.replace("\n\n\n", "\n\n")
|
||||
.replace('\n', "<br>\n")
|
||||
.replace("[nobr]<br>", "")
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruma::{events::room::message::RoomMessageEventContent, EventId};
|
||||
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::{service::admin::MxcUri, services, Result};
|
||||
use crate::{services, Result};
|
||||
|
||||
pub(crate) async fn delete(
|
||||
_body: Vec<&str>, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
|
||||
@@ -23,7 +23,7 @@ pub(crate) async fn delete(
|
||||
debug!("Got event ID to delete media from: {event_id}");
|
||||
|
||||
let mut mxc_urls = vec![];
|
||||
let mut mxc_deletion_count = 0;
|
||||
let mut mxc_deletion_count: usize = 0;
|
||||
|
||||
// parsing the PDU for any MXC URLs begins here
|
||||
if let Some(event_json) = services().rooms.timeline.get_pdu_json(&event_id)? {
|
||||
@@ -123,7 +123,7 @@ pub(crate) async fn delete(
|
||||
|
||||
for mxc_url in mxc_urls {
|
||||
services().media.delete(mxc_url).await?;
|
||||
mxc_deletion_count += 1;
|
||||
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -138,31 +138,38 @@ pub(crate) async fn delete(
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
|
||||
let mxc_list = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
||||
|
||||
let mut mxc_deletion_count = 0;
|
||||
|
||||
for mxc in mxc_list {
|
||||
debug!("Deleting MXC {mxc} in bulk");
|
||||
services().media.delete(mxc.to_owned()).await?;
|
||||
mxc_deletion_count += 1;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.",
|
||||
)));
|
||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
))
|
||||
let mxc_list = body
|
||||
.clone()
|
||||
.drain(1..body.len().checked_sub(1).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut mxc_deletion_count: usize = 0;
|
||||
|
||||
for mxc in mxc_list {
|
||||
debug!("Deleting MXC {mxc} in bulk");
|
||||
services().media.delete(mxc.to_owned()).await?;
|
||||
mxc_deletion_count = mxc_deletion_count
|
||||
.checked_add(1)
|
||||
.expect("mxc_deletion_count should not get this high");
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.",
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_past_remote_media(_body: Vec<&str>, duration: String) -> Result<RoomMessageEventContent> {
|
||||
pub(crate) async fn delete_past_remote_media(
|
||||
_body: Vec<&str>, duration: String, force: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let deleted_count = services()
|
||||
.media
|
||||
.delete_all_remote_media_at_after_time(duration)
|
||||
.delete_all_remote_media_at_after_time(duration, force)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -1,8 +1,8 @@
|
||||
use clap::Subcommand;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, EventId};
|
||||
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
|
||||
|
||||
use self::media_commands::{delete, delete_list, delete_past_remote_media};
|
||||
use crate::{service::admin::MxcUri, Result};
|
||||
use crate::Result;
|
||||
|
||||
pub(crate) mod media_commands;
|
||||
|
||||
@@ -32,6 +32,9 @@ pub(crate) enum MediaCommand {
|
||||
/// - The duration (at or after), e.g. "5m" to delete all media in the
|
||||
/// past 5 minutes
|
||||
duration: String,
|
||||
/// Continues deleting remote media if an undeletable object is found
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -44,6 +47,7 @@ pub(crate) async fn process(command: MediaCommand, body: Vec<&str>) -> Result<Ro
|
||||
MediaCommand::DeleteList => delete_list(body).await?,
|
||||
MediaCommand::DeletePastRemoteMedia {
|
||||
duration,
|
||||
} => delete_past_remote_media(body, duration).await?,
|
||||
force,
|
||||
} => delete_past_remote_media(body, duration, force).await?,
|
||||
})
|
||||
}
|
||||
55
src/admin/mod.rs
Normal file
55
src/admin/mod.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
pub(crate) mod appservice;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod federation;
|
||||
pub(crate) mod fsck;
|
||||
pub(crate) mod handler;
|
||||
pub(crate) mod media;
|
||||
pub(crate) mod query;
|
||||
pub(crate) mod room;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod tester;
|
||||
pub(crate) mod user;
|
||||
pub(crate) mod utils;
|
||||
|
||||
extern crate conduit_api as api;
|
||||
extern crate conduit_core as conduit;
|
||||
extern crate conduit_service as service;
|
||||
|
||||
pub(crate) use conduit::{mod_ctor, mod_dtor, Result};
|
||||
pub use handler::handle;
|
||||
pub(crate) use service::{services, user_is_local};
|
||||
|
||||
pub(crate) use crate::{
|
||||
handler::Service,
|
||||
utils::{escape_html, get_room_info},
|
||||
};
|
||||
|
||||
mod_ctor! {}
|
||||
mod_dtor! {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use clap::Parser;
|
||||
|
||||
use crate::handler::AdminCommand;
|
||||
|
||||
#[test]
|
||||
fn get_help_short() { get_help_inner("-h"); }
|
||||
|
||||
#[test]
|
||||
fn get_help_long() { get_help_inner("--help"); }
|
||||
|
||||
#[test]
|
||||
fn get_help_subcommand() { get_help_inner("help"); }
|
||||
|
||||
fn get_help_inner(input: &str) {
|
||||
let error = AdminCommand::try_parse_from(["argv[0] doesn't matter", input])
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
|
||||
// Search for a handful of keywords that suggest the help printed properly
|
||||
assert!(error.contains("Usage:"));
|
||||
assert!(error.contains("Commands:"));
|
||||
assert!(error.contains("Options:"));
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,8 @@ pub(crate) async fn account_data(subcommand: AccountData) -> Result<RoomMessageE
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
AccountData::Get {
|
||||
@@ -39,11 +36,8 @@ pub(crate) async fn account_data(subcommand: AccountData) -> Result<RoomMessageE
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
}
|
||||
@@ -17,11 +17,8 @@ pub(crate) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEven
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Appservice::All => {
|
||||
@@ -30,11 +27,8 @@ pub(crate) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEven
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
}
|
||||
@@ -12,11 +12,8 @@ pub(crate) async fn globals(subcommand: Globals) -> Result<RoomMessageEventConte
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Globals::CurrentCount => {
|
||||
@@ -25,11 +22,8 @@ pub(crate) async fn globals(subcommand: Globals) -> Result<RoomMessageEventConte
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Globals::LastCheckForUpdatesId => {
|
||||
@@ -38,11 +32,8 @@ pub(crate) async fn globals(subcommand: Globals) -> Result<RoomMessageEventConte
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Globals::LoadKeypair => {
|
||||
@@ -51,11 +42,8 @@ pub(crate) async fn globals(subcommand: Globals) -> Result<RoomMessageEventConte
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Globals::SigningKeysFor {
|
||||
@@ -66,11 +54,8 @@ pub(crate) async fn globals(subcommand: Globals) -> Result<RoomMessageEventConte
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
}
|
||||
@@ -3,10 +3,12 @@
|
||||
pub(crate) mod globals;
|
||||
pub(crate) mod presence;
|
||||
pub(crate) mod room_alias;
|
||||
pub(crate) mod room_state_cache;
|
||||
pub(crate) mod sending;
|
||||
pub(crate) mod users;
|
||||
|
||||
use clap::Subcommand;
|
||||
use room_state_cache::room_state_cache;
|
||||
use ruma::{
|
||||
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
|
||||
RoomAliasId, RoomId, ServerName, UserId,
|
||||
@@ -38,6 +40,10 @@ pub(crate) enum QueryCommand {
|
||||
#[command(subcommand)]
|
||||
RoomAlias(RoomAlias),
|
||||
|
||||
/// - rooms/state_cache iterators and getters
|
||||
#[command(subcommand)]
|
||||
RoomStateCache(RoomStateCache),
|
||||
|
||||
/// - globals.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
Globals(Globals),
|
||||
@@ -127,6 +133,78 @@ pub(crate) enum RoomAlias {
|
||||
AllLocalAliases,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum RoomStateCache {
|
||||
ServerInRoom {
|
||||
server: Box<ServerName>,
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomServers {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
ServerRooms {
|
||||
server: Box<ServerName>,
|
||||
},
|
||||
|
||||
RoomMembers {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
LocalUsersInRoom {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
ActiveLocalUsersInRoom {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomJoinedCount {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomInvitedCount {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomUserOnceJoined {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomMembersInvited {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
GetInviteCount {
|
||||
room_id: Box<RoomId>,
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
GetLeftCount {
|
||||
room_id: Box<RoomId>,
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
RoomsJoined {
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
RoomsLeft {
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
RoomsInvited {
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
InviteState {
|
||||
user_id: Box<UserId>,
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
#[derive(Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/globals.rs
|
||||
@@ -216,6 +294,7 @@ pub(crate) async fn process(command: QueryCommand, _body: Vec<&str>) -> Result<R
|
||||
QueryCommand::Appservice(command) => appservice(command).await?,
|
||||
QueryCommand::Presence(command) => presence(command).await?,
|
||||
QueryCommand::RoomAlias(command) => room_alias(command).await?,
|
||||
QueryCommand::RoomStateCache(command) => room_state_cache(command).await?,
|
||||
QueryCommand::Globals(command) => globals(command).await?,
|
||||
QueryCommand::Sending(command) => sending(command).await?,
|
||||
QueryCommand::Users(command) => users(command).await?,
|
||||
@@ -14,11 +14,8 @@ pub(crate) async fn presence(subcommand: Presence) -> Result<RoomMessageEventCon
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Presence::PresenceSince {
|
||||
@@ -31,11 +28,8 @@ pub(crate) async fn presence(subcommand: Presence) -> Result<RoomMessageEventCon
|
||||
let presence_since: Vec<(_, _, _)> = results.collect();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", presence_since),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
presence_since
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{presence_since:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{presence_since:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
}
|
||||
@@ -14,11 +14,8 @@ pub(crate) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEvent
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomAlias::LocalAliasesForRoom {
|
||||
@@ -31,11 +28,8 @@ pub(crate) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEvent
|
||||
let aliases: Vec<_> = results.collect();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", aliases),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
aliases
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{aliases:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{aliases:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomAlias::AllLocalAliases => {
|
||||
@@ -46,11 +40,8 @@ pub(crate) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEvent
|
||||
let aliases: Vec<_> = results.collect();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", aliases),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
aliases
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{aliases:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{aliases:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
}
|
||||
249
src/admin/query/room_state_cache.rs
Normal file
249
src/admin/query/room_state_cache.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use super::RoomStateCache;
|
||||
use crate::{services, Result};
|
||||
|
||||
pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomMessageEventContent> {
|
||||
match subcommand {
|
||||
RoomStateCache::ServerInRoom {
|
||||
server,
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&server, &room_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{result:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{result:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomServers {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_servers(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::ServerRooms {
|
||||
server,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services().rooms.state_cache.server_rooms(&server).collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomMembers {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::LocalUsersInRoom {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.local_users_in_room(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::ActiveLocalUsersInRoom {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomJoinedCount {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services().rooms.state_cache.room_joined_count(&room_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomInvitedCount {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services().rooms.state_cache.room_invited_count(&room_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomUserOnceJoined {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_useroncejoined(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomMembersInvited {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members_invited(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::GetInviteCount {
|
||||
room_id,
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_invite_count(&room_id, &user_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::GetLeftCount {
|
||||
room_id,
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_left_count(&room_id, &user_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomsJoined {
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomsInvited {
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_invited(&user_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::RoomsLeft {
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services().rooms.state_cache.rooms_left(&user_id).collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
RoomStateCache::InviteState {
|
||||
user_id,
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(&user_id, &room_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,8 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
let active_requests: Result<Vec<(_, _, _)>> = results.collect();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", active_requests),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
active_requests
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{active_requests:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{active_requests:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Sending::QueuedRequests {
|
||||
@@ -96,11 +93,8 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
let queued_requests = results.collect::<Result<Vec<(_, _)>>>();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", queued_requests),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
queued_requests
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{queued_requests:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{queued_requests:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Sending::ActiveRequestsFor {
|
||||
@@ -178,11 +172,8 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
let active_requests = results.collect::<Result<Vec<(_, _)>>>();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", active_requests),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
active_requests
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{active_requests:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{active_requests:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
Sending::GetLatestEduCount {
|
||||
@@ -193,11 +184,8 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
}
|
||||
@@ -14,11 +14,8 @@ pub(crate) async fn users(subcommand: Users) -> Result<RoomMessageEventContent>
|
||||
let users = results.collect::<Vec<_>>();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", users),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
users
|
||||
),
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{users:?}```"),
|
||||
format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{users:?}\n</code></pre>"),
|
||||
))
|
||||
},
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
pub(crate) mod room_alias_commands;
|
||||
pub(crate) mod room_commands;
|
||||
pub(crate) mod room_directory_commands;
|
||||
pub(crate) mod room_info_commands;
|
||||
pub(crate) mod room_moderation_commands;
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
@@ -17,6 +18,10 @@ pub(crate) enum RoomCommand {
|
||||
page: Option<usize>,
|
||||
},
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - View information about a room we know about
|
||||
Info(RoomInfoCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Manage moderation of remote or local rooms
|
||||
Moderation(RoomModerationCommand),
|
||||
@@ -30,6 +35,23 @@ pub(crate) enum RoomCommand {
|
||||
Directory(RoomDirectoryCommand),
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum RoomInfoCommand {
|
||||
/// - List joined members in a room
|
||||
ListJoinedMembers {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Displays room topic
|
||||
///
|
||||
/// Room topics can be huge, so this is in its
|
||||
/// own separate command
|
||||
ViewRoomTopic {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum RoomAliasCommand {
|
||||
@@ -46,7 +68,7 @@ pub(crate) enum RoomAliasCommand {
|
||||
room_alias_localpart: String,
|
||||
},
|
||||
|
||||
/// - Remove an alias
|
||||
/// - Remove a local alias
|
||||
Remove {
|
||||
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
|
||||
room_alias_localpart: String,
|
||||
@@ -147,6 +169,8 @@ pub(crate) enum RoomModerationCommand {
|
||||
|
||||
pub(crate) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
Ok(match command {
|
||||
RoomCommand::Info(command) => room_info_commands::process(command, body).await?,
|
||||
|
||||
RoomCommand::Alias(command) => room_alias_commands::process(command, body).await?,
|
||||
|
||||
RoomCommand::Directory(command) => room_directory_commands::process(command, body).await?,
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId};
|
||||
|
||||
use super::RoomAliasCommand;
|
||||
use crate::{service::admin::escape_html, services, Result};
|
||||
use crate::{escape_html, services, Result};
|
||||
|
||||
pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
let server_user = &services().globals.server_user;
|
||||
|
||||
match command {
|
||||
RoomAliasCommand::Set {
|
||||
ref room_alias_localpart,
|
||||
@@ -20,7 +22,7 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
let room_alias_str = format!("#{}:{}", room_alias_localpart, services().globals.server_name());
|
||||
let room_alias = match RoomAliasId::parse_box(room_alias_str) {
|
||||
Ok(alias) => alias,
|
||||
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {}", err))),
|
||||
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))),
|
||||
};
|
||||
match command {
|
||||
RoomAliasCommand::Set {
|
||||
@@ -28,18 +30,24 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
room_id,
|
||||
..
|
||||
} => match (force, services().rooms.alias.resolve_local_alias(&room_alias)) {
|
||||
(true, Ok(Some(id))) => match services().rooms.alias.set_alias(&room_alias, &room_id) {
|
||||
(true, Ok(Some(id))) => match services()
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&room_alias, &room_id, server_user)
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully overwrote alias (formerly {})",
|
||||
id
|
||||
"Successfully overwrote alias (formerly {id})"
|
||||
))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {}", err))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
(false, Ok(Some(id))) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Refusing to overwrite in use alias for {}, use -f or --force to overwrite",
|
||||
id
|
||||
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
|
||||
))),
|
||||
(_, Ok(None)) => match services().rooms.alias.set_alias(&room_alias, &room_id) {
|
||||
(_, Ok(None)) => match services()
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&room_alias, &room_id, server_user)
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Successfully set alias")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
@@ -48,19 +56,24 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
RoomAliasCommand::Remove {
|
||||
..
|
||||
} => match services().rooms.alias.resolve_local_alias(&room_alias) {
|
||||
Ok(Some(id)) => match services().rooms.alias.remove_alias(&room_alias) {
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!("Removed alias from {}", id))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {}", err))),
|
||||
Ok(Some(id)) => match services()
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&room_alias, server_user)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!("Removed alias from {id}"))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {}", err))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))),
|
||||
},
|
||||
RoomAliasCommand::Which {
|
||||
..
|
||||
} => match services().rooms.alias.resolve_local_alias(&room_alias) {
|
||||
Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {}", id))),
|
||||
Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))),
|
||||
Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {}", err))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))),
|
||||
},
|
||||
RoomAliasCommand::List {
|
||||
..
|
||||
@@ -79,12 +92,13 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
match aliases {
|
||||
Ok(aliases) => {
|
||||
let plain_list = aliases.iter().fold(String::new(), |mut output, alias| {
|
||||
writeln!(output, "- {alias}").unwrap();
|
||||
writeln!(output, "- {alias}").expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
let html_list = aliases.iter().fold(String::new(), |mut output, alias| {
|
||||
writeln!(output, "<li>{}</li>", escape_html(alias.as_ref())).unwrap();
|
||||
writeln!(output, "<li>{}</li>", escape_html(alias.as_ref()))
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
@@ -92,7 +106,7 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
let html = format!("Aliases for {room_id}:\n<ul>{html_list}</ul>");
|
||||
Ok(RoomMessageEventContent::text_html(plain, html))
|
||||
},
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {}", err))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {err}"))),
|
||||
}
|
||||
} else {
|
||||
let aliases = services()
|
||||
@@ -106,7 +120,8 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
let plain_list = aliases
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (alias, id)| {
|
||||
writeln!(output, "- `{alias}` -> #{id}:{server_name}").unwrap();
|
||||
writeln!(output, "- `{alias}` -> #{id}:{server_name}")
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
@@ -120,7 +135,7 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
escape_html(id.as_ref()),
|
||||
server_name
|
||||
)
|
||||
.unwrap();
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
|
||||
|
||||
use crate::{
|
||||
service::admin::{escape_html, get_room_info, PAGE_SIZE},
|
||||
services, Result,
|
||||
};
|
||||
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
|
||||
|
||||
pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMessageEventContent> {
|
||||
// TODO: i know there's a way to do this with clap, but i can't seem to find it
|
||||
@@ -22,7 +19,7 @@ pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMe
|
||||
|
||||
let rooms = rooms
|
||||
.into_iter()
|
||||
.skip(page.saturating_sub(1) * PAGE_SIZE)
|
||||
.skip(page.saturating_sub(1).saturating_mul(PAGE_SIZE))
|
||||
.take(PAGE_SIZE)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -51,7 +48,7 @@ pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMe
|
||||
members,
|
||||
escape_html(name)
|
||||
)
|
||||
.unwrap();
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
@@ -1,12 +1,9 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
|
||||
|
||||
use super::RoomDirectoryCommand;
|
||||
use crate::{
|
||||
service::admin::{escape_html, get_room_info, PAGE_SIZE},
|
||||
services, Result,
|
||||
};
|
||||
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
|
||||
|
||||
pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
match command {
|
||||
@@ -39,7 +36,7 @@ pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
|
||||
|
||||
let rooms = rooms
|
||||
.into_iter()
|
||||
.skip(page.saturating_sub(1) * PAGE_SIZE)
|
||||
.skip(page.saturating_sub(1).saturating_mul(PAGE_SIZE))
|
||||
.take(PAGE_SIZE)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -68,7 +65,7 @@ pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
|
||||
members,
|
||||
escape_html(name.as_ref())
|
||||
)
|
||||
.unwrap();
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
93
src/admin/room/room_info_commands.rs
Normal file
93
src/admin/room/room_info_commands.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
|
||||
use service::services;
|
||||
|
||||
use super::RoomInfoCommand;
|
||||
use crate::{escape_html, Result};
|
||||
|
||||
pub(crate) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
match command {
|
||||
RoomInfoCommand::ListJoinedMembers {
|
||||
room_id,
|
||||
} => list_joined_members(body, room_id).await,
|
||||
RoomInfoCommand::ViewRoomTopic {
|
||||
room_id,
|
||||
} => view_room_topic(body, room_id).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
let room_name = services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(&room_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| room_id.to_string());
|
||||
|
||||
let members = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(Result::ok);
|
||||
|
||||
let member_info = members
|
||||
.into_iter()
|
||||
.map(|user_id| {
|
||||
(
|
||||
user_id.clone(),
|
||||
services()
|
||||
.users
|
||||
.displayname(&user_id)
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(|| user_id.to_string()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let output_plain = format!(
|
||||
"{} Members in Room \"{}\":\n```\n{}\n```",
|
||||
member_info.len(),
|
||||
room_name,
|
||||
member_info
|
||||
.iter()
|
||||
.map(|(mxid, displayname)| format!("{mxid} | {displayname}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
let output_html = format!(
|
||||
"<table><caption>{} Members in Room \"{}\" </caption>\n<tr><th>MXID</th>\t<th>Display \
|
||||
Name</th></tr>\n{}</table>",
|
||||
member_info.len(),
|
||||
room_name,
|
||||
member_info
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (mxid, displayname)| {
|
||||
writeln!(
|
||||
output,
|
||||
"<tr><td>{}</td>\t<td>{}</td></tr>",
|
||||
mxid,
|
||||
escape_html(displayname.as_ref())
|
||||
)
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
|
||||
}
|
||||
|
||||
async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
let Some(room_topic) = services().rooms.state_accessor.get_room_topic(&room_id)? else {
|
||||
return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set."));
|
||||
};
|
||||
|
||||
let output_html = format!("<p>Room topic:</p>\n<hr>\n{}<hr>", escape_html(&room_topic));
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Room topic:\n\n```{room_topic}\n```"),
|
||||
output_html,
|
||||
))
|
||||
}
|
||||
522
src/admin/room/room_moderation_commands.rs
Normal file
522
src/admin/room/room_moderation_commands.rs
Normal file
@@ -0,0 +1,522 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use api::client::{get_alias_helper, leave_room};
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContent, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId,
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use super::{super::Service, RoomModerationCommand};
|
||||
use crate::{escape_html, get_room_info, services, user_is_local, Result};
|
||||
|
||||
pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
match command {
|
||||
RoomModerationCommand::BanRoom {
|
||||
force,
|
||||
room,
|
||||
disable_federation,
|
||||
} => ban_room(body, force, room, disable_federation).await,
|
||||
RoomModerationCommand::BanListOfRooms {
|
||||
force,
|
||||
disable_federation,
|
||||
} => ban_list_of_rooms(body, force, disable_federation).await,
|
||||
RoomModerationCommand::UnbanRoom {
|
||||
room,
|
||||
enable_federation,
|
||||
} => unban_room(body, room, enable_federation).await,
|
||||
RoomModerationCommand::ListBannedRooms => list_banned_rooms(body).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn ban_room(
|
||||
_body: Vec<&str>, force: bool, room: Box<RoomOrAliasId>, disable_federation: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
debug!("Got room alias or ID: {}", room);
|
||||
|
||||
let admin_room_alias = &services().globals.admin_alias;
|
||||
|
||||
if let Some(admin_room_id) = Service::get_admin_room()? {
|
||||
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
|
||||
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
|
||||
}
|
||||
}
|
||||
|
||||
let room_id = if room.is_room_id() {
|
||||
let room_id = match RoomId::parse(&room) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
};
|
||||
|
||||
debug!("Room specified is a room ID, banning room ID");
|
||||
|
||||
services().rooms.metadata.ban_room(&room_id, true)?;
|
||||
|
||||
room_id
|
||||
} else if room.is_room_alias_id() {
|
||||
let room_alias = match RoomAliasId::parse(&room) {
|
||||
Ok(room_alias) => room_alias,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Room specified is not a room ID, attempting to resolve room alias to a room ID locally, if not using \
|
||||
get_alias_helper to fetch room ID remotely"
|
||||
);
|
||||
|
||||
let room_id = if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
|
||||
room_id
|
||||
} else {
|
||||
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
|
||||
|
||||
match get_alias_helper(room_alias, None).await {
|
||||
Ok(response) => {
|
||||
debug!("Got federation response fetching room ID for room {room}: {:?}", response);
|
||||
response.room_id
|
||||
},
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
services().rooms.metadata.ban_room(&room_id, true)?;
|
||||
|
||||
room_id
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room specified is not a room ID or room alias. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`)",
|
||||
));
|
||||
};
|
||||
|
||||
debug!("Making all users leave the room {}", &room);
|
||||
if force {
|
||||
for local_user in services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user| {
|
||||
user.ok().filter(|local_user| {
|
||||
user_is_local(local_user)
|
||||
// additional wrapped check here is to avoid adding remote users
|
||||
// who are in the admin room to the list of local users (would fail auth check)
|
||||
&& (user_is_local(local_user)
|
||||
&& services()
|
||||
.users
|
||||
.is_admin(local_user)
|
||||
.unwrap_or(true)) // since this is a force
|
||||
// operation, assume user
|
||||
// is an admin if somehow
|
||||
// this fails
|
||||
})
|
||||
})
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
{
|
||||
debug!(
|
||||
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
|
||||
&local_user, &room_id
|
||||
);
|
||||
|
||||
if let Err(e) = leave_room(&local_user, &room_id, None).await {
|
||||
warn!(%e, "Failed to leave room");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for local_user in services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user| {
|
||||
user.ok().filter(|local_user| {
|
||||
local_user.server_name() == services().globals.server_name()
|
||||
// additional wrapped check here is to avoid adding remote users
|
||||
// who are in the admin room to the list of local users (would fail auth check)
|
||||
&& (local_user.server_name()
|
||||
== services().globals.server_name()
|
||||
&& !services()
|
||||
.users
|
||||
.is_admin(local_user)
|
||||
.unwrap_or(false))
|
||||
})
|
||||
})
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
{
|
||||
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
||||
if let Err(e) = leave_room(&local_user, &room_id, None).await {
|
||||
error!(
|
||||
"Error attempting to make local user {} leave room {} during room banning: {}",
|
||||
&local_user, &room_id, e
|
||||
);
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Error attempting to make local user {} leave room {} during room banning (room is still banned \
|
||||
but not removing any more users): {}\nIf you would like to ignore errors, use --force",
|
||||
&local_user, &room_id, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if disable_federation {
|
||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room banned, removed all our local users, and disabled incoming federation with room.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Room banned and removed all our local users, use `!admin federation disable-room` to stop receiving new \
|
||||
inbound federation events as well if needed.",
|
||||
))
|
||||
}
|
||||
|
||||
async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
|
||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let rooms_s = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
||||
|
||||
let admin_room_alias = &services().globals.admin_alias;
|
||||
|
||||
let mut room_ban_count: usize = 0;
|
||||
let mut room_ids: Vec<OwnedRoomId> = Vec::new();
|
||||
|
||||
for &room in &rooms_s {
|
||||
match <&RoomOrAliasId>::try_from(room) {
|
||||
Ok(room_alias_or_id) => {
|
||||
if let Some(admin_room_id) = Service::get_admin_room()? {
|
||||
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) {
|
||||
info!("User specified admin room in bulk ban list, ignoring");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if room_alias_or_id.is_room_id() {
|
||||
let room_id = match RoomId::parse(room_alias_or_id) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force banning
|
||||
warn!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
|
||||
logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
|
||||
room_ids.push(room_id);
|
||||
}
|
||||
|
||||
if room_alias_or_id.is_room_alias_id() {
|
||||
match RoomAliasId::parse(room_alias_or_id) {
|
||||
Ok(room_alias) => {
|
||||
let room_id =
|
||||
if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
|
||||
room_id
|
||||
} else {
|
||||
debug!(
|
||||
"We don't have this room alias to a room ID locally, attempting to fetch room \
|
||||
ID over federation"
|
||||
);
|
||||
|
||||
match get_alias_helper(room_alias, None).await {
|
||||
Ok(response) => {
|
||||
debug!(
|
||||
"Got federation response fetching room ID for room {room}: {:?}",
|
||||
response
|
||||
);
|
||||
response.room_id
|
||||
},
|
||||
Err(e) => {
|
||||
// don't fail if force blocking
|
||||
if force {
|
||||
warn!("Failed to resolve room alias {room} to a room ID: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
room_ids.push(room_id);
|
||||
},
|
||||
Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force deleting
|
||||
error!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
|
||||
logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force deleting
|
||||
error!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for room_id in room_ids {
|
||||
if services().rooms.metadata.ban_room(&room_id, true).is_ok() {
|
||||
debug!("Banned {room_id} successfully");
|
||||
room_ban_count = room_ban_count.saturating_add(1);
|
||||
}
|
||||
|
||||
debug!("Making all users leave the room {}", &room_id);
|
||||
if force {
|
||||
for local_user in services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user| {
|
||||
user.ok().filter(|local_user| {
|
||||
local_user.server_name() == services().globals.server_name()
|
||||
// additional wrapped check here is to avoid adding remote users
|
||||
// who are in the admin room to the list of local users (would fail auth check)
|
||||
&& (local_user.server_name()
|
||||
== services().globals.server_name()
|
||||
&& services()
|
||||
.users
|
||||
.is_admin(local_user)
|
||||
.unwrap_or(true)) // since this is a
|
||||
// force operation,
|
||||
// assume user is
|
||||
// an admin if
|
||||
// somehow this
|
||||
// fails
|
||||
})
|
||||
})
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
{
|
||||
debug!(
|
||||
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
|
||||
&local_user, room_id
|
||||
);
|
||||
if let Err(e) = leave_room(&local_user, &room_id, None).await {
|
||||
warn!(%e, "Failed to leave room");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for local_user in services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user| {
|
||||
user.ok().filter(|local_user| {
|
||||
local_user.server_name() == services().globals.server_name()
|
||||
// additional wrapped check here is to avoid adding remote users
|
||||
// who are in the admin room to the list of local users (would fail auth check)
|
||||
&& (local_user.server_name()
|
||||
== services().globals.server_name()
|
||||
&& !services()
|
||||
.users
|
||||
.is_admin(local_user)
|
||||
.unwrap_or(false))
|
||||
})
|
||||
})
|
||||
.collect::<Vec<OwnedUserId>>()
|
||||
{
|
||||
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
||||
if let Err(e) = leave_room(&local_user, &room_id, None).await {
|
||||
error!(
|
||||
"Error attempting to make local user {} leave room {} during bulk room banning: {}",
|
||||
&local_user, &room_id, e
|
||||
);
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Error attempting to make local user {} leave room {} during room banning (room is still \
|
||||
banned but not removing any more users and not banning any more rooms): {}\nIf you would \
|
||||
like to ignore errors, use --force",
|
||||
&local_user, &room_id, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if disable_federation {
|
||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
||||
}
|
||||
}
|
||||
|
||||
if disable_federation {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and disabled incoming \
|
||||
federation with the room."
|
||||
)))
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk room ban, banned {room_ban_count} total rooms and evicted all users."
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn unban_room(
|
||||
_body: Vec<&str>, room: Box<RoomOrAliasId>, enable_federation: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let room_id = if room.is_room_id() {
|
||||
let room_id = match RoomId::parse(&room) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
};
|
||||
|
||||
debug!("Room specified is a room ID, unbanning room ID");
|
||||
|
||||
services().rooms.metadata.ban_room(&room_id, false)?;
|
||||
|
||||
room_id
|
||||
} else if room.is_room_alias_id() {
|
||||
let room_alias = match RoomAliasId::parse(&room) {
|
||||
Ok(room_alias) => room_alias,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Room specified is not a room ID, attempting to resolve room alias to a room ID locally, if not using \
|
||||
get_alias_helper to fetch room ID remotely"
|
||||
);
|
||||
|
||||
let room_id = if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
|
||||
room_id
|
||||
} else {
|
||||
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
|
||||
|
||||
match get_alias_helper(room_alias, None).await {
|
||||
Ok(response) => {
|
||||
debug!("Got federation response fetching room ID for room {room}: {:?}", response);
|
||||
response.room_id
|
||||
},
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
services().rooms.metadata.ban_room(&room_id, false)?;
|
||||
|
||||
room_id
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room specified is not a room ID or room alias. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`)",
|
||||
));
|
||||
};
|
||||
|
||||
if enable_federation {
|
||||
services().rooms.metadata.disable_room(&room_id, false)?;
|
||||
return Ok(RoomMessageEventContent::text_plain("Room unbanned."));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Room unbanned, you may need to re-enable federation with the room using enable-room if this is a remote room \
|
||||
to make it fully functional.",
|
||||
))
|
||||
}
|
||||
|
||||
async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
let rooms = services()
|
||||
.rooms
|
||||
.metadata
|
||||
.list_banned_rooms()
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
|
||||
match rooms {
|
||||
Ok(room_ids) => {
|
||||
if room_ids.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain("No rooms are banned."));
|
||||
}
|
||||
|
||||
let mut rooms = room_ids
|
||||
.into_iter()
|
||||
.map(|room_id| get_room_info(&room_id))
|
||||
.collect::<Vec<_>>();
|
||||
rooms.sort_by_key(|r| r.1);
|
||||
rooms.reverse();
|
||||
|
||||
let output_plain = format!(
|
||||
"Rooms Banned ({}):\n```\n{}```",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.map(|(id, members, name)| format!("{id}\tMembers: {members}\tName: {name}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
let output_html = format!(
|
||||
"<table><caption>Rooms Banned ({}) \
|
||||
</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (id, members, name)| {
|
||||
writeln!(
|
||||
output,
|
||||
"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
|
||||
id,
|
||||
members,
|
||||
escape_html(name.as_ref())
|
||||
)
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to list banned rooms: {}", e);
|
||||
Ok(RoomMessageEventContent::text_plain(format!("Unable to list banned rooms: {e}")))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
pub(crate) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
let seconds = services()
|
||||
.globals
|
||||
.server
|
||||
.started
|
||||
.elapsed()
|
||||
.expect("standard duration")
|
||||
@@ -28,14 +28,14 @@ pub(crate) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventCont
|
||||
pub(crate) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
let response0 = services().memory_usage().await;
|
||||
let response1 = services().globals.db.memory_usage();
|
||||
let response2 = crate::alloc::memory_usage();
|
||||
let response2 = conduit::alloc::memory_usage();
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Services:\n{response0}\n\nDatabase:\n{response1}\n{}",
|
||||
if !response2.is_empty() {
|
||||
"Allocator:\n{response2}"
|
||||
format!("Allocator:\n {response2}")
|
||||
} else {
|
||||
""
|
||||
String::new()
|
||||
}
|
||||
)))
|
||||
}
|
||||
@@ -69,12 +69,15 @@ pub(crate) async fn backup_database(_body: Vec<&str>) -> Result<RoomMessageEvent
|
||||
));
|
||||
}
|
||||
|
||||
let mut result = tokio::task::spawn_blocking(move || match services().globals.db.backup() {
|
||||
Ok(()) => String::new(),
|
||||
Err(e) => (*e).to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let mut result = services()
|
||||
.server
|
||||
.runtime()
|
||||
.spawn_blocking(move || match services().globals.db.backup() {
|
||||
Ok(()) => String::new(),
|
||||
Err(e) => (*e).to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if result.is_empty() {
|
||||
result = services().globals.db.backup_list()?;
|
||||
@@ -9,6 +9,6 @@ pub(crate) enum TesterCommands {
|
||||
}
|
||||
pub(crate) async fn process(command: TesterCommands, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
Ok(match command {
|
||||
TesterCommands::Tester => RoomMessageEventContent::notice_plain(String::from("complete")),
|
||||
TesterCommands::Tester => RoomMessageEventContent::notice_plain(String::from("completed")),
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
pub(crate) mod user_commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
|
||||
use user_commands::{delete_room_tag, get_room_tags, put_room_tag};
|
||||
|
||||
use self::user_commands::{create, deactivate, deactivate_all, list, list_joined_rooms, reset_password};
|
||||
use crate::Result;
|
||||
@@ -25,11 +26,11 @@ pub(crate) enum UserCommand {
|
||||
|
||||
/// - Deactivate a user
|
||||
///
|
||||
/// User will not be removed from all rooms by default.
|
||||
/// Use --leave-rooms to force the user to leave all rooms
|
||||
/// User will be removed from all rooms by default.
|
||||
/// Use --no-leave-rooms to not leave all rooms by default.
|
||||
Deactivate {
|
||||
#[arg(short, long)]
|
||||
leave_rooms: bool,
|
||||
no_leave_rooms: bool,
|
||||
user_id: String,
|
||||
},
|
||||
|
||||
@@ -37,8 +38,10 @@ pub(crate) enum UserCommand {
|
||||
///
|
||||
/// Recommended to use in conjunction with list-local-users.
|
||||
///
|
||||
/// Users will not be removed from joined rooms by default.
|
||||
/// Can be overridden with --leave-rooms flag.
|
||||
/// Users will be removed from joined rooms by default.
|
||||
///
|
||||
/// Can be overridden with --no-leave-rooms.
|
||||
///
|
||||
/// Removing a mass amount of users from a room may cause a significant
|
||||
/// amount of leave events. The time to leave rooms may depend significantly
|
||||
/// on joined rooms and servers.
|
||||
@@ -48,9 +51,9 @@ pub(crate) enum UserCommand {
|
||||
DeactivateAll {
|
||||
#[arg(short, long)]
|
||||
/// Remove users from their joined rooms
|
||||
leave_rooms: bool,
|
||||
no_leave_rooms: bool,
|
||||
#[arg(short, long)]
|
||||
/// Also deactivate admin accounts
|
||||
/// Also deactivate admin accounts and will assume leave all rooms too
|
||||
force: bool,
|
||||
},
|
||||
|
||||
@@ -62,6 +65,32 @@ pub(crate) enum UserCommand {
|
||||
ListJoinedRooms {
|
||||
user_id: String,
|
||||
},
|
||||
|
||||
/// - Puts a room tag for the specified user and room ID.
|
||||
///
|
||||
/// This is primarily useful if you'd like to set your admin room
|
||||
/// to the special "System Alerts" section in Element as a way to
|
||||
/// permanently see your admin room without it being buried away in your
|
||||
/// favourites or rooms. To do this, you would pass your user, your admin
|
||||
/// room's internal ID, and the tag name `m.server_notice`.
|
||||
PutRoomTag {
|
||||
user_id: String,
|
||||
room_id: Box<RoomId>,
|
||||
tag: String,
|
||||
},
|
||||
|
||||
/// - Deletes the room tag for the specified user and room ID
|
||||
DeleteRoomTag {
|
||||
user_id: String,
|
||||
room_id: Box<RoomId>,
|
||||
tag: String,
|
||||
},
|
||||
|
||||
/// - Gets all the room tags for the specified user and room ID
|
||||
GetRoomTags {
|
||||
user_id: String,
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
}
|
||||
|
||||
pub(crate) async fn process(command: UserCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
@@ -72,18 +101,32 @@ pub(crate) async fn process(command: UserCommand, body: Vec<&str>) -> Result<Roo
|
||||
password,
|
||||
} => create(body, username, password).await?,
|
||||
UserCommand::Deactivate {
|
||||
leave_rooms,
|
||||
no_leave_rooms,
|
||||
user_id,
|
||||
} => deactivate(body, leave_rooms, user_id).await?,
|
||||
} => deactivate(body, no_leave_rooms, user_id).await?,
|
||||
UserCommand::ResetPassword {
|
||||
username,
|
||||
} => reset_password(body, username).await?,
|
||||
UserCommand::DeactivateAll {
|
||||
leave_rooms,
|
||||
no_leave_rooms,
|
||||
force,
|
||||
} => deactivate_all(body, leave_rooms, force).await?,
|
||||
} => deactivate_all(body, no_leave_rooms, force).await?,
|
||||
UserCommand::ListJoinedRooms {
|
||||
user_id,
|
||||
} => list_joined_rooms(body, user_id).await?,
|
||||
UserCommand::PutRoomTag {
|
||||
user_id,
|
||||
room_id,
|
||||
tag,
|
||||
} => put_room_tag(body, user_id, room_id, tag).await?,
|
||||
UserCommand::DeleteRoomTag {
|
||||
user_id,
|
||||
room_id,
|
||||
tag,
|
||||
} => delete_room_tag(body, user_id, room_id, tag).await?,
|
||||
UserCommand::GetRoomTags {
|
||||
user_id,
|
||||
room_id,
|
||||
} => get_room_tags(body, user_id, room_id).await?,
|
||||
})
|
||||
}
|
||||
427
src/admin/user/user_commands.rs
Normal file
427
src/admin/user/user_commands.rs
Normal file
@@ -0,0 +1,427 @@
|
||||
use std::{collections::BTreeMap, fmt::Write as _};
|
||||
|
||||
use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname};
|
||||
use conduit::utils;
|
||||
use ruma::{
|
||||
events::{
|
||||
room::message::RoomMessageEventContent,
|
||||
tag::{TagEvent, TagEventContent, TagInfo},
|
||||
RoomAccountDataEventType,
|
||||
},
|
||||
OwnedRoomId, OwnedUserId, RoomId,
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::{
|
||||
escape_html, get_room_info, services,
|
||||
utils::{parse_active_local_user_id, parse_local_user_id},
|
||||
Result,
|
||||
};
|
||||
|
||||
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
|
||||
|
||||
pub(crate) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
match services().users.list_local_users() {
|
||||
Ok(users) => {
|
||||
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
|
||||
plain_msg += &users.join("\n");
|
||||
plain_msg += "\n```";
|
||||
|
||||
let mut html_msg = format!("<p>Found {} local user account(s):</p><pre><code>", users.len());
|
||||
html_msg += &users.join("\n");
|
||||
html_msg += "\n</code></pre>";
|
||||
Ok(RoomMessageEventContent::text_html(&plain_msg, &html_msg))
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn create(
|
||||
_body: Vec<&str>, username: String, password: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
// Validate user id
|
||||
let user_id = parse_local_user_id(&username)?;
|
||||
|
||||
if services().users.exists(&user_id)? {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!("Userid {user_id} already exists")));
|
||||
}
|
||||
|
||||
let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
||||
|
||||
// Create user
|
||||
services().users.create(&user_id, Some(password.as_str()))?;
|
||||
|
||||
// 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
|
||||
.config
|
||||
.new_user_displayname_suffix
|
||||
.is_empty()
|
||||
{
|
||||
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix)
|
||||
.expect("should be able to write to string buffer");
|
||||
}
|
||||
|
||||
services()
|
||||
.users
|
||||
.set_displayname(&user_id, Some(displayname))
|
||||
.await?;
|
||||
|
||||
// Initial account data
|
||||
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),
|
||||
},
|
||||
})
|
||||
.expect("to json value always works"),
|
||||
)?;
|
||||
|
||||
if !services().globals.config.auto_join_rooms.is_empty() {
|
||||
for room in &services().globals.config.auto_join_rooms {
|
||||
if !services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services().globals.server_name(), room)?
|
||||
{
|
||||
warn!("Skipping room {room} to automatically join as we have never joined before.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(room_id_server_name) = room.server_name() {
|
||||
match join_room_by_id_helper(
|
||||
Some(&user_id),
|
||||
room,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
|
||||
None,
|
||||
)
|
||||
.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}");
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we dont add a device since we're not the user, just the creator
|
||||
|
||||
// Inhibit login does not work for guests
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Created user with user_id: {user_id} and password: `{password}`"
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn deactivate(
|
||||
_body: Vec<&str>, no_leave_rooms: bool, user_id: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
// Validate user id
|
||||
let user_id = parse_local_user_id(&user_id)?;
|
||||
|
||||
// don't deactivate the server service account
|
||||
if user_id == services().globals.server_user {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to deactivate the server service account.",
|
||||
));
|
||||
}
|
||||
|
||||
services().users.deactivate_account(&user_id)?;
|
||||
|
||||
if !no_leave_rooms {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Making {user_id} leave all rooms after deactivation..."
|
||||
)))
|
||||
.await;
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
update_displayname(user_id.clone(), None, all_joined_rooms.clone()).await?;
|
||||
update_avatar_url(user_id.clone(), None, None, all_joined_rooms).await?;
|
||||
leave_all_rooms(&user_id).await;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"User {user_id} has been deactivated"
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn reset_password(_body: Vec<&str>, username: String) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_local_user_id(&username)?;
|
||||
|
||||
if user_id == services().globals.server_user {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to set the password for the server account. Please use the emergency password config option.",
|
||||
));
|
||||
}
|
||||
|
||||
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
|
||||
|
||||
match services()
|
||||
.users
|
||||
.set_password(&user_id, Some(new_password.as_str()))
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully reset the password for user {user_id}: `{new_password}`"
|
||||
))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Couldn't reset the password for user {user_id}: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn deactivate_all(
|
||||
body: Vec<&str>, no_leave_rooms: bool, force: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
||||
|
||||
let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
|
||||
let mut admins = Vec::new();
|
||||
|
||||
for username in usernames {
|
||||
match parse_active_local_user_id(username) {
|
||||
Ok(user_id) => {
|
||||
if services().users.is_admin(&user_id)? && !force {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"{username} is an admin and --force is not set, skipping over"
|
||||
)))
|
||||
.await;
|
||||
admins.push(username);
|
||||
continue;
|
||||
}
|
||||
|
||||
// don't deactivate the server service account
|
||||
if user_id == services().globals.server_user {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"{username} is the server service account, skipping over"
|
||||
)))
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
|
||||
user_ids.push(user_id);
|
||||
},
|
||||
Err(e) => {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"{username} is not a valid username, skipping over: {e}"
|
||||
)))
|
||||
.await;
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let mut deactivation_count: usize = 0;
|
||||
|
||||
for user_id in user_ids {
|
||||
match services().users.deactivate_account(&user_id) {
|
||||
Ok(()) => {
|
||||
deactivation_count = deactivation_count.saturating_add(1);
|
||||
if !no_leave_rooms {
|
||||
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
update_displayname(user_id.clone(), None, all_joined_rooms.clone()).await?;
|
||||
update_avatar_url(user_id.clone(), None, None, all_joined_rooms).await?;
|
||||
leave_all_rooms(&user_id).await;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
|
||||
.await;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if admins.is_empty() {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deactivated {deactivation_count} accounts."
|
||||
)))
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deactivated {deactivation_count} accounts.\nSkipped admin accounts: {}. Use --force to deactivate admin \
|
||||
accounts",
|
||||
admins.join(", ")
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> {
|
||||
// Validate user id
|
||||
let user_id = parse_local_user_id(&user_id)?;
|
||||
|
||||
let mut rooms: Vec<(OwnedRoomId, u64, String)> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.filter_map(Result::ok)
|
||||
.map(|room_id| get_room_info(&room_id))
|
||||
.collect();
|
||||
|
||||
if rooms.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain("User is not in any rooms."));
|
||||
}
|
||||
|
||||
rooms.sort_by_key(|r| r.1);
|
||||
rooms.reverse();
|
||||
|
||||
let output_plain = format!(
|
||||
"Rooms {user_id} Joined ({}):\n{}",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.map(|(id, members, name)| format!("{id}\tMembers: {members}\tName: {name}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
let output_html = format!(
|
||||
"<table><caption>Rooms {user_id} Joined \
|
||||
({})</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (id, members, name)| {
|
||||
writeln!(
|
||||
output,
|
||||
"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
|
||||
escape_html(id.as_ref()),
|
||||
members,
|
||||
escape_html(name)
|
||||
)
|
||||
.unwrap();
|
||||
output
|
||||
})
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
|
||||
}
|
||||
|
||||
pub(crate) async fn put_room_tag(
|
||||
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_active_local_user_id(&user_id)?;
|
||||
|
||||
let event = services()
|
||||
.account_data
|
||||
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
||||
|
||||
let mut tags_event = event.map_or_else(
|
||||
|| TagEvent {
|
||||
content: TagEventContent {
|
||||
tags: BTreeMap::new(),
|
||||
},
|
||||
},
|
||||
|e| serde_json::from_str(e.get()).expect("Bad account data in database for user {user_id}"),
|
||||
);
|
||||
|
||||
tags_event
|
||||
.content
|
||||
.tags
|
||||
.insert(tag.clone().into(), TagInfo::new());
|
||||
|
||||
services().account_data.update(
|
||||
Some(&room_id),
|
||||
&user_id,
|
||||
RoomAccountDataEventType::Tag,
|
||||
&serde_json::to_value(tags_event).expect("to json value always works"),
|
||||
)?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully updated room account data for {user_id} and room {room_id} with tag {tag}"
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_room_tag(
|
||||
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_active_local_user_id(&user_id)?;
|
||||
|
||||
let event = services()
|
||||
.account_data
|
||||
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
||||
|
||||
let mut tags_event = event.map_or_else(
|
||||
|| TagEvent {
|
||||
content: TagEventContent {
|
||||
tags: BTreeMap::new(),
|
||||
},
|
||||
},
|
||||
|e| serde_json::from_str(e.get()).expect("Bad account data in database for user {user_id}"),
|
||||
);
|
||||
|
||||
tags_event.content.tags.remove(&tag.clone().into());
|
||||
|
||||
services().account_data.update(
|
||||
Some(&room_id),
|
||||
&user_id,
|
||||
RoomAccountDataEventType::Tag,
|
||||
&serde_json::to_value(tags_event).expect("to json value always works"),
|
||||
)?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully updated room account data for {user_id} and room {room_id}, deleting room tag {tag}"
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn get_room_tags(
|
||||
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_active_local_user_id(&user_id)?;
|
||||
|
||||
let event = services()
|
||||
.account_data
|
||||
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
||||
|
||||
let tags_event = event.map_or_else(
|
||||
|| TagEvent {
|
||||
content: TagEventContent {
|
||||
tags: BTreeMap::new(),
|
||||
},
|
||||
},
|
||||
|e| serde_json::from_str(e.get()).expect("Bad account data in database for user {user_id}"),
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("<pre><code>\n{:?}\n</code></pre>", tags_event.content.tags),
|
||||
format!("```\n{:?}\n```", tags_event.content.tags),
|
||||
))
|
||||
}
|
||||
64
src/admin/utils.rs
Normal file
64
src/admin/utils.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
pub(crate) use conduit::utils::HtmlEscape;
|
||||
use conduit_core::Error;
|
||||
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
|
||||
use service::user_is_local;
|
||||
|
||||
use crate::{services, Result};
|
||||
|
||||
pub(crate) fn escape_html(s: &str) -> String {
|
||||
s.replace('&', "&")
|
||||
.replace('<', "<")
|
||||
.replace('>', ">")
|
||||
}
|
||||
|
||||
pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
|
||||
(
|
||||
id.into(),
|
||||
services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or(0),
|
||||
services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| id.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses user ID
|
||||
pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> {
|
||||
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name())
|
||||
.map_err(|e| Error::Err(format!("The supplied username is not a valid username: {e}")))
|
||||
}
|
||||
|
||||
/// Parses user ID as our local user
|
||||
pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> {
|
||||
let user_id = parse_user_id(user_id)?;
|
||||
|
||||
if !user_is_local(&user_id) {
|
||||
return Err(Error::Err(String::from("User does not belong to our server.")));
|
||||
}
|
||||
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
/// Parses user ID that is an active (not guest or deactivated) local user
|
||||
pub(crate) fn parse_active_local_user_id(user_id: &str) -> Result<OwnedUserId> {
|
||||
let user_id = parse_local_user_id(user_id)?;
|
||||
|
||||
if !services().users.exists(&user_id)? {
|
||||
return Err(Error::Err(String::from("User does not exist on this server.")));
|
||||
}
|
||||
|
||||
if services().users.is_deactivated(&user_id)? {
|
||||
return Err(Error::Err(String::from("User is deactivated.")));
|
||||
}
|
||||
|
||||
Ok(user_id)
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//! Default allocator with no special features
|
||||
|
||||
/// Always returns the empty string
|
||||
pub(crate) fn memory_stats() -> String { Default::default() }
|
||||
|
||||
/// Always returns the empty string
|
||||
pub(crate) fn memory_usage() -> String { Default::default() }
|
||||
@@ -1,8 +0,0 @@
|
||||
#[global_allocator]
|
||||
static HMALLOC: hardened_malloc_rs::HardenedMalloc = hardened_malloc_rs::HardenedMalloc;
|
||||
|
||||
pub(crate) fn memory_usage() -> String {
|
||||
String::default() //TODO: get usage
|
||||
}
|
||||
|
||||
pub(crate) fn memory_stats() -> String { "Extended statistics are not available from hardened_malloc.".to_owned() }
|
||||
65
src/api/Cargo.toml
Normal file
65
src/api/Cargo.toml
Normal file
@@ -0,0 +1,65 @@
|
||||
[package]
|
||||
name = "conduit_api"
|
||||
categories.workspace = true
|
||||
description.workspace = true
|
||||
edition.workspace = true
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "mod.rs"
|
||||
crate-type = [
|
||||
"rlib",
|
||||
# "dylib",
|
||||
]
|
||||
|
||||
[features]
|
||||
element_hacks = []
|
||||
dev_release_log_level = []
|
||||
release_max_log_level = [
|
||||
"tracing/max_level_trace",
|
||||
"tracing/release_max_level_info",
|
||||
"log/max_level_trace",
|
||||
"log/release_max_level_info",
|
||||
]
|
||||
gzip_compression = [
|
||||
"reqwest/gzip",
|
||||
]
|
||||
brotli_compression = [
|
||||
"reqwest/brotli",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
axum-client-ip.workspace = true
|
||||
axum-extra.workspace = true
|
||||
axum.workspace = true
|
||||
base64.workspace = true
|
||||
bytes.workspace = true
|
||||
conduit-core.workspace = true
|
||||
conduit-database.workspace = true
|
||||
conduit-service.workspace = true
|
||||
futures-util.workspace = true
|
||||
hmac.workspace = true
|
||||
http.workspace = true
|
||||
hyper.workspace = true
|
||||
image.workspace = true
|
||||
ipaddress.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
log.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest.workspace = true
|
||||
ruma.workspace = true
|
||||
serde_html_form.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
sha-1.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
webpage.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,8 +1,12 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::debug_info;
|
||||
use register::RegistrationKind;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
account::{
|
||||
change_password, deactivate, get_3pids, get_username_availability,
|
||||
change_password, check_registration_token_validity, deactivate, get_3pids, get_username_availability,
|
||||
register::{self, LoginType},
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, whoami,
|
||||
ThirdPartyIdRemovalStatus,
|
||||
@@ -11,15 +15,15 @@
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
|
||||
push, UserId,
|
||||
push, OwnedRoomId, UserId,
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
||||
use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::{
|
||||
api::client_server::{self, join_room_by_id_helper},
|
||||
service, services,
|
||||
utils::{self, user_id::user_is_local},
|
||||
service::user_is_local,
|
||||
services,
|
||||
utils::{self},
|
||||
Error, Result, Ruma,
|
||||
};
|
||||
|
||||
@@ -36,8 +40,9 @@
|
||||
///
|
||||
/// Note: This will not reserve the username, so the username might become
|
||||
/// invalid when trying to register
|
||||
#[tracing::instrument(skip_all, fields(%client_ip))]
|
||||
pub(crate) async fn get_register_available_route(
|
||||
body: Ruma<get_username_availability::v3::Request>,
|
||||
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_username_availability::v3::Request>,
|
||||
) -> Result<get_username_availability::v3::Response> {
|
||||
// Validate user id
|
||||
let user_id = UserId::parse_with_server_name(body.username.to_lowercase(), services().globals.server_name())
|
||||
@@ -84,7 +89,10 @@ pub(crate) async fn get_register_available_route(
|
||||
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
||||
/// access_token
|
||||
#[allow(clippy::doc_markdown)]
|
||||
pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<register::v3::Response> {
|
||||
#[tracing::instrument(skip_all, fields(%client_ip))]
|
||||
pub(crate) async fn register_route(
|
||||
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<register::v3::Request>,
|
||||
) -> Result<register::v3::Response> {
|
||||
if !services().globals.allow_registration() && body.appservice_info.is_none() {
|
||||
info!(
|
||||
"Registration disabled and request not from known appservice, rejecting registration attempt for username \
|
||||
@@ -101,8 +109,8 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
|| (services().globals.allow_registration() && services().globals.config.registration_token.is_some()))
|
||||
{
|
||||
info!(
|
||||
"Guest registration disabled / registration enabled with token configured, rejecting guest registration, \
|
||||
initial device name: {:?}",
|
||||
"Guest registration disabled / registration enabled with token configured, rejecting guest registration \
|
||||
attempt, initial device name: {:?}",
|
||||
body.initial_device_display_name
|
||||
);
|
||||
return Err(Error::BadRequest(
|
||||
@@ -170,8 +178,7 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo;
|
||||
let skip_auth;
|
||||
if services().globals.config.registration_token.is_some() {
|
||||
let skip_auth = if services().globals.config.registration_token.is_some() {
|
||||
// Registration token required
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
@@ -182,7 +189,7 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
skip_auth = body.appservice_info.is_some();
|
||||
body.appservice_info.is_some()
|
||||
} else {
|
||||
// No registration token necessary, but clients must still go through the flow
|
||||
uiaainfo = UiaaInfo {
|
||||
@@ -194,8 +201,8 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
skip_auth = body.appservice_info.is_some() || is_guest;
|
||||
}
|
||||
body.appservice_info.is_some() || is_guest
|
||||
};
|
||||
|
||||
if !skip_auth {
|
||||
if let Some(auth) = &body.auth {
|
||||
@@ -238,7 +245,8 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
// 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() {
|
||||
displayname.push_str(&(" ".to_owned() + services().globals.new_user_displayname_suffix()));
|
||||
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix)
|
||||
.expect("should be able to write to string buffer");
|
||||
}
|
||||
|
||||
services()
|
||||
@@ -286,20 +294,23 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
.users
|
||||
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())?;
|
||||
|
||||
info!("New user \"{}\" registered on this server.", user_id);
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
|
||||
// log in conduit admin channel if a non-guest user registered
|
||||
if body.appservice_info.is_none() && !is_guest {
|
||||
info!("New user \"{user_id}\" registered on this server.");
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"New user \"{user_id}\" registered on this server."
|
||||
"New user \"{user_id}\" registered on this server from IP {client_ip}."
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
|
||||
// log in conduit admin channel if a guest registered
|
||||
if body.appservice_info.is_none() && is_guest && services().globals.log_guest_registrations() {
|
||||
info!("New guest user \"{user_id}\" registered on this server from IP.");
|
||||
|
||||
if let Some(device_display_name) = &body.initial_device_display_name {
|
||||
if body
|
||||
.initial_device_display_name
|
||||
@@ -310,14 +321,15 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \
|
||||
server."
|
||||
server from IP {client_ip}."
|
||||
)))
|
||||
.await;
|
||||
} else {
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on this server.",
|
||||
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
|
||||
{client_ip}.",
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
@@ -325,7 +337,8 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on this server.",
|
||||
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
|
||||
{client_ip}.",
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
@@ -334,7 +347,7 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
// If this is the first real user, grant them admin privileges except for guest
|
||||
// users Note: the server user, @conduit:servername, is generated first
|
||||
if !is_guest {
|
||||
if let Some(admin_room) = service::admin::Service::get_admin_room().await? {
|
||||
if let Some(admin_room) = service::admin::Service::get_admin_room()? {
|
||||
if services()
|
||||
.rooms
|
||||
.state_cache
|
||||
@@ -346,7 +359,7 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
.make_user_admin(&user_id, displayname)
|
||||
.await?;
|
||||
|
||||
warn!("Granting {} admin privileges as the first user", user_id);
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,8 +423,9 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
|
||||
/// last seen ts)
|
||||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
#[tracing::instrument(skip_all, fields(%client_ip))]
|
||||
pub(crate) async fn change_password_route(
|
||||
body: Ruma<change_password::v3::Request>,
|
||||
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<change_password::v3::Request>,
|
||||
) -> Result<change_password::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
@@ -460,7 +474,7 @@ pub(crate) async fn change_password_route(
|
||||
}
|
||||
}
|
||||
|
||||
info!("User {} changed their password.", sender_user);
|
||||
info!("User {sender_user} changed their password.");
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
@@ -498,7 +512,10 @@ pub(crate) async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoa
|
||||
/// - Forgets all to-device events
|
||||
/// - Triggers device list updates
|
||||
/// - Removes ability to log in again
|
||||
pub(crate) async fn deactivate_route(body: Ruma<deactivate::v3::Request>) -> Result<deactivate::v3::Response> {
|
||||
#[tracing::instrument(skip_all, fields(%client_ip))]
|
||||
pub(crate) async fn deactivate_route(
|
||||
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<deactivate::v3::Request>,
|
||||
) -> Result<deactivate::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -530,13 +547,23 @@ pub(crate) async fn deactivate_route(body: Ruma<deactivate::v3::Request>) -> Res
|
||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
}
|
||||
|
||||
// Make the user leave all rooms before deactivation
|
||||
client_server::leave_all_rooms(sender_user).await?;
|
||||
|
||||
// Remove devices and mark account as deactivated
|
||||
services().users.deactivate_account(sender_user)?;
|
||||
|
||||
info!("User {} deactivated their account.", sender_user);
|
||||
// Remove profile pictures and display name
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services()
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
super::update_displayname(sender_user.clone(), None, all_joined_rooms.clone()).await?;
|
||||
super::update_avatar_url(sender_user.clone(), None, None, all_joined_rooms).await?;
|
||||
|
||||
// Make the user leave all rooms before deactivation
|
||||
super::leave_all_rooms(sender_user).await;
|
||||
|
||||
info!("User {sender_user} deactivated their account.");
|
||||
services()
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
@@ -591,3 +618,24 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||
"Third party identifier is not allowed",
|
||||
))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||
///
|
||||
/// Checks if the provided registration token is valid at the time of checking
|
||||
///
|
||||
/// Currently does not have any ratelimiting, and this isn't very practical as
|
||||
/// there is only one registration token allowed.
|
||||
pub(crate) async fn check_registration_token_validity(
|
||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||
) -> Result<check_registration_token_validity::v1::Response> {
|
||||
let Some(reg_token) = services().globals.config.registration_token.clone() else {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::forbidden(),
|
||||
"Server does not allow token registration.",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response {
|
||||
valid: reg_token == body.token,
|
||||
})
|
||||
}
|
||||
@@ -8,19 +8,22 @@
|
||||
},
|
||||
federation,
|
||||
},
|
||||
OwnedRoomAliasId, OwnedRoomId, OwnedServerName,
|
||||
OwnedRoomAliasId, OwnedServerName, RoomAliasId, RoomId,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
debug_info, debug_warn, service::appservice::RegistrationInfo, services, utils::server_name::server_is_ours, Error,
|
||||
Result, Ruma,
|
||||
debug_info, debug_warn,
|
||||
service::{appservice::RegistrationInfo, server_is_ours},
|
||||
services, Error, Result, Ruma,
|
||||
};
|
||||
|
||||
/// # `PUT /_matrix/client/v3/directory/room/{roomAlias}`
|
||||
///
|
||||
/// Creates a new room alias on this server.
|
||||
pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) -> Result<create_alias::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
alias_checks(&body.room_alias, &body.appservice_info).await?;
|
||||
|
||||
// this isn't apart of alias_checks or delete alias route because we should
|
||||
@@ -42,17 +45,10 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
|
||||
return Err(Error::Conflict("Alias already exists."));
|
||||
}
|
||||
|
||||
if services()
|
||||
services()
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&body.room_alias, &body.room_id)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid room alias. Alias must be in the form of '#localpart:server_name'",
|
||||
));
|
||||
};
|
||||
.set_alias(&body.room_alias, &body.room_id, sender_user)?;
|
||||
|
||||
Ok(create_alias::v3::Response::new())
|
||||
}
|
||||
@@ -61,9 +57,10 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
|
||||
///
|
||||
/// Deletes a room alias from this server.
|
||||
///
|
||||
/// - TODO: additional access control checks
|
||||
/// - TODO: Update canonical alias event
|
||||
pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) -> Result<delete_alias::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
alias_checks(&body.room_alias, &body.appservice_info).await?;
|
||||
|
||||
if services()
|
||||
@@ -75,17 +72,11 @@ pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) ->
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Alias does not exist."));
|
||||
}
|
||||
|
||||
if services()
|
||||
services()
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&body.room_alias)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid room alias. Alias must be in the form of '#localpart:server_name'",
|
||||
));
|
||||
};
|
||||
.remove_alias(&body.room_alias, sender_user)
|
||||
.await?;
|
||||
|
||||
// TODO: update alt_aliases?
|
||||
|
||||
@@ -99,7 +90,7 @@ pub(crate) async fn get_alias_route(body: Ruma<get_alias::v3::Request>) -> Resul
|
||||
get_alias_helper(body.body.room_alias, None).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_alias_helper(
|
||||
pub async fn get_alias_helper(
|
||||
room_alias: OwnedRoomAliasId, servers: Option<Vec<OwnedServerName>>,
|
||||
) -> Result<get_alias::v3::Response> {
|
||||
debug!("get_alias_helper servers: {servers:?}");
|
||||
@@ -119,7 +110,7 @@ pub(crate) async fn get_alias_helper(
|
||||
)
|
||||
.await;
|
||||
|
||||
debug_info!("room alias server_name get_alias_helper response: {response:?}");
|
||||
debug!("room alias server_name get_alias_helper response: {response:?}");
|
||||
|
||||
if let Err(ref e) = response {
|
||||
debug_info!(
|
||||
@@ -140,7 +131,7 @@ pub(crate) async fn get_alias_helper(
|
||||
},
|
||||
)
|
||||
.await;
|
||||
debug_info!("Got response from server {server} for room aliases: {response:?}");
|
||||
debug!("Got response from server {server} for room aliases: {response:?}");
|
||||
|
||||
if let Ok(ref response) = response {
|
||||
if !response.servers.is_empty() {
|
||||
@@ -162,7 +153,7 @@ pub(crate) async fn get_alias_helper(
|
||||
pre_servers.push(room_alias.server_name().into());
|
||||
|
||||
let servers = room_available_servers(&room_id, &room_alias, &Some(pre_servers));
|
||||
debug_warn!(
|
||||
debug!(
|
||||
"room alias servers from federation response for room ID {room_id} and room alias {room_alias}: \
|
||||
{servers:?}"
|
||||
);
|
||||
@@ -213,13 +204,13 @@ pub(crate) async fn get_alias_helper(
|
||||
|
||||
let servers = room_available_servers(&room_id, &room_alias, &None);
|
||||
|
||||
debug_warn!("room alias servers for room ID {room_id} and room alias {room_alias}");
|
||||
debug!("room alias servers for room ID {room_id} and room alias {room_alias}");
|
||||
|
||||
Ok(get_alias::v3::Response::new(room_id, servers))
|
||||
}
|
||||
|
||||
fn room_available_servers(
|
||||
room_id: &OwnedRoomId, room_alias: &OwnedRoomAliasId, pre_servers: &Option<Vec<OwnedServerName>>,
|
||||
room_id: &RoomId, room_alias: &RoomAliasId, pre_servers: &Option<Vec<OwnedServerName>>,
|
||||
) -> Vec<OwnedServerName> {
|
||||
// find active servers in room state cache to suggest
|
||||
let mut servers: Vec<OwnedServerName> = services()
|
||||
@@ -247,20 +238,20 @@ fn room_available_servers(
|
||||
.iter()
|
||||
.position(|server_name| server_is_ours(server_name))
|
||||
{
|
||||
servers.remove(server_index);
|
||||
servers.swap_remove(server_index);
|
||||
servers.insert(0, services().globals.server_name().to_owned());
|
||||
} else if let Some(alias_server_index) = servers
|
||||
.iter()
|
||||
.position(|server| server == room_alias.server_name())
|
||||
{
|
||||
servers.remove(alias_server_index);
|
||||
servers.swap_remove(alias_server_index);
|
||||
servers.insert(0, room_alias.server_name().into());
|
||||
}
|
||||
|
||||
servers
|
||||
}
|
||||
|
||||
async fn alias_checks(room_alias: &OwnedRoomAliasId, appservice_info: &Option<RegistrationInfo>) -> Result<()> {
|
||||
async fn alias_checks(room_alias: &RoomAliasId, appservice_info: &Option<RegistrationInfo>) -> Result<()> {
|
||||
if !server_is_ours(room_alias.server_name()) {
|
||||
return Err(Error::BadRequest(ErrorKind::InvalidParam, "Alias is from another server."));
|
||||
}
|
||||
@@ -269,9 +260,7 @@ async fn alias_checks(room_alias: &OwnedRoomAliasId, appservice_info: &Option<Re
|
||||
if !info.aliases.is_match(room_alias.as_str()) {
|
||||
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace."));
|
||||
}
|
||||
}
|
||||
|
||||
if services().appservice.is_exclusive_alias(room_alias).await {
|
||||
} else if services().appservice.is_exclusive_alias(room_alias).await {
|
||||
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias reserved by appservice."));
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use ruma::api::client::{
|
||||
backup::{
|
||||
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
|
||||
delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
|
||||
get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
|
||||
get_latest_backup_info, update_backup_version,
|
||||
use ruma::{
|
||||
api::client::{
|
||||
backup::{
|
||||
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
|
||||
delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
|
||||
get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
|
||||
get_latest_backup_info, update_backup_version,
|
||||
},
|
||||
error::ErrorKind,
|
||||
},
|
||||
error::ErrorKind,
|
||||
UInt,
|
||||
};
|
||||
|
||||
use crate::{services, Error, Result, Ruma};
|
||||
@@ -56,7 +59,8 @@ pub(crate) async fn get_latest_backup_info_route(
|
||||
|
||||
Ok(get_latest_backup_info::v3::Response {
|
||||
algorithm,
|
||||
count: (services().key_backups.count_keys(sender_user, &version)? as u32).into(),
|
||||
count: (UInt::try_from(services().key_backups.count_keys(sender_user, &version)?)
|
||||
.expect("user backup keys count should not be that high")),
|
||||
etag: services().key_backups.get_etag(sender_user, &version)?,
|
||||
version,
|
||||
})
|
||||
@@ -76,10 +80,12 @@ pub(crate) async fn get_backup_info_route(
|
||||
|
||||
Ok(get_backup_info::v3::Response {
|
||||
algorithm,
|
||||
count: (services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)? as u32)
|
||||
.into(),
|
||||
count: (UInt::try_from(
|
||||
services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)?,
|
||||
)
|
||||
.expect("user backup keys count should not be that high")),
|
||||
etag: services()
|
||||
.key_backups
|
||||
.get_etag(sender_user, &body.version)?,
|
||||
@@ -139,10 +145,12 @@ pub(crate) async fn add_backup_keys_route(
|
||||
}
|
||||
|
||||
Ok(add_backup_keys::v3::Response {
|
||||
count: (services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)? as u32)
|
||||
.into(),
|
||||
count: (UInt::try_from(
|
||||
services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)?,
|
||||
)
|
||||
.expect("user backup keys count should not be that high")),
|
||||
etag: services()
|
||||
.key_backups
|
||||
.get_etag(sender_user, &body.version)?,
|
||||
@@ -181,10 +189,12 @@ pub(crate) async fn add_backup_keys_for_room_route(
|
||||
}
|
||||
|
||||
Ok(add_backup_keys_for_room::v3::Response {
|
||||
count: (services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)? as u32)
|
||||
.into(),
|
||||
count: (UInt::try_from(
|
||||
services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)?,
|
||||
)
|
||||
.expect("user backup keys count should not be that high")),
|
||||
etag: services()
|
||||
.key_backups
|
||||
.get_etag(sender_user, &body.version)?,
|
||||
@@ -221,10 +231,12 @@ pub(crate) async fn add_backup_keys_for_session_route(
|
||||
.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?;
|
||||
|
||||
Ok(add_backup_keys_for_session::v3::Response {
|
||||
count: (services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)? as u32)
|
||||
.into(),
|
||||
count: (UInt::try_from(
|
||||
services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)?,
|
||||
)
|
||||
.expect("user backup keys count should not be that high")),
|
||||
etag: services()
|
||||
.key_backups
|
||||
.get_etag(sender_user, &body.version)?,
|
||||
@@ -294,10 +306,12 @@ pub(crate) async fn delete_backup_keys_route(
|
||||
.delete_all_keys(sender_user, &body.version)?;
|
||||
|
||||
Ok(delete_backup_keys::v3::Response {
|
||||
count: (services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)? as u32)
|
||||
.into(),
|
||||
count: (UInt::try_from(
|
||||
services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)?,
|
||||
)
|
||||
.expect("user backup keys count should not be that high")),
|
||||
etag: services()
|
||||
.key_backups
|
||||
.get_etag(sender_user, &body.version)?,
|
||||
@@ -317,10 +331,12 @@ pub(crate) async fn delete_backup_keys_for_room_route(
|
||||
.delete_room_keys(sender_user, &body.version, &body.room_id)?;
|
||||
|
||||
Ok(delete_backup_keys_for_room::v3::Response {
|
||||
count: (services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)? as u32)
|
||||
.into(),
|
||||
count: (UInt::try_from(
|
||||
services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)?,
|
||||
)
|
||||
.expect("user backup keys count should not be that high")),
|
||||
etag: services()
|
||||
.key_backups
|
||||
.get_etag(sender_user, &body.version)?,
|
||||
@@ -340,10 +356,12 @@ pub(crate) async fn delete_backup_keys_for_session_route(
|
||||
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?;
|
||||
|
||||
Ok(delete_backup_keys_for_session::v3::Response {
|
||||
count: (services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)? as u32)
|
||||
.into(),
|
||||
count: (UInt::try_from(
|
||||
services()
|
||||
.key_backups
|
||||
.count_keys(sender_user, &body.version)?,
|
||||
)
|
||||
.expect("user backup keys count should not be that high")),
|
||||
etag: services()
|
||||
.key_backups
|
||||
.get_etag(sender_user, &body.version)?,
|
||||
@@ -63,8 +63,8 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
||||
lazy_loaded.insert(base_event.sender.as_str().to_owned());
|
||||
}
|
||||
|
||||
// Use limit with maximum 100
|
||||
let limit = u64::from(body.limit).min(100) as usize;
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = usize::try_from(body.limit).unwrap_or(10).min(100);
|
||||
|
||||
let base_event = base_event.to_room_event();
|
||||
|
||||
@@ -163,7 +163,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
||||
.map(|(_, pdu)| pdu.to_room_event())
|
||||
.collect();
|
||||
|
||||
let mut state = Vec::new();
|
||||
let mut state = Vec::with_capacity(state_ids.len());
|
||||
|
||||
for (shortstatekey, id) in state_ids {
|
||||
let (event_type, state_key) = services()
|
||||
@@ -1,3 +1,4 @@
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use ruma::{
|
||||
api::{
|
||||
client::{
|
||||
@@ -11,28 +12,25 @@
|
||||
events::{
|
||||
room::{
|
||||
avatar::RoomAvatarEventContent,
|
||||
canonical_alias::RoomCanonicalAliasEventContent,
|
||||
create::RoomCreateEventContent,
|
||||
guest_access::{GuestAccess, RoomGuestAccessEventContent},
|
||||
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
|
||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||
topic::RoomTopicEventContent,
|
||||
},
|
||||
StateEventType,
|
||||
},
|
||||
ServerName, UInt,
|
||||
uint, ServerName, UInt,
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::{services, utils::server_name::server_is_ours, Error, Result, Ruma};
|
||||
use crate::{service::server_is_ours, services, Error, Result, Ruma};
|
||||
|
||||
/// # `POST /_matrix/client/v3/publicRooms`
|
||||
///
|
||||
/// Lists the public rooms on this server.
|
||||
///
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
#[tracing::instrument(skip_all, fields(%client_ip))]
|
||||
pub(crate) async fn get_public_rooms_filtered_route(
|
||||
body: Ruma<get_public_rooms_filtered::v3::Request>,
|
||||
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_public_rooms_filtered::v3::Request>,
|
||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||
if let Some(server) = &body.server {
|
||||
if services()
|
||||
@@ -56,8 +54,8 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Failed to return our /publicRooms: {e}");
|
||||
Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.")
|
||||
warn!(?body.server, "Failed to return /publicRooms: {e}");
|
||||
Error::BadRequest(ErrorKind::Unknown, "Failed to return the requested server's public room list.")
|
||||
})?;
|
||||
|
||||
Ok(response)
|
||||
@@ -68,8 +66,9 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
/// Lists the public rooms on this server.
|
||||
///
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
#[tracing::instrument(skip_all, fields(%client_ip))]
|
||||
pub(crate) async fn get_public_rooms_route(
|
||||
body: Ruma<get_public_rooms::v3::Request>,
|
||||
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_public_rooms::v3::Request>,
|
||||
) -> Result<get_public_rooms::v3::Response> {
|
||||
if let Some(server) = &body.server {
|
||||
if services()
|
||||
@@ -93,8 +92,8 @@ pub(crate) async fn get_public_rooms_route(
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Failed to return our /publicRooms: {e}");
|
||||
Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.")
|
||||
warn!(?body.server, "Failed to return /publicRooms: {e}");
|
||||
Error::BadRequest(ErrorKind::Unknown, "Failed to return the requested server's public room list.")
|
||||
})?;
|
||||
|
||||
Ok(get_public_rooms::v3::Response {
|
||||
@@ -110,8 +109,9 @@ pub(crate) async fn get_public_rooms_route(
|
||||
/// Sets the visibility of a given room in the room directory.
|
||||
///
|
||||
/// - TODO: Access control checks
|
||||
#[tracing::instrument(skip_all, fields(%client_ip))]
|
||||
pub(crate) async fn set_room_visibility_route(
|
||||
body: Ruma<set_room_visibility::v3::Request>,
|
||||
InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<set_room_visibility::v3::Request>,
|
||||
) -> Result<set_room_visibility::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -198,8 +198,9 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
});
|
||||
}
|
||||
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit = limit.map_or(10, u64::from);
|
||||
let mut num_since = 0_u64;
|
||||
let mut num_since: u64 = 0;
|
||||
|
||||
if let Some(s) = &since {
|
||||
let mut characters = s.chars();
|
||||
@@ -230,12 +231,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
canonical_alias: services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomCanonicalAlias, "")?
|
||||
.map_or(Ok(None), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomCanonicalAliasEventContent| c.alias)
|
||||
.map_err(|_| Error::bad_database("Invalid canonical alias event in database."))
|
||||
})?,
|
||||
.get_canonical_alias(&room_id)?,
|
||||
name: services().rooms.state_accessor.get_name(&room_id)?,
|
||||
num_joined_members: services()
|
||||
.rooms
|
||||
@@ -250,40 +246,13 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
topic: services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomTopic, "")?
|
||||
.map_or(Ok(None), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomTopicEventContent| Some(c.topic))
|
||||
.map_err(|e| {
|
||||
error!("Invalid room topic event in database for room {room_id}: {e}");
|
||||
Error::bad_database("Invalid room topic event in database.")
|
||||
})
|
||||
})
|
||||
.get_room_topic(&room_id)
|
||||
.unwrap_or(None),
|
||||
world_readable: services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomHistoryVisibility, "")?
|
||||
.map_or(Ok(false), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomHistoryVisibilityEventContent| {
|
||||
c.history_visibility == HistoryVisibility::WorldReadable
|
||||
})
|
||||
.map_err(|e| {
|
||||
error!(
|
||||
"Invalid room history visibility event in database for room {room_id}, assuming is \"shared\": {e}",
|
||||
);
|
||||
Error::bad_database("Invalid room history visibility event in database.")
|
||||
})}).unwrap_or(false),
|
||||
world_readable: services().rooms.state_accessor.is_world_readable(&room_id)?,
|
||||
guest_can_join: services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomGuestAccess, "")?
|
||||
.map_or(Ok(false), |s| {
|
||||
serde_json::from_str(s.content.get())
|
||||
.map(|c: RoomGuestAccessEventContent| c.guest_access == GuestAccess::CanJoin)
|
||||
.map_err(|_| Error::bad_database("Invalid room guest access event in database."))
|
||||
})?,
|
||||
.guest_can_join(&room_id)?,
|
||||
avatar_url: services()
|
||||
.rooms
|
||||
.state_accessor
|
||||
@@ -363,12 +332,16 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
|
||||
all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members));
|
||||
|
||||
let total_room_count_estimate = (all_rooms.len() as u32).into();
|
||||
let total_room_count_estimate = UInt::try_from(all_rooms.len()).unwrap_or_else(|_| uint!(0));
|
||||
|
||||
let chunk: Vec<_> = all_rooms
|
||||
.into_iter()
|
||||
.skip(num_since as usize)
|
||||
.take(limit as usize)
|
||||
.skip(
|
||||
num_since
|
||||
.try_into()
|
||||
.expect("num_since should not be this high"),
|
||||
)
|
||||
.take(limit.try_into().expect("limit should not be this high"))
|
||||
.collect();
|
||||
|
||||
let prev_batch = if num_since == 0 {
|
||||
@@ -377,10 +350,15 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
Some(format!("p{num_since}"))
|
||||
};
|
||||
|
||||
let next_batch = if chunk.len() < limit as usize {
|
||||
let next_batch = if chunk.len() < limit.try_into().unwrap() {
|
||||
None
|
||||
} else {
|
||||
Some(format!("n{}", num_since + limit))
|
||||
Some(format!(
|
||||
"n{}",
|
||||
num_since
|
||||
.checked_add(limit)
|
||||
.expect("num_since and limit should not be that large")
|
||||
))
|
||||
};
|
||||
|
||||
Ok(get_public_rooms_filtered::v3::Response {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user