mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-01 21:35:46 +00:00
Compare commits
294 Commits
jade/http3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cc1e4685c | ||
|
|
3d2915093c | ||
|
|
e1c54f4dec | ||
|
|
0c9fa3b7e5 | ||
|
|
a95b488e6a | ||
|
|
4f8833e937 | ||
|
|
f32599e030 | ||
|
|
b6f0b41d3d | ||
|
|
d5675b85cf | ||
|
|
951b5abe19 | ||
|
|
a325ad16f1 | ||
|
|
f93a1cc506 | ||
|
|
6e8dbcbfab | ||
|
|
97458207e5 | ||
|
|
ab8929e2fa | ||
|
|
166d7d0f63 | ||
|
|
20a6f0c6fb | ||
|
|
3885e43b5d | ||
|
|
ef7ad6082c | ||
|
|
717d319708 | ||
|
|
0b04757bef | ||
|
|
f2b7dd6519 | ||
|
|
9d06208a7a | ||
|
|
955da3a74f | ||
|
|
7e79a544cf | ||
|
|
f5db4d17d6 | ||
|
|
54fd1d313f | ||
|
|
bb7fd9efc1 | ||
|
|
aa79072411 | ||
|
|
8b72c5eb11 | ||
|
|
e5cfc503d8 | ||
|
|
07d5081008 | ||
|
|
dba7f47972 | ||
|
|
0a2d4e1cb2 | ||
|
|
f45857acd4 | ||
|
|
9209b847f6 | ||
|
|
cf9c2c23b6 | ||
|
|
1bd161a306 | ||
|
|
0a0206e866 | ||
|
|
e6f31d7d4f | ||
|
|
f0c3fdfe3a | ||
|
|
3c3314b498 | ||
|
|
8e7846c644 | ||
|
|
3ebaba920f | ||
|
|
19e620c8c6 | ||
|
|
300b6d81e7 | ||
|
|
ed81dfc6cd | ||
|
|
2ffafc17d2 | ||
|
|
8589563a2f | ||
|
|
27d806e961 | ||
|
|
7aa02a1cd9 | ||
|
|
fc342f5401 | ||
|
|
ef089c1800 | ||
|
|
279c505af9 | ||
|
|
f9058ee062 | ||
|
|
6c856bd1a4 | ||
|
|
4dbda8692c | ||
|
|
075914d8e8 | ||
|
|
a2a644194b | ||
|
|
093ef742c3 | ||
|
|
010daf079d | ||
|
|
58c4f5d5b5 | ||
|
|
c78a72bbef | ||
|
|
7e8f1ffd63 | ||
|
|
3d0b886ab8 | ||
|
|
2e7bfea240 | ||
|
|
b9456c1130 | ||
|
|
3ce6e909dd | ||
|
|
3b4b401a51 | ||
|
|
260b88975d | ||
|
|
be8e3772c1 | ||
|
|
8b91db2918 | ||
|
|
34758c52cc | ||
|
|
8b8c015dcc | ||
|
|
9afe5f6bed | ||
|
|
fe03b3b8b7 | ||
|
|
a04ef6d686 | ||
|
|
fd807ff1f6 | ||
|
|
b0632dde41 | ||
|
|
cc3a8a1d40 | ||
|
|
30a540d8bc | ||
|
|
6d0832a6ee | ||
|
|
119aa6476d | ||
|
|
b9854662f3 | ||
|
|
dab50b1ec3 | ||
|
|
0338539221 | ||
|
|
e94e614498 | ||
|
|
098e8a0b92 | ||
|
|
1c3890476a | ||
|
|
8ef6f02ee9 | ||
|
|
11020df89d | ||
|
|
47e3738807 | ||
|
|
8afb19757e | ||
|
|
de3dfb2bea | ||
|
|
bbb2615f2c | ||
|
|
af1b4de231 | ||
|
|
677c407755 | ||
|
|
e3ae714248 | ||
|
|
fb9a2aa4d6 | ||
|
|
5164822090 | ||
|
|
6b013bcf60 | ||
|
|
05a49ceb60 | ||
|
|
728c5828ba | ||
|
|
50c94d85a1 | ||
|
|
0cc188f62c | ||
|
|
6451671f66 | ||
|
|
ca21a885d5 | ||
|
|
4af4110f6d | ||
|
|
51b450c05c | ||
|
|
f9d1f71343 | ||
|
|
7901e4b996 | ||
|
|
7b6bf4b78e | ||
|
|
67d5619ccb | ||
|
|
bf001f96d6 | ||
|
|
ae2b87f03f | ||
|
|
957cd3502f | ||
|
|
a109542eb8 | ||
|
|
8c4844b00b | ||
|
|
eec7103910 | ||
|
|
43aa172829 | ||
|
|
9b4c483b6d | ||
|
|
b885e206ce | ||
|
|
07a935f625 | ||
|
|
d13801e976 | ||
|
|
5716c36b47 | ||
|
|
f11943b956 | ||
|
|
8b726a9c94 | ||
|
|
ffa3c53847 | ||
|
|
da8833fca4 | ||
|
|
267feb3c09 | ||
|
|
3d50af0943 | ||
|
|
9515019641 | ||
|
|
f0f53dfada | ||
|
|
acef746d26 | ||
|
|
3356b60e97 | ||
|
|
c988c2b387 | ||
|
|
3121229707 | ||
|
|
ff85145ee8 | ||
|
|
f61d1a11e0 | ||
|
|
11ba8979ff | ||
|
|
f6956ccf12 | ||
|
|
977a5ac8c1 | ||
|
|
906c3df953 | ||
|
|
33e5fdc16f | ||
|
|
77ac17855a | ||
|
|
65ffcd2884 | ||
|
|
7ec88bdbfe | ||
|
|
da3fac8cb4 | ||
|
|
3366113939 | ||
|
|
9039784f41 | ||
|
|
7f165e5bbe | ||
|
|
c97111e3ca | ||
|
|
e8746760fa | ||
|
|
9dbd75e740 | ||
|
|
85b2fd91b9 | ||
|
|
6420c218a9 | ||
|
|
ec9402a328 | ||
|
|
d01f06a5c2 | ||
|
|
aee51b3b0d | ||
|
|
afcbccd9dd | ||
|
|
02448000f9 | ||
|
|
6af8918aa8 | ||
|
|
08f83cc438 | ||
|
|
a0468db121 | ||
|
|
4f23d566ed | ||
|
|
dac619b5f8 | ||
|
|
fdc9cc8074 | ||
|
|
40b1dabcca | ||
|
|
94c5af40cf | ||
|
|
36a3144757 | ||
|
|
220b61c589 | ||
|
|
38e93cde3e | ||
|
|
7e501cdb09 | ||
|
|
da182c162d | ||
|
|
9a3f7f4af7 | ||
|
|
5ce1f682f6 | ||
|
|
5feb08dff2 | ||
|
|
1e527c1075 | ||
|
|
c6943ae683 | ||
|
|
8932dacdc4 | ||
|
|
0be3d850ac | ||
|
|
57e7cf7057 | ||
|
|
1005585ccb | ||
|
|
1188566dbd | ||
|
|
0058212757 | ||
|
|
dbf8fd3320 | ||
|
|
ce295b079e | ||
|
|
5eb74bc1dd | ||
|
|
da561ab792 | ||
|
|
80c9bb4796 | ||
|
|
22a47d1e59 | ||
|
|
83883a002c | ||
|
|
8dd4b71e0e | ||
|
|
6fe3b1563c | ||
|
|
44d3825c8e | ||
|
|
d6c5484c3a | ||
|
|
1fd6056f3f | ||
|
|
525a0ae52b | ||
|
|
60210754d9 | ||
|
|
08dd787083 | ||
|
|
2c7233812b | ||
|
|
d725e98220 | ||
|
|
0226ca1e83 | ||
|
|
1695b6d19e | ||
|
|
c40cc3b236 | ||
|
|
754959e80d | ||
|
|
37888fb670 | ||
|
|
7207398a9e | ||
|
|
1a7bda209b | ||
|
|
7e1950b3d2 | ||
|
|
b507898c62 | ||
|
|
f4af67575e | ||
|
|
6adb99397e | ||
|
|
8ce83a8a14 | ||
|
|
052c4dfa21 | ||
|
|
a43dee1728 | ||
|
|
763d9b3de8 | ||
|
|
1e6d95583c | ||
|
|
8a254a33cc | ||
|
|
c97dd54766 | ||
|
|
8ddb7c70c0 | ||
|
|
cb9786466b | ||
|
|
18d2662b01 | ||
|
|
558262dd1f | ||
|
|
d311b87579 | ||
|
|
8702f55cf5 | ||
|
|
d4481b07ac | ||
|
|
92351df925 | ||
|
|
47e2733ea1 | ||
|
|
6637e4c6a7 | ||
|
|
35e441452f | ||
|
|
66bbb655bf | ||
|
|
81b202ce51 | ||
|
|
4657844d46 | ||
|
|
9016cd11a6 | ||
|
|
dd70094719 | ||
|
|
fcd49b7ab3 | ||
|
|
470c9b52dd | ||
|
|
0d8cafc329 | ||
|
|
2f9956ddca | ||
|
|
21a97cdd0b | ||
|
|
e986cd4536 | ||
|
|
526d862296 | ||
|
|
fbeb5bf186 | ||
|
|
a336f2df44 | ||
|
|
19b78ec73e | ||
|
|
27ff2d9363 | ||
|
|
50fa8c3abf | ||
|
|
18c4be869f | ||
|
|
fc00b96d8b | ||
|
|
fa4156d8a6 | ||
|
|
23638cd714 | ||
|
|
9f1a483e76 | ||
|
|
688ef727e5 | ||
|
|
3de026160e | ||
|
|
9fe761513d | ||
|
|
abf1e1195a | ||
|
|
d9537e9b55 | ||
|
|
0d1de70d8f | ||
|
|
4aa03a71eb | ||
|
|
f847918575 | ||
|
|
7569a0545b | ||
|
|
b6c5991e1f | ||
|
|
efd879fcd8 | ||
|
|
92a848f74d | ||
|
|
776b5865ba | ||
|
|
722bacbe89 | ||
|
|
46907e3dce | ||
|
|
31e2195e56 | ||
|
|
7ecac93ddc | ||
|
|
6a0b103722 | ||
|
|
23d77b614f | ||
|
|
e01aa44b16 | ||
|
|
a08739c246 | ||
|
|
c14864b881 | ||
|
|
1773e72e68 | ||
|
|
0f94d55689 | ||
|
|
abfb6377c2 | ||
|
|
91d64f5b24 | ||
|
|
9a3f3f6e78 | ||
|
|
b3e31a4aad | ||
|
|
8cda431cc6 | ||
|
|
02b9a3f713 | ||
|
|
d40893730c | ||
|
|
28fae58cf6 | ||
|
|
f458f6ab76 | ||
|
|
fdf9cea533 | ||
|
|
ecb1b73c84 | ||
|
|
e03082480a | ||
|
|
f9e7f019ad | ||
|
|
12069e7c86 | ||
|
|
77928a62b4 | ||
|
|
c73cb5c1bf | ||
|
|
a140eacb04 |
@@ -44,7 +44,7 @@ runs:
|
||||
|
||||
- name: Login to builtin registry
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.BUILTIN_REGISTRY }}
|
||||
username: ${{ inputs.registry_user }}
|
||||
@@ -52,7 +52,7 @@ runs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
|
||||
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
|
||||
@@ -61,7 +61,7 @@ runs:
|
||||
- name: Extract metadata (tags) for Docker
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
flavor: |
|
||||
latest=auto
|
||||
|
||||
@@ -67,7 +67,7 @@ runs:
|
||||
uses: ./.forgejo/actions/rust-toolchain
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
|
||||
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
|
||||
@@ -75,11 +75,11 @@ runs:
|
||||
|
||||
- name: Set up QEMU
|
||||
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Login to builtin registry
|
||||
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.BUILTIN_REGISTRY }}
|
||||
username: ${{ inputs.registry_user }}
|
||||
@@ -87,7 +87,7 @@ runs:
|
||||
|
||||
- name: Extract metadata (labels, annotations) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ${{ inputs.images }}
|
||||
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
|
||||
@@ -152,7 +152,7 @@ runs:
|
||||
|
||||
- name: inject cache into docker
|
||||
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
|
||||
uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.0
|
||||
uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.2
|
||||
with:
|
||||
cache-map: |
|
||||
{
|
||||
|
||||
@@ -62,10 +62,6 @@ sync:
|
||||
target: registry.gitlab.com/continuwuity/continuwuity
|
||||
type: repository
|
||||
<<: *tags-main
|
||||
- source: *source
|
||||
target: git.nexy7574.co.uk/mirrored/continuwuity
|
||||
type: repository
|
||||
<<: *tags-releases
|
||||
- source: *source
|
||||
target: ghcr.io/continuwuity/continuwuity
|
||||
type: repository
|
||||
|
||||
@@ -30,22 +30,22 @@ jobs:
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT
|
||||
echo "Debian distribution: $DISTRIBUTION ($VERSION)"
|
||||
- name: Work around llvm-project#153385
|
||||
id: llvm-workaround
|
||||
run: |
|
||||
if [ -f /usr/share/apt/default-sequoia.config ]; then
|
||||
echo "Applying workaround for llvm-project#153385"
|
||||
mkdir -p /etc/crypto-policies/back-ends/
|
||||
cp /usr/share/apt/default-sequoia.config /etc/crypto-policies/back-ends/apt-sequoia.config
|
||||
sed -i 's/\(sha1\.second_preimage_resistance = \)2026-02-01/\12026-06-01/' /etc/crypto-policies/back-ends/apt-sequoia.config
|
||||
else
|
||||
echo "No workaround needed for llvm-project#153385"
|
||||
fi
|
||||
#- name: Work around llvm-project#153385
|
||||
# id: llvm-workaround
|
||||
# run: |
|
||||
# if [ -f /usr/share/apt/default-sequoia.config ]; then
|
||||
# echo "Applying workaround for llvm-project#153385"
|
||||
# mkdir -p /etc/crypto-policies/back-ends/
|
||||
# cp /usr/share/apt/default-sequoia.config /etc/crypto-policies/back-ends/apt-sequoia.config
|
||||
# sed -i 's/\(sha1\.second_preimage_resistance = \)2026-02-01/\12026-06-01/' /etc/crypto-policies/back-ends/apt-sequoia.config
|
||||
# else
|
||||
# echo "No workaround needed for llvm-project#153385"
|
||||
# fi
|
||||
- name: Pick compatible clang version
|
||||
id: clang-version
|
||||
run: |
|
||||
# both latest need to use clang-23, but oldstable and previous can just use clang
|
||||
if [[ "${{ matrix.container }}" == "ubuntu-latest" || "${{ matrix.container }}" == "debian-latest" ]]; then
|
||||
if [[ "${{ matrix.container }}" == "ubuntu-latest" ]]; then
|
||||
echo "Using clang-23 package for ${{ matrix.container }}"
|
||||
echo "version=clang-23" >> $GITHUB_OUTPUT
|
||||
else
|
||||
|
||||
103
.forgejo/workflows/check-changelog.yml
Normal file
103
.forgejo/workflows/check-changelog.yml
Normal file
@@ -0,0 +1,103 @@
|
||||
name: Check Changelog
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.ref }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-changelog:
|
||||
name: Check for changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
sparse-checkout: .
|
||||
|
||||
- name: Check for changelog entry
|
||||
id: check_files
|
||||
run: |
|
||||
git fetch origin ${GITHUB_BASE_REF}
|
||||
|
||||
# Check for Added (A) or Modified (M) files in changelog.d
|
||||
CHANGELOG_CHANGES=$(git diff --name-status origin/${GITHUB_BASE_REF} HEAD -- changelog.d/)
|
||||
|
||||
SRC_CHANGES=$(git diff --name-status origin/${GITHUB_BASE_REF} HEAD -- src/)
|
||||
|
||||
echo "Changes in changelog.d/:"
|
||||
echo "$CHANGELOG_CHANGES"
|
||||
echo "Changes in src/:"
|
||||
echo "$SRC_CHANGES"
|
||||
|
||||
if echo "$CHANGELOG_CHANGES" | grep -q "^[AM]"; then
|
||||
echo "has_changelog=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changelog=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [ -n "$SRC_CHANGES" ]; then
|
||||
echo "src_changed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "src_changed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Manage PR Comment
|
||||
uses: https://github.com/actions/github-script@v8
|
||||
env:
|
||||
HAS_CHANGELOG: ${{ steps.check_files.outputs.has_changelog }}
|
||||
SRC_CHANGED: ${{ steps.check_files.outputs.src_changed }}
|
||||
with:
|
||||
script: |
|
||||
const hasChangelog = process.env.HAS_CHANGELOG === 'true';
|
||||
const srcChanged = process.env.SRC_CHANGED === 'true';
|
||||
const commentSignature = '<!-- changelog-check-action -->';
|
||||
const commentBody = `${commentSignature}\nPlease add a changelog fragment to \`changelog.d/\` describing your changes.`;
|
||||
|
||||
const { data: currentUser } = await github.rest.users.getAuthenticated();
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.id === currentUser.id &&
|
||||
comment.body.includes(commentSignature)
|
||||
);
|
||||
|
||||
const shouldWarn = srcChanged && !hasChangelog;
|
||||
|
||||
if (!shouldWarn) {
|
||||
if (botComment) {
|
||||
console.log('Changelog found or not required. Deleting existing warning comment.');
|
||||
await github.rest.issues.deleteComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!botComment) {
|
||||
console.log('Changelog missing and required. Creating warning comment.');
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: commentBody,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Docker image by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: "docker/Dockerfile"
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push max-perf Docker image by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
file: "docker/Dockerfile"
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
name: Renovate
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/renovatebot/renovate:42.70.2@sha256:3c2ac1b94fa92ef2fa4d1a0493f2c3ba564454720a32fdbcac2db2846ff1ee47
|
||||
image: ghcr.io/renovatebot/renovate:43.59.4@sha256:f951508dea1e7d71cbe6deca298ab0a05488e7631229304813f630cc06010892
|
||||
options: --tmpfs /tmp:exec
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
persist-credentials: true
|
||||
token: ${{ secrets.FORGEJO_TOKEN }}
|
||||
|
||||
- uses: https://github.com/cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0
|
||||
- uses: https://github.com/cachix/install-nix-action@19effe9fe722874e6d46dd7182e4b8b7a43c4a99 # v31.10.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
|
||||
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
@@ -1,4 +1,4 @@
|
||||
github: [JadedBlueEyes, nexy7574, gingershaped]
|
||||
custom:
|
||||
- https://ko-fi.com/nexy7574
|
||||
- https://ko-fi.com/JadedBlueEyes
|
||||
- https://timedout.uk/donate.html
|
||||
- https://jade.ellis.link/sponsors
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
default_install_hook_types:
|
||||
- pre-commit
|
||||
- pre-push
|
||||
- commit-msg
|
||||
default_stages:
|
||||
- pre-commit
|
||||
@@ -23,7 +24,7 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.43.4
|
||||
rev: v1.44.0
|
||||
hooks:
|
||||
- id: typos
|
||||
- id: typos
|
||||
@@ -31,7 +32,7 @@ repos:
|
||||
stages: [commit-msg]
|
||||
|
||||
- repo: https://github.com/crate-ci/committed
|
||||
rev: v1.1.10
|
||||
rev: v1.1.11
|
||||
hooks:
|
||||
- id: committed
|
||||
|
||||
@@ -45,3 +46,14 @@ repos:
|
||||
pass_filenames: false
|
||||
stages:
|
||||
- pre-commit
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: cargo-clippy
|
||||
name: cargo clippy
|
||||
entry: cargo clippy -- -D warnings
|
||||
language: system
|
||||
pass_filenames: false
|
||||
types: [rust]
|
||||
stages:
|
||||
- pre-push
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,3 +1,32 @@
|
||||
# Continuwuity 0.5.6 (2026-03-03)
|
||||
|
||||
## Security
|
||||
|
||||
- Admin escape commands received over federation will never be executed, as this is never valid in a genuine situation. Contributed by @Jade.
|
||||
- Fixed data amplification vulnerability (CWE-409) that affected configurations with server-side compression enabled (non-default). Contributed by @nex.
|
||||
|
||||
## Features
|
||||
|
||||
- Outgoing presence is now disabled by default, and the config option documentation has been adjusted to more accurately represent the weight of presence, typing indicators, and read receipts. Contributed by @nex. ([#1399](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1399))
|
||||
- Improved the concurrency handling of federation transactions, vastly improving performance and reliability by more accurately handling inbound transactions and reducing the amount of repeated wasted work. Contributed by @nex and @Jade. ([#1428](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1428))
|
||||
- Added [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202) Device masquerading (not all of MSC3202). This should fix issues with enabling [MSC4190](https://github.com/matrix-org/matrix-spec-proposals/pull/4190) for some Mautrix bridges. Contributed by @Jade ([#1435](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1435))
|
||||
- Added [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) Dehydrated Devices - you can now decrypt messages sent while all devices were logged out. ([#1436](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1436))
|
||||
- Implement [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) MatrixRTC transport discovery endpoint. Move RTC foci configuration from `[global.well_known]` to a new `[global.matrix_rtc]` section with a `foci` field. Contributed by @0xnim ([#1442](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1442))
|
||||
- Updated `list-backups` admin command to output one backup per line. ([#1394](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1394))
|
||||
- Improved URL preview fetching with a more compatible user agent for sites like YouTube Music. Added `!admin media delete-url-preview <url>` command to clear cached URL previews that were stuck and broken. ([#1434](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1434))
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Removed non-compliant nor functional room alias lookups over federation. Contributed by @nex ([#1393](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1393))
|
||||
- Removed ability to set rocksdb as read only. Doing so would cause unintentional and buggy behaviour. Contributed by @Terryiscool160. ([#1418](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1418))
|
||||
- Fixed a startup crash in the sender service if we can't detect the number of CPU cores, even if the `sender_workers` config option is set correctly. Contributed by @katie. ([#1421](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1421))
|
||||
- Removed the `allow_public_room_directory_without_auth` config option. Contributed by @0xnim. ([#1441](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1441))
|
||||
- Fixed sliding sync v5 list ranges always starting from 0, causing extra rooms to be unnecessarily processed and returned. Contributed by @0xnim ([#1445](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1445))
|
||||
- Fixed a bug that (repairably) caused a room split between continuwuity and non-continuwuity servers when the room had both `m.room.policy` and `org.matrix.msc4284.policy` in its room state. Contributed by @nex ([#1481](https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1481))
|
||||
- Fixed `!admin media delete --mxc <url>` responding with an error message when the media was deleted successfully. Contributed by @lynxize
|
||||
- Fixed spurious 404 media errors in the logs. Contributed by @benbot.
|
||||
- Fixed spurious warn about needed backfill via federation for non-federated rooms. Contributed by @kraem.
|
||||
|
||||
# Continuwuity v0.5.5 (2026-02-15)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -1,131 +1 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement over Matrix at [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) or email at <tom@tcpip.uk>, <jade@continuwuity.org> and <nex@continuwuity.org> respectively.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
Contributors are expected to follow the [Continuwuity Community Guidelines](continuwuity.org/community/guidelines).
|
||||
|
||||
@@ -22,25 +22,21 @@ ### Pre-commit Checks
|
||||
- Validating YAML, JSON, and TOML files
|
||||
- Checking for merge conflicts
|
||||
|
||||
You can run these checks locally by installing [prefligit](https://github.com/j178/prefligit):
|
||||
You can run these checks locally by installing [prek](https://github.com/j178/prek):
|
||||
|
||||
|
||||
```bash
|
||||
# Requires UV: https://docs.astral.sh/uv/getting-started/installation/
|
||||
# Mac/linux: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
# Windows: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
|
||||
# Install prefligit using cargo-binstall
|
||||
cargo binstall prefligit
|
||||
# Install prek using cargo-binstall
|
||||
cargo binstall prek
|
||||
|
||||
# Install git hooks to run checks automatically
|
||||
prefligit install
|
||||
prek install
|
||||
|
||||
# Run all checks
|
||||
prefligit --all-files
|
||||
prek --all-files
|
||||
```
|
||||
|
||||
Alternatively, you can use [pre-commit](https://pre-commit.com/):
|
||||
Alternatively, you can use [pre-commit][pre-commit]:
|
||||
```bash
|
||||
# Requires python
|
||||
|
||||
@@ -54,7 +50,9 @@ # Run all checks manually
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
These same checks are run in CI via the prefligit-checks workflow to ensure consistency. These must pass before the PR is merged.
|
||||
These same checks are run in CI via the prek-checks workflow to ensure consistency. These must pass before the PR is merged.
|
||||
|
||||
[pre-commit]: https://pre-commit.com/
|
||||
|
||||
### Running tests locally
|
||||
|
||||
@@ -85,28 +83,35 @@ ### Matrix tests
|
||||
|
||||
### Writing documentation
|
||||
|
||||
Continuwuity's website uses [`mdbook`][mdbook] and is deployed via CI using Cloudflare Pages
|
||||
Continuwuity's website uses [`rspress`][rspress] and is deployed via CI using Cloudflare Pages
|
||||
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/`
|
||||
directory at the top level.
|
||||
|
||||
To build the documentation locally:
|
||||
To load the documentation locally:
|
||||
|
||||
1. Install NodeJS and npm from their [official website][nodejs-download] or via your package manager of choice
|
||||
|
||||
2. From the project's root directory, install the relevant npm modules
|
||||
|
||||
1. Install mdbook if you don't have it already:
|
||||
```bash
|
||||
cargo install mdbook # or cargo binstall, or another method
|
||||
npm ci
|
||||
```
|
||||
|
||||
2. Build the documentation:
|
||||
3. Make changes to the document pages as you see fit
|
||||
|
||||
4. Generate a live preview of the documentation
|
||||
|
||||
```bash
|
||||
mdbook build
|
||||
npm run docs:dev
|
||||
```
|
||||
|
||||
The output of the mdbook generation is in `public/`. You can open the HTML files directly in your browser without needing a web server.
|
||||
A webserver for the docs will be spun up for you (e.g. at `http://localhost:3000`). Any changes you make to the documentation will be live-reloaded on the webpage.
|
||||
|
||||
Alternatively, you can build the documentation using `npm run docs:build` - the output of this will be in the `/doc_build` directory. Once you're happy with your documentation updates, you can commit the changes.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Continuwuity follows the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. This provides a standardized format that makes the commit history more readable and enables automated tools to generate changelogs.
|
||||
Continuwuity follows the [Conventional Commits][conventional-commits] specification for commit messages. This provides a standardized format that makes the commit history more readable and enables automated tools to generate changelogs.
|
||||
|
||||
The basic structure is:
|
||||
|
||||
@@ -165,9 +170,40 @@ ### Creating pull requests
|
||||
their contributions accepted. This includes users who have been banned from
|
||||
continuwuity Matrix rooms for Code of Conduct violations.
|
||||
|
||||
[conventional-commits]: https://www.conventionalcommits.org/
|
||||
[issues]: https://forgejo.ellis.link/continuwuation/continuwuity/issues
|
||||
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
|
||||
[complement]: https://github.com/matrix-org/complement/
|
||||
[sytest]: https://github.com/matrix-org/sytest/
|
||||
[mdbook]: https://rust-lang.github.io/mdBook/
|
||||
[nodejs-download]: https://nodejs.org/en/download
|
||||
[rspress]: https://rspress.rs/
|
||||
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml
|
||||
|
||||
#### Writing news fragments
|
||||
|
||||
In order to make writing our changelogs easier, we make use of [Towncrier]. Towncrier builds changelogs based on
|
||||
"news fragments", which are little markdown files in the `changelog.d/` directory that describe individual changes.
|
||||
|
||||
When you make a pull request that changes functionality, fixes a bug, or adds documentation, please add a news fragment
|
||||
describing your change. The file name *MUST* be in the format of `{pull_request_number}.{type}`, where `{type}` is one
|
||||
of the following:
|
||||
|
||||
- `feature` - for new features
|
||||
- `bugfix` - for bug fixes
|
||||
- `doc` - for documentation changes
|
||||
- `misc` - for other changes that don't fit the above categories
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ echo "Fixed the quantum flux stabiliser. Contributed by @alice." > changelog.d/42.bugfix
|
||||
```
|
||||
|
||||
(Note: If you want to credit yourself, you should reference your forgejo handle, however links to other platforms are also acceptable.)
|
||||
|
||||
When the next release is made, Towncrier will automatically include your news fragment in the changelog.
|
||||
|
||||
You can read more about writing news fragments in the [Towncrier tutorial][tt].
|
||||
|
||||
[Towncrier]: https://towncrier.readthedocs.io/
|
||||
[tt]: https://towncrier.readthedocs.io/en/stable/tutorial.html#creating-news-fragments
|
||||
|
||||
1485
Cargo.lock
generated
1485
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
82
Cargo.toml
82
Cargo.toml
@@ -12,7 +12,7 @@ license = "Apache-2.0"
|
||||
# See also `rust-toolchain.toml`
|
||||
readme = "README.md"
|
||||
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||
version = "0.5.5"
|
||||
version = "0.5.7-alpha.1"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "conduwuit"
|
||||
@@ -68,7 +68,7 @@ default-features = false
|
||||
version = "0.1.3"
|
||||
|
||||
[workspace.dependencies.rand]
|
||||
version = "0.8.5"
|
||||
version = "0.10.0"
|
||||
|
||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||
[workspace.dependencies.bytes]
|
||||
@@ -84,7 +84,7 @@ version = "1.3.1"
|
||||
version = "1.11.1"
|
||||
|
||||
[workspace.dependencies.axum]
|
||||
version = "0.7.9"
|
||||
version = "0.8.8"
|
||||
default-features = false
|
||||
features = [
|
||||
"form",
|
||||
@@ -97,9 +97,9 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.axum-extra]
|
||||
version = "0.9.6"
|
||||
version = "0.12.0"
|
||||
default-features = false
|
||||
features = ["typed-header", "tracing"]
|
||||
features = ["typed-header", "tracing", "cookie"]
|
||||
|
||||
[workspace.dependencies.axum-server]
|
||||
version = "0.7.2"
|
||||
@@ -110,7 +110,7 @@ default-features = false
|
||||
version = "0.7"
|
||||
|
||||
[workspace.dependencies.axum-client-ip]
|
||||
version = "0.6.1"
|
||||
version = "0.7"
|
||||
|
||||
[workspace.dependencies.tower]
|
||||
version = "0.5.2"
|
||||
@@ -118,7 +118,7 @@ default-features = false
|
||||
features = ["util"]
|
||||
|
||||
[workspace.dependencies.tower-http]
|
||||
version = "0.6.2"
|
||||
version = "0.6.8"
|
||||
default-features = false
|
||||
features = [
|
||||
"add-extension",
|
||||
@@ -144,6 +144,7 @@ features = [
|
||||
"socks",
|
||||
"hickory-dns",
|
||||
"http2",
|
||||
"stream",
|
||||
]
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
@@ -158,7 +159,7 @@ features = ["raw_value"]
|
||||
|
||||
# Used for appservice registration files
|
||||
[workspace.dependencies.serde-saphyr]
|
||||
version = "0.0.19"
|
||||
version = "0.0.21"
|
||||
|
||||
# Used to load forbidden room/user regex from config
|
||||
[workspace.dependencies.serde_regex]
|
||||
@@ -253,7 +254,7 @@ features = [
|
||||
version = "0.4.0"
|
||||
|
||||
[workspace.dependencies.libloading]
|
||||
version = "0.8.6"
|
||||
version = "0.9.0"
|
||||
|
||||
# Validating urls in config, was already a transitive dependency
|
||||
[workspace.dependencies.url]
|
||||
@@ -277,7 +278,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.hyper-util]
|
||||
version = "=0.1.17"
|
||||
version = "=0.1.20"
|
||||
default-features = false
|
||||
features = [
|
||||
"server-auto",
|
||||
@@ -298,7 +299,7 @@ default-features = false
|
||||
features = ["env", "toml"]
|
||||
|
||||
[workspace.dependencies.hickory-resolver]
|
||||
version = "0.25.1"
|
||||
version = "0.25.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"serde",
|
||||
@@ -331,7 +332,7 @@ version = "0.4.0"
|
||||
|
||||
# used for MPMC channels
|
||||
[workspace.dependencies.async-channel]
|
||||
version = "2.3.1"
|
||||
version = "2.5.0"
|
||||
|
||||
[workspace.dependencies.async-trait]
|
||||
version = "0.1.88"
|
||||
@@ -342,7 +343,8 @@ version = "0.1.2"
|
||||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||
rev = "b496b7f38d517149361a882e75d3fd4faf210441"
|
||||
#branch = "conduwuit-changes"
|
||||
rev = "a97b91adcc012ef04991d823b8b5a79c6686ae48"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
@@ -362,6 +364,7 @@ features = [
|
||||
"unstable-msc2870",
|
||||
"unstable-msc3026",
|
||||
"unstable-msc3061",
|
||||
"unstable-msc3814",
|
||||
"unstable-msc3245",
|
||||
"unstable-msc3266",
|
||||
"unstable-msc3381", # polls
|
||||
@@ -380,11 +383,12 @@ features = [
|
||||
"unstable-pdu",
|
||||
"unstable-msc4155",
|
||||
"unstable-msc4143", # livekit well_known response
|
||||
"unstable-msc4284"
|
||||
]
|
||||
|
||||
[workspace.dependencies.rust-rocksdb]
|
||||
git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
|
||||
rev = "61d9d23872197e9ace4a477f2617d5c9f50ecb23"
|
||||
rev = "31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9"
|
||||
default-features = false
|
||||
features = [
|
||||
"multi-threaded-cf",
|
||||
@@ -424,7 +428,7 @@ features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
|
||||
|
||||
# optional sentry metrics for crash/panic reporting
|
||||
[workspace.dependencies.sentry]
|
||||
version = "0.45.0"
|
||||
version = "0.46.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"backtrace",
|
||||
@@ -440,14 +444,14 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.sentry-tracing]
|
||||
version = "0.45.0"
|
||||
version = "0.46.0"
|
||||
[workspace.dependencies.sentry-tower]
|
||||
version = "0.45.0"
|
||||
version = "0.46.0"
|
||||
|
||||
# jemalloc usage
|
||||
[workspace.dependencies.tikv-jemalloc-sys]
|
||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
rev = "df86ff89d4b1e223b9f7d2dd2fbb7f202da7f554"
|
||||
default-features = false
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
@@ -455,7 +459,7 @@ features = [
|
||||
]
|
||||
[workspace.dependencies.tikv-jemallocator]
|
||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
rev = "df86ff89d4b1e223b9f7d2dd2fbb7f202da7f554"
|
||||
default-features = false
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
@@ -463,7 +467,7 @@ features = [
|
||||
]
|
||||
[workspace.dependencies.tikv-jemalloc-ctl]
|
||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
rev = "df86ff89d4b1e223b9f7d2dd2fbb7f202da7f554"
|
||||
default-features = false
|
||||
features = ["use_std"]
|
||||
|
||||
@@ -471,7 +475,7 @@ features = ["use_std"]
|
||||
version = "0.5"
|
||||
|
||||
[workspace.dependencies.nix]
|
||||
version = "0.30.1"
|
||||
version = "0.31.0"
|
||||
default-features = false
|
||||
features = ["resource"]
|
||||
|
||||
@@ -489,7 +493,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.rustyline-async]
|
||||
version = "0.4.3"
|
||||
version = "0.4.9"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.termimad]
|
||||
@@ -522,7 +526,7 @@ version = "0.4.13"
|
||||
version = "2.0"
|
||||
|
||||
[workspace.dependencies.core_affinity]
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
|
||||
[workspace.dependencies.libc]
|
||||
version = "0.2"
|
||||
@@ -546,14 +550,24 @@ version = "0.12.0"
|
||||
default-features = false
|
||||
features = ["sync", "tls-rustls", "rustls-provider"]
|
||||
|
||||
[workspace.dependencies.resolv-conf]
|
||||
version = "0.7.5"
|
||||
|
||||
[workspace.dependencies.yansi]
|
||||
version = "1.0.1"
|
||||
|
||||
[workspace.dependencies.askama]
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
|
||||
[workspace.dependencies.lettre]
|
||||
version = "0.11.19"
|
||||
default-features = false
|
||||
features = ["smtp-transport", "pool", "hostname", "builder", "rustls", "aws-lc-rs", "rustls-native-certs", "tokio1", "tokio1-rustls", "tracing", "serde"]
|
||||
|
||||
[workspace.dependencies.governor]
|
||||
version = "0.10.4"
|
||||
default-features = false
|
||||
features = ["std"]
|
||||
|
||||
[workspace.dependencies.nonzero_ext]
|
||||
version = "0.3.0"
|
||||
|
||||
#
|
||||
# Patches
|
||||
@@ -567,25 +581,25 @@ version = "0.14.0"
|
||||
# adds event for CTRL+\: https://forgejo.ellis.link/continuwuation/rustyline-async/src/branch/main/.patchy/0001-add-event-for-ctrl.patch
|
||||
[patch.crates-io.rustyline-async]
|
||||
git = "https://forgejo.ellis.link/continuwuation/rustyline-async"
|
||||
rev = "e9f01cf8c6605483cb80b3b0309b400940493d7f"
|
||||
rev = "b13aca2cc08d5f78303746cd192d9a03d73e768e"
|
||||
|
||||
# adds LIFO queue scheduling; this should be updated with PR progress.
|
||||
[patch.crates-io.event-listener]
|
||||
git = "https://forgejo.ellis.link/continuwuation/event-listener"
|
||||
rev = "fe4aebeeaae435af60087ddd56b573a2e0be671d"
|
||||
rev = "b2c19bcaf5a0a69c38c034e417bda04a9b991529"
|
||||
[patch.crates-io.async-channel]
|
||||
git = "https://forgejo.ellis.link/continuwuation/async-channel"
|
||||
rev = "92e5e74063bf2a3b10414bcc8a0d68b235644280"
|
||||
rev = "e990f0006b68dc9bace7a3c95fc90b5c4e44948d"
|
||||
|
||||
# adds affinity masks for selecting more than one core at a time
|
||||
[patch.crates-io.core_affinity]
|
||||
git = "https://forgejo.ellis.link/continuwuation/core_affinity_rs"
|
||||
rev = "9c8e51510c35077df888ee72a36b4b05637147da"
|
||||
rev = "7c7a9dea35382743a63837cdd1d977efdb8f1b8a"
|
||||
|
||||
# reverts hyperium#148 conflicting with our delicate federation resolver hooks
|
||||
[patch.crates-io.hyper-util]
|
||||
git = "https://forgejo.ellis.link/continuwuation/hyper-util"
|
||||
rev = "5886d5292bf704c246206ad72d010d674a7b77d0"
|
||||
rev = "09fcd3bf4656c81a8ad573bee410ab2b57f60b86"
|
||||
|
||||
#
|
||||
# Our crates
|
||||
@@ -915,7 +929,6 @@ fn_to_numeric_cast_any = "warn"
|
||||
format_push_string = "warn"
|
||||
get_unwrap = "warn"
|
||||
impl_trait_in_params = "warn"
|
||||
let_underscore_untyped = "warn"
|
||||
lossy_float_literal = "warn"
|
||||
mem_forget = "warn"
|
||||
missing_assert_message = "warn"
|
||||
@@ -965,3 +978,6 @@ needless_raw_string_hashes = "allow"
|
||||
|
||||
# TODO: Enable this lint & fix all instances
|
||||
collapsible_if = "allow"
|
||||
|
||||
# TODO: break these apart
|
||||
cognitive_complexity = "allow"
|
||||
|
||||
3
LICENSE
3
LICENSE
@@ -1,4 +1,3 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
@@ -187,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2023 June
|
||||
Copyright 2023 Continuwuity Team and contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
11
README.md
11
README.md
@@ -57,10 +57,15 @@ ### What are the project's goals?
|
||||
|
||||
### Can I try it out?
|
||||
|
||||
Check out the [documentation](https://continuwuity.org) for installation instructions, or join one of these vetted public homeservers running Continuwuity to get a feel for things!
|
||||
Check out the [documentation](https://continuwuity.org) for installation instructions.
|
||||
|
||||
- https://continuwuity.rocks -- A public demo server operated by the Continuwuity Team.
|
||||
- https://federated.nexus -- Federated Nexus is a community resource hosting multiple FOSS (especially federated) services, including Matrix and Forgejo.
|
||||
If you want to try it out as a user, we have some partnered homeservers you can use:
|
||||
* You can head over to [https://federated.nexus](https://federated.nexus/) in your browser.
|
||||
* Hit the `Apply to Join` button. Once your request has been accepted, you will receive an email with your username and password.
|
||||
* Head over to [https://app.federated.nexus](https://app.federated.nexus/) and you can sign in there, or use any other matrix chat client you wish elsewhere.
|
||||
* Your username for matrix will be in the form of `@username:federated.nexus`, however you can simply use the `username` part to log in. Your password is your password.
|
||||
|
||||
* There's also [https://continuwuity.rocks/](https://continuwuity.rocks/). You can register a new account using Cinny via [this convenient link](https://app.cinny.in/register/continuwuity.rocks), or you can use Element or another matrix client *that supports registration*.
|
||||
|
||||
### What are we working on?
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ set -euo pipefail
|
||||
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
|
||||
|
||||
# A `.jsonl` file to write test logs to
|
||||
LOG_FILE="${2:-complement_test_logs.jsonl}"
|
||||
LOG_FILE="${2:-tests/test_results/complement/test_logs.jsonl}"
|
||||
|
||||
# A `.jsonl` file to write test results to
|
||||
RESULTS_FILE="${3:-complement_test_results.jsonl}"
|
||||
RESULTS_FILE="${3:-tests/test_results/complement/test_results.jsonl}"
|
||||
|
||||
# The base docker image to use for complement tests
|
||||
# You can build the default with `docker build -t continuwuity:complement -f ./docker/complement.Dockerfile .`
|
||||
|
||||
23
book.toml
23
book.toml
@@ -1,23 +0,0 @@
|
||||
[book]
|
||||
title = "continuwuity"
|
||||
description = "continuwuity is a community continuation of the conduwuit Matrix homeserver, written in Rust."
|
||||
language = "en"
|
||||
authors = ["The continuwuity Community"]
|
||||
text-direction = "ltr"
|
||||
src = "docs"
|
||||
|
||||
[build]
|
||||
build-dir = "public"
|
||||
create-missing = true
|
||||
extra-watch-dirs = ["debian", "docs"]
|
||||
|
||||
[rust]
|
||||
edition = "2024"
|
||||
|
||||
[output.html]
|
||||
edit-url-template = "https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/{path}"
|
||||
git-repository-url = "https://forgejo.ellis.link/continuwuation/continuwuity"
|
||||
git-repository-icon = "fa-git-alt"
|
||||
|
||||
[output.html.search]
|
||||
limit-results = 15
|
||||
1
changelog.d/+1770db35.feature.md
Normal file
1
changelog.d/+1770db35.feature.md
Normal file
@@ -0,0 +1 @@
|
||||
Added support for associating email addresses with accounts, requiring email addresses for registration, and resetting passwords via email. Contributed by @ginger
|
||||
1
changelog.d/+6368729a.feature.md
Normal file
1
changelog.d/+6368729a.feature.md
Normal file
@@ -0,0 +1 @@
|
||||
Added support for using an admin command to issue self-service password reset links.
|
||||
1
changelog.d/+6e57599d.bugfix.md
Normal file
1
changelog.d/+6e57599d.bugfix.md
Normal file
@@ -0,0 +1 @@
|
||||
Stopped left rooms from being unconditionally sent on initial sync, hopefully fixing spurious appearances of left rooms in some clients (and making sync faster as a bonus). Contributed by @ginger
|
||||
1
changelog.d/+alias-enumeration-delete.bugfix.md
Normal file
1
changelog.d/+alias-enumeration-delete.bugfix.md
Normal file
@@ -0,0 +1 @@
|
||||
Fixed room alias deletion so removing one local alias no longer removes other aliases from room alias listings.
|
||||
1
changelog.d/1265.bugfix
Normal file
1
changelog.d/1265.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fixed corrupted appservice registrations causing the server to enter a crash loop. Contributed by @nex.
|
||||
1
changelog.d/1371.feature.md
Normal file
1
changelog.d/1371.feature.md
Normal file
@@ -0,0 +1 @@
|
||||
Re-added support for reading registration tokens from a file. Contributed by @ginger and @benbot.
|
||||
1
changelog.d/1429.doc
Normal file
1
changelog.d/1429.doc
Normal file
@@ -0,0 +1 @@
|
||||
Added Testing and Troubleshooting instructions for Livekit documentation. Contributed by @stratself.
|
||||
1
changelog.d/1448.bugfix
Normal file
1
changelog.d/1448.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Prevent removing the admin room alias (`#admins`) to avoid accidentally breaking admin room functionality. Contributed by @0xnim
|
||||
1
changelog.d/1527.feature.md
Normal file
1
changelog.d/1527.feature.md
Normal file
@@ -0,0 +1 @@
|
||||
Add new config option to allow or disallow search engine indexing through a `<meta ../>` tag. Defaults to blocking indexing (`content="noindex"`). Contributed by @s1lv3r and @ginger.
|
||||
1
changelog.d/1542.bugfix.md
Normal file
1
changelog.d/1542.bugfix.md
Normal file
@@ -0,0 +1 @@
|
||||
Stripped `join_authorised_via_users_server` from json if user is already in room (@partha:cxy.run)
|
||||
1
changelog.d/1572.bugfix
Normal file
1
changelog.d/1572.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fixed internal server errors for fetching thumbnails. Contributed by @PerformativeJade
|
||||
18
clippy.toml
18
clippy.toml
@@ -15,6 +15,18 @@ disallowed-macros = [
|
||||
{ path = "log::trace", reason = "use conduwuit_core::trace" },
|
||||
]
|
||||
|
||||
disallowed-methods = [
|
||||
{ path = "tokio::spawn", reason = "use and pass conduuwit_core::server::Server::runtime() to spawn from" },
|
||||
]
|
||||
[[disallowed-methods]]
|
||||
path = "tokio::spawn"
|
||||
reason = "use and pass conduwuit_core::server::Server::runtime() to spawn from"
|
||||
|
||||
[[disallowed-methods]]
|
||||
path = "reqwest::Response::bytes"
|
||||
reason = "bytes is unsafe, use limit_read via the conduwuit_core::utils::LimitReadExt trait instead"
|
||||
|
||||
[[disallowed-methods]]
|
||||
path = "reqwest::Response::text"
|
||||
reason = "text is unsafe, use limit_read_text via the conduwuit_core::utils::LimitReadExt trait instead"
|
||||
|
||||
[[disallowed-methods]]
|
||||
path = "reqwest::Response::json"
|
||||
reason = "json is unsafe, use limit_read_text via the conduwuit_core::utils::LimitReadExt trait instead"
|
||||
|
||||
@@ -9,10 +9,9 @@ address = "0.0.0.0"
|
||||
allow_device_name_federation = true
|
||||
allow_guest_registration = true
|
||||
allow_public_room_directory_over_federation = true
|
||||
allow_public_room_directory_without_auth = true
|
||||
allow_registration = true
|
||||
database_path = "/database"
|
||||
log = "trace,h2=debug,hyper=debug"
|
||||
log = "trace,h2=debug,hyper=debug,conduwuit_database=warn,conduwuit_service::manager=info,conduwuit_api::router=error,conduwuit_router=error,tower_http=error"
|
||||
port = [8008, 8448]
|
||||
trusted_servers = []
|
||||
only_query_trusted_key_servers = false
|
||||
@@ -25,7 +24,7 @@ url_preview_domain_explicit_denylist = ["*"]
|
||||
media_compat_file_link = false
|
||||
media_startup_check = true
|
||||
prune_missing_media = true
|
||||
log_colors = true
|
||||
log_colors = false
|
||||
admin_room_notices = false
|
||||
allow_check_for_updates = false
|
||||
intentionally_unknown_config_option_for_testing = true
|
||||
@@ -48,6 +47,7 @@ federation_idle_timeout = 300
|
||||
sender_timeout = 300
|
||||
sender_idle_timeout = 300
|
||||
sender_retry_backoff_limit = 300
|
||||
force_disable_first_run_mode = true
|
||||
|
||||
[global.tls]
|
||||
dual_protocol = true
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
#
|
||||
# Also see the `[global.well_known]` config section at the very bottom.
|
||||
#
|
||||
# If `client` is not set under `[global.well_known]`, the server name will
|
||||
# be used as the base domain for user-facing links (such as password
|
||||
# reset links) created by Continuwuity.
|
||||
#
|
||||
# Examples of delegation:
|
||||
# - https://continuwuity.org/.well-known/matrix/server
|
||||
# - https://continuwuity.org/.well-known/matrix/client
|
||||
@@ -91,6 +95,10 @@
|
||||
# engine API. To use this, set a database backup path that continuwuity
|
||||
# can write to.
|
||||
#
|
||||
# If you are using systemd, you will need to add the path to
|
||||
# ReadWritePaths in the service file, preferably via a drop-in file
|
||||
# through `systemctl edit`.
|
||||
#
|
||||
# For more information, see:
|
||||
# https://continuwuity.org/maintenance.html#backups
|
||||
#
|
||||
@@ -290,6 +298,25 @@
|
||||
#
|
||||
#max_fetch_prev_events = 192
|
||||
|
||||
# How many incoming federation transactions the server is willing to be
|
||||
# processing at any given time before it becomes overloaded and starts
|
||||
# rejecting further transactions until some slots become available.
|
||||
#
|
||||
# Setting this value too low or too high may result in unstable
|
||||
# federation, and setting it too high may cause runaway resource usage.
|
||||
#
|
||||
#max_concurrent_inbound_transactions = 150
|
||||
|
||||
# Maximum age (in seconds) for cached federation transaction responses.
|
||||
# Entries older than this will be removed during cleanup.
|
||||
#
|
||||
#transaction_id_cache_max_age_secs = 7200 (2 hours)
|
||||
|
||||
# Maximum number of cached federation transaction responses.
|
||||
# When the cache exceeds this limit, older entries will be removed.
|
||||
#
|
||||
#transaction_id_cache_max_entries = 8192
|
||||
|
||||
# Default/base connection timeout (seconds). This is used only by URL
|
||||
# previews and update/news endpoint checks.
|
||||
#
|
||||
@@ -457,18 +484,25 @@
|
||||
#yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false
|
||||
|
||||
# A static registration token that new users will have to provide when
|
||||
# creating an account. If unset and `allow_registration` is true,
|
||||
# you must set
|
||||
# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse`
|
||||
# to true to allow open registration without any conditions.
|
||||
#
|
||||
# If you do not want to set a static token, the `!admin token` commands
|
||||
# may also be used to manage registration tokens.
|
||||
# creating an account. This token does not supersede tokens from other
|
||||
# sources, such as the `!admin token` command or the
|
||||
# `registration_token_file` configuration option.
|
||||
#
|
||||
# example: "o&^uCtes4HPf0Vu@F20jQeeWE7"
|
||||
#
|
||||
#registration_token =
|
||||
|
||||
# A path to a file containing static registration tokens, one per line.
|
||||
# Tokens in this file do not supersede tokens from other sources, such as
|
||||
# the `!admin token` command or the `registration_token` configuration
|
||||
# option.
|
||||
#
|
||||
# The file will be read once, when Continuwuity starts. It is not
|
||||
# currently reread when the server configuration is reloaded. If the file
|
||||
# cannot be read, Continuwuity will fail to start.
|
||||
#
|
||||
#registration_token_file =
|
||||
|
||||
# The public site key for reCaptcha. If this is provided, reCaptcha
|
||||
# becomes required during registration. If both captcha *and*
|
||||
# registration token are enabled, both will be required during
|
||||
@@ -527,12 +561,6 @@
|
||||
#
|
||||
#allow_public_room_directory_over_federation = false
|
||||
|
||||
# Set this to true to allow your server's public room directory to be
|
||||
# queried without client authentication (access token) through the Client
|
||||
# APIs. Set this to false to protect against /publicRooms spiders.
|
||||
#
|
||||
#allow_public_room_directory_without_auth = false
|
||||
|
||||
# Allow guests/unauthenticated users to access TURN credentials.
|
||||
#
|
||||
# This is the equivalent of Synapse's `turn_allow_guests` config option.
|
||||
@@ -1056,14 +1084,6 @@
|
||||
#
|
||||
#rocksdb_repair = false
|
||||
|
||||
# This item is undocumented. Please contribute documentation for it.
|
||||
#
|
||||
#rocksdb_read_only = false
|
||||
|
||||
# This item is undocumented. Please contribute documentation for it.
|
||||
#
|
||||
#rocksdb_secondary = false
|
||||
|
||||
# Enables idle CPU priority for compaction thread. This is not enabled by
|
||||
# default to prevent compaction from falling too far behind on busy
|
||||
# systems.
|
||||
@@ -1120,27 +1140,34 @@
|
||||
|
||||
# Allow local (your server only) presence updates/requests.
|
||||
#
|
||||
# Note that presence on continuwuity is very fast unlike Synapse's. If
|
||||
# using outgoing presence, this MUST be enabled.
|
||||
# Local presence must be enabled for outgoing presence to function.
|
||||
#
|
||||
# Note that local presence is not as heavy on the CPU as federated
|
||||
# presence, but will still become more expensive the more local users you
|
||||
# have.
|
||||
#
|
||||
#allow_local_presence = true
|
||||
|
||||
# Allow incoming federated presence updates/requests.
|
||||
# Allow incoming federated presence updates.
|
||||
#
|
||||
# This option receives presence updates from other servers, but does not
|
||||
# send any unless `allow_outgoing_presence` is true. Note that presence on
|
||||
# continuwuity is very fast unlike Synapse's.
|
||||
# This option enables processing inbound presence updates from other
|
||||
# servers. Without it, remote users will appear as if they are always
|
||||
# offline to your local users. This does not affect typing indicators or
|
||||
# read receipts.
|
||||
#
|
||||
#allow_incoming_presence = true
|
||||
|
||||
# Allow outgoing presence updates/requests.
|
||||
#
|
||||
# This option sends presence updates to other servers, but does not
|
||||
# receive any unless `allow_incoming_presence` is true. Note that presence
|
||||
# on continuwuity is very fast unlike Synapse's. If using outgoing
|
||||
# presence, you MUST enable `allow_local_presence` as well.
|
||||
# This option sends presence updates to other servers, and requires that
|
||||
# `allow_local_presence` is also enabled.
|
||||
#
|
||||
#allow_outgoing_presence = true
|
||||
# Note that outgoing presence is very heavy on the CPU and network, and
|
||||
# will typically cause extreme strain and slowdowns for no real benefit.
|
||||
# There are only a few clients that even implement presence, so you
|
||||
# probably don't want to enable this.
|
||||
#
|
||||
#allow_outgoing_presence = false
|
||||
|
||||
# How many seconds without presence updates before you become idle.
|
||||
# Defaults to 5 minutes.
|
||||
@@ -1174,6 +1201,10 @@
|
||||
|
||||
# Allow sending read receipts to remote servers.
|
||||
#
|
||||
# Note that sending read receipts to remote servers in large rooms with
|
||||
# lots of other homeservers may cause additional strain on the CPU and
|
||||
# network.
|
||||
#
|
||||
#allow_outgoing_read_receipts = true
|
||||
|
||||
# Allow local typing updates.
|
||||
@@ -1185,6 +1216,10 @@
|
||||
|
||||
# Allow outgoing typing updates to federation.
|
||||
#
|
||||
# Note that sending typing indicators to remote servers in large rooms
|
||||
# with lots of other homeservers may cause additional strain on the CPU
|
||||
# and network.
|
||||
#
|
||||
#allow_outgoing_typing = true
|
||||
|
||||
# Allow incoming typing updates from federation.
|
||||
@@ -1318,7 +1353,7 @@
|
||||
# sender user's server name, inbound federation X-Matrix origin, and
|
||||
# outbound federation handler.
|
||||
#
|
||||
# You can set this to ["*"] to block all servers by default, and then
|
||||
# You can set this to [".*"] to block all servers by default, and then
|
||||
# use `allowed_remote_server_names` to allow only specific servers.
|
||||
#
|
||||
# example: ["badserver\\.tld$", "badphrase", "19dollarfortnitecards"]
|
||||
@@ -1478,6 +1513,11 @@
|
||||
#
|
||||
#url_preview_user_agent = "continuwuity/<version> (bot; +https://continuwuity.org)"
|
||||
|
||||
# Determines whether audio and video files will be downloaded for URL
|
||||
# previews.
|
||||
#
|
||||
#url_preview_allow_audio_video = false
|
||||
|
||||
# List of forbidden room aliases and room IDs as strings of regex
|
||||
# patterns.
|
||||
#
|
||||
@@ -1763,6 +1803,11 @@
|
||||
#
|
||||
#config_reload_signal = true
|
||||
|
||||
# Allow search engines and crawlers to index Continuwuity's built-in
|
||||
# webpages served under the `/_continuwuity/` prefix.
|
||||
#
|
||||
#allow_web_indexing = false
|
||||
|
||||
[global.tls]
|
||||
|
||||
# Path to a valid TLS certificate file.
|
||||
@@ -1824,14 +1869,13 @@
|
||||
#
|
||||
#support_mxid =
|
||||
|
||||
# A list of MatrixRTC foci URLs which will be served as part of the
|
||||
# MSC4143 client endpoint at /.well-known/matrix/client. If you're
|
||||
# setting up livekit, you'd want something like:
|
||||
# rtc_focus_server_urls = [
|
||||
# { type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
||||
# ]
|
||||
# **DEPRECATED**: Use `[global.matrix_rtc].foci` instead.
|
||||
#
|
||||
# To disable, set this to be an empty vector (`[]`).
|
||||
# A list of MatrixRTC foci URLs which will be served as part of the
|
||||
# MSC4143 client endpoint at /.well-known/matrix/client.
|
||||
#
|
||||
# This option is deprecated and will be removed in a future release.
|
||||
# Please migrate to the new `[global.matrix_rtc]` config section.
|
||||
#
|
||||
#rtc_focus_server_urls = []
|
||||
|
||||
@@ -1853,6 +1897,23 @@
|
||||
#
|
||||
#blurhash_max_raw_size = 33554432
|
||||
|
||||
[global.matrix_rtc]
|
||||
|
||||
# A list of MatrixRTC foci (transports) which will be served via the
|
||||
# MSC4143 RTC transports endpoint at
|
||||
# `/_matrix/client/v1/rtc/transports`. If you're setting up livekit,
|
||||
# you'd want something like:
|
||||
# ```toml
|
||||
# [global.matrix_rtc]
|
||||
# foci = [
|
||||
# { type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
||||
# ]
|
||||
# ```
|
||||
#
|
||||
# To disable, set this to an empty list (`[]`).
|
||||
#
|
||||
#foci = []
|
||||
|
||||
[global.ldap]
|
||||
|
||||
# Whether to enable LDAP login.
|
||||
@@ -1980,3 +2041,41 @@
|
||||
# web->synapseHTTPAntispam->authorization
|
||||
#
|
||||
#secret =
|
||||
|
||||
#[global.smtp]
|
||||
|
||||
# A `smtp://`` URI which will be used to connect to a mail server.
|
||||
# Uncommenting the [global.smtp] group and setting this option enables
|
||||
# features which depend on the ability to send email,
|
||||
# such as self-service password resets.
|
||||
#
|
||||
# For most modern mail servers, format the URI like this:
|
||||
# `smtps://username:password@hostname:port`
|
||||
# Note that you will need to URL-encode the username and password. If your
|
||||
# username _is_ your email address, you will need to replace the `@` with
|
||||
# `%40`.
|
||||
#
|
||||
# For a guide on the accepted URI syntax, consult Lettre's documentation:
|
||||
# https://docs.rs/lettre/latest/lettre/transport/smtp/struct.AsyncSmtpTransport.html#method.from_url
|
||||
#
|
||||
#connection_uri =
|
||||
|
||||
# The outgoing address which will be used for sending emails.
|
||||
#
|
||||
# For a syntax guide, see https://datatracker.ietf.org/doc/html/rfc2822#section-3.4
|
||||
#
|
||||
# ...or if you don't want to read the RFC, for some reason:
|
||||
# - `Name <address@domain.org>` to specify a sender name
|
||||
# - `address@domain.org` to not use a name
|
||||
#
|
||||
#sender =
|
||||
|
||||
# Whether to require that users provide an email address when they
|
||||
# register.
|
||||
#
|
||||
#require_email_for_registration = false
|
||||
|
||||
# Whether to require that users who register with a registration token
|
||||
# provide an email address.
|
||||
#
|
||||
#require_email_for_token_registration = false
|
||||
|
||||
@@ -10,7 +10,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean
|
||||
|
||||
# Match Rustc version as close as possible
|
||||
# rustc -vV
|
||||
ARG LLVM_VERSION=20
|
||||
ARG LLVM_VERSION=21
|
||||
# ENV RUSTUP_TOOLCHAIN=${RUST_VERSION}
|
||||
|
||||
# Install repo tools
|
||||
@@ -48,11 +48,11 @@ EOF
|
||||
|
||||
# Developer tool versions
|
||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||
ENV BINSTALL_VERSION=1.17.5
|
||||
ENV BINSTALL_VERSION=1.17.8
|
||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||
ENV CARGO_SBOM_VERSION=0.9.1
|
||||
# renovate: datasource=crate depName=lddtree
|
||||
ENV LDDTREE_VERSION=0.4.0
|
||||
ENV LDDTREE_VERSION=0.5.0
|
||||
# renovate: datasource=crate depName=timelord-cli
|
||||
ENV TIMELORD_VERSION=3.0.1
|
||||
|
||||
@@ -180,6 +180,11 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
export RUSTFLAGS="${RUSTFLAGS}"
|
||||
fi
|
||||
|
||||
RUST_PROFILE_DIR="${RUST_PROFILE}"
|
||||
if [[ "${RUST_PROFILE}" == "dev" ]]; then
|
||||
RUST_PROFILE_DIR="debug"
|
||||
fi
|
||||
|
||||
TARGET_DIR=($(cargo metadata --no-deps --format-version 1 | \
|
||||
jq -r ".target_directory"))
|
||||
mkdir /out/sbin
|
||||
@@ -191,8 +196,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
|
||||
for BINARY in "${BINARIES[@]}"; do
|
||||
echo $BINARY
|
||||
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY
|
||||
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE}/$BINARY /out/sbin/$BINARY
|
||||
xx-verify $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE_DIR}/$BINARY
|
||||
cp $TARGET_DIR/$(xx-cargo --print-target-triple)/${RUST_PROFILE_DIR}/$BINARY /out/sbin/$BINARY
|
||||
done
|
||||
EOF
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
|
||||
|
||||
# Developer tool versions
|
||||
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
|
||||
ENV BINSTALL_VERSION=1.17.5
|
||||
ENV BINSTALL_VERSION=1.17.8
|
||||
# renovate: datasource=github-releases depName=psastras/sbom-rs
|
||||
ENV CARGO_SBOM_VERSION=0.9.1
|
||||
# renovate: datasource=crate depName=lddtree
|
||||
ENV LDDTREE_VERSION=0.4.0
|
||||
ENV LDDTREE_VERSION=0.5.0
|
||||
|
||||
# Install unpackaged tools
|
||||
RUN <<EOF
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
"name": "troubleshooting",
|
||||
"label": "Troubleshooting"
|
||||
},
|
||||
{
|
||||
"type": "dir",
|
||||
"name": "advanced",
|
||||
"label": "Advanced"
|
||||
},
|
||||
"security",
|
||||
{
|
||||
"type": "dir-section-header",
|
||||
@@ -64,6 +69,11 @@
|
||||
"label": "Configuration Reference",
|
||||
"name": "/reference/config"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"label": "Environment Variables",
|
||||
"name": "/reference/environment-variables"
|
||||
},
|
||||
{
|
||||
"type": "dir",
|
||||
"label": "Admin Command Reference",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"text": "Guide",
|
||||
"link": "/introduction",
|
||||
"activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting)"
|
||||
"activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting|advanced)"
|
||||
},
|
||||
{
|
||||
"text": "Development",
|
||||
|
||||
7
docs/advanced/_meta.json
Normal file
7
docs/advanced/_meta.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"type": "file",
|
||||
"name": "delegation",
|
||||
"label": "Delegation / split-domain"
|
||||
}
|
||||
]
|
||||
206
docs/advanced/delegation.mdx
Normal file
206
docs/advanced/delegation.mdx
Normal file
@@ -0,0 +1,206 @@
|
||||
# Delegation/split-domain deployment
|
||||
|
||||
Matrix allows clients and servers to discover a homeserver's "true" destination via **`.well-known` delegation**. This is especially useful if you would like to:
|
||||
|
||||
- Serve Continuwuity on a subdomain while having only the base domain for your usernames
|
||||
- Use a port other than `:8448` for server-to-server connections
|
||||
|
||||
This guide will show you how to have `@user:example.com` usernames while serving Continuwuity on `https://matrix.example.com`. It assumes you are using port 443 for both client-to-server connections and server-to-server federation.
|
||||
|
||||
## Configuration
|
||||
|
||||
First, ensure you have set up A/AAAA records for `matrix.example.com` and `example.com` pointing to your IP.
|
||||
|
||||
Then, ensure that the `server_name` field matches your intended username suffix. If this is not the case, you **MUST** wipe the database directory and reinstall Continuwuity with your desired `server_name`.
|
||||
|
||||
Then, in the `[global.well_known]` section of your config file, add the following fields:
|
||||
|
||||
```toml
|
||||
[global.well_known]
|
||||
|
||||
client = "https://matrix.example.com"
|
||||
|
||||
# port number MUST be specified
|
||||
server = "matrix.example.com:443"
|
||||
|
||||
# (optional) customize your support contacts
|
||||
#support_page =
|
||||
#support_role = "m.role.admin"
|
||||
#support_email =
|
||||
#support_mxid = "@user:example.com"
|
||||
```
|
||||
|
||||
Alternatively if you are using Docker, you can set the `CONTINUWUITY_WELL_KNOWN` environment variable as below:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
continuwuity:
|
||||
...
|
||||
environment:
|
||||
CONTINUWUITY_WELL_KNOWN: |
|
||||
{
|
||||
client=https://matrix.example.com,
|
||||
server=matrix.example.com:443
|
||||
}
|
||||
```
|
||||
|
||||
## Serving with a reverse proxy
|
||||
|
||||
After doing the steps above, Continuwuity will serve these 3 JSON files:
|
||||
|
||||
- `/.well-known/matrix/client`: for Client-Server discovery
|
||||
- `/.well-known/matrix/server`: for Server-Server (federation) discovery
|
||||
- `/.well-known/matrix/support`: admin contact details (strongly recommended to have)
|
||||
|
||||
To enable full discovery, you will need to reverse proxy these paths from the base domain back to Continuwuity.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>For Caddy</summary>
|
||||
|
||||
```
|
||||
matrix.example.com:443 {
|
||||
reverse_proxy 127.0.0.1:8008
|
||||
}
|
||||
|
||||
example.com:443 {
|
||||
reverse_proxy /.well-known/matrix* 127.0.0.1:8008
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>For Traefik (via Docker labels)</summary>
|
||||
|
||||
```
|
||||
services:
|
||||
continuwuity:
|
||||
...
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.continuwuity.rule=(Host(`matrix.example.com`) || (Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))"
|
||||
- "traefik.http.routers.continuwuity.service=continuwuity"
|
||||
- "traefik.http.services.continuwuity.loadbalancer.server.port=8008"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Restart Continuwuity and your reverse proxy. Once that's done, visit these routes and check that the responses match the examples below:
|
||||
|
||||
<details open>
|
||||
|
||||
<summary>`https://example.com/.well-known/matrix/server`</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"m.server": "matrix.example.com:443"
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
|
||||
<summary>`https://example.com/.well-known/matrix/client`</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.example.com/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot log in with web clients
|
||||
|
||||
Make sure there is an `Access-Control-Allow-Origin: *` header in your `/.well-known/matrix/client` path. While Continuwuity serves this header by default, it may be dropped by reverse proxies or other middlewares.
|
||||
|
||||
---
|
||||
|
||||
## Using SRV records (not recommended)
|
||||
|
||||
:::warning
|
||||
The following methods are **not recommended** due to increased complexity with little benefits. If you have already set up `.well-known` delegation as above, you can safely skip this part.
|
||||
:::
|
||||
|
||||
The following methods uses SRV DNS records and only work with federation traffic. They are only included for completeness.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Using only SRV records</summary>
|
||||
|
||||
If you can't set up `/.well-known/matrix/server` on :443 for some reason, you can set up a SRV record (via your DNS provider) as below:
|
||||
|
||||
- Service and name: `_matrix-fed._tcp.example.com.`
|
||||
- Priority: `10` (can be any number)
|
||||
- Weight: `10` (can be any number)
|
||||
- Port: `443`
|
||||
- Target: `matrix.example.com.`
|
||||
|
||||
On the target's IP at port 443, you must configure a valid route and cert for your server name, `example.com`. Therefore, this method only works to redirect traffic into the right IP/port combo, and can not delegate your federation to a different domain.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Using SRV records + .well-known</summary>
|
||||
|
||||
You can also set up `/.well-known/matrix/server` with a delegated domain but no ports:
|
||||
|
||||
```toml
|
||||
[global.well_known]
|
||||
server = "matrix.example.com"
|
||||
```
|
||||
|
||||
Then, set up a SRV record (via your DNS provider) to announce the port number as below:
|
||||
|
||||
- Service and name: `_matrix-fed._tcp.matrix.example.com.`
|
||||
- Priority: `10` (can be any number)
|
||||
- Weight: `10` (can be any number)
|
||||
- Port: `443`
|
||||
- Target: `matrix.example.com.`
|
||||
|
||||
On the target's IP at port 443, you'll need to provide a valid route and cert for `matrix.example.com`. It provides the same feature as pure `.well-known` delegation, albeit with more parts to handle.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Using SRV records as a fallback for .well-known delegation</summary>
|
||||
|
||||
Assume your delegation is as below:
|
||||
|
||||
```toml
|
||||
[global.well_known]
|
||||
server = "example.com:443"
|
||||
```
|
||||
|
||||
If your Continuwuity instance becomes temporarily unreachable, other servers will not be able to find your `/.well-known/matrix/server` file, and defaults to using `server_name:8448`. This incorrect cache can persist for a long time, and would hinder re-federation when your server eventually comes back online.
|
||||
|
||||
If you want other servers to default to using port :443 even when it is offline, you could set up a SRV record (via your DNS provider) as follows:
|
||||
|
||||
- Service and name: `_matrix-fed._tcp.example.com.`
|
||||
- Priority: `10` (can be any number)
|
||||
- Weight: `10` (can be any number)
|
||||
- Port: `443`
|
||||
- Target: `example.com.`
|
||||
|
||||
On the target's IP at port 443, you'll need to provide a valid route and cert for `example.com`.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
See the following Matrix Specs for full details on client/server resolution mechanisms:
|
||||
|
||||
- [Server-to-Server resolution](https://spec.matrix.org/v1.17/server-server-api/#resolving-server-names) (see this for more information on SRV records)
|
||||
- [Client-to-Server resolution](https://spec.matrix.org/v1.17/client-server-api/#server-discovery)
|
||||
- [MSC1929: Homeserver Admin Contact and Support page](https://github.com/matrix-org/matrix-spec-proposals/pull/1929)
|
||||
@@ -10,4 +10,4 @@ # Calls
|
||||
For either one to work correctly, you have to do some additional setup.
|
||||
|
||||
- For legacy calls to work, you need to set up a TURN/STUN server. [Read the TURN guide for tips on how to set up coturn](./calls/turn.mdx)
|
||||
- For MatrixRTC / Element Call to work, you have to set up the LiveKit backend (foci). LiveKit also uses TURN/STUN to increase reliability, so you might want to configure your TURN server first. [Read the LiveKit guide](./calls/livekit.mdx)
|
||||
- For MatrixRTC / Element Call to work, you have to set up the LiveKit backend (foci). LiveKit also uses TURN/STUN to increase reliability - you can set up its built-in TURN server, or integrate with an existing one. [Read the LiveKit guide](./calls/livekit.mdx)
|
||||
|
||||
@@ -4,6 +4,10 @@ # Matrix RTC/Element Call Setup
|
||||
This guide assumes that you are using docker compose for deployment. LiveKit only provides Docker images.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
You can find help setting up MatrixRTC in our dedicated room - [#matrixrtc:continuwuity.org](https://matrix.to/#/%23matrixrtc%3Acontinuwuity.org)
|
||||
:::
|
||||
|
||||
## Instructions
|
||||
|
||||
### 1. Domain
|
||||
@@ -14,17 +18,21 @@ ### 1. Domain
|
||||
|
||||
### 2. Services
|
||||
|
||||
Using LiveKit with Matrix requires two services - Livekit itself, and a service (`lk-jwt-service`) that grants Matrix users permission to connect to it.
|
||||
Using LiveKit with Matrix requires two services - LiveKit itself, and a service (`lk-jwt-service`) that grants Matrix users permission to connect to it.
|
||||
|
||||
You must generate a key and secret to allow the Matrix service to authenticate with LiveKit. `LK_MATRIX_KEY` should be around 20 random characters, and `LK_MATRIX_SECRET` should be around 64. Remember to replace these with the actual values!
|
||||
|
||||
:::tip Generating the secrets
|
||||
LiveKit provides a utility to generate secure random keys
|
||||
```bash
|
||||
docker run --rm livekit/livekit-server:latest generate-keys
|
||||
~$ docker run --rm livekit/livekit-server:latest generate-keys
|
||||
API Key: APIUxUnMnSkuFWV
|
||||
API Secret: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
|
||||
```
|
||||
:::
|
||||
|
||||
Create a `docker-compose.yml` file as following:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
lk-jwt-service:
|
||||
@@ -32,10 +40,11 @@ ### 2. Services
|
||||
container_name: lk-jwt-service
|
||||
environment:
|
||||
- LIVEKIT_JWT_BIND=:8081
|
||||
- LIVEKIT_URL=wss://livekit.example.com
|
||||
- LIVEKIT_KEY=LK_MATRIX_KEY
|
||||
- LIVEKIT_SECRET=LK_MATRIX_SECRET
|
||||
- LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com
|
||||
- LIVEKIT_URL=wss://livekit.example.com # your LiveKit domain
|
||||
- LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com # your server_name
|
||||
# Replace these with the generated values as above
|
||||
- LIVEKIT_KEY=LK_MATRIX_KEY # APIUxUnMnSkuFWV
|
||||
- LIVEKIT_SECRET=LK_MATRIX_SECRET # t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8081:8081"
|
||||
@@ -70,6 +79,8 @@ # - "50100-50200:50100-50200/udp"
|
||||
enable_loopback_candidate: false
|
||||
keys:
|
||||
LK_MATRIX_KEY: LK_MATRIX_SECRET
|
||||
# replace these with your key-secret pair. Example:
|
||||
# APIUxUnMnSkuFWV: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
|
||||
```
|
||||
|
||||
#### Firewall hints
|
||||
@@ -78,52 +89,24 @@ #### Firewall hints
|
||||
|
||||
### 3. Telling clients where to find LiveKit
|
||||
|
||||
To tell clients where to find LiveKit, you need to add the address of your `lk-jwt-service` to your client .well-known file. To do so, in the config section `global.well-known`, add (or modify) the option `rtc_focus_server_urls`.
|
||||
To tell clients where to find LiveKit, you need to add the address of your `lk-jwt-service` to the `[global.matrix_rtc]` config section using the `foci` option.
|
||||
|
||||
The variable should be a list of servers serving as MatrixRTC endpoints to serve in the well-known file to the client.
|
||||
The variable should be a list of servers serving as MatrixRTC endpoints. Clients discover these via the `/_matrix/client/v1/rtc/transports` endpoint (MSC4143).
|
||||
|
||||
```toml
|
||||
rtc_focus_server_urls = [
|
||||
[global.matrix_rtc]
|
||||
foci = [
|
||||
{ type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
||||
]
|
||||
```
|
||||
|
||||
Remember to replace the URL with the address you are deploying your instance of lk-jwt-service to.
|
||||
|
||||
#### Serving .well-known manually
|
||||
|
||||
If you don't let Continuwuity serve your `.well-known` files, you need to add the following lines to your `.well-known/matrix/client` file, remembering to replace the URL with your own `lk-jwt-service` deployment:
|
||||
|
||||
```json
|
||||
"org.matrix.msc4143.rtc_foci": [
|
||||
{
|
||||
"type": "livekit",
|
||||
"livekit_service_url": "https://livekit.example.com"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The final file should look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"m.homeserver": {
|
||||
"base_url":"https://matrix.example.com"
|
||||
},
|
||||
"org.matrix.msc4143.rtc_foci": [
|
||||
{
|
||||
"type": "livekit",
|
||||
"livekit_service_url": "https://livekit.example.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Configure your Reverse Proxy
|
||||
|
||||
Reverse proxies can be configured in many different ways - so we can't provide a step by step for this.
|
||||
|
||||
By default, all routes should be forwarded to Livekit with the exception of the following path prefixes, which should be forwarded to the JWT/Authentication service:
|
||||
All paths should be forwarded to LiveKit by default, with the exception of the following path prefixes, which should be forwarded to the JWT/Authentication service:
|
||||
|
||||
- `/sfu/get`
|
||||
- `/healthz`
|
||||
@@ -132,12 +115,12 @@ ### 4. Configure your Reverse Proxy
|
||||
<details>
|
||||
<summary>Example caddy config</summary>
|
||||
```
|
||||
matrix-rtc.example.com {
|
||||
livekit.example.com {
|
||||
|
||||
# for lk-jwt-service
|
||||
@lk-jwt-service path /sfu/get* /healthz* /get_token*
|
||||
route @lk-jwt-service {
|
||||
reverse_proxy 127.0.0.1:8080
|
||||
reverse_proxy 127.0.0.1:8081
|
||||
}
|
||||
|
||||
# for livekit
|
||||
@@ -146,6 +129,46 @@ ### 4. Configure your Reverse Proxy
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Example nginx config</summary>
|
||||
```
|
||||
server {
|
||||
server_name livekit.example.com;
|
||||
|
||||
# for lk-jwt-service
|
||||
location ~ ^/(sfu/get|healthz|get_token) {
|
||||
proxy_pass http://127.0.0.1:8081$request_uri;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# for LiveKit
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:7880$request_uri;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_buffering off;
|
||||
|
||||
# websocket
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that for websockets to work, you need to have this somewhere outside your server block:
|
||||
```
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Example traefik router</summary>
|
||||
```
|
||||
@@ -161,44 +184,11 @@ ### 6. Start Everything
|
||||
|
||||
Start up the services using your usual method - for example `docker compose up -d`.
|
||||
|
||||
## Additional Configuration
|
||||
## Additional TURN configuration
|
||||
|
||||
### TURN Integration
|
||||
### Using LiveKit's built-in TURN server
|
||||
|
||||
If you've already set up coturn, there may be a port clash between the two services. To fix this, make sure the `min-port` and `max-port` for coturn so it doesn't overlap with LiveKit's range:
|
||||
|
||||
```ini
|
||||
min-port=50201
|
||||
max-port=65535
|
||||
```
|
||||
|
||||
To improve LiveKit's reliability, you can configure it to use your coturn server.
|
||||
|
||||
Generate a long random secret for LiveKit, and add it to your coturn config under the `static-auth-secret` option. You can add as many secrets as you want - so set a different one for each thing using your TURN server.
|
||||
|
||||
Then configure livekit, making sure to replace `COTURN_SECRET`:
|
||||
|
||||
```yaml
|
||||
# livekit.yaml
|
||||
rtc:
|
||||
turn_servers:
|
||||
- host: coturn.ellis.link
|
||||
port: 3478
|
||||
protocol: tcp
|
||||
secret: "COTURN_SECRET"
|
||||
- host: coturn.ellis.link
|
||||
port: 5349
|
||||
protocol: tls # Only if you've set up TLS in your coturn
|
||||
secret: "COTURN_SECRET"
|
||||
- host: coturn.ellis.link
|
||||
port: 3478
|
||||
protocol: udp
|
||||
secret: "COTURN_SECRET"
|
||||
```
|
||||
|
||||
## LiveKit's built in TURN server
|
||||
|
||||
Livekit includes a built in TURN server which can be used in place of an external option. This TURN server will only work with Livekit, so you can't use it for legacy Matrix calling - or anything else.
|
||||
LiveKit includes a built-in TURN server which can be used in place of an external option. This TURN server will only work with LiveKit, so you can't use it for legacy Matrix calling or anything else.
|
||||
|
||||
If you don't want to set up a separate TURN server, you can enable this with the following changes:
|
||||
|
||||
@@ -209,21 +199,175 @@ ### add this to livekit.yaml ###
|
||||
udp_port: 3478
|
||||
relay_range_start: 50300
|
||||
relay_range_end: 50400
|
||||
domain: matrix-rtc.example.com
|
||||
domain: livekit.example.com
|
||||
```
|
||||
|
||||
```yaml
|
||||
### Add these to docker-compose ###
|
||||
- "3478:3478/udp"
|
||||
- "50300-50400:50300-50400/udp"
|
||||
### add these to livekit's docker-compose ###
|
||||
ports:
|
||||
- "3478:3478/udp"
|
||||
- "50300-50400:50300-50400/udp"
|
||||
### if you're using `network_mode: host`, you can skip this part
|
||||
```
|
||||
|
||||
### Related Documentation
|
||||
Recreate the LiveKit container (with `docker-compose up -d livekit`) to apply these changes. Remember to allow the new `3478/udp` and `50100:50200/udp` ports through your firewall.
|
||||
|
||||
- [LiveKit GitHub](https://github.com/livekit/livekit)
|
||||
- [LiveKit Connection Tester](https://livekit.io/connection-test) - use with the token returned by `/sfu/get` or `/get_token`
|
||||
- [MatrixRTC proposal](https://half-shot.github.io/msc-crafter/#msc/4143)
|
||||
- [Synapse documentation](https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md)
|
||||
- [Community guide](https://tomfos.tr/matrix/livekit/)
|
||||
- [Community guide](https://blog.kimiblock.top/2024/12/24/hosting-element-call/)
|
||||
-
|
||||
### Integration with an external TURN server
|
||||
|
||||
If you've already [set up coturn](./turn), you can configure Livekit to use it.
|
||||
|
||||
:::tip Avoid port clashes between the two services
|
||||
|
||||
Before continuing, make sure coturn's `min-port` and `max-port` do not overlap with LiveKit's port range:
|
||||
|
||||
```ini
|
||||
# in your coturn.conf
|
||||
min-port=50201
|
||||
max-port=65535
|
||||
```
|
||||
:::
|
||||
|
||||
Generate a long random secret for LiveKit, and add it to your coturn config under the `static-auth-secret` option. You can add as many secrets as you want, so set a different one for LiveKit to use.
|
||||
|
||||
Then configure LiveKit, making sure to replace `COTURN_SECRET` with the one you generated:
|
||||
|
||||
```yaml
|
||||
# livekit.yaml
|
||||
rtc:
|
||||
turn_servers:
|
||||
- host: coturn.example.com
|
||||
port: 3478
|
||||
protocol: udp
|
||||
secret: "COTURN_SECRET"
|
||||
- host: coturn.example.com
|
||||
port: 3478
|
||||
protocol: tcp
|
||||
secret: "COTURN_SECRET"
|
||||
- host: coturn.example.com
|
||||
port: 5349
|
||||
protocol: tls # Only if you have already set up TLS in your coturn
|
||||
secret: "COTURN_SECRET"
|
||||
```
|
||||
|
||||
Restart LiveKit and coturn to apply these changes.
|
||||
|
||||
## Testing
|
||||
|
||||
To test that LiveKit is successfully integrated with Continuwuity, you will need to replicate its [Token Exchange Flow](https://github.com/element-hq/lk-jwt-service#%EF%B8%8F-how-it-works--token-exchange-flow).
|
||||
|
||||
First, you will need an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html).
|
||||
|
||||
Then, using that token, request another OpenID token for use with the lk-jwt-service:
|
||||
|
||||
```bash
|
||||
~$ curl -X POST -H "Authorization: Bearer <session-access-token>" \
|
||||
https://matrix.example.com/_matrix/client/v3/user/@user:example.com/openid/request_token
|
||||
{"access_token":"<openid_access_token>","token_type":"Bearer","matrix_server_name":"example.com","expires_in":3600}
|
||||
```
|
||||
|
||||
Next, create a `payload.json` file with the following content:
|
||||
|
||||
<details>
|
||||
|
||||
<summary>`payload.json`</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"room_id": "abc",
|
||||
"slot_id": "xyz",
|
||||
"openid_token": {
|
||||
"matrix_server_name": "example.com",
|
||||
"access_token": "<openid_access_token>",
|
||||
"token_type": "Bearer"
|
||||
},
|
||||
"member": {
|
||||
"id": "xyz",
|
||||
"claimed_device_id": "DEVICEID",
|
||||
"claimed_user_id": "@user:example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Replace `matrix_server_name` and `claimed_user_id` with your information, and `<openid_access_token>` with the one you got from the previous step. Other values can be left as-is.
|
||||
|
||||
</details>
|
||||
|
||||
You can then send this payload to the lk-jwt-service:
|
||||
|
||||
```bash
|
||||
~$ curl -X POST -d @payload.json https://livekit.example.com/get_token
|
||||
{"url":"wss://livekit.example.com","jwt":"a_really_really_long_string"}
|
||||
```
|
||||
|
||||
The lk-jwt-service will, after checking against Continuwuity, answer with a `jwt` token to create a LiveKit media room. Use this token to test at the [LiveKit Connection Tester](https://livekit.io/connection-test). If everything works there, then you have set up LiveKit successfully!
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
To debug any issues, you can place a call or redo the Testing instructions, and check the container logs for any specific errors. Use `docker-compose logs --follow` to follow them in real-time.
|
||||
|
||||
### Common errors in Element Call UI
|
||||
|
||||
- `MISSING_MATRIX_RTC_FOCUS`: LiveKit is missing from Continuwuity's config file
|
||||
- "Waiting for media" popup always showing: a LiveKit URL has been configured in Continuwuity, but your client cannot connect to it for some reason
|
||||
|
||||
### Docker loopback networking issues
|
||||
|
||||
Some distros do not allow Docker containers to connect to its host's public IP by default. This would cause `lk-jwt-service` to fail connecting to `livekit` or `continuwuity` on the same host. As a result, you would see connection refused/connection timeouts log entries in the JWT service, even when `LIVEKIT_URL` has been configured correctly.
|
||||
|
||||
To alleviate this, you can try one of the following workarounds:
|
||||
|
||||
- Use `network_mode: host` for the `lk-jwt-service` container (instead of the default bridge networking).
|
||||
|
||||
- Add an `extra_hosts` file mapping livekit's (and continuwuity's) domain name to a localhost address:
|
||||
|
||||
```diff
|
||||
# in docker-compose.yaml
|
||||
services:
|
||||
lk-jwt-service:
|
||||
...
|
||||
+ extra_hosts:
|
||||
+ - "livekit.example.com:127.0.0.1"
|
||||
+ - "matrix.example.com:127.0.0.1"
|
||||
```
|
||||
|
||||
- (**untested, use at your own risk**) Implement an iptables workaround as shown [here](https://forums.docker.com/t/unable-to-connect-to-host-service-from-inside-docker-container/145749/6).
|
||||
|
||||
After implementing the changes and restarting your compose, you can test whether the connection works by cURLing from a sidecar container:
|
||||
|
||||
```bash
|
||||
~$ docker run --rm --net container:lk-jwt-service docker.io/curlimages/curl https://livekit.example.com
|
||||
OK
|
||||
```
|
||||
|
||||
### Workaround for non-federating servers
|
||||
|
||||
When deploying on servers with federation disabled (`allow_federation = false`), LiveKit will fail as it can't fetch the required [OpenID endpoint](https://spec.matrix.org/v1.17/server-server-api/#get_matrixfederationv1openiduserinfo) via federation paths.
|
||||
|
||||
As a workaround, you can enable federation, but forbid all remote servers via the following config parameters:
|
||||
|
||||
```toml
|
||||
### in your continuwuity.toml file ###
|
||||
allow_federation = true
|
||||
forbidden_remote_server_names = [".*"]
|
||||
```
|
||||
|
||||
Subscribe to issue [!1440](https://forgejo.ellis.link/continuwuation/continuwuity/issues/1440) for future updates on this matter.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
Guides:
|
||||
|
||||
- [Element Call self-hosting documentation](https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md)
|
||||
- [Community guide with overview of LiveKit's mechanisms](https://tomfos.tr/matrix/livekit/)
|
||||
- [Community guide using systemd](https://blog.kimiblock.top/2024/12/24/hosting-element-call/)
|
||||
|
||||
Specifications:
|
||||
|
||||
- [MatrixRTC proposal](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)
|
||||
- [LiveKit proposal](https://github.com/matrix-org/matrix-spec-proposals/pull/4195)
|
||||
|
||||
Source code:
|
||||
|
||||
- [Element Call](https://github.com/element-hq/element-call)
|
||||
- [lk-jwt-service](https://github.com/element-hq/lk-jwt-service)
|
||||
- [LiveKit server](https://github.com/livekit/livekit)
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
# Continuwuity Community Guidelines
|
||||
|
||||
Welcome to the Continuwuity commuwunity! We're excited to have you here. Continuwuity is a
|
||||
continuation of the conduwuit homeserver, which in turn is a hard-fork of the Conduit homeserver,
|
||||
aimed at making Matrix more accessible and inclusive for everyone.
|
||||
Welcome to the Continuwuity commuwunity! We're excited to have you here.
|
||||
|
||||
This space is dedicated to fostering a positive, supportive, and welcoming environment for everyone.
|
||||
These guidelines apply to all Continuwuity spaces, including our Matrix rooms and any other
|
||||
community channels that reference them. We've written these guidelines to help us all create an
|
||||
environment where everyone feels safe and respected.
|
||||
Our project aims to make Matrix more accessible and inclusive for everyone. To that end, we are dedicated to fostering a positive, supportive, safe and welcoming environment for our community.
|
||||
|
||||
For code and contribution guidelines, please refer to the
|
||||
[Contributor's Covenant](https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/CODE_OF_CONDUCT.md).
|
||||
Below are additional guidelines specific to the Continuwuity community.
|
||||
These guidelines apply to all Continuwuity spaces, including our Matrix rooms and code forge.
|
||||
|
||||
Our community spaces are intended for individuals aged 16 or over, because we expect maturity and respect from our community members.
|
||||
|
||||
## Our Values and Expected Behaviors
|
||||
|
||||
@@ -29,17 +24,21 @@ ## Our Values and Expected Behaviors
|
||||
|
||||
3. **Communicate Clearly and Kindly**: Our community includes neurodivergent individuals and those
|
||||
who may not appreciate sarcasm or subtlety. Communicate clearly and kindly. Avoid ambiguity and
|
||||
ensure your messages can be easily understood by all. Avoid placing the burden of education on
|
||||
ensure your messages can be easily understood by all.
|
||||
|
||||
4. **Be Considerate and Proactive**: Not everyone has the same time, resource and experience to spare.
|
||||
Don't expect others to give up their time and labour for you; be thankful for what you have already been given.
|
||||
Avoid placing the burden of education on
|
||||
marginalized groups; please make an effort to look into your questions before asking others for
|
||||
detailed explanations.
|
||||
|
||||
4. **Be Open to Improving Inclusivity**: Actively participate in making our community more inclusive.
|
||||
5. **Be Engaged and Open-Minded**: Actively participate in making our community more inclusive.
|
||||
Report behaviour that contradicts these guidelines (see Reporting and Enforcement below) and be
|
||||
open to constructive feedback aimed at improving our community. Understand that discussing
|
||||
negative experiences can be emotionally taxing; focus on the message, not the tone.
|
||||
|
||||
5. **Commit to Our Values**: Building an inclusive community requires ongoing effort from everyone.
|
||||
Recognise that addressing bias and discrimination is a continuous process that needs commitment
|
||||
6. **Commit to Our Values**: Building an inclusive community requires ongoing effort from everyone.
|
||||
Recognise that creating a welcoming and open community is a continuous process that needs commitment
|
||||
and action from all members.
|
||||
|
||||
## Unacceptable Behaviors
|
||||
@@ -72,36 +71,6 @@ ## Unacceptable Behaviors
|
||||
This is not an exhaustive list. Any behaviour that makes others feel unsafe or unwelcome may be
|
||||
subject to enforcement action.
|
||||
|
||||
## Matrix Community
|
||||
|
||||
These Community Guidelines apply to the entire
|
||||
[Continuwuity Matrix Space](https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) and its rooms, including:
|
||||
|
||||
### [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
|
||||
|
||||
This room is for support and discussions about Continuwuity. Ask questions, share insights, and help
|
||||
each other out while adhering to these guidelines.
|
||||
|
||||
We ask that this room remain focused on the Continuwuity software specifically: the team are
|
||||
typically happy to engage in conversations about related subjects in the off-topic room.
|
||||
|
||||
### [#offtopic:continuwuity.org](https://matrix.to/#/#offtopic:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
|
||||
|
||||
For off-topic community conversations about any subject. While this room allows for a wide range of
|
||||
topics, the same guidelines apply. Please keep discussions respectful and inclusive, and avoid
|
||||
divisive or stressful subjects like specific country/world politics unless handled with exceptional
|
||||
care and respect for diverse viewpoints.
|
||||
|
||||
General topics, such as world events, are welcome as long as they follow the guidelines. If a member
|
||||
of the team asks for the conversation to end, please respect their decision.
|
||||
|
||||
### [#dev:continuwuity.org](https://matrix.to/#/#dev:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
|
||||
|
||||
This room is dedicated to discussing active development of Continuwuity, including ongoing issues or
|
||||
code development. Collaboration here must follow these guidelines, and please consider raising
|
||||
[an issue](https://forgejo.ellis.link/continuwuation/continuwuity/issues) on the repository to help
|
||||
track progress.
|
||||
|
||||
## Reporting and Enforcement
|
||||
|
||||
We take these Community Guidelines seriously to protect our community members. If you witness or
|
||||
@@ -114,6 +83,7 @@ ## Reporting and Enforcement
|
||||
will immediately alert all available moderators.
|
||||
* **Direct Message:** If you're not comfortable raising the issue publicly, please send a direct
|
||||
message (DM) to one of the room moderators.
|
||||
* **Email**: Please email Jade and/or Nex at `jade@continuwuity.org` and `nex@continuwuity.org` respectively, or email `team@continuwuity.org`.
|
||||
|
||||
Reports will be handled with discretion. We will investigate promptly and thoroughly.
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ ## Basics
|
||||
|
||||
The config file to use can be specified on the commandline when running
|
||||
Continuwuity by specifying the `-c`, `--config` flag. Alternatively, you can use
|
||||
the environment variable `CONDUWUIT_CONFIG` to specify the config file to used.
|
||||
Conduit's environment variables are supported for backwards compatibility.
|
||||
the environment variable `CONTINUWUITY_CONFIG` to specify the config file to be
|
||||
used; see [the section on environment variables](#environment-variables) for
|
||||
more information.
|
||||
|
||||
## Option commandline flag
|
||||
|
||||
@@ -52,13 +53,15 @@ ## Environment variables
|
||||
|
||||
All of the settings that are found in the config file can be specified by using
|
||||
environment variables. The environment variable names should be all caps and
|
||||
prefixed with `CONDUWUIT_`.
|
||||
prefixed with `CONTINUWUITY_`.
|
||||
|
||||
For example, if the setting you are changing is `max_request_size`, then the
|
||||
environment variable to set is `CONDUWUIT_MAX_REQUEST_SIZE`.
|
||||
environment variable to set is `CONTINUWUITY_MAX_REQUEST_SIZE`.
|
||||
|
||||
To modify config options not in the `[global]` context such as
|
||||
`[global.well_known]`, use the `__` suffix split: `CONDUWUIT_WELL_KNOWN__SERVER`
|
||||
`[global.well_known]`, use the `__` suffix split:
|
||||
`CONTINUWUITY_WELL_KNOWN__SERVER`
|
||||
|
||||
Conduit's environment variables are supported for backwards compatibility (e.g.
|
||||
Conduit and conduwuit's environment variables are also supported for backwards
|
||||
compatibility, via the `CONDUIT_` and `CONDUWUIT_` prefixes respectively (e.g.
|
||||
`CONDUIT_SERVER_NAME`).
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../CONTRIBUTING.md
|
||||
@@ -6,9 +6,9 @@ services:
|
||||
### then you are ready to go.
|
||||
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||
restart: unless-stopped
|
||||
command: /sbin/conduwuit
|
||||
volumes:
|
||||
- db:/var/lib/continuwuity
|
||||
- /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's.
|
||||
#- ./continuwuity.toml:/etc/continuwuity.toml
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
@@ -16,14 +16,14 @@ services:
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
caddy: example.com
|
||||
caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
|
||||
caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}
|
||||
caddy.reverse_proxy: /.well-known/matrix/* homeserver:6167
|
||||
|
||||
homeserver:
|
||||
### If you already built the Continuwuity image with 'docker build' or want to use a registry image,
|
||||
### then you are ready to go.
|
||||
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||
restart: unless-stopped
|
||||
command: /sbin/conduwuit
|
||||
volumes:
|
||||
- db:/var/lib/continuwuity
|
||||
- /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's.
|
||||
@@ -42,6 +42,10 @@ services:
|
||||
#CONTINUWUITY_LOG: warn,state_res=warn
|
||||
CONTINUWUITY_ADDRESS: 0.0.0.0
|
||||
#CONTINUWUITY_CONFIG: '/etc/continuwuity.toml' # Uncomment if you mapped config toml above
|
||||
|
||||
# Required for .well-known delegation - edit these according to your chosen domain
|
||||
CONTINUWUITY_WELL_KNOWN__CLIENT: https://matrix.example.com
|
||||
CONTINUWUITY_WELL_KNOWN__SERVER: matrix.example.com:443
|
||||
networks:
|
||||
- caddy
|
||||
labels:
|
||||
|
||||
@@ -6,6 +6,7 @@ services:
|
||||
### then you are ready to go.
|
||||
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||
restart: unless-stopped
|
||||
command: /sbin/conduwuit
|
||||
volumes:
|
||||
- db:/var/lib/continuwuity
|
||||
- /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's.
|
||||
|
||||
@@ -6,6 +6,7 @@ services:
|
||||
### then you are ready to go.
|
||||
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||
restart: unless-stopped
|
||||
command: /sbin/conduwuit
|
||||
ports:
|
||||
- 8448:6167
|
||||
volumes:
|
||||
|
||||
@@ -2,28 +2,26 @@ # Continuwuity for Docker
|
||||
|
||||
## Docker
|
||||
|
||||
To run Continuwuity with Docker, you can either build the image yourself or pull it
|
||||
from a registry.
|
||||
To run Continuwuity with Docker, you can either build the image yourself or pull
|
||||
it from a registry.
|
||||
|
||||
### Use a registry
|
||||
|
||||
OCI images for Continuwuity are available in the registries listed below.
|
||||
Available OCI images:
|
||||
|
||||
| Registry | Image | Notes |
|
||||
| --------------- | --------------------------------------------------------------- | -----------------------|
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest) | Latest tagged image. |
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main) | Main branch image. |
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:latest-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest-maxperf) | [Performance optimised version.](./generic.mdx#performance-optimised-builds) |
|
||||
| Forgejo Registry| [forgejo.ellis.link/continuwuation/continuwuity:main-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main-maxperf) | [Performance optimised version.](./generic.mdx#performance-optimised-builds) |
|
||||
| Registry | Image | Notes |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| Forgejo Registry | [forgejo.ellis.link/continuwuation/continuwuity:latest](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest) | Latest tagged image. |
|
||||
| Forgejo Registry | [forgejo.ellis.link/continuwuation/continuwuity:main](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main) | Main branch image. |
|
||||
| Forgejo Registry | [forgejo.ellis.link/continuwuation/continuwuity:latest-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/latest-maxperf) | [Performance optimised version.](./generic.mdx#performance-optimised-builds) |
|
||||
| Forgejo Registry | [forgejo.ellis.link/continuwuation/continuwuity:main-maxperf](https://forgejo.ellis.link/continuwuation/-/packages/container/continuwuity/main-maxperf) | [Performance optimised version.](./generic.mdx#performance-optimised-builds) |
|
||||
|
||||
Use
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
docker image pull $LINK
|
||||
docker image pull forgejo.ellis.link/continuwuation/continuwuity:main-maxperf
|
||||
```
|
||||
|
||||
to pull it to your machine.
|
||||
|
||||
#### Mirrors
|
||||
|
||||
Images are mirrored to multiple locations automatically, on a schedule:
|
||||
@@ -33,39 +31,146 @@ #### Mirrors
|
||||
- `registry.gitlab.com/continuwuity/continuwuity`
|
||||
- `git.nexy7574.co.uk/mirrored/continuwuity` (releases only, no `main`)
|
||||
|
||||
### Run
|
||||
### Quick Run
|
||||
|
||||
When you have the image, you can simply run it with
|
||||
Get a working Continuwuity server with an admin user in four steps:
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
Continuwuity requires HTTPS for Matrix federation. You'll need:
|
||||
|
||||
- A domain name pointing to your server
|
||||
- A reverse proxy with SSL/TLS certificates (Traefik, Caddy, nginx, etc.)
|
||||
|
||||
See [Docker Compose](#docker-compose) for complete examples.
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
- `CONTINUWUITY_SERVER_NAME` - Your Matrix server's domain name
|
||||
- `CONTINUWUITY_DATABASE_PATH` - Where to store your database (must match the
|
||||
volume mount)
|
||||
- `CONTINUWUITY_ADDRESS` - Bind address (use `0.0.0.0` to listen on all
|
||||
interfaces)
|
||||
- `CONTINUWUITY_ALLOW_REGISTRATION` - Set to `false` to disable registration, or
|
||||
use with `CONTINUWUITY_REGISTRATION_TOKEN` to require a token (see
|
||||
[reference](../reference/environment-variables.mdx#registration--user-configuration)
|
||||
for details)
|
||||
|
||||
See the
|
||||
[Environment Variables Reference](../reference/environment-variables.mdx) for
|
||||
more configuration options.
|
||||
|
||||
#### 1. Pull the image
|
||||
|
||||
```bash
|
||||
docker run -d -p 8448:6167 \
|
||||
-v db:/var/lib/continuwuity/ \
|
||||
-e CONTINUWUITY_SERVER_NAME="your.server.name" \
|
||||
-e CONTINUWUITY_ALLOW_REGISTRATION=false \
|
||||
--name continuwuity $LINK
|
||||
docker pull forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||
```
|
||||
|
||||
or you can use [Docker Compose](#docker-compose).
|
||||
#### 2. Start the server with initial admin user
|
||||
|
||||
The `-d` flag lets the container run in detached mode. You may supply an
|
||||
optional `continuwuity.toml` config file, the example config can be found
|
||||
[here](../reference/config.mdx). You can pass in different env vars to
|
||||
change config values on the fly. You can even configure Continuwuity completely by
|
||||
using env vars. For an overview of possible values, please take a look at the
|
||||
<a href="/examples/docker-compose.yml" target="_blank">`docker-compose.yml`</a> file.
|
||||
```bash
|
||||
docker run -d \
|
||||
-p 6167:6167 \
|
||||
-v continuwuity_db:/var/lib/continuwuity \
|
||||
-e CONTINUWUITY_SERVER_NAME="matrix.example.com" \
|
||||
-e CONTINUWUITY_DATABASE_PATH="/var/lib/continuwuity" \
|
||||
-e CONTINUWUITY_ADDRESS="0.0.0.0" \
|
||||
-e CONTINUWUITY_ALLOW_REGISTRATION="false" \
|
||||
--name continuwuity \
|
||||
forgejo.ellis.link/continuwuation/continuwuity:latest \
|
||||
/sbin/conduwuit --execute "users create-user admin"
|
||||
```
|
||||
|
||||
If you just want to test Continuwuity for a short time, you can use the `--rm`
|
||||
flag, which cleans up everything related to your container after you stop
|
||||
it.
|
||||
Replace `matrix.example.com` with your actual server name and `admin` with
|
||||
your preferred username.
|
||||
|
||||
#### 3. Get your admin password
|
||||
|
||||
```bash
|
||||
docker logs continuwuity 2>&1 | grep "Created user"
|
||||
```
|
||||
|
||||
You'll see output like:
|
||||
|
||||
```
|
||||
Created user with user_id: @admin:matrix.example.com and password: `[auto-generated-password]`
|
||||
```
|
||||
|
||||
#### 4. Configure your reverse proxy
|
||||
|
||||
Configure your reverse proxy to forward HTTPS traffic to Continuwuity. See
|
||||
[Docker Compose](#docker-compose) for examples.
|
||||
|
||||
Once configured, log in with any Matrix client using `@admin:matrix.example.com`
|
||||
and the generated password. You'll automatically be invited to the admin room
|
||||
where you can manage your server.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
If the `docker run` command is not suitable for you or your setup, you can also use one
|
||||
of the provided `docker-compose` files.
|
||||
Docker Compose is the recommended deployment method. These examples include
|
||||
reverse proxy configurations for Matrix federation.
|
||||
|
||||
Depending on your proxy setup, you can use one of the following files:
|
||||
#### Matrix Federation Requirements
|
||||
|
||||
### For existing Traefik setup
|
||||
For Matrix federation to work, you need to serve `.well-known/matrix/client` and
|
||||
`.well-known/matrix/server` endpoints. You can achieve this either by:
|
||||
|
||||
1. **Using a well-known service** - The compose files below include an nginx
|
||||
container to serve these files
|
||||
2. **Using Continuwuity's built-in delegation** (easier for Traefik) - Configure
|
||||
delegation files in your config, then proxy `/.well-known/matrix/*` to
|
||||
Continuwuity
|
||||
|
||||
**Traefik example using built-in delegation:**
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
traefik.http.routers.continuwuity.rule: >-
|
||||
(Host(`matrix.example.com`) ||
|
||||
(Host(`example.com`) && PathPrefix(`/.well-known/matrix`)))
|
||||
```
|
||||
|
||||
This routes your Matrix domain and well-known paths to Continuwuity.
|
||||
|
||||
#### Creating Your First Admin User
|
||||
|
||||
Add the `--execute` command to create an admin user on first startup. In your
|
||||
compose file, add under the `continuwuity` service:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
continuwuity:
|
||||
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||
command: /sbin/conduwuit --execute "users create-user admin"
|
||||
# ... rest of configuration
|
||||
```
|
||||
|
||||
Then retrieve the auto-generated password:
|
||||
|
||||
```bash
|
||||
docker compose logs continuwuity | grep "Created user"
|
||||
```
|
||||
|
||||
#### Choose Your Reverse Proxy
|
||||
|
||||
Select the compose file that matches your setup:
|
||||
|
||||
:::note DNS Performance
|
||||
Docker's default DNS resolver can cause performance issues with Matrix
|
||||
federation. If you experience slow federation or DNS timeouts, you may need to
|
||||
use your host's DNS resolver instead. Add this volume mount to the
|
||||
`continuwuity` service:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /etc/resolv.conf:/etc/resolv.conf:ro
|
||||
```
|
||||
|
||||
See [Troubleshooting - DNS Issues](../troubleshooting.mdx#potential-dns-issues-when-using-docker)
|
||||
for more details and alternative solutions.
|
||||
:::
|
||||
|
||||
##### For existing Traefik setup
|
||||
|
||||
<details>
|
||||
<summary>docker-compose.for-traefik.yml</summary>
|
||||
@@ -76,7 +181,7 @@ ### For existing Traefik setup
|
||||
|
||||
</details>
|
||||
|
||||
### With Traefik included
|
||||
##### With Traefik included
|
||||
|
||||
<details>
|
||||
<summary>docker-compose.with-traefik.yml</summary>
|
||||
@@ -87,7 +192,7 @@ ### With Traefik included
|
||||
|
||||
</details>
|
||||
|
||||
### With Caddy Docker Proxy
|
||||
##### With Caddy Docker Proxy
|
||||
|
||||
<details>
|
||||
<summary>docker-compose.with-caddy.yml</summary>
|
||||
@@ -98,9 +203,15 @@ ### With Caddy Docker Proxy
|
||||
|
||||
```
|
||||
|
||||
If you don't already have a network for Caddy to monitor, create one first:
|
||||
|
||||
```bash
|
||||
docker network create caddy
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### For other reverse proxies
|
||||
##### For other reverse proxies
|
||||
|
||||
<details>
|
||||
<summary>docker-compose.yml</summary>
|
||||
@@ -111,7 +222,7 @@ ### For other reverse proxies
|
||||
|
||||
</details>
|
||||
|
||||
### Override file
|
||||
##### Override file for customisation
|
||||
|
||||
<details>
|
||||
<summary>docker-compose.override.yml</summary>
|
||||
@@ -122,98 +233,24 @@ ### Override file
|
||||
|
||||
</details>
|
||||
|
||||
When picking the Traefik-related compose file, rename it to
|
||||
`docker-compose.yml`, and rename the override file to
|
||||
`docker-compose.override.yml`. Edit the latter with the values you want for your
|
||||
server.
|
||||
#### Starting Your Server
|
||||
|
||||
When picking the `caddy-docker-proxy` compose file, it's important to first
|
||||
create the `caddy` network before spinning up the containers:
|
||||
|
||||
```bash
|
||||
docker network create caddy
|
||||
```
|
||||
|
||||
After that, you can rename it to `docker-compose.yml` and spin up the
|
||||
containers!
|
||||
|
||||
Additional info about deploying Continuwuity can be found [here](generic.mdx).
|
||||
|
||||
### Build
|
||||
|
||||
Official Continuwuity images are built using **Docker Buildx** and the Dockerfile found at [`docker/Dockerfile`][dockerfile-path]. This approach uses common Docker tooling and enables efficient multi-platform builds.
|
||||
|
||||
The resulting images are widely compatible with Docker and other container runtimes like Podman or containerd.
|
||||
|
||||
The images *do not contain a shell*. They contain only the Continuwuity binary, required libraries, TLS certificates, and metadata.
|
||||
|
||||
<details>
|
||||
<summary>Click to view the Dockerfile</summary>
|
||||
|
||||
You can also <a href="https://forgejo.ellis.link/continuwuation/continuwuation/src/branch/main/docker/Dockerfile" target="_blank">view the Dockerfile on Forgejo</a>.
|
||||
|
||||
```dockerfile file="../../docker/Dockerfile"
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
To build an image locally using Docker Buildx, you can typically run a command like:
|
||||
|
||||
```bash
|
||||
# Build for the current platform and load into the local Docker daemon
|
||||
docker buildx build --load --tag continuwuity:latest -f docker/Dockerfile .
|
||||
|
||||
# Example: Build for specific platforms and push to a registry.
|
||||
# docker buildx build --platform linux/amd64,linux/arm64 --tag registry.io/org/continuwuity:latest -f docker/Dockerfile . --push
|
||||
|
||||
# Example: Build binary optimised for the current CPU (standard release profile)
|
||||
# docker buildx build --load \
|
||||
# --tag continuwuity:latest \
|
||||
# --build-arg TARGET_CPU=native \
|
||||
# -f docker/Dockerfile .
|
||||
|
||||
# Example: Build maxperf variant (release-max-perf profile with LTO)
|
||||
# Optimised for runtime performance and smaller binary size, but requires longer build time
|
||||
# docker buildx build --load \
|
||||
# --tag continuwuity:latest-maxperf \
|
||||
# --build-arg TARGET_CPU=native \
|
||||
# --build-arg RUST_PROFILE=release-max-perf \
|
||||
# -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
Refer to the Docker Buildx documentation for more advanced build options.
|
||||
|
||||
[dockerfile-path]: https://forgejo.ellis.link/continuwuation/continuwuation/src/branch/main/docker/Dockerfile
|
||||
|
||||
### Run
|
||||
|
||||
If you have already built the image or want to use one from the registries, you
|
||||
can start the container and everything else in the compose file in detached
|
||||
mode with:
|
||||
1. Choose your compose file and rename it to `docker-compose.yml`
|
||||
2. If using the override file, rename it to `docker-compose.override.yml` and
|
||||
edit your values
|
||||
3. Start the server:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
> **Note:** Don't forget to modify and adjust the compose file to your needs.
|
||||
See the [generic deployment guide](generic.mdx) for more deployment options.
|
||||
|
||||
### Use Traefik as Proxy
|
||||
### Building Custom Images
|
||||
|
||||
As a container user, you probably know about Traefik. It is an easy-to-use
|
||||
reverse proxy for making containerized apps and services available through the
|
||||
web. With the Traefik-related docker-compose files provided above, it is equally easy
|
||||
to deploy and use Continuwuity, with a small caveat. If you have already looked at
|
||||
the files, you should have seen the `well-known` service, which is the
|
||||
small caveat. Traefik is simply a proxy and load balancer and cannot
|
||||
serve any kind of content. For Continuwuity to federate, we need to either
|
||||
expose ports `443` and `8448` or serve two endpoints: `.well-known/matrix/client`
|
||||
and `.well-known/matrix/server`.
|
||||
|
||||
With the service `well-known`, we use a single `nginx` container that serves
|
||||
those two files.
|
||||
|
||||
Alternatively, you can use Continuwuity's built-in delegation file capability. Set up the delegation files in the configuration file, and then proxy paths under `/.well-known/matrix` to continuwuity. For example, the label ``traefik.http.routers.continuwuity.rule=(Host(`matrix.ellis.link`) || (Host(`ellis.link`) && PathPrefix(`/.well-known/matrix`)))`` does this for the domain `ellis.link`.
|
||||
For information on building your own Continuwuity Docker images, see the
|
||||
[Building Docker Images](../development/index.mdx#building-docker-images)
|
||||
section in the development documentation.
|
||||
|
||||
## Voice communication
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Continuwuity for FreeBSD
|
||||
|
||||
Continuwuity currently does not provide FreeBSD builds or FreeBSD packaging. However, Continuwuity does build and work on FreeBSD using the system-provided RocksDB.
|
||||
Continuwuity doesn't provide official FreeBSD packages; however, a community-maintained set of packages is available on [Forgejo](https://forgejo.ellis.link/katie/continuwuity-bsd). Note that these are provided as standalone packages and are not part of a FreeBSD package repository (yet), so updates need to be downloaded and installed manually.
|
||||
|
||||
Contributions to get Continuwuity packaged for FreeBSD are welcome.
|
||||
Please see the installation instructions in that repository. Direct any questions to its issue tracker or to [@katie:kat5.dev](https://matrix.to/#/@katie:kat5.dev).
|
||||
|
||||
For general BSD support, please join our [Continuwuity BSD](https://matrix.to/#/%23bsd:continuwuity.org) community room.
|
||||
|
||||
@@ -1,7 +1,110 @@
|
||||
# Continuwuity for Kubernetes
|
||||
|
||||
Continuwuity doesn't support horizontal scalability or distributed loading
|
||||
natively. However, [a community-maintained Helm Chart is available here to run
|
||||
natively. However, a deployment in Kubernetes is very similar to the docker
|
||||
setup. This is because Continuwuity can be fully configured using environment
|
||||
variables. A sample StatefulSet is shared below. The only thing missing is
|
||||
a PVC definition (named `continuwuity-data`) for the volume mounted to
|
||||
the StatefulSet, an Ingress resources to point your webserver to the
|
||||
Continuwuity Pods, and a Service resource (targeting `app.kubernetes.io/name: continuwuity`)
|
||||
to glue the Ingress and Pod together.
|
||||
|
||||
Carefully go through the `env` section and add, change, and remove any env vars you like using the [Configuration reference](https://continuwuity.org/reference/config.html)
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: continuwuity
|
||||
namespace: matrix
|
||||
labels:
|
||||
app.kubernetes.io/name: continuwuity
|
||||
spec:
|
||||
replicas: 1
|
||||
serviceName: continuwuity
|
||||
podManagementPolicy: Parallel
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: continuwuity
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: continuwuity
|
||||
spec:
|
||||
securityContext:
|
||||
sysctls:
|
||||
- name: net.ipv4.ip_unprivileged_port_start
|
||||
value: "0"
|
||||
containers:
|
||||
- name: continuwuity
|
||||
# use a sha hash <3
|
||||
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
||||
command: ["/sbin/conduwuit"]
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
volumeMounts:
|
||||
- mountPath: /data
|
||||
name: data
|
||||
subPath: data
|
||||
securityContext:
|
||||
capabilities:
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
env:
|
||||
- name: TOKIO_WORKER_THREADS
|
||||
value: "2"
|
||||
- name: CONTINUWUITY_SERVER_NAME
|
||||
value: "example.com"
|
||||
- name: CONTINUWUITY_DATABASE_PATH
|
||||
value: "/data/db"
|
||||
- name: CONTINUWUITY_DATABASE_BACKEND
|
||||
value: "rocksdb"
|
||||
- name: CONTINUWUITY_PORT
|
||||
value: "80"
|
||||
- name: CONTINUWUITY_MAX_REQUEST_SIZE
|
||||
value: "20000000"
|
||||
- name: CONTINUWUITY_ALLOW_FEDERATION
|
||||
value: "true"
|
||||
- name: CONTINUWUITY_TRUSTED_SERVERS
|
||||
value: '["matrix.org"]'
|
||||
- name: CONTINUWUITY_ADDRESS
|
||||
value: "0.0.0.0"
|
||||
- name: CONTINUWUITY_ROCKSDB_PARALLELISM_THREADS
|
||||
value: "1"
|
||||
- name: CONTINUWUITY_WELL_KNOWN__SERVER
|
||||
value: "matrix.example.com:443"
|
||||
- name: CONTINUWUITY_WELL_KNOWN__CLIENT
|
||||
value: "https://matrix.example.com"
|
||||
- name: CONTINUWUITY_ALLOW_REGISTRATION
|
||||
value: "false"
|
||||
- name: RUST_LOG
|
||||
value: info
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /_matrix/federation/v1/version
|
||||
port: http
|
||||
periodSeconds: 4
|
||||
failureThreshold: 5
|
||||
resources:
|
||||
# Continuwuity might use quite some RAM :3
|
||||
requests:
|
||||
cpu: "2"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "4"
|
||||
memory: "2048Mi"
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: continuwuity-data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Apart from manually configuring the containers,
|
||||
[a community-maintained Helm Chart is available here to run
|
||||
conduwuit on Kubernetes](https://gitlab.cronce.io/charts/conduwuit)
|
||||
|
||||
This should be compatible with Continuwuity, but you will need to change the image reference.
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
# Contributing guide
|
||||
|
||||
This page is about contributing to Continuwuity. The
|
||||
[development](./index.mdx) and [code style guide](./code_style.mdx) pages 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 [#continuwuity:continuwuity.org][continuwuity-matrix],
|
||||
and comment on it.
|
||||
|
||||
### Code Style
|
||||
|
||||
Please review and follow the [code style guide](./code_style) for formatting, linting, naming conventions, and other code standards.
|
||||
|
||||
### Pre-commit Checks
|
||||
|
||||
Continuwuity uses pre-commit hooks to enforce various coding standards and catch common issues before they're committed. These checks include:
|
||||
|
||||
- Code formatting and linting
|
||||
- Typo detection (both in code and commit messages)
|
||||
- Checking for large files
|
||||
- Ensuring proper line endings and no trailing whitespace
|
||||
- Validating YAML, JSON, and TOML files
|
||||
- Checking for merge conflicts
|
||||
|
||||
You can run these checks locally by installing [prefligit](https://github.com/j178/prefligit):
|
||||
|
||||
|
||||
```bash
|
||||
# Requires UV: https://docs.astral.sh/uv/getting-started/installation/
|
||||
# Mac/linux: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
# Windows: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
|
||||
# Install prefligit using cargo-binstall
|
||||
cargo binstall prefligit
|
||||
|
||||
# Install git hooks to run checks automatically
|
||||
prefligit install
|
||||
|
||||
# Run all checks
|
||||
prefligit --all-files
|
||||
```
|
||||
|
||||
Alternatively, you can use [pre-commit](https://pre-commit.com/):
|
||||
```bash
|
||||
# Requires python
|
||||
|
||||
# Install pre-commit
|
||||
pip install pre-commit
|
||||
|
||||
# Install the hooks
|
||||
pre-commit install
|
||||
|
||||
# Run all checks manually
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
These same checks are run in CI via the prefligit-checks workflow to ensure consistency. These must pass before the PR is merged.
|
||||
|
||||
### Running tests locally
|
||||
|
||||
Tests, compilation, and linting can be run with standard Cargo commands:
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
cargo test
|
||||
|
||||
# Check compilation
|
||||
cargo check --workspace --features full
|
||||
|
||||
# Run lints
|
||||
cargo clippy --workspace --features full
|
||||
# Auto-fix: cargo clippy --workspace --features full --fix --allow-staged;
|
||||
|
||||
# Format code (must use nightly)
|
||||
cargo +nightly fmt
|
||||
```
|
||||
|
||||
### Matrix tests
|
||||
|
||||
Continuwuity uses [Complement][complement] for Matrix protocol compliance testing. Complement tests are run manually by developers, and documentation on how to run these tests locally is currently being developed.
|
||||
|
||||
If your changes are done to fix Matrix tests, please note that in your pull request. If more Complement tests start failing from your changes, please review the logs and determine if they're intended or not.
|
||||
|
||||
[Sytest][sytest] is currently unsupported.
|
||||
|
||||
### Writing documentation
|
||||
|
||||
Continuwuity's website uses [`mdbook`][mdbook] and is deployed via CI using Cloudflare Pages
|
||||
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/`
|
||||
directory at the top level.
|
||||
|
||||
To build the documentation locally:
|
||||
|
||||
1. Install mdbook if you don't have it already:
|
||||
```bash
|
||||
cargo install mdbook # or cargo binstall, or another method
|
||||
```
|
||||
|
||||
2. Build the documentation:
|
||||
```bash
|
||||
mdbook build
|
||||
```
|
||||
|
||||
The output of the mdbook generation is in `public/`. You can open the HTML files directly in your browser without needing a web server.
|
||||
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Continuwuity follows the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. This provides a standardized format that makes the commit history more readable and enables automated tools to generate changelogs.
|
||||
|
||||
The basic structure is:
|
||||
|
||||
```
|
||||
<type>[(optional scope)]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
The allowed types for commits are:
|
||||
- `fix`: Bug fixes
|
||||
- `feat`: New features
|
||||
- `docs`: Documentation changes
|
||||
- `style`: Changes that don't affect the meaning of the code (formatting, etc.)
|
||||
- `refactor`: Code changes that neither fix bugs nor add features
|
||||
- `perf`: Performance improvements
|
||||
- `test`: Adding or fixing tests
|
||||
- `build`: Changes to the build system or dependencies
|
||||
- `ci`: Changes to CI configuration
|
||||
- `chore`: Other changes that don't modify source or test files
|
||||
|
||||
Examples:
|
||||
```
|
||||
feat: add user authentication
|
||||
fix(database): resolve connection pooling issue
|
||||
docs: update installation instructions
|
||||
```
|
||||
|
||||
The project uses the `committed` hook to validate commit messages in pre-commit. This ensures all commits follow the conventional format.
|
||||
|
||||
### Creating pull requests
|
||||
|
||||
Please try to keep contributions to the Forgejo Instance. While the mirrors of continuwuity
|
||||
allow for pull/merge requests, there is no guarantee the maintainers will see them in a timely
|
||||
manner. Additionally, please mark WIP or unfinished or incomplete PRs as drafts.
|
||||
This prevents us 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.
|
||||
|
||||
Before submitting a pull request, please ensure:
|
||||
1. Your code passes all CI checks (formatting, linting, typo detection, etc.). Run pre-commit for this.
|
||||
2. Your code follows the [code style guide](./code_style)
|
||||
3. Your commit messages follow the conventional commits format
|
||||
4. Tests are added for new functionality
|
||||
5. Documentation is updated if needed
|
||||
6. You have written a [news fragment](#writing-news-fragments) for your changes
|
||||
|
||||
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, and continuwuity's Code of Conduct.
|
||||
|
||||
Contribution by users who violate either of these code of conducts may not have
|
||||
their contributions accepted. This includes users who have been banned from
|
||||
continuwuity Matrix rooms for Code of Conduct violations.
|
||||
|
||||
[issues]: https://forgejo.ellis.link/continuwuation/continuwuity/issues
|
||||
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org
|
||||
[complement]: https://github.com/matrix-org/complement/
|
||||
[sytest]: https://github.com/matrix-org/sytest/
|
||||
[mdbook]: https://rust-lang.github.io/mdBook/
|
||||
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml
|
||||
|
||||
#### Writing news fragments
|
||||
|
||||
In order to make writing our changelogs easier, we make use of [Towncrier]. Towncrier builds changelogs based on
|
||||
"news fragments", which are little markdown files in the `changelog.d/` directory that describe individual changes.
|
||||
|
||||
When you make a pull request that changes functionality, fixes a bug, or adds documentation, please add a news fragment
|
||||
describing your change. The file name *MUST* be in the format of `{pull_request_number}.{type}`, where `{type}` is one
|
||||
of the following:
|
||||
|
||||
- `feature` - for new features
|
||||
- `bugfix` - for bug fixes
|
||||
- `doc` - for documentation changes
|
||||
- `misc` - for other changes that don't fit the above categories
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ echo "Fixed the quantum flux stabiliser. Contributed by @alice." > changelog.d/42.bugfix
|
||||
```
|
||||
|
||||
(Note: If you want to credit yourself, you should reference your forgejo handle, however links to other platforms are also acceptable.)
|
||||
|
||||
When the next release is made, Towncrier will automatically include your news fragment in the changelog.
|
||||
|
||||
You can read more about writing news fragments in the [Towncrier tutorial][tt].
|
||||
|
||||
[Towncrier]: https://towncrier.readthedocs.io/
|
||||
[tt]: https://towncrier.readthedocs.io/en/stable/tutorial.html#creating-news-fragments
|
||||
1
docs/development/contributing.mdx
Symbolic link
1
docs/development/contributing.mdx
Symbolic link
@@ -0,0 +1 @@
|
||||
../../CONTRIBUTING.md
|
||||
@@ -2,7 +2,8 @@ # Development
|
||||
|
||||
Information about developing the project. If you are only interested in using
|
||||
it, you can safely ignore this page. If you plan on contributing, see the
|
||||
[contributor's guide](./contributing.mdx) and [code style guide](./code_style.mdx).
|
||||
[contributor's guide](./contributing.mdx) and
|
||||
[code style guide](./code_style.mdx).
|
||||
|
||||
## Continuwuity project layout
|
||||
|
||||
@@ -12,86 +13,98 @@ ## Continuwuity project layout
|
||||
`Cargo.toml`.
|
||||
|
||||
The crate names are generally self-explanatory:
|
||||
|
||||
- `admin` is the admin room
|
||||
- `api` is the HTTP API, Matrix C-S and S-S endpoints, etc
|
||||
- `core` is core Continuwuity functionality like config loading, error definitions,
|
||||
global utilities, logging infrastructure, etc
|
||||
- `database` is RocksDB methods, helpers, RocksDB config, and general database definitions,
|
||||
utilities, or functions
|
||||
- `macros` are Continuwuity Rust [macros][macros] like general helper macros, logging
|
||||
and error handling macros, and [syn][syn] and [procedural macros][proc-macro]
|
||||
used for admin room commands and others
|
||||
- `core` is core Continuwuity functionality like config loading, error
|
||||
definitions, global utilities, logging infrastructure, etc
|
||||
- `database` is RocksDB methods, helpers, RocksDB config, and general database
|
||||
definitions, utilities, or functions
|
||||
- `macros` are Continuwuity Rust [macros][macros] like general helper macros,
|
||||
logging and error handling macros, and [syn][syn] and [procedural
|
||||
macros][proc-macro] used for admin room commands and others
|
||||
- `main` is the "primary" sub-crate. This is where the `main()` function lives,
|
||||
tokio worker and async initialisation, Sentry initialisation, [clap][clap] init,
|
||||
and signal handling. If you are adding new [Rust features][features], they *must*
|
||||
go here.
|
||||
- `router` is the webserver and request handling bits, using axum, tower, tower-http,
|
||||
hyper, etc, and the [global server state][state] to access `services`.
|
||||
tokio worker and async initialisation, Sentry initialisation, [clap][clap]
|
||||
init, and signal handling. If you are adding new [Rust features][features],
|
||||
they _must_ go here.
|
||||
- `router` is the webserver and request handling bits, using axum, tower,
|
||||
tower-http, hyper, etc, and the [global server state][state] to access
|
||||
`services`.
|
||||
- `service` is the high-level database definitions and functions for data,
|
||||
outbound/sending code, and other business logic such as media fetching.
|
||||
outbound/sending code, and other business logic such as media fetching.
|
||||
|
||||
It is highly unlikely you will ever need to add a new workspace member, but
|
||||
if you truly find yourself needing to, we recommend reaching out to us in
|
||||
the Matrix room for discussions about it beforehand.
|
||||
It is highly unlikely you will ever need to add a new workspace member, but if
|
||||
you truly find yourself needing to, we recommend reaching out to us in the
|
||||
Matrix room for discussions about it beforehand.
|
||||
|
||||
The primary inspiration for this design was apart of hot reloadable development,
|
||||
to support "Continuwuity as a library" where specific parts can simply be swapped out.
|
||||
There is evidence Conduit wanted to go this route too as `axum` is technically an
|
||||
optional feature in Conduit, and can be compiled without the binary or axum library
|
||||
for handling inbound web requests; but it was never completed or worked.
|
||||
to support "Continuwuity as a library" where specific parts can simply be
|
||||
swapped out. There is evidence Conduit wanted to go this route too as `axum` is
|
||||
technically an optional feature in Conduit, and can be compiled without the
|
||||
binary or axum library for handling inbound web requests; but it was never
|
||||
completed or worked.
|
||||
|
||||
See the Rust documentation on [Workspaces][workspaces] for general questions
|
||||
and information on Cargo workspaces.
|
||||
See the Rust documentation on [Workspaces][workspaces] for general questions and
|
||||
information on Cargo workspaces.
|
||||
|
||||
## Adding compile-time [features][features]
|
||||
|
||||
If you'd like to add a compile-time feature, you must first define it in
|
||||
the `main` workspace crate located in `src/main/Cargo.toml`. The feature must
|
||||
enable a feature in the other workspace crate(s) you intend to use it in. Then
|
||||
the said workspace crate(s) must define the feature there in its `Cargo.toml`.
|
||||
If you'd like to add a compile-time feature, you must first define it in the
|
||||
`main` workspace crate located in `src/main/Cargo.toml`. The feature must enable
|
||||
a feature in the other workspace crate(s) you intend to use it in. Then the said
|
||||
workspace crate(s) must define the feature there in its `Cargo.toml`.
|
||||
|
||||
So, if this is adding a feature to the API such as `woof`, you define the feature
|
||||
in the `api` crate's `Cargo.toml` as `woof = []`. The feature definition in `main`'s
|
||||
`Cargo.toml` will be `woof = ["conduwuit-api/woof"]`.
|
||||
So, if this is adding a feature to the API such as `woof`, you define the
|
||||
feature in the `api` crate's `Cargo.toml` as `woof = []`. The feature definition
|
||||
in `main`'s `Cargo.toml` will be `woof = ["conduwuit-api/woof"]`.
|
||||
|
||||
The rationale for this is due to Rust / Cargo not supporting
|
||||
["workspace level features"][9], we must make a choice of; either scattering
|
||||
features all over the workspace crates, making it difficult for anyone to add
|
||||
or remove default features; or define all the features in one central workspace
|
||||
crate that propagate down/up to the other workspace crates. It is a Cargo pitfall,
|
||||
and we'd like to see better developer UX in Rust's Workspaces.
|
||||
The rationale for this is due to Rust / Cargo not supporting ["workspace level
|
||||
features"][9], we must make a choice of; either scattering features all over the
|
||||
workspace crates, making it difficult for anyone to add or remove default
|
||||
features; or define all the features in one central workspace crate that
|
||||
propagate down/up to the other workspace crates. It is a Cargo pitfall, and we'd
|
||||
like to see better developer UX in Rust's Workspaces.
|
||||
|
||||
Additionally, the definition of one single place makes "feature collection" in our
|
||||
Nix flake a million times easier instead of collecting and deduping them all from
|
||||
searching in all the workspace crates' `Cargo.toml`s. Though we wouldn't need to
|
||||
do this if Rust supported workspace-level features to begin with.
|
||||
Additionally, the definition of one single place makes "feature collection" in
|
||||
our Nix flake a million times easier instead of collecting and deduping them all
|
||||
from searching in all the workspace crates' `Cargo.toml`s. Though we wouldn't
|
||||
need to do this if Rust supported workspace-level features to begin with.
|
||||
|
||||
## List of forked dependencies
|
||||
|
||||
During Continuwuity (and prior projects) development, we have had to fork some dependencies to support our use-cases.
|
||||
These forks exist for various reasons including features that upstream projects won't accept,
|
||||
faster-paced development, Continuwuity-specific usecases, or lack of time to upstream changes.
|
||||
During Continuwuity (and prior projects) development, we have had to fork some
|
||||
dependencies to support our use-cases. These forks exist for various reasons
|
||||
including features that upstream projects won't accept, faster-paced
|
||||
development, Continuwuity-specific usecases, or lack of time to upstream
|
||||
changes.
|
||||
|
||||
All forked dependencies are maintained under the [continuwuation organization on Forgejo](https://forgejo.ellis.link/continuwuation):
|
||||
All forked dependencies are maintained under the
|
||||
[continuwuation organization on Forgejo](https://forgejo.ellis.link/continuwuation):
|
||||
|
||||
- [ruwuma][continuwuation-ruwuma] - Fork of [ruma/ruma][ruma] with various performance improvements, more features and better client/server interop
|
||||
- [rocksdb][continuwuation-rocksdb] - Fork of [facebook/rocksdb][rocksdb] via [`@zaidoon1`][8] with liburing build fixes and GCC debug build fixes
|
||||
- [jemallocator][continuwuation-jemallocator] - Fork of [tikv/jemallocator][jemallocator] fixing musl builds, suspicious code,
|
||||
and adding support for redzones in Valgrind
|
||||
- [rustyline-async][continuwuation-rustyline-async] - Fork of [zyansheep/rustyline-async][rustyline-async] with tab completion callback
|
||||
and `CTRL+\` signal quit event for Continuwuity console CLI
|
||||
- [rust-rocksdb][continuwuation-rust-rocksdb] - Fork of [rust-rocksdb/rust-rocksdb][rust-rocksdb] fixing musl build issues,
|
||||
removing unnecessary `gtest` include, and using our RocksDB and jemallocator forks
|
||||
- [tracing][continuwuation-tracing] - Fork of [tokio-rs/tracing][tracing] implementing `Clone` for `EnvFilter` to
|
||||
support dynamically changing tracing environments
|
||||
- [ruwuma][continuwuation-ruwuma] - Fork of [ruma/ruma][ruma] with various
|
||||
performance improvements, more features and better client/server interop
|
||||
- [rocksdb][continuwuation-rocksdb] - Fork of [facebook/rocksdb][rocksdb] via
|
||||
[`@zaidoon1`][8] with liburing build fixes and GCC debug build fixes
|
||||
- [jemallocator][continuwuation-jemallocator] - Fork of
|
||||
[tikv/jemallocator][jemallocator] fixing musl builds, suspicious code, and
|
||||
adding support for redzones in Valgrind
|
||||
- [rustyline-async][continuwuation-rustyline-async] - Fork of
|
||||
[zyansheep/rustyline-async][rustyline-async] with tab completion callback and
|
||||
`CTRL+\` signal quit event for Continuwuity console CLI
|
||||
- [rust-rocksdb][continuwuation-rust-rocksdb] - Fork of
|
||||
[rust-rocksdb/rust-rocksdb][rust-rocksdb] fixing musl build issues, removing
|
||||
unnecessary `gtest` include, and using our RocksDB and jemallocator forks
|
||||
- [tracing][continuwuation-tracing] - Fork of [tokio-rs/tracing][tracing]
|
||||
implementing `Clone` for `EnvFilter` to support dynamically changing tracing
|
||||
environments
|
||||
|
||||
## Debugging with `tokio-console`
|
||||
|
||||
[`tokio-console`][7] can be a useful tool for debugging and profiling. To make a
|
||||
`tokio-console`-enabled build of Continuwuity, enable the `tokio_console` feature,
|
||||
disable the default `release_max_log_level` feature, and set the `--cfg
|
||||
tokio_unstable` flag to enable experimental tokio APIs. A build might look like
|
||||
this:
|
||||
`tokio-console`-enabled build of Continuwuity, enable the `tokio_console`
|
||||
feature, disable the default `release_max_log_level` feature, and set the
|
||||
`--cfg tokio_unstable` flag to enable experimental tokio APIs. A build might
|
||||
look like this:
|
||||
|
||||
```bash
|
||||
RUSTFLAGS="--cfg tokio_unstable" cargo +nightly build \
|
||||
@@ -100,34 +113,84 @@ ## Debugging with `tokio-console`
|
||||
--features=systemd,element_hacks,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
||||
```
|
||||
|
||||
You will also need to enable the `tokio_console` config option in Continuwuity when
|
||||
starting it. This was due to tokio-console causing gradual memory leak/usage
|
||||
if left enabled.
|
||||
You will also need to enable the `tokio_console` config option in Continuwuity
|
||||
when starting it. This was due to tokio-console causing gradual memory
|
||||
leak/usage if left enabled.
|
||||
|
||||
## Building Docker Images
|
||||
|
||||
To build a Docker image for Continuwuity, use the standard Docker build command:
|
||||
Official Continuwuity images are built using **Docker Buildx** and the
|
||||
Dockerfile found at [`docker/Dockerfile`][dockerfile-path].
|
||||
|
||||
The images are compatible with Docker and other container runtimes like Podman
|
||||
or containerd.
|
||||
|
||||
The images _do not contain a shell_. They contain only the Continuwuity binary,
|
||||
required libraries, TLS certificates, and metadata.
|
||||
|
||||
<details>
|
||||
<summary>Click to view the Dockerfile</summary>
|
||||
|
||||
You can also
|
||||
|
||||
<a
|
||||
href="<https://forgejo.ellis.link/continuwuation/continuwuation/src/branch/main/docker/Dockerfile>"
|
||||
target="_blank"
|
||||
>
|
||||
view the Dockerfile on Forgejo
|
||||
</a>
|
||||
.
|
||||
|
||||
```dockerfile file="../../docker/Dockerfile"
|
||||
|
||||
```bash
|
||||
docker build -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
The image can be cross-compiled for different architectures.
|
||||
</details>
|
||||
|
||||
### Building Locally
|
||||
|
||||
To build an image locally using Docker Buildx:
|
||||
|
||||
```bash
|
||||
# Build for the current platform and load into the local Docker daemon
|
||||
docker buildx build --load --tag continuwuity:latest -f docker/Dockerfile .
|
||||
|
||||
# Example: Build for specific platforms and push to a registry
|
||||
# docker buildx build --platform linux/amd64,linux/arm64 --tag registry.io/org/continuwuity:latest -f docker/Dockerfile . --push
|
||||
|
||||
# Example: Build binary optimised for the current CPU (standard release profile)
|
||||
# docker buildx build --load \
|
||||
# --tag continuwuity:latest \
|
||||
# --build-arg TARGET_CPU=native \
|
||||
# -f docker/Dockerfile .
|
||||
|
||||
# Example: Build maxperf variant (release-max-perf profile with LTO)
|
||||
# docker buildx build --load \
|
||||
# --tag continuwuity:latest-maxperf \
|
||||
# --build-arg TARGET_CPU=native \
|
||||
# --build-arg RUST_PROFILE=release-max-perf \
|
||||
# -f docker/Dockerfile .
|
||||
```
|
||||
|
||||
Refer to the Docker Buildx documentation for more advanced build options.
|
||||
|
||||
[dockerfile-path]:
|
||||
https://forgejo.ellis.link/continuwuation/continuwuation/src/branch/main/docker/Dockerfile
|
||||
[continuwuation-ruwuma]: https://forgejo.ellis.link/continuwuation/ruwuma
|
||||
[continuwuation-rocksdb]: https://forgejo.ellis.link/continuwuation/rocksdb
|
||||
[continuwuation-jemallocator]: https://forgejo.ellis.link/continuwuation/jemallocator
|
||||
[continuwuation-rustyline-async]: https://forgejo.ellis.link/continuwuation/rustyline-async
|
||||
[continuwuation-rust-rocksdb]: https://forgejo.ellis.link/continuwuation/rust-rocksdb
|
||||
[continuwuation-jemallocator]:
|
||||
https://forgejo.ellis.link/continuwuation/jemallocator
|
||||
[continuwuation-rustyline-async]:
|
||||
https://forgejo.ellis.link/continuwuation/rustyline-async
|
||||
[continuwuation-rust-rocksdb]:
|
||||
https://forgejo.ellis.link/continuwuation/rust-rocksdb
|
||||
[continuwuation-tracing]: https://forgejo.ellis.link/continuwuation/tracing
|
||||
|
||||
[ruma]: https://github.com/ruma/ruma/
|
||||
[rocksdb]: https://github.com/facebook/rocksdb/
|
||||
[jemallocator]: https://github.com/tikv/jemallocator/
|
||||
[rustyline-async]: https://github.com/zyansheep/rustyline-async/
|
||||
[rust-rocksdb]: https://github.com/rust-rocksdb/rust-rocksdb/
|
||||
[tracing]: https://github.com/tokio-rs/tracing/
|
||||
|
||||
[7]: https://docs.rs/tokio-console/latest/tokio_console/
|
||||
[8]: https://github.com/zaidoon1/
|
||||
[9]: https://github.com/rust-lang/cargo/issues/12162
|
||||
|
||||
@@ -51,7 +51,13 @@ ## Can I try it out?
|
||||
|
||||
Check out the [documentation](https://continuwuity.org) for installation instructions.
|
||||
|
||||
There are currently no open registration continuwuity instances available.
|
||||
If you want to try it out as a user, we have some partnered homeservers you can use:
|
||||
* You can head over to [https://federated.nexus](https://federated.nexus/) in your browser.
|
||||
* Hit the `Apply to Join` button. Once your request has been accepted, you will receive an email with your username and password.
|
||||
* Head over to [https://app.federated.nexus](https://app.federated.nexus/) and you can sign in there, or use any other matrix chat client you wish elsewhere.
|
||||
* Your username for matrix will be in the form of `@username:federated.nexus`, however you can simply use the `username` part to log in. Your password is your password.
|
||||
|
||||
* There's also [https://continuwuity.rocks/](https://continuwuity.rocks/). You can register a new account using Cinny via [this convenient link](https://app.cinny.in/register/continuwuity.rocks), or you can use Element or another matrix client *that supports registration*.
|
||||
|
||||
## What are we working on?
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"id": 10,
|
||||
"mention_room": false,
|
||||
"date": "2026-02-09",
|
||||
"message": "Yesterday we released [v0.5.4](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.4). Bugfixes, performance improvements and more moderation features! There's also a security fix, so please update as soon as possible. Don't forget to join [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM/%2489TY9CqRg4-ff1MGo3Ulc5r5X4pakfdzT-99RD8Docc?via=ellis.link&via=explodie.org&via=matrix.org) to get important information sooner <3 "
|
||||
"date": "2026-03-03",
|
||||
"message": "We've just released [v0.5.6](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.6), which contains a few security improvements - plus significant reliability and performance improvements. Please update as soon as possible. \n\nWe released [v0.5.5](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.5) two weeks ago, but it skipped your admin room straight to [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM?via=ellis.link&via=gingershaped.computer&via=matrix.org). Make sure you're there to get important information as soon as we announce it! [Our space](https://matrix.to/#/!8cR4g-i9ucof69E4JHNg9LbPVkGprHb3SzcrGBDDJgk?via=continuwuity.org&via=ellis.link&via=matrix.org) has also gained a bunch of new and interesting rooms - be there or be square."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"m.homeserver":{"base_url": "https://matrix.continuwuity.org"},"org.matrix.msc3575.proxy":{"url": "https://matrix.continuwuity.org"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://livekit.ellis.link"}]}
|
||||
{"m.homeserver":{"base_url": "https://matrix.continuwuity.org"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://livekit.ellis.link"}]}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"$id": "https://continwuity.org/schema/announcements.schema.json",
|
||||
"$id": "https://continuwuity.org/schema/announcements.schema.json",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"announcements": {
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
"name": "config",
|
||||
"label": "Configuration"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"name": "environment-variables",
|
||||
"label": "Environment Variables"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"name": "admin",
|
||||
|
||||
@@ -130,6 +130,10 @@ ## `!admin debug database-files`
|
||||
|
||||
List database files
|
||||
|
||||
## `!admin debug send-test-email`
|
||||
|
||||
Send a test email to the invoking admin's email address
|
||||
|
||||
## `!admin debug tester`
|
||||
|
||||
Developer test stubs
|
||||
|
||||
@@ -27,7 +27,7 @@ ## `!admin media delete-past-remote-media`
|
||||
* Delete all remote and local media from 3 days ago, up until now:
|
||||
|
||||
`!admin media delete-past-remote-media -a 3d
|
||||
-yes-i-want-to-delete-local-media`
|
||||
--yes-i-want-to-delete-local-media`
|
||||
|
||||
## `!admin media delete-all-from-user`
|
||||
|
||||
@@ -36,3 +36,7 @@ ## `!admin media delete-all-from-user`
|
||||
## `!admin media delete-all-from-server`
|
||||
|
||||
Deletes all remote media from the specified remote server. This will always ignore errors by default
|
||||
|
||||
## `!admin media delete-url-preview`
|
||||
|
||||
Deletes a cached URL preview, forcing it to be re-fetched. Use --all to purge all cached URL previews
|
||||
|
||||
@@ -12,6 +12,24 @@ ## `!admin users reset-password`
|
||||
|
||||
Reset user password
|
||||
|
||||
## `!admin users issue-password-reset-link`
|
||||
|
||||
Issue a self-service password reset link for a user
|
||||
|
||||
## `!admin users get-email`
|
||||
|
||||
Get a user's associated email address
|
||||
|
||||
## `!admin users get-user-by-email`
|
||||
|
||||
Get the user with the given email address
|
||||
|
||||
## `!admin users change-email`
|
||||
|
||||
Update or remove a user's email address.
|
||||
|
||||
If `email` is not supplied, the user's existing address will be removed.
|
||||
|
||||
## `!admin users deactivate`
|
||||
|
||||
Deactivate a user
|
||||
|
||||
281
docs/reference/environment-variables.mdx
Normal file
281
docs/reference/environment-variables.mdx
Normal file
@@ -0,0 +1,281 @@
|
||||
# Environment Variables
|
||||
|
||||
Continuwuity can be configured entirely through environment variables, making it
|
||||
ideal for containerised deployments and infrastructure-as-code scenarios.
|
||||
|
||||
This is a convenience reference and may not be exhaustive. The
|
||||
[Configuration Reference](./config.mdx) is the primary source for all
|
||||
configuration options.
|
||||
|
||||
## Prefix System
|
||||
|
||||
Continuwuity supports three environment variable prefixes for backwards
|
||||
compatibility:
|
||||
|
||||
- `CONTINUWUITY_*` (current, recommended)
|
||||
- `CONDUWUIT_*` (compatibility)
|
||||
- `CONDUIT_*` (legacy)
|
||||
|
||||
All three prefixes work identically. Use double underscores (`__`) to represent
|
||||
nested configuration sections from the TOML config.
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Simple top-level config
|
||||
CONTINUWUITY_SERVER_NAME="matrix.example.com"
|
||||
CONTINUWUITY_PORT="8008"
|
||||
|
||||
# Nested config sections use double underscores
|
||||
# This maps to [database] section in TOML
|
||||
CONTINUWUITY_DATABASE__PATH="/var/lib/continuwuity"
|
||||
|
||||
# This maps to [tls] section in TOML
|
||||
CONTINUWUITY_TLS__CERTS="/path/to/cert.pem"
|
||||
```
|
||||
|
||||
## Configuration File Override
|
||||
|
||||
You can specify a custom configuration file path:
|
||||
|
||||
- `CONTINUWUITY_CONFIG` - Path to continuwuity.toml (current)
|
||||
- `CONDUWUIT_CONFIG` - Path to config file (compatibility)
|
||||
- `CONDUIT_CONFIG` - Path to config file (legacy)
|
||||
|
||||
## Essential Variables
|
||||
|
||||
These are the minimum variables needed for a working deployment:
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ---------------------------- | ---------------------------------- | ---------------------- |
|
||||
| `CONTINUWUITY_SERVER_NAME` | Your Matrix server's domain name | Required |
|
||||
| `CONTINUWUITY_DATABASE_PATH` | Path to RocksDB database directory | `/var/lib/conduwuit` |
|
||||
| `CONTINUWUITY_ADDRESS` | IP address to bind to | `["127.0.0.1", "::1"]` |
|
||||
| `CONTINUWUITY_PORT` | Port to listen on | `8008` |
|
||||
|
||||
## Network Configuration
|
||||
|
||||
| Variable | Description | Default |
|
||||
| -------------------------------- | ----------------------------------------------- | ---------------------- |
|
||||
| `CONTINUWUITY_ADDRESS` | Bind address (use `0.0.0.0` for all interfaces) | `["127.0.0.1", "::1"]` |
|
||||
| `CONTINUWUITY_PORT` | HTTP port | `8008` |
|
||||
| `CONTINUWUITY_UNIX_SOCKET_PATH` | UNIX socket path (alternative to TCP) | - |
|
||||
| `CONTINUWUITY_UNIX_SOCKET_PERMS` | Socket permissions (octal) | `660` |
|
||||
|
||||
## Database Configuration
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------------------ | --------------------------- | -------------------- |
|
||||
| `CONTINUWUITY_DATABASE_PATH` | RocksDB data directory | `/var/lib/conduwuit` |
|
||||
| `CONTINUWUITY_DATABASE_BACKUP_PATH` | Backup directory | - |
|
||||
| `CONTINUWUITY_DATABASE_BACKUPS_TO_KEEP` | Number of backups to retain | `1` |
|
||||
| `CONTINUWUITY_DB_CACHE_CAPACITY_MB` | Database read cache (MB) | - |
|
||||
| `CONTINUWUITY_DB_WRITE_BUFFER_CAPACITY_MB` | Write cache (MB) | - |
|
||||
|
||||
## Cache Configuration
|
||||
|
||||
| Variable | Description |
|
||||
| ---------------------------------------- | ------------------------ |
|
||||
| `CONTINUWUITY_CACHE_CAPACITY_MODIFIER` | LRU cache multiplier |
|
||||
| `CONTINUWUITY_PDU_CACHE_CAPACITY` | PDU cache entries |
|
||||
| `CONTINUWUITY_AUTH_CHAIN_CACHE_CAPACITY` | Auth chain cache entries |
|
||||
|
||||
## DNS Configuration
|
||||
|
||||
Configure DNS resolution behaviour for federation and external requests.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------------ | ---------------------------- | -------- |
|
||||
| `CONTINUWUITY_DNS_CACHE_ENTRIES` | Max DNS cache entries | `32768` |
|
||||
| `CONTINUWUITY_DNS_MIN_TTL` | Minimum cache TTL (seconds) | `10800` |
|
||||
| `CONTINUWUITY_DNS_MIN_TTL_NXDOMAIN` | NXDOMAIN cache TTL (seconds) | `259200` |
|
||||
| `CONTINUWUITY_DNS_ATTEMPTS` | Retry attempts | - |
|
||||
| `CONTINUWUITY_DNS_TIMEOUT` | Query timeout (seconds) | - |
|
||||
| `CONTINUWUITY_DNS_TCP_FALLBACK` | Allow TCP fallback | - |
|
||||
| `CONTINUWUITY_QUERY_ALL_NAMESERVERS` | Query all nameservers | - |
|
||||
| `CONTINUWUITY_QUERY_OVER_TCP_ONLY` | TCP-only queries | - |
|
||||
|
||||
## Request Configuration
|
||||
|
||||
| Variable | Description |
|
||||
| ------------------------------------ | ----------------------------- |
|
||||
| `CONTINUWUITY_MAX_REQUEST_SIZE` | Max HTTP request size (bytes) |
|
||||
| `CONTINUWUITY_REQUEST_CONN_TIMEOUT` | Connection timeout (seconds) |
|
||||
| `CONTINUWUITY_REQUEST_TIMEOUT` | Overall request timeout |
|
||||
| `CONTINUWUITY_REQUEST_TOTAL_TIMEOUT` | Total timeout |
|
||||
| `CONTINUWUITY_REQUEST_IDLE_TIMEOUT` | Idle timeout |
|
||||
| `CONTINUWUITY_REQUEST_IDLE_PER_HOST` | Idle connections per host |
|
||||
|
||||
## Federation Configuration
|
||||
|
||||
Control how your server federates with other Matrix servers.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ---------------------------------------------- | ----------------------------- | ------- |
|
||||
| `CONTINUWUITY_ALLOW_FEDERATION` | Enable federation | `true` |
|
||||
| `CONTINUWUITY_FEDERATION_LOOPBACK` | Allow loopback federation | - |
|
||||
| `CONTINUWUITY_FEDERATION_CONN_TIMEOUT` | Connection timeout | - |
|
||||
| `CONTINUWUITY_FEDERATION_TIMEOUT` | Request timeout | - |
|
||||
| `CONTINUWUITY_FEDERATION_IDLE_TIMEOUT` | Idle timeout | - |
|
||||
| `CONTINUWUITY_FEDERATION_IDLE_PER_HOST` | Idle connections per host | - |
|
||||
| `CONTINUWUITY_TRUSTED_SERVERS` | JSON array of trusted servers | - |
|
||||
| `CONTINUWUITY_QUERY_TRUSTED_KEY_SERVERS_FIRST` | Query trusted first | - |
|
||||
| `CONTINUWUITY_ONLY_QUERY_TRUSTED_KEY_SERVERS` | Only query trusted | - |
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
# Trust matrix.org for key verification
|
||||
CONTINUWUITY_TRUSTED_SERVERS='["matrix.org"]'
|
||||
```
|
||||
|
||||
## Registration & User Configuration
|
||||
|
||||
Control user registration and account creation behaviour.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------------------ | --------------------- | ------- |
|
||||
| `CONTINUWUITY_ALLOW_REGISTRATION` | Enable registration | `true` |
|
||||
| `CONTINUWUITY_REGISTRATION_TOKEN` | Token requirement | - |
|
||||
| `CONTINUWUITY_SUSPEND_ON_REGISTER` | Suspend new accounts | - |
|
||||
| `CONTINUWUITY_NEW_USER_DISPLAYNAME_SUFFIX` | Display name suffix | 🏳️⚧️ |
|
||||
| `CONTINUWUITY_RECAPTCHA_SITE_KEY` | reCAPTCHA site key | - |
|
||||
| `CONTINUWUITY_RECAPTCHA_PRIVATE_SITE_KEY` | reCAPTCHA private key | - |
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
# Disable open registration
|
||||
CONTINUWUITY_ALLOW_REGISTRATION="false"
|
||||
|
||||
# Require a registration token
|
||||
CONTINUWUITY_REGISTRATION_TOKEN="your_secret_token_here"
|
||||
```
|
||||
|
||||
## Feature Configuration
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ---------------------------------------------------------- | -------------------------- | ------- |
|
||||
| `CONTINUWUITY_ALLOW_ENCRYPTION` | Enable E2EE | `true` |
|
||||
| `CONTINUWUITY_ALLOW_ROOM_CREATION` | Enable room creation | - |
|
||||
| `CONTINUWUITY_ALLOW_UNSTABLE_ROOM_VERSIONS` | Allow unstable versions | - |
|
||||
| `CONTINUWUITY_DEFAULT_ROOM_VERSION` | Default room version | `v11` |
|
||||
| `CONTINUWUITY_REQUIRE_AUTH_FOR_PROFILE_REQUESTS` | Auth for profiles | - |
|
||||
| `CONTINUWUITY_ALLOW_PUBLIC_ROOM_DIRECTORY_OVER_FEDERATION` | Federate directory | - |
|
||||
| `CONTINUWUITY_ALLOW_PUBLIC_ROOM_DIRECTORY_WITHOUT_AUTH` | Unauth directory | - |
|
||||
| `CONTINUWUITY_ALLOW_DEVICE_NAME_FEDERATION` | Device names in federation | - |
|
||||
|
||||
## TLS Configuration
|
||||
|
||||
Built-in TLS support is primarily for testing. **For production deployments,
|
||||
especially when federating on the internet, use a reverse proxy** (Traefik,
|
||||
Caddy, nginx) to handle TLS termination.
|
||||
|
||||
| Variable | Description |
|
||||
| --------------------------------- | ------------------------- |
|
||||
| `CONTINUWUITY_TLS__CERTS` | TLS certificate file path |
|
||||
| `CONTINUWUITY_TLS__KEY` | TLS private key path |
|
||||
| `CONTINUWUITY_TLS__DUAL_PROTOCOL` | Support TLS 1.2 + 1.3 |
|
||||
|
||||
**Example (testing only):**
|
||||
|
||||
```bash
|
||||
CONTINUWUITY_TLS__CERTS="/etc/letsencrypt/live/matrix.example.com/fullchain.pem"
|
||||
CONTINUWUITY_TLS__KEY="/etc/letsencrypt/live/matrix.example.com/privkey.pem"
|
||||
```
|
||||
|
||||
## Logging Configuration
|
||||
|
||||
Control log output format and verbosity.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------ | ------------------ | ------- |
|
||||
| `CONTINUWUITY_LOG` | Log filter level | - |
|
||||
| `CONTINUWUITY_LOG_COLORS` | ANSI colours | `true` |
|
||||
| `CONTINUWUITY_LOG_SPAN_EVENTS` | Log span events | `none` |
|
||||
| `CONTINUWUITY_LOG_THREAD_IDS` | Include thread IDs | - |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Set log level to info
|
||||
CONTINUWUITY_LOG="info"
|
||||
|
||||
# Enable debug logging for specific modules
|
||||
CONTINUWUITY_LOG="warn,continuwuity::api=debug"
|
||||
|
||||
# Disable colours for log aggregation
|
||||
CONTINUWUITY_LOG_COLORS="false"
|
||||
```
|
||||
|
||||
## Observability Configuration
|
||||
|
||||
| Variable | Description |
|
||||
| ---------------------------------------- | --------------------- |
|
||||
| `CONTINUWUITY_ALLOW_OTLP` | Enable OpenTelemetry |
|
||||
| `CONTINUWUITY_OTLP_FILTER` | OTLP filter level |
|
||||
| `CONTINUWUITY_OTLP_PROTOCOL` | Protocol (http/grpc) |
|
||||
| `CONTINUWUITY_TRACING_FLAME` | Enable flame graphs |
|
||||
| `CONTINUWUITY_TRACING_FLAME_FILTER` | Flame graph filter |
|
||||
| `CONTINUWUITY_TRACING_FLAME_OUTPUT_PATH` | Output directory |
|
||||
| `CONTINUWUITY_SENTRY` | Enable Sentry |
|
||||
| `CONTINUWUITY_SENTRY_ENDPOINT` | Sentry DSN |
|
||||
| `CONTINUWUITY_SENTRY_SEND_SERVER_NAME` | Include server name |
|
||||
| `CONTINUWUITY_SENTRY_TRACES_SAMPLE_RATE` | Sample rate (0.0-1.0) |
|
||||
|
||||
## Admin Configuration
|
||||
|
||||
Configure admin users and automated command execution.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| ------------------------------------------ | -------------------------------- | ----------------- |
|
||||
| `CONTINUWUITY_ADMINS_LIST` | JSON array of admin user IDs | - |
|
||||
| `CONTINUWUITY_ADMINS_FROM_ROOM` | Derive admins from room | - |
|
||||
| `CONTINUWUITY_ADMIN_ESCAPE_COMMANDS` | Allow `\` prefix in public rooms | - |
|
||||
| `CONTINUWUITY_ADMIN_CONSOLE_AUTOMATIC` | Auto-activate console | - |
|
||||
| `CONTINUWUITY_ADMIN_EXECUTE` | JSON array of startup commands | - |
|
||||
| `CONTINUWUITY_ADMIN_EXECUTE_ERRORS_IGNORE` | Ignore command errors | - |
|
||||
| `CONTINUWUITY_ADMIN_SIGNAL_EXECUTE` | Commands on SIGUSR2 | - |
|
||||
| `CONTINUWUITY_ADMIN_ROOM_TAG` | Admin room tag | `m.server_notice` |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Create admin user on startup
|
||||
CONTINUWUITY_ADMIN_EXECUTE='["users create-user admin", "users make-user-admin admin"]'
|
||||
|
||||
# Specify admin users directly
|
||||
CONTINUWUITY_ADMINS_LIST='["@alice:example.com", "@bob:example.com"]'
|
||||
```
|
||||
|
||||
## Media & URL Preview Configuration
|
||||
|
||||
| Variable | Description |
|
||||
| ---------------------------------------------------- | ------------------ |
|
||||
| `CONTINUWUITY_URL_PREVIEW_BOUND_INTERFACE` | Bind interface |
|
||||
| `CONTINUWUITY_URL_PREVIEW_DOMAIN_CONTAINS_ALLOWLIST` | Domain allowlist |
|
||||
| `CONTINUWUITY_URL_PREVIEW_DOMAIN_EXPLICIT_ALLOWLIST` | Explicit allowlist |
|
||||
| `CONTINUWUITY_URL_PREVIEW_DOMAIN_EXPLICIT_DENYLIST` | Explicit denylist |
|
||||
| `CONTINUWUITY_URL_PREVIEW_MAX_SPIDER_SIZE` | Max fetch size |
|
||||
| `CONTINUWUITY_URL_PREVIEW_TIMEOUT` | Fetch timeout |
|
||||
| `CONTINUWUITY_IP_RANGE_DENYLIST` | IP range denylist |
|
||||
|
||||
## Tokio Runtime Configuration
|
||||
|
||||
These can be set as environment variables or CLI arguments:
|
||||
|
||||
| Variable | Description |
|
||||
| ----------------------------------------- | -------------------------- |
|
||||
| `TOKIO_WORKER_THREADS` | Worker thread count |
|
||||
| `TOKIO_GLOBAL_QUEUE_INTERVAL` | Global queue interval |
|
||||
| `TOKIO_EVENT_INTERVAL` | Event interval |
|
||||
| `TOKIO_MAX_IO_EVENTS_PER_TICK` | Max I/O events per tick |
|
||||
| `CONTINUWUITY_RUNTIME_HISTOGRAM_INTERVAL` | Histogram bucket size (μs) |
|
||||
| `CONTINUWUITY_RUNTIME_HISTOGRAM_BUCKETS` | Bucket count |
|
||||
| `CONTINUWUITY_RUNTIME_WORKER_AFFINITY` | Enable worker affinity |
|
||||
|
||||
## See Also
|
||||
|
||||
- [Configuration Reference](./config.mdx) - Complete TOML configuration
|
||||
documentation
|
||||
- [Admin Commands](./admin/) - Admin command reference
|
||||
@@ -1,12 +1,37 @@
|
||||
# Troubleshooting Continuwuity
|
||||
|
||||
> **Docker users ⚠️**
|
||||
>
|
||||
> Docker can be difficult to use and debug. It's common for Docker
|
||||
> misconfigurations to cause issues, particularly with networking and permissions.
|
||||
> Please check that your issues are not due to problems with your Docker setup.
|
||||
:::warning{title="Docker users:"}
|
||||
Docker can be difficult to use and debug. It's common for Docker
|
||||
misconfigurations to cause issues, particularly with networking and permissions.
|
||||
Please check that your issues are not due to problems with your Docker setup.
|
||||
:::
|
||||
|
||||
## Continuwuity and Matrix issues
|
||||
## Continuwuity issues
|
||||
|
||||
### Slow joins to rooms
|
||||
|
||||
Some slowness is to be expected if you're the first person on your homserver to join a room (which will
|
||||
always be the case for single-user homeservers). In this situation, your homeserver has to verify the signatures of
|
||||
all of the state events sent by other servers before your join. To make this process as fast as possible, make sure you have
|
||||
multiple fast, trusted servers listed in `trusted_servers` in your configuration, and ensure
|
||||
`query_trusted_key_servers_first_on_join` is set to true (the default).
|
||||
If you need suggestions for trusted servers, ask in the Continuwuity main room.
|
||||
|
||||
However, _very_ slow joins, especially to rooms with only a few users in them or rooms created by another user
|
||||
on your homeserver, may be caused by [issue !779](https://forgejo.ellis.link/continuwuation/continuwuity/issues/779),
|
||||
which is a longstanding bug with synchronizing room joins to clients. In this situation, you did succeed in joining the room, but
|
||||
the bug caused your homeserver to forget to tell your client. **To fix this, clear your client's cache.** Both Element and Cinny
|
||||
have a button to clear their cache in the "About" section of their settings.
|
||||
|
||||
### Configuration not working as expected
|
||||
|
||||
Sometimes you can make a mistake in your configuration that
|
||||
means things don't get passed to Continuwuity correctly.
|
||||
This is particularly easy to do with environment variables.
|
||||
To check what configuration Continuwuity actually sees, you can
|
||||
use the `!admin server show-config` command in your admin room.
|
||||
Beware that this prints out any secrets in your configuration,
|
||||
so you might want to delete the result afterwards!
|
||||
|
||||
### Lost access to admin room
|
||||
|
||||
@@ -18,17 +43,7 @@ ### Lost access to admin room
|
||||
- Or specify the `emergency_password` config option to allow you to temporarily
|
||||
log into the server account (`@conduit`) from a web client
|
||||
|
||||
## General potential issues
|
||||
|
||||
### Configuration not working as expected
|
||||
|
||||
Sometimes you can make a mistake in your configuration that
|
||||
means things don't get passed to Continuwuity correctly.
|
||||
This is particularly easy to do with environment variables.
|
||||
To check what configuration Continuwuity actually sees, you can
|
||||
use the `!admin server show-config` command in your admin room.
|
||||
Beware that this prints out any secrets in your configuration,
|
||||
so you might want to delete the result afterwards!
|
||||
## DNS issues
|
||||
|
||||
### Potential DNS issues when using Docker
|
||||
|
||||
|
||||
54
flake.lock
generated
54
flake.lock
generated
@@ -3,11 +3,11 @@
|
||||
"advisory-db": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1766324728,
|
||||
"narHash": "sha256-9C+WyE5U3y5w4WQXxmb0ylRyMMsPyzxielWXSHrcDpE=",
|
||||
"lastModified": 1773786698,
|
||||
"narHash": "sha256-o/J7ZculgwSs1L4H4UFlFZENOXTJzq1X0n71x6oNNvY=",
|
||||
"owner": "rustsec",
|
||||
"repo": "advisory-db",
|
||||
"rev": "c88b88c62bda077be8aa621d4e89d8701e39cb5d",
|
||||
"rev": "99e9de91bb8b61f06ef234ff84e11f758ecd5384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -18,11 +18,11 @@
|
||||
},
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1766194365,
|
||||
"narHash": "sha256-4AFsUZ0kl6MXSm4BaQgItD0VGlEKR3iq7gIaL7TjBvc=",
|
||||
"lastModified": 1773189535,
|
||||
"narHash": "sha256-E1G/Or6MWeP+L6mpQ0iTFLpzSzlpGrITfU2220Gq47g=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "7d8ec2c71771937ab99790b45e6d9b93d15d9379",
|
||||
"rev": "6fa2fb4cf4a89ba49fc9dd5a3eb6cde99d388269",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -39,11 +39,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766299592,
|
||||
"narHash": "sha256-7u+q5hexu2eAxL2VjhskHvaUKg+GexmelIR2ve9Nbb4=",
|
||||
"lastModified": 1773732206,
|
||||
"narHash": "sha256-HKibxaUXyWd4Hs+ZUnwo6XslvaFqFqJh66uL9tphU4Q=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "381579dee168d5ced412e2990e9637ecc7cf1c5d",
|
||||
"rev": "0aa13c1b54063a8d8679b28a5cd357ba98f4a56b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -55,11 +55,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1765121682,
|
||||
"narHash": "sha256-4VBOP18BFeiPkyhy9o4ssBNQEvfvv1kXkasAYd0+rrA=",
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -74,11 +74,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1765835352,
|
||||
"narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
|
||||
"lastModified": 1772408722,
|
||||
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "a34fae9c08a15ad73f295041fec82323541400a9",
|
||||
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -89,11 +89,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1766070988,
|
||||
"narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=",
|
||||
"lastModified": 1773734432,
|
||||
"narHash": "sha256-IF5ppUWh6gHGHYDbtVUyhwy/i7D261P7fWD1bPefOsw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c6245e83d836d0433170a16eb185cefe0572f8b8",
|
||||
"rev": "cda48547b432e8d3b18b4180ba07473762ec8558",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -105,11 +105,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1765674936,
|
||||
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
|
||||
"lastModified": 1772328832,
|
||||
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
|
||||
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -132,11 +132,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1766253897,
|
||||
"narHash": "sha256-ChK07B1aOlJ4QzWXpJo+y8IGAxp1V9yQ2YloJ+RgHRw=",
|
||||
"lastModified": 1773697963,
|
||||
"narHash": "sha256-xdKI77It9PM6eNrCcDZsnP4SKulZwk8VkDgBRVMnCb8=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "765b7bdb432b3740f2d564afccfae831d5a972e4",
|
||||
"rev": "2993637174252ff60a582fd1f55b9ab52c39db6d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -153,11 +153,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1766000401,
|
||||
"narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=",
|
||||
"lastModified": 1773297127,
|
||||
"narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd",
|
||||
"rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
rocksdbAllFeatures = self'.packages.rocksdb.override {
|
||||
enableJemalloc = true;
|
||||
enableLiburing = true;
|
||||
};
|
||||
|
||||
commonAttrs = (uwulib.build.commonAttrs { }) // {
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
commonAttrsArgs.profile = "release";
|
||||
rocksdb = self'.packages.rocksdb.override {
|
||||
enableJemalloc = true;
|
||||
enableLiburing = true;
|
||||
};
|
||||
features = {
|
||||
enabledFeatures = "all";
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
rust-jemalloc-sys-unprefixed,
|
||||
|
||||
enableJemalloc ? false,
|
||||
enableLiburing ? false,
|
||||
|
||||
fetchFromGitea,
|
||||
|
||||
@@ -32,7 +31,7 @@ in
|
||||
|
||||
# for some reason enableLiburing in nixpkgs rocksdb is default true
|
||||
# which breaks Darwin entirely
|
||||
enableLiburing = enableLiburing && notDarwin;
|
||||
enableLiburing = notDarwin;
|
||||
}).overrideAttrs
|
||||
(old: {
|
||||
src = fetchFromGitea {
|
||||
@@ -74,7 +73,7 @@ in
|
||||
"USE_RTTI"
|
||||
]);
|
||||
|
||||
enableLiburing = enableLiburing && notDarwin;
|
||||
enableLiburing = notDarwin;
|
||||
|
||||
# outputs has "tools" which we don't need or use
|
||||
outputs = [ "out" ];
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
file = inputs.self + "/rust-toolchain.toml";
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-SJwZ8g0zF2WrKDVmHrVG3pD2RGoQeo24MEXnNx5FyuI=";
|
||||
sha256 = "sha256-sqSWJDUxc+zaz1nBWMAJKTAGBuGWP25GCftIOlCEAtA=";
|
||||
};
|
||||
in
|
||||
{
|
||||
|
||||
@@ -77,7 +77,12 @@ rec {
|
||||
craneLib.buildDepsOnly (
|
||||
(commonAttrs commonAttrsArgs)
|
||||
// {
|
||||
env = uwuenv.buildDepsOnlyEnv // (makeRocksDBEnv { inherit rocksdb; });
|
||||
env = uwuenv.buildDepsOnlyEnv
|
||||
// (makeRocksDBEnv { inherit rocksdb; })
|
||||
// {
|
||||
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
|
||||
RUSTFLAGS = "--cfg reqwest_unstable";
|
||||
};
|
||||
inherit (features) cargoExtraArgs;
|
||||
}
|
||||
|
||||
@@ -102,7 +107,13 @@ rec {
|
||||
'';
|
||||
cargoArtifacts = deps;
|
||||
doCheck = true;
|
||||
env = uwuenv.buildPackageEnv // rocksdbEnv;
|
||||
env =
|
||||
uwuenv.buildPackageEnv
|
||||
// rocksdbEnv
|
||||
// {
|
||||
# required since we started using unstable reqwest apparently ... otherwise the all-features build will fail
|
||||
RUSTFLAGS = "--cfg reqwest_unstable";
|
||||
};
|
||||
passthru.env = uwuenv.buildPackageEnv // rocksdbEnv;
|
||||
meta.mainProgram = crateInfo.pname;
|
||||
inherit (features) cargoExtraArgs;
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
uwulib = inputs.self.uwulib.init pkgs;
|
||||
rocksdbAllFeatures = self'.packages.rocksdb.override {
|
||||
enableJemalloc = true;
|
||||
enableLiburing = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
# basic nix shell containing all things necessary to build continuwuity in all flavors manually (on x86_64-linux)
|
||||
devShells.default = uwulib.build.craneLib.devShell {
|
||||
packages = [
|
||||
pkgs.nodejs
|
||||
pkgs.pkg-config
|
||||
pkgs.liburing
|
||||
pkgs.rust-jemalloc-sys-unprefixed
|
||||
|
||||
656
package-lock.json
generated
656
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,6 @@
|
||||
"@rspress/core": "^2.0.0",
|
||||
"@rspress/plugin-client-redirects": "^2.0.0",
|
||||
"@rspress/plugin-sitemap": "^2.0.0",
|
||||
"typescript": "^5.9.3"
|
||||
"typescript": "^6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ Environment="CONTINUWUITY_DATABASE_PATH=%S/conduwuit"
|
||||
Environment="CONTINUWUITY_CONFIG_RELOAD_SIGNAL=true"
|
||||
|
||||
LoadCredential=conduwuit.toml:/etc/conduwuit/conduwuit.toml
|
||||
RefreshOnReload=yes
|
||||
|
||||
ExecStart=/usr/bin/conduwuit --config ${CREDENTIALS_DIRECTORY}/conduwuit.toml
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended", "replacements:all"],
|
||||
"extends": ["config:recommended", "replacements:all", ":semanticCommitTypeAll(chore)", "helpers:pinGitHubActionDigests"],
|
||||
"dependencyDashboard": true,
|
||||
"osvVulnerabilityAlerts": true,
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
@@ -35,10 +36,18 @@
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"description": "Batch patch-level Rust dependency updates",
|
||||
"description": "Batch minor and patch Rust dependency updates",
|
||||
"matchManagers": ["cargo"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"matchCurrentVersion": ">=1.0.0",
|
||||
"groupName": "rust-non-major"
|
||||
},
|
||||
{
|
||||
"description": "Batch patch-level zerover Rust dependency updates",
|
||||
"matchManagers": ["cargo"],
|
||||
"matchUpdateTypes": ["patch"],
|
||||
"groupName": "rust-patch-updates"
|
||||
"matchCurrentVersion": ">=0.1.0,<1.0.0",
|
||||
"groupName": "rust-zerover-patch-updates"
|
||||
},
|
||||
{
|
||||
"description": "Limit concurrent Cargo PRs",
|
||||
@@ -57,12 +66,25 @@
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"groupName": "github-actions-non-major"
|
||||
},
|
||||
{
|
||||
"description": "Batch patch-level Node.js dependency updates",
|
||||
"matchManagers": ["npm"],
|
||||
"matchUpdateTypes": ["patch"],
|
||||
"groupName": "node-patch-updates"
|
||||
},
|
||||
{
|
||||
"description": "Pin forgejo artifact actions to prevent breaking changes",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchPackageNames": ["forgejo/upload-artifact", "forgejo/download-artifact"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"description": "Auto-merge crate-ci/typos minor updates",
|
||||
"matchPackageNames": ["crate-ci/typos"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"automerge": true,
|
||||
"automergeStrategy": "fast-forward"
|
||||
},
|
||||
{
|
||||
"description": "Auto-merge renovatebot docker image updates",
|
||||
"matchDatasources": ["docker"],
|
||||
@@ -73,16 +95,16 @@
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Update _VERSION variables in Dockerfiles",
|
||||
"managerFilePatterns": [
|
||||
"/(^|/)([Dd]ocker|[Cc]ontainer)file[^/]*$/",
|
||||
"/(^|/|\\.)([Dd]ocker|[Cc]ontainer)file$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"# renovate: datasource=(?<datasource>[a-zA-Z0-9-._]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?(?: registryUrl=(?<registryUrl>[^\\s]+?))?\\s+(?:ENV\\s+|ARG\\s+)?[A-Za-z0-9_]+?_VERSION[ =][\"']?(?<currentValue>.+?)[\"']?\\s+(?:(?:ENV\\s+|ARG\\s+)?[A-Za-z0-9_]+?_CHECKSUM[ =][\"']?(?<currentDigest>.+?)[\"']?\\s)?"
|
||||
]
|
||||
}
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Update _VERSION variables in Dockerfiles",
|
||||
"managerFilePatterns": [
|
||||
"/(^|/)([Dd]ocker|[Cc]ontainer)file[^/]*$/",
|
||||
"/(^|/|\\.)([Dd]ocker|[Cc]ontainer)file$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"# renovate: datasource=(?<datasource>[a-zA-Z0-9-._]+?) depName=(?<depName>[^\\s]+?)(?: (lookupName|packageName)=(?<packageName>[^\\s]+?))?(?: versioning=(?<versioning>[^\\s]+?))?(?: extractVersion=(?<extractVersion>[^\\s]+?))?(?: registryUrl=(?<registryUrl>[^\\s]+?))?\\s+(?:ENV\\s+|ARG\\s+)?[A-Za-z0-9_]+?_VERSION[ =][\"']?(?<currentValue>.+?)[\"']?\\s+(?:(?:ENV\\s+|ARG\\s+)?[A-Za-z0-9_]+?_CHECKSUM[ =][\"']?(?<currentDigest>.+?)[\"']?\\s)?"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
[toolchain]
|
||||
profile = "minimal"
|
||||
channel = "1.90.0"
|
||||
channel = "1.92.0"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
"rust-src",
|
||||
|
||||
@@ -80,6 +80,7 @@ conduwuit-macros.workspace = true
|
||||
conduwuit-service.workspace = true
|
||||
const-str.workspace = true
|
||||
futures.workspace = true
|
||||
lettre.workspace = true
|
||||
log.workspace = true
|
||||
ruma.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
warn,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use lettre::message::Mailbox;
|
||||
use ruma::{
|
||||
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
|
||||
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
|
||||
@@ -876,3 +877,31 @@ pub(super) async fn trim_memory(&self) -> Result {
|
||||
|
||||
writeln!(self, "done").await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn send_test_email(&self) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let mailer = self.services.mailer.expect_mailer()?;
|
||||
let Some(sender) = self.sender else {
|
||||
return Err!("No sender user provided in context");
|
||||
};
|
||||
|
||||
let Some(email) = self
|
||||
.services
|
||||
.threepid
|
||||
.get_email_for_localpart(sender.localpart())
|
||||
.await
|
||||
else {
|
||||
return Err!("{} has no associated email address", sender);
|
||||
};
|
||||
|
||||
mailer
|
||||
.send(Mailbox::new(None, email.clone()), service::mailer::messages::Test)
|
||||
.await?;
|
||||
|
||||
self.write_str(&format!("Test email successfully sent to {email}"))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -225,6 +225,9 @@ pub enum DebugCommand {
|
||||
level: Option<i32>,
|
||||
},
|
||||
|
||||
/// Send a test email to the invoking admin's email address
|
||||
SendTestEmail,
|
||||
|
||||
/// Developer test stubs
|
||||
#[command(subcommand)]
|
||||
#[allow(non_snake_case)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use conduwuit::{Err, Result};
|
||||
use conduwuit::{Err, Result, utils::response::LimitReadExt};
|
||||
use futures::StreamExt;
|
||||
use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
|
||||
|
||||
@@ -30,12 +30,15 @@ pub(super) async fn incoming_federation(&self) -> Result {
|
||||
.federation_handletime
|
||||
.read();
|
||||
|
||||
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
|
||||
let mut msg = format!(
|
||||
"Handling {} incoming PDUs across {} active transactions:\n",
|
||||
map.len(),
|
||||
self.services.transactions.txn_active_handle_count()
|
||||
);
|
||||
for (r, (e, i)) in map.iter() {
|
||||
let elapsed = i.elapsed();
|
||||
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
|
||||
}
|
||||
|
||||
msg
|
||||
};
|
||||
|
||||
@@ -52,7 +55,15 @@ pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let text = response.text().await?;
|
||||
let text = response
|
||||
.limit_read_text(
|
||||
self.services
|
||||
.config
|
||||
.max_request_size
|
||||
.try_into()
|
||||
.expect("u64 fits into usize"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if text.is_empty() {
|
||||
return Err!("Response text/body is empty.");
|
||||
|
||||
@@ -29,7 +29,9 @@ pub(super) async fn delete(
|
||||
.delete(&mxc.as_str().try_into()?)
|
||||
.await?;
|
||||
|
||||
return Err!("Deleted the MXC from our database and on our filesystem.",);
|
||||
return self
|
||||
.write_str("Deleted the MXC from our database and on our filesystem.")
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(event_id) = event_id {
|
||||
@@ -388,3 +390,19 @@ pub(super) async fn get_remote_thumbnail(
|
||||
self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```"))
|
||||
.await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_url_preview(&self, url: Option<String>, all: bool) -> Result {
|
||||
if all {
|
||||
self.services.media.clear_url_previews().await;
|
||||
|
||||
return self.write_str("Deleted all cached URL previews.").await;
|
||||
}
|
||||
|
||||
let url = url.expect("clap enforces url is required unless --all");
|
||||
|
||||
self.services.media.remove_url_preview(&url).await?;
|
||||
|
||||
self.write_str(&format!("Deleted cached URL preview for: {url}"))
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ pub enum MediaCommand {
|
||||
/// * Delete all remote and local media from 3 days ago, up until now:
|
||||
///
|
||||
/// `!admin media delete-past-remote-media -a 3d
|
||||
///-yes-i-want-to-delete-local-media`
|
||||
///--yes-i-want-to-delete-local-media`
|
||||
#[command(verbatim_doc_comment)]
|
||||
DeletePastRemoteMedia {
|
||||
/// The relative time (e.g. 30s, 5m, 7d) from now within which to
|
||||
@@ -108,4 +108,16 @@ pub enum MediaCommand {
|
||||
#[arg(long, default_value("800"))]
|
||||
height: u32,
|
||||
},
|
||||
|
||||
/// Deletes a cached URL preview, forcing it to be re-fetched.
|
||||
/// Use --all to purge all cached URL previews.
|
||||
DeleteUrlPreview {
|
||||
/// The URL to clear from the saved preview data
|
||||
#[arg(required_unless_present = "all")]
|
||||
url: Option<String>,
|
||||
|
||||
/// Purge all cached URL previews
|
||||
#[arg(long, conflicts_with = "url")]
|
||||
all: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ pub(super) async fn compact(
|
||||
let parallelism = parallelism.unwrap_or(1);
|
||||
let results = maps
|
||||
.into_iter()
|
||||
.try_stream()
|
||||
.try_stream::<conduwuit::Error>()
|
||||
.paralleln_and_then(runtime, parallelism, move |map| {
|
||||
map.compact_blocking(options.clone())?;
|
||||
Ok(map.name().to_owned())
|
||||
|
||||
@@ -20,7 +20,17 @@ pub enum ResolverCommand {
|
||||
name: Option<String>,
|
||||
},
|
||||
|
||||
/// Flush a specific server from the resolver caches or everything
|
||||
/// Flush a given server from the resolver caches or flush them completely
|
||||
///
|
||||
/// * Examples:
|
||||
/// * Flush a specific server:
|
||||
///
|
||||
/// `!admin query resolver flush-cache matrix.example.com`
|
||||
///
|
||||
/// * Flush all resolver caches completely:
|
||||
///
|
||||
/// `!admin query resolver flush-cache --all`
|
||||
#[command(verbatim_doc_comment)]
|
||||
FlushCache {
|
||||
name: Option<OwnedServerName>,
|
||||
|
||||
|
||||
@@ -89,13 +89,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
locally, if not using get_alias_helper to fetch room ID remotely"
|
||||
);
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
{
|
||||
match self.services.rooms.alias.resolve_alias(room_alias).await {
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
%room_id,
|
||||
@@ -235,7 +229,7 @@ async fn ban_list_of_rooms(&self) -> Result {
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.resolve_alias(room_alias)
|
||||
.await
|
||||
{
|
||||
| Ok((room_id, servers)) => {
|
||||
@@ -388,13 +382,7 @@ async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
|
||||
room ID over federation"
|
||||
);
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
{
|
||||
match self.services.rooms.alias.resolve_alias(room_alias).await {
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
%room_id,
|
||||
|
||||
@@ -86,7 +86,7 @@ pub(super) async fn list_backups(&self) -> Result {
|
||||
.db
|
||||
.backup_list()?
|
||||
.try_stream()
|
||||
.try_for_each(|result| write!(self, "{result}"))
|
||||
.try_for_each(|result| writeln!(self, "{result}"))
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
warn,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use lettre::Address;
|
||||
use ruma::{
|
||||
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName, OwnedUserId, UserId,
|
||||
events::{
|
||||
@@ -296,6 +297,31 @@ pub(super) async fn reset_password(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn issue_password_reset_link(&self, username: String) -> Result {
|
||||
use conduwuit_service::password_reset::{PASSWORD_RESET_PATH, RESET_TOKEN_QUERY_PARAM};
|
||||
|
||||
self.bail_restricted()?;
|
||||
|
||||
let mut reset_url = self
|
||||
.services
|
||||
.config
|
||||
.get_client_domain()
|
||||
.join(PASSWORD_RESET_PATH)
|
||||
.unwrap();
|
||||
|
||||
let user_id = parse_local_user_id(self.services, &username)?;
|
||||
let token = self.services.password_reset.issue_token(user_id).await?;
|
||||
reset_url
|
||||
.query_pairs_mut()
|
||||
.append_pair(RESET_TOKEN_QUERY_PARAM, &token.token);
|
||||
|
||||
self.write_str(&format!("Password reset link issued for {username}: {reset_url}"))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) -> Result {
|
||||
if self.body.len() < 2
|
||||
@@ -1069,3 +1095,106 @@ pub(super) async fn enable_login(&self, user_id: String) -> Result {
|
||||
|
||||
self.write_str(&format!("{user_id} can now log in.")).await
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_email(&self, user_id: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
|
||||
match self
|
||||
.services
|
||||
.threepid
|
||||
.get_email_for_localpart(user_id.localpart())
|
||||
.await
|
||||
{
|
||||
| Some(email) =>
|
||||
self.write_str(&format!("{user_id} has the associated email address {email}."))
|
||||
.await,
|
||||
| None =>
|
||||
self.write_str(&format!("{user_id} has no associated email address."))
|
||||
.await,
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_user_by_email(&self, email: String) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let Ok(email) = Address::try_from(email) else {
|
||||
return Err!("Invalid email address.");
|
||||
};
|
||||
|
||||
match self.services.threepid.get_localpart_for_email(&email).await {
|
||||
| Some(localpart) => {
|
||||
let user_id = OwnedUserId::parse(format!(
|
||||
"@{localpart}:{}",
|
||||
self.services.globals.server_name()
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
self.write_str(&format!("{email} belongs to {user_id}."))
|
||||
.await
|
||||
},
|
||||
| None =>
|
||||
self.write_str(&format!("No user has {email} as their email address."))
|
||||
.await,
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn change_email(&self, user_id: String, email: Option<String>) -> Result {
|
||||
self.bail_restricted()?;
|
||||
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let Ok(new_email) = email.map(Address::try_from).transpose() else {
|
||||
return Err!("Invalid email address.");
|
||||
};
|
||||
|
||||
if self.services.mailer.mailer().is_none() {
|
||||
warn!("SMTP has not been configured on this server, emails cannot be sent.");
|
||||
}
|
||||
|
||||
let current_email = self
|
||||
.services
|
||||
.threepid
|
||||
.get_email_for_localpart(user_id.localpart())
|
||||
.await;
|
||||
|
||||
match (current_email, new_email) {
|
||||
| (None, None) =>
|
||||
self.write_str(&format!(
|
||||
"{user_id} already had no associated email. No changes have been made."
|
||||
))
|
||||
.await,
|
||||
| (current_email, Some(new_email)) => {
|
||||
self.services
|
||||
.threepid
|
||||
.associate_localpart_email(user_id.localpart(), &new_email)
|
||||
.await?;
|
||||
|
||||
if let Some(current_email) = current_email {
|
||||
self.write_str(&format!(
|
||||
"The associated email of {user_id} has been changed from {current_email} to \
|
||||
{new_email}."
|
||||
))
|
||||
.await
|
||||
} else {
|
||||
self.write_str(&format!(
|
||||
"{user_id} has been associated with the email {new_email}."
|
||||
))
|
||||
.await
|
||||
}
|
||||
},
|
||||
| (Some(current_email), None) => {
|
||||
self.services
|
||||
.threepid
|
||||
.disassociate_localpart_email(user_id.localpart())
|
||||
.await;
|
||||
|
||||
self.write_str(&format!(
|
||||
"The associated email of {user_id} has been removed (it was {current_email})."
|
||||
))
|
||||
.await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,30 @@ pub enum UserCommand {
|
||||
password: Option<String>,
|
||||
},
|
||||
|
||||
/// Issue a self-service password reset link for a user.
|
||||
IssuePasswordResetLink {
|
||||
/// Username of the user who may use the link
|
||||
username: String,
|
||||
},
|
||||
|
||||
/// Get a user's associated email address.
|
||||
GetEmail {
|
||||
user_id: String,
|
||||
},
|
||||
|
||||
/// Get the user with the given email address.
|
||||
GetUserByEmail {
|
||||
email: String,
|
||||
},
|
||||
|
||||
/// Update or remove a user's email address.
|
||||
///
|
||||
/// If `email` is not supplied, the user's existing address will be removed.
|
||||
ChangeEmail {
|
||||
user_id: String,
|
||||
email: Option<String>,
|
||||
},
|
||||
|
||||
/// Deactivate a user
|
||||
///
|
||||
/// User will be removed from all rooms by default.
|
||||
|
||||
@@ -85,6 +85,7 @@ http-body-util.workspace = true
|
||||
hyper.workspace = true
|
||||
ipaddress.workspace = true
|
||||
itertools.workspace = true
|
||||
lettre.workspace = true
|
||||
log.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest.workspace = true
|
||||
|
||||
@@ -1,973 +0,0 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Error, Event, Result, debug_info, err, error, info,
|
||||
matrix::pdu::PduBuilder,
|
||||
utils::{self, ReadyExt, stream::BroadbandExt},
|
||||
warn,
|
||||
};
|
||||
use conduwuit_service::Services;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use register::RegistrationKind;
|
||||
use ruma::{
|
||||
OwnedRoomId, UserId,
|
||||
api::client::{
|
||||
account::{
|
||||
ThirdPartyIdRemovalStatus, change_password, check_registration_token_validity,
|
||||
deactivate, get_3pids, get_username_availability,
|
||||
register::{self, LoginType},
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
whoami,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
events::{
|
||||
GlobalAccountDataEventType, StateEventType,
|
||||
room::{
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
message::RoomMessageEventContent,
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
},
|
||||
push,
|
||||
};
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
||||
use crate::Ruma;
|
||||
|
||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
|
||||
/// # `GET /_matrix/client/v3/register/available`
|
||||
///
|
||||
/// Checks if a username is valid and available on this server.
|
||||
///
|
||||
/// Conditions for returning true:
|
||||
/// - The user id is not historical
|
||||
/// - The server name of the user id matches this server
|
||||
/// - No user or appservice on this server already claimed this username
|
||||
///
|
||||
/// Note: This will not reserve the username, so the username might become
|
||||
/// invalid when trying to register
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available", level = "info")]
|
||||
pub(crate) async fn get_register_available_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_username_availability::v3::Request>,
|
||||
) -> Result<get_username_availability::v3::Response> {
|
||||
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||
let is_matrix_appservice_irc = body.appservice_info.as_ref().is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice.registration.id.contains("matrix-appservice-irc")
|
||||
|| appservice.registration.id.contains("matrix_appservice_irc")
|
||||
});
|
||||
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.is_match(&body.username)
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
}
|
||||
|
||||
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||
let body_username = if is_matrix_appservice_irc {
|
||||
body.username.clone()
|
||||
} else {
|
||||
body.username.to_lowercase()
|
||||
};
|
||||
|
||||
// Validate user id
|
||||
let user_id =
|
||||
match UserId::parse_with_server_name(&body_username, services.globals.server_name()) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
// unless the username is from the broken matrix appservice IRC bridge, we
|
||||
// should follow synapse's behaviour on not allowing things like spaces
|
||||
// and UTF-8 characters in usernames
|
||||
if !is_matrix_appservice_irc {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} contains disallowed characters or spaces: \
|
||||
{e}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} is not valid: {e}"
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
// Check if username is creative enough
|
||||
if services.users.exists(&user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
if let Some(ref info) = body.appservice_info {
|
||||
if !info.is_user_match(&user_id) {
|
||||
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
|
||||
}
|
||||
}
|
||||
|
||||
if services.appservice.is_exclusive_user_id(&user_id).await {
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
Ok(get_username_availability::v3::Response { available: true })
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
///
|
||||
/// Register an account on this homeserver.
|
||||
///
|
||||
/// You can use [`GET
|
||||
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
|
||||
/// html) to check if the user id is valid and available.
|
||||
///
|
||||
/// - Only works if registration is enabled
|
||||
/// - If type is guest: ignores all parameters except
|
||||
/// initial_device_display_name
|
||||
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
|
||||
/// - If type is not guest and no username is given: Always fails after UIAA
|
||||
/// check
|
||||
/// - Creates a new account and populates it with default account data
|
||||
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
||||
/// access_token
|
||||
#[allow(clippy::doc_markdown)]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
|
||||
pub(crate) async fn register_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<register::v3::Request>,
|
||||
) -> Result<register::v3::Response> {
|
||||
let is_guest = body.kind == RegistrationKind::Guest;
|
||||
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
||||
|
||||
// Allow registration if it's enabled in the config file or if this is the first
|
||||
// run (so the first user account can be created)
|
||||
let allow_registration =
|
||||
services.config.allow_registration || services.firstrun.is_first_run();
|
||||
|
||||
if !allow_registration && body.appservice_info.is_none() {
|
||||
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
|
||||
| (Some(username), Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (Some(username), _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (_, Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (None, _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden(
|
||||
"This server is not accepting registrations at this time."
|
||||
)));
|
||||
}
|
||||
|
||||
if is_guest && !services.config.allow_guest_registration {
|
||||
info!(
|
||||
"Guest registration disabled, rejecting guest registration attempt, initial device \
|
||||
name: \"{}\"",
|
||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
||||
);
|
||||
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
||||
}
|
||||
|
||||
// forbid guests from registering if there is not a real admin user yet. give
|
||||
// generic user error.
|
||||
if is_guest && services.users.count().await < 2 {
|
||||
warn!(
|
||||
"Guest account attempted to register before a real admin user has been registered, \
|
||||
rejecting registration. Guest's initial device name: \"{}\"",
|
||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
||||
);
|
||||
return Err!(Request(Forbidden(
|
||||
"This server is not accepting registrations at this time."
|
||||
)));
|
||||
}
|
||||
|
||||
let user_id = match (body.username.as_ref(), is_guest) {
|
||||
| (Some(username), false) => {
|
||||
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||
let is_matrix_appservice_irc =
|
||||
body.appservice_info.as_ref().is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice.registration.id.contains("matrix-appservice-irc")
|
||||
|| appservice.registration.id.contains("matrix_appservice_irc")
|
||||
});
|
||||
|
||||
if services.globals.forbidden_usernames().is_match(username)
|
||||
&& !emergency_mode_enabled
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
}
|
||||
|
||||
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||
let body_username = if is_matrix_appservice_irc {
|
||||
username.clone()
|
||||
} else {
|
||||
username.to_lowercase()
|
||||
};
|
||||
|
||||
let proposed_user_id = match UserId::parse_with_server_name(
|
||||
&body_username,
|
||||
services.globals.server_name(),
|
||||
) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
// unless the username is from the broken matrix appservice IRC bridge, or
|
||||
// we are in emergency mode, we should follow synapse's behaviour on
|
||||
// not allowing things like spaces and UTF-8 characters in usernames
|
||||
if !is_matrix_appservice_irc && !emergency_mode_enabled {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} contains disallowed characters or \
|
||||
spaces: {e}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {body_username} is not valid: {e}"
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
if services.users.exists(&proposed_user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
proposed_user_id
|
||||
},
|
||||
| _ => loop {
|
||||
let proposed_user_id = UserId::parse_with_server_name(
|
||||
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
||||
services.globals.server_name(),
|
||||
)
|
||||
.unwrap();
|
||||
if !services.users.exists(&proposed_user_id).await {
|
||||
break proposed_user_id;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if body.body.login_type == Some(LoginType::ApplicationService) {
|
||||
match body.appservice_info {
|
||||
| Some(ref info) =>
|
||||
if !info.is_user_match(&user_id) && !emergency_mode_enabled {
|
||||
return Err!(Request(Exclusive(
|
||||
"Username is not in an appservice namespace."
|
||||
)));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(MissingToken("Missing appservice token.")));
|
||||
},
|
||||
}
|
||||
} else if services.appservice.is_exclusive_user_id(&user_id).await && !emergency_mode_enabled
|
||||
{
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: Vec::new(),
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
let skip_auth = body.appservice_info.is_some() || is_guest;
|
||||
|
||||
// Populate required UIAA flows
|
||||
|
||||
if services.firstrun.is_first_run() {
|
||||
// Registration token forced while in first-run mode
|
||||
uiaainfo.flows.push(AuthFlow {
|
||||
stages: vec![AuthType::RegistrationToken],
|
||||
});
|
||||
} else {
|
||||
if services
|
||||
.registration_tokens
|
||||
.iterate_tokens()
|
||||
.next()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
// Registration token required
|
||||
uiaainfo.flows.push(AuthFlow {
|
||||
stages: vec![AuthType::RegistrationToken],
|
||||
});
|
||||
}
|
||||
|
||||
if services.config.recaptcha_private_site_key.is_some() {
|
||||
if let Some(pubkey) = &services.config.recaptcha_site_key {
|
||||
// ReCaptcha required
|
||||
uiaainfo
|
||||
.flows
|
||||
.push(AuthFlow { stages: vec![AuthType::ReCaptcha] });
|
||||
uiaainfo.params = serde_json::value::to_raw_value(&serde_json::json!({
|
||||
"m.login.recaptcha": {
|
||||
"public_key": pubkey,
|
||||
},
|
||||
}))
|
||||
.expect("Failed to serialize recaptcha params");
|
||||
}
|
||||
}
|
||||
|
||||
if uiaainfo.flows.is_empty() && !skip_auth {
|
||||
// Registration isn't _disabled_, but there's no captcha configured and no
|
||||
// registration tokens currently set. Bail out by default unless open
|
||||
// registration was explicitly enabled.
|
||||
if !services
|
||||
.config
|
||||
.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"This server is not accepting registrations at this time."
|
||||
)));
|
||||
}
|
||||
|
||||
// We have open registration enabled (😧), provide a dummy stage
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if !skip_auth {
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(
|
||||
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||
.unwrap(),
|
||||
"".into(),
|
||||
auth,
|
||||
&uiaainfo,
|
||||
)
|
||||
.await?;
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services.uiaa.create(
|
||||
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||
.unwrap(),
|
||||
"".into(),
|
||||
&uiaainfo,
|
||||
json,
|
||||
);
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let password = if is_guest { None } else { body.password.as_deref() };
|
||||
|
||||
// Create user
|
||||
services.users.create(&user_id, password, None).await?;
|
||||
|
||||
// Default to pretty displayname
|
||||
let mut displayname = user_id.localpart().to_owned();
|
||||
|
||||
// If `new_user_displayname_suffix` is set, registration will push whatever
|
||||
// content is set to the user's display name with a space before it
|
||||
if !services.globals.new_user_displayname_suffix().is_empty()
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(&user_id, Some(displayname.clone()));
|
||||
|
||||
// Initial account data
|
||||
services
|
||||
.account_data
|
||||
.update(
|
||||
None,
|
||||
&user_id,
|
||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
|
||||
content: ruma::events::push_rules::PushRulesEventContent {
|
||||
global: push::Ruleset::server_default(&user_id),
|
||||
},
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let no_device = body.inhibit_login
|
||||
|| body
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|aps| aps.registration.device_management);
|
||||
let (token, device) = if !no_device {
|
||||
// Don't create a device for inhibited logins
|
||||
let device_id = if is_guest { None } else { body.device_id.clone() }
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate new token for the device
|
||||
let new_token = utils::random_string(TOKEN_LENGTH);
|
||||
|
||||
// Create device for this account
|
||||
services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&new_token,
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
(Some(new_token), Some(device_id))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
|
||||
|
||||
// log in conduit admin channel if a non-guest user registered
|
||||
if body.appservice_info.is_none() && !is_guest {
|
||||
if !device_display_name.is_empty() {
|
||||
let notice = format!(
|
||||
"New user \"{user_id}\" registered on this server from IP {client} and device \
|
||||
display name \"{device_display_name}\""
|
||||
);
|
||||
|
||||
info!("{notice}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
} else {
|
||||
let notice = format!("New user \"{user_id}\" registered on this server.");
|
||||
|
||||
info!("{notice}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log in conduit admin channel if a guest registered
|
||||
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
|
||||
debug_info!("New guest user \"{user_id}\" registered on this server.");
|
||||
|
||||
if !device_display_name.is_empty() {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with device display name \
|
||||
\"{device_display_name}\" registered on this server from IP {client}"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on \
|
||||
this server from IP {client}",
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_guest {
|
||||
// Make the first user to register an administrator and disable first-run mode.
|
||||
let was_first_user = services.firstrun.empower_first_user(&user_id).await?;
|
||||
|
||||
// If the registering user was not the first and we're suspending users on
|
||||
// register, suspend them.
|
||||
if !was_first_user && services.config.suspend_on_register {
|
||||
// Note that we can still do auto joins for suspended users
|
||||
services
|
||||
.users
|
||||
.suspend_account(&user_id, &services.globals.server_user)
|
||||
.await;
|
||||
// And send an @room notice to the admin room, to prompt admins to review the
|
||||
// new user and ideally unsuspend them if deemed appropriate.
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_loud_message(RoomMessageEventContent::text_plain(format!(
|
||||
"User {user_id} has been suspended as they are not the first user on \
|
||||
this server. Please review and unsuspend them if appropriate."
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if body.appservice_info.is_none()
|
||||
&& !services.server.config.auto_join_rooms.is_empty()
|
||||
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
||||
{
|
||||
for room in &services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
|
||||
error!(
|
||||
"Failed to resolve room alias to room ID when attempting to auto join \
|
||||
{room}, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), &room_id)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Skipping room {room} to automatically join as we have never joined before."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(room_server_name) = room.server_name() {
|
||||
match join_room_by_id_helper(
|
||||
&services,
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
&body.appservice_info,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
| Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
error!(
|
||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(register::v3::Response {
|
||||
access_token: token,
|
||||
user_id,
|
||||
device_id: device,
|
||||
refresh_token: None,
|
||||
expires_in: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/password`
|
||||
///
|
||||
/// Changes the password of this account.
|
||||
///
|
||||
/// - Requires UIAA to verify user password
|
||||
/// - Changes the password of the sender user
|
||||
/// - The password hash is calculated using argon2 with 32 character salt, the
|
||||
/// plain password is
|
||||
/// not saved
|
||||
///
|
||||
/// If logout_devices is true it does the following for each device except the
|
||||
/// sender device:
|
||||
/// - Invalidates access token
|
||||
/// - Deletes device metadata (device id, device display name, last seen ip,
|
||||
/// last seen ts)
|
||||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "change_password", level = "info")]
|
||||
pub(crate) async fn change_password_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<change_password::v3::Request>,
|
||||
) -> Result<change_password::v3::Response> {
|
||||
// Authentication for this endpoint was made optional, but we need
|
||||
// authentication currently
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_ref()
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_password(sender_user, Some(&body.new_password))
|
||||
.await?;
|
||||
|
||||
if body.logout_devices {
|
||||
// Logout all devices except the current one
|
||||
services
|
||||
.users
|
||||
.all_device_ids(sender_user)
|
||||
.ready_filter(|id| *id != body.sender_device())
|
||||
.for_each(|id| services.users.remove_device(sender_user, id))
|
||||
.await;
|
||||
|
||||
// Remove all pushers except the ones associated with this session
|
||||
services
|
||||
.pusher
|
||||
.get_pushkeys(sender_user)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_filter_map(async |pushkey| {
|
||||
services
|
||||
.pusher
|
||||
.get_pusher_device(&pushkey)
|
||||
.await
|
||||
.ok()
|
||||
.filter(|pusher_device| pusher_device != body.sender_device())
|
||||
.is_some()
|
||||
.then_some(pushkey)
|
||||
})
|
||||
.for_each(async |pushkey| {
|
||||
services.pusher.delete_pusher(sender_user, &pushkey).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
info!("User {sender_user} changed their password.");
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!("User {sender_user} changed their password."))
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(change_password::v3::Response {})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v3/account/whoami`
|
||||
///
|
||||
/// Get `user_id` of the sender user.
|
||||
///
|
||||
/// Note: Also works for Application Services
|
||||
pub(crate) async fn whoami_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<whoami::v3::Request>,
|
||||
) -> Result<whoami::v3::Response> {
|
||||
let is_guest = services
|
||||
.users
|
||||
.is_deactivated(body.sender_user())
|
||||
.await
|
||||
.map_err(|_| {
|
||||
err!(Request(Forbidden("Application service has not registered this user.")))
|
||||
})? && body.appservice_info.is_none();
|
||||
Ok(whoami::v3::Response {
|
||||
user_id: body.sender_user().to_owned(),
|
||||
device_id: body.sender_device.clone(),
|
||||
is_guest,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/deactivate`
|
||||
///
|
||||
/// Deactivate sender user account.
|
||||
///
|
||||
/// - Leaves all rooms and rejects all invitations
|
||||
/// - Invalidates all access tokens
|
||||
/// - Deletes all device metadata (device id, device display name, last seen ip,
|
||||
/// last seen ts)
|
||||
/// - Forgets all to-device events
|
||||
/// - Triggers device list updates
|
||||
/// - Removes ability to log in again
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "deactivate", level = "info")]
|
||||
pub(crate) async fn deactivate_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<deactivate::v3::Request>,
|
||||
) -> Result<deactivate::v3::Response> {
|
||||
// Authentication for this endpoint was made optional, but we need
|
||||
// authentication currently
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_ref()
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("JSON body is not valid")));
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Remove profile pictures and display name
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
full_user_deactivate(&services, sender_user, &all_joined_rooms)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
info!("User {sender_user} deactivated their account.");
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!("User {sender_user} deactivated their account."))
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(deactivate::v3::Response {
|
||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET _matrix/client/v3/account/3pid`
|
||||
///
|
||||
/// Get a list of third party identifiers associated with this account.
|
||||
///
|
||||
/// - Currently always returns empty list
|
||||
pub(crate) async fn third_party_route(
|
||||
body: Ruma<get_3pids::v3::Request>,
|
||||
) -> Result<get_3pids::v3::Response> {
|
||||
let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
Ok(get_3pids::v3::Response::new(Vec::new()))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/email/requestToken`
|
||||
///
|
||||
/// "This API should be used to request validation tokens when adding an email
|
||||
/// address to an account"
|
||||
///
|
||||
/// - 403 signals that The homeserver does not allow the third party identifier
|
||||
/// as a contact option.
|
||||
pub(crate) async fn request_3pid_management_token_via_email_route(
|
||||
_body: Ruma<request_3pid_management_token_via_email::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_email::v3::Response> {
|
||||
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/msisdn/requestToken`
|
||||
///
|
||||
/// "This API should be used to request validation tokens when adding an phone
|
||||
/// number to an account"
|
||||
///
|
||||
/// - 403 signals that The homeserver does not allow the third party identifier
|
||||
/// as a contact option.
|
||||
pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||
_body: Ruma<request_3pid_management_token_via_msisdn::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
|
||||
Err!(Request(ThreepidDenied("Third party identifiers are not implemented")))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||
///
|
||||
/// Checks if the provided registration token is valid at the time of checking.
|
||||
pub(crate) async fn check_registration_token_validity(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||
) -> Result<check_registration_token_validity::v1::Response> {
|
||||
// TODO: ratelimit this pretty heavily
|
||||
|
||||
let valid = services
|
||||
.registration_tokens
|
||||
.validate_token(body.token.clone())
|
||||
.await
|
||||
.is_some();
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response { valid })
|
||||
}
|
||||
|
||||
/// Runs through all the deactivation steps:
|
||||
///
|
||||
/// - Mark as deactivated
|
||||
/// - Removing display name
|
||||
/// - Removing avatar URL and blurhash
|
||||
/// - Removing all profile data
|
||||
/// - Leaving all rooms (and forgets all of them)
|
||||
pub async fn full_user_deactivate(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
all_joined_rooms: &[OwnedRoomId],
|
||||
) -> Result<()> {
|
||||
services.users.deactivate_account(user_id).await.ok();
|
||||
|
||||
services
|
||||
.users
|
||||
.all_profile_keys(user_id)
|
||||
.ready_for_each(|(profile_key, _)| {
|
||||
services.users.set_profile_key(user_id, &profile_key, None);
|
||||
})
|
||||
.await;
|
||||
|
||||
// TODO: Rescind all user invites
|
||||
|
||||
let mut pdu_queue: Vec<(PduBuilder, &OwnedRoomId)> = Vec::new();
|
||||
|
||||
for room_id in all_joined_rooms {
|
||||
let room_power_levels = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<RoomPowerLevelsEventContent>(
|
||||
room_id,
|
||||
&StateEventType::RoomPowerLevels,
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let user_can_demote_self =
|
||||
room_power_levels
|
||||
.as_ref()
|
||||
.is_some_and(|power_levels_content| {
|
||||
RoomPowerLevels::from(power_levels_content.clone())
|
||||
.user_can_change_user_power_level(user_id, user_id)
|
||||
}) || services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.is_ok_and(|event| event.sender() == user_id);
|
||||
|
||||
if user_can_demote_self {
|
||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||
power_levels_content.users.remove(user_id);
|
||||
let pl_evt = PduBuilder::state(String::new(), &power_levels_content);
|
||||
pdu_queue.push((pl_evt, room_id));
|
||||
}
|
||||
|
||||
// Leave the room
|
||||
pdu_queue.push((
|
||||
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
avatar_url: None,
|
||||
blurhash: None,
|
||||
membership: MembershipState::Leave,
|
||||
displayname: None,
|
||||
join_authorized_via_users_server: None,
|
||||
reason: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
redact_events: None,
|
||||
}),
|
||||
room_id,
|
||||
));
|
||||
|
||||
// TODO: Redact all messages sent by the user in the room
|
||||
}
|
||||
|
||||
super::update_all_rooms(services, pdu_queue, user_id).await;
|
||||
for room_id in all_joined_rooms {
|
||||
services.rooms.state_cache.forget(room_id, user_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
426
src/api/client/account/mod.rs
Normal file
426
src/api/client/account/mod.rs
Normal file
@@ -0,0 +1,426 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Event, Result, err, info,
|
||||
pdu::PduBuilder,
|
||||
utils::{ReadyExt, stream::BroadbandExt},
|
||||
};
|
||||
use conduwuit_service::Services;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use lettre::{Address, message::Mailbox};
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, UserId,
|
||||
api::client::{
|
||||
account::{
|
||||
ThirdPartyIdRemovalStatus, change_password, check_registration_token_validity,
|
||||
deactivate, get_username_availability, request_password_change_token_via_email,
|
||||
whoami,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType},
|
||||
},
|
||||
events::{
|
||||
StateEventType,
|
||||
room::{
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
},
|
||||
};
|
||||
use service::{mailer::messages, uiaa::Identity};
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
||||
use crate::Ruma;
|
||||
|
||||
pub(crate) mod register;
|
||||
pub(crate) mod threepid;
|
||||
|
||||
/// # `GET /_matrix/client/v3/register/available`
|
||||
///
|
||||
/// Checks if a username is valid and available on this server.
|
||||
///
|
||||
/// Conditions for returning true:
|
||||
/// - The user id is not historical
|
||||
/// - The server name of the user id matches this server
|
||||
/// - No user or appservice on this server already claimed this username
|
||||
///
|
||||
/// Note: This will not reserve the username, so the username might become
|
||||
/// invalid when trying to register
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available", level = "info")]
|
||||
pub(crate) async fn get_register_available_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_username_availability::v3::Request>,
|
||||
) -> Result<get_username_availability::v3::Response> {
|
||||
// Validate user id
|
||||
let user_id =
|
||||
match UserId::parse_with_server_name(&body.username, services.globals.server_name()) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {} contains disallowed characters or spaces: {e}",
|
||||
body.username
|
||||
))));
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {} is not valid: {e}",
|
||||
body.username
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
// Check if username is creative enough
|
||||
if services.users.exists(&user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
if let Some(ref info) = body.appservice_info {
|
||||
if !info.is_user_match(&user_id) {
|
||||
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
|
||||
}
|
||||
}
|
||||
|
||||
if services.appservice.is_exclusive_user_id(&user_id).await {
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
Ok(get_username_availability::v3::Response { available: true })
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/password`
|
||||
///
|
||||
/// Changes the password of this account.
|
||||
///
|
||||
/// - Requires UIAA to verify user password
|
||||
/// - Changes the password of the sender user
|
||||
/// - The password hash is calculated using argon2 with 32 character salt, the
|
||||
/// plain password is
|
||||
/// not saved
|
||||
///
|
||||
/// If logout_devices is true it does the following for each device except the
|
||||
/// sender device:
|
||||
/// - Invalidates access token
|
||||
/// - Deletes device metadata (device id, device display name, last seen ip,
|
||||
/// last seen ts)
|
||||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "change_password", level = "info")]
|
||||
pub(crate) async fn change_password_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<change_password::v3::Request>,
|
||||
) -> Result<change_password::v3::Response> {
|
||||
let identity = if let Some(ref user_id) = body.sender_user {
|
||||
// A signed-in user is trying to change their password, prompt them for their
|
||||
// existing one
|
||||
|
||||
services
|
||||
.uiaa
|
||||
.authenticate(
|
||||
&body.auth,
|
||||
vec![AuthFlow::new(vec![AuthType::Password])],
|
||||
Box::default(),
|
||||
Some(Identity::from_user_id(user_id)),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
// A signed-out user is trying to reset their password, prompt them for email
|
||||
// confirmation. Note that we do not _send_ an email here, their client should
|
||||
// have already hit `/account/password/requestToken` to send the email. We
|
||||
// just validate it.
|
||||
|
||||
services
|
||||
.uiaa
|
||||
.authenticate(
|
||||
&body.auth,
|
||||
vec![AuthFlow::new(vec![AuthType::EmailIdentity])],
|
||||
Box::default(),
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let sender_user = OwnedUserId::parse(format!(
|
||||
"@{}:{}",
|
||||
identity.localpart.expect("localpart should be known"),
|
||||
services.globals.server_name()
|
||||
))
|
||||
.expect("user ID should be valid");
|
||||
|
||||
services
|
||||
.users
|
||||
.set_password(&sender_user, Some(&body.new_password))
|
||||
.await?;
|
||||
|
||||
if body.logout_devices {
|
||||
// Logout all devices except the current one
|
||||
services
|
||||
.users
|
||||
.all_device_ids(&sender_user)
|
||||
.ready_filter(|id| *id != body.sender_device())
|
||||
.for_each(|id| services.users.remove_device(&sender_user, id))
|
||||
.await;
|
||||
|
||||
// Remove all pushers except the ones associated with this session
|
||||
services
|
||||
.pusher
|
||||
.get_pushkeys(&sender_user)
|
||||
.map(ToOwned::to_owned)
|
||||
.broad_filter_map(async |pushkey| {
|
||||
services
|
||||
.pusher
|
||||
.get_pusher_device(&pushkey)
|
||||
.await
|
||||
.ok()
|
||||
.filter(|pusher_device| pusher_device != body.sender_device())
|
||||
.is_some()
|
||||
.then_some(pushkey)
|
||||
})
|
||||
.for_each(async |pushkey| {
|
||||
services.pusher.delete_pusher(&sender_user, &pushkey).await;
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
info!("User {} changed their password.", &sender_user);
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!("User {} changed their password.", &sender_user))
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(change_password::v3::Response {})
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/password/email/requestToken`
|
||||
///
|
||||
/// Requests a validation email for the purpose of resetting a user's password.
|
||||
pub(crate) async fn request_password_change_token_via_email_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<request_password_change_token_via_email::v3::Request>,
|
||||
) -> Result<request_password_change_token_via_email::v3::Response> {
|
||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
||||
};
|
||||
|
||||
let Some(localpart) = services.threepid.get_localpart_for_email(&email).await else {
|
||||
return Err!(Request(ThreepidNotFound(
|
||||
"No account is associated with this email address"
|
||||
)));
|
||||
};
|
||||
|
||||
let user_id =
|
||||
OwnedUserId::parse(format!("@{localpart}:{}", services.globals.server_name())).unwrap();
|
||||
let display_name = services.users.displayname(&user_id).await.ok();
|
||||
|
||||
let session = services
|
||||
.threepid
|
||||
.send_validation_email(
|
||||
Mailbox::new(display_name.clone(), email),
|
||||
|verification_link| messages::PasswordReset {
|
||||
display_name: display_name.as_deref(),
|
||||
user_id: &user_id,
|
||||
verification_link,
|
||||
},
|
||||
&body.client_secret,
|
||||
body.send_attempt.try_into().unwrap(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(request_password_change_token_via_email::v3::Response::new(session))
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v3/account/whoami`
|
||||
///
|
||||
/// Get `user_id` of the sender user.
|
||||
///
|
||||
/// Note: Also works for Application Services
|
||||
pub(crate) async fn whoami_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<whoami::v3::Request>,
|
||||
) -> Result<whoami::v3::Response> {
|
||||
let is_guest = services
|
||||
.users
|
||||
.is_deactivated(body.sender_user())
|
||||
.await
|
||||
.map_err(|_| {
|
||||
err!(Request(Forbidden("Application service has not registered this user.")))
|
||||
})? && body.appservice_info.is_none();
|
||||
Ok(whoami::v3::Response {
|
||||
user_id: body.sender_user().to_owned(),
|
||||
device_id: body.sender_device.clone(),
|
||||
is_guest,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/r0/account/deactivate`
|
||||
///
|
||||
/// Deactivate sender user account.
|
||||
///
|
||||
/// - Leaves all rooms and rejects all invitations
|
||||
/// - Invalidates all access tokens
|
||||
/// - Deletes all device metadata (device id, device display name, last seen ip,
|
||||
/// last seen ts)
|
||||
/// - Forgets all to-device events
|
||||
/// - Triggers device list updates
|
||||
/// - Removes ability to log in again
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "deactivate", level = "info")]
|
||||
pub(crate) async fn deactivate_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<deactivate::v3::Request>,
|
||||
) -> Result<deactivate::v3::Response> {
|
||||
// Authentication for this endpoint is technically optional,
|
||||
// but we require the user to be logged in
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_ref()
|
||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||
|
||||
// Prompt the user to confirm with their password using UIAA
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
|
||||
// Remove profile pictures and display name
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
full_user_deactivate(&services, sender_user, &all_joined_rooms)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
info!("User {sender_user} deactivated their account.");
|
||||
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!("User {sender_user} deactivated their account."))
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(deactivate::v3::Response {
|
||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::Success,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||
///
|
||||
/// Checks if the provided registration token is valid at the time of checking.
|
||||
pub(crate) async fn check_registration_token_validity(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||
) -> Result<check_registration_token_validity::v1::Response> {
|
||||
// TODO: ratelimit this pretty heavily
|
||||
|
||||
let valid = services
|
||||
.registration_tokens
|
||||
.validate_token(body.token.clone())
|
||||
.await
|
||||
.is_some();
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response { valid })
|
||||
}
|
||||
|
||||
/// Runs through all the deactivation steps:
|
||||
///
|
||||
/// - Mark as deactivated
|
||||
/// - Removing display name
|
||||
/// - Removing avatar URL and blurhash
|
||||
/// - Removing all profile data
|
||||
/// - Leaving all rooms (and forgets all of them)
|
||||
pub async fn full_user_deactivate(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
all_joined_rooms: &[OwnedRoomId],
|
||||
) -> Result<()> {
|
||||
services.users.deactivate_account(user_id).await.ok();
|
||||
|
||||
if services.globals.user_is_local(user_id) {
|
||||
let _ = services
|
||||
.threepid
|
||||
.disassociate_localpart_email(user_id.localpart())
|
||||
.await;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.all_profile_keys(user_id)
|
||||
.ready_for_each(|(profile_key, _)| {
|
||||
services.users.set_profile_key(user_id, &profile_key, None);
|
||||
})
|
||||
.await;
|
||||
|
||||
// TODO: Rescind all user invites
|
||||
|
||||
let mut pdu_queue: Vec<(PduBuilder, &OwnedRoomId)> = Vec::new();
|
||||
|
||||
for room_id in all_joined_rooms {
|
||||
let room_power_levels = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<RoomPowerLevelsEventContent>(
|
||||
room_id,
|
||||
&StateEventType::RoomPowerLevels,
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let user_can_demote_self =
|
||||
room_power_levels
|
||||
.as_ref()
|
||||
.is_some_and(|power_levels_content| {
|
||||
RoomPowerLevels::from(power_levels_content.clone())
|
||||
.user_can_change_user_power_level(user_id, user_id)
|
||||
}) || services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.is_ok_and(|event| event.sender() == user_id);
|
||||
|
||||
if user_can_demote_self {
|
||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||
power_levels_content.users.remove(user_id);
|
||||
let pl_evt = PduBuilder::state(String::new(), &power_levels_content);
|
||||
pdu_queue.push((pl_evt, room_id));
|
||||
}
|
||||
|
||||
// Leave the room
|
||||
pdu_queue.push((
|
||||
PduBuilder::state(user_id.to_string(), &RoomMemberEventContent {
|
||||
avatar_url: None,
|
||||
blurhash: None,
|
||||
membership: MembershipState::Leave,
|
||||
displayname: None,
|
||||
join_authorized_via_users_server: None,
|
||||
reason: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
redact_events: None,
|
||||
}),
|
||||
room_id,
|
||||
));
|
||||
|
||||
// TODO: Redact all messages sent by the user in the room
|
||||
}
|
||||
|
||||
super::update_all_rooms(services, pdu_queue, user_id).await;
|
||||
for room_id in all_joined_rooms {
|
||||
services.rooms.state_cache.forget(room_id, user_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
601
src/api/client/account/register.rs
Normal file
601
src/api/client/account/register.rs
Normal file
@@ -0,0 +1,601 @@
|
||||
use std::{collections::HashMap, fmt::Write};
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result, debug_info, error, info,
|
||||
utils::{self},
|
||||
warn,
|
||||
};
|
||||
use conduwuit_service::Services;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use lettre::{Address, message::Mailbox};
|
||||
use register::RegistrationKind;
|
||||
use ruma::{
|
||||
OwnedUserId, UserId,
|
||||
api::client::{
|
||||
account::{
|
||||
register::{self, LoginType},
|
||||
request_registration_token_via_email,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType},
|
||||
},
|
||||
events::{GlobalAccountDataEventType, room::message::RoomMessageEventContent},
|
||||
push,
|
||||
};
|
||||
use serde_json::value::RawValue;
|
||||
use service::mailer::messages;
|
||||
|
||||
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH, join_room_by_id_helper};
|
||||
use crate::Ruma;
|
||||
|
||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
///
|
||||
/// Register an account on this homeserver.
|
||||
///
|
||||
/// You can use [`GET
|
||||
/// /_matrix/client/v3/register/available`](fn.get_register_available_route.
|
||||
/// html) to check if the user id is valid and available.
|
||||
///
|
||||
/// - Only works if registration is enabled
|
||||
/// - If type is guest: ignores all parameters except
|
||||
/// initial_device_display_name
|
||||
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
|
||||
/// - If type is not guest and no username is given: Always fails after UIAA
|
||||
/// check
|
||||
/// - Creates a new account and populates it with default account data
|
||||
/// - If `inhibit_login` is false: Creates a device and returns device id and
|
||||
/// access_token
|
||||
#[allow(clippy::doc_markdown)]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register", level = "info")]
|
||||
pub(crate) async fn register_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<register::v3::Request>,
|
||||
) -> Result<register::v3::Response> {
|
||||
let is_guest = body.kind == RegistrationKind::Guest;
|
||||
let emergency_mode_enabled = services.config.emergency_password.is_some();
|
||||
|
||||
// Allow registration if it's enabled in the config file or if this is the first
|
||||
// run (so the first user account can be created)
|
||||
let allow_registration =
|
||||
services.config.allow_registration || services.firstrun.is_first_run();
|
||||
|
||||
if !allow_registration && body.appservice_info.is_none() {
|
||||
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
|
||||
| (Some(username), Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (Some(username), _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
user = %username,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (_, Some(device_display_name)) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
device_name = %device_display_name,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
| (None, _) => {
|
||||
info!(
|
||||
%is_guest,
|
||||
"Rejecting registration attempt as registration is disabled"
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden(
|
||||
"This server is not accepting registrations at this time."
|
||||
)));
|
||||
}
|
||||
|
||||
if is_guest && !services.config.allow_guest_registration {
|
||||
info!(
|
||||
"Guest registration disabled, rejecting guest registration attempt, initial device \
|
||||
name: \"{}\"",
|
||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
||||
);
|
||||
return Err!(Request(GuestAccessForbidden("Guest registration is disabled.")));
|
||||
}
|
||||
|
||||
// forbid guests from registering if there is not a real admin user yet. give
|
||||
// generic user error.
|
||||
if is_guest && services.firstrun.is_first_run() {
|
||||
warn!(
|
||||
"Guest account attempted to register before a real admin user has been registered, \
|
||||
rejecting registration. Guest's initial device name: \"{}\"",
|
||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
||||
);
|
||||
return Err!(Request(Forbidden(
|
||||
"This server is not accepting registrations at this time."
|
||||
)));
|
||||
}
|
||||
|
||||
// Appeservices and guests get to skip auth
|
||||
let skip_auth = body.appservice_info.is_some() || is_guest;
|
||||
|
||||
let identity = if skip_auth {
|
||||
// Appservices and guests have no identity
|
||||
None
|
||||
} else {
|
||||
// Perform UIAA to determine the user's identity
|
||||
let (flows, params) = create_registration_uiaa_session(&services).await?;
|
||||
|
||||
Some(
|
||||
services
|
||||
.uiaa
|
||||
.authenticate(&body.auth, flows, params, None)
|
||||
.await?,
|
||||
)
|
||||
};
|
||||
|
||||
// If the user didn't supply a username but did supply an email, use
|
||||
// the email's user as their initial localpart to avoid falling back to
|
||||
// a randomly generated localpart
|
||||
let supplied_username = body.username.clone().or_else(|| {
|
||||
if let Some(identity) = &identity
|
||||
&& let Some(email) = &identity.email
|
||||
{
|
||||
Some(email.user().to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let user_id = determine_registration_user_id(
|
||||
&services,
|
||||
supplied_username,
|
||||
is_guest,
|
||||
emergency_mode_enabled,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if body.body.login_type == Some(LoginType::ApplicationService) {
|
||||
// For appservice logins, make sure that the user ID is in the appservice's
|
||||
// namespace
|
||||
|
||||
match body.appservice_info {
|
||||
| Some(ref info) =>
|
||||
if !info.is_user_match(&user_id) && !emergency_mode_enabled {
|
||||
return Err!(Request(Exclusive(
|
||||
"Username is not in an appservice namespace."
|
||||
)));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(MissingToken("Missing appservice token.")));
|
||||
},
|
||||
}
|
||||
} else if services.appservice.is_exclusive_user_id(&user_id).await && !emergency_mode_enabled
|
||||
{
|
||||
// For non-appservice logins, ban user IDs which are in an appservice's
|
||||
// namespace (unless emergency mode is enabled)
|
||||
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
|
||||
}
|
||||
|
||||
let password = if is_guest { None } else { body.password.as_deref() };
|
||||
|
||||
// Create user
|
||||
services.users.create(&user_id, password, None).await?;
|
||||
|
||||
// Set an initial display name
|
||||
let mut displayname = user_id.localpart().to_owned();
|
||||
|
||||
// Apply the new user displayname suffix, if it's set
|
||||
if !services.globals.new_user_displayname_suffix().is_empty()
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(&user_id, Some(displayname.clone()));
|
||||
|
||||
// Initial account data
|
||||
services
|
||||
.account_data
|
||||
.update(
|
||||
None,
|
||||
&user_id,
|
||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
|
||||
content: ruma::events::push_rules::PushRulesEventContent {
|
||||
global: push::Ruleset::server_default(&user_id),
|
||||
},
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let no_device = body.inhibit_login
|
||||
|| body
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.is_some_and(|aps| aps.registration.device_management);
|
||||
|
||||
let (token, device) = if !no_device {
|
||||
// Don't create a device for inhibited logins
|
||||
let device_id = if is_guest { None } else { body.device_id.clone() }
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate new token for the device
|
||||
let new_token = utils::random_string(TOKEN_LENGTH);
|
||||
|
||||
// Create device for this account
|
||||
services
|
||||
.users
|
||||
.create_device(
|
||||
&user_id,
|
||||
&device_id,
|
||||
&new_token,
|
||||
body.initial_device_display_name.clone(),
|
||||
Some(client.to_string()),
|
||||
)
|
||||
.await?;
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
(Some(new_token), Some(device_id))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
// If the user registered with an email, associate it with their account.
|
||||
if let Some(identity) = identity
|
||||
&& let Some(email) = identity.email
|
||||
{
|
||||
// This may fail if the email is already in use, but we already check for that
|
||||
// in `/requestToken`, so ignoring the error is acceptable here in the rare case
|
||||
// that an email is sniped by another user between the `/requestToken` request
|
||||
// and the `/register` request.
|
||||
let _ = services
|
||||
.threepid
|
||||
.associate_localpart_email(user_id.localpart(), &email)
|
||||
.await;
|
||||
}
|
||||
|
||||
let device_display_name = body.initial_device_display_name.as_deref().unwrap_or("");
|
||||
|
||||
// log in conduit admin channel if a non-guest user registered
|
||||
if body.appservice_info.is_none() && !is_guest {
|
||||
if !device_display_name.is_empty() {
|
||||
let notice = format!(
|
||||
"New user \"{user_id}\" registered on this server from IP {client} and device \
|
||||
display name \"{device_display_name}\""
|
||||
);
|
||||
|
||||
info!("{notice}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
} else {
|
||||
let notice = format!("New user \"{user_id}\" registered on this server.");
|
||||
|
||||
info!("{notice}");
|
||||
if services.server.config.admin_room_notices {
|
||||
services.admin.notice(¬ice).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log in conduit admin channel if a guest registered
|
||||
if body.appservice_info.is_none() && is_guest && services.config.log_guest_registrations {
|
||||
debug_info!("New guest user \"{user_id}\" registered on this server.");
|
||||
|
||||
if !device_display_name.is_empty() {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with device display name \
|
||||
\"{device_display_name}\" registered on this server from IP {client}"
|
||||
))
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.notice(&format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on \
|
||||
this server from IP {client}",
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_guest {
|
||||
// Make the first user to register an administrator and disable first-run mode.
|
||||
let was_first_user = services.firstrun.empower_first_user(&user_id).await?;
|
||||
|
||||
// If the registering user was not the first and we're suspending users on
|
||||
// register, suspend them.
|
||||
if !was_first_user && services.config.suspend_on_register {
|
||||
// Note that we can still do auto joins for suspended users
|
||||
services
|
||||
.users
|
||||
.suspend_account(&user_id, &services.globals.server_user)
|
||||
.await;
|
||||
// And send an @room notice to the admin room, to prompt admins to review the
|
||||
// new user and ideally unsuspend them if deemed appropriate.
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_loud_message(RoomMessageEventContent::text_plain(format!(
|
||||
"User {user_id} has been suspended as they are not the first user on \
|
||||
this server. Please review and unsuspend them if appropriate."
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if body.appservice_info.is_none()
|
||||
&& !services.server.config.auto_join_rooms.is_empty()
|
||||
&& (services.config.allow_guests_auto_join_rooms || !is_guest)
|
||||
{
|
||||
for room in &services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
|
||||
error!(
|
||||
"Failed to resolve room alias to room ID when attempting to auto join \
|
||||
{room}, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(services.globals.server_name(), &room_id)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Skipping room {room} to automatically join as we have never joined before."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(room_server_name) = room.server_name() {
|
||||
match join_room_by_id_helper(
|
||||
&services,
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
&body.appservice_info,
|
||||
)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
| Err(e) => {
|
||||
// don't return this error so we don't fail registrations
|
||||
error!(
|
||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||
);
|
||||
},
|
||||
| _ => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(register::v3::Response {
|
||||
access_token: token,
|
||||
user_id,
|
||||
device_id: device,
|
||||
refresh_token: None,
|
||||
expires_in: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Determine which flows and parameters should be presented when
|
||||
/// registering a new account.
|
||||
async fn create_registration_uiaa_session(
|
||||
services: &Services,
|
||||
) -> Result<(Vec<AuthFlow>, Box<RawValue>)> {
|
||||
let mut params = HashMap::<String, serde_json::Value>::new();
|
||||
|
||||
let flows = if services.firstrun.is_first_run() {
|
||||
// Registration token forced while in first-run mode
|
||||
vec![AuthFlow::new(vec![AuthType::RegistrationToken])]
|
||||
} else {
|
||||
let mut flows = vec![];
|
||||
|
||||
if services
|
||||
.registration_tokens
|
||||
.iterate_tokens()
|
||||
.next()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
// Trusted registration flow with a token is available
|
||||
let mut token_flow = AuthFlow::new(vec![AuthType::RegistrationToken]);
|
||||
|
||||
if let Some(smtp) = &services.config.smtp
|
||||
&& smtp.require_email_for_token_registration
|
||||
{
|
||||
// Email is required for token registrations
|
||||
token_flow.stages.push(AuthType::EmailIdentity);
|
||||
}
|
||||
|
||||
flows.push(token_flow);
|
||||
}
|
||||
|
||||
let mut untrusted_flow = AuthFlow::default();
|
||||
|
||||
if services.config.recaptcha_private_site_key.is_some() {
|
||||
if let Some(pubkey) = &services.config.recaptcha_site_key {
|
||||
// ReCaptcha is configured for untrusted registrations
|
||||
untrusted_flow.stages.push(AuthType::ReCaptcha);
|
||||
|
||||
params.insert(
|
||||
AuthType::ReCaptcha.as_str().to_owned(),
|
||||
serde_json::json!({
|
||||
"public_key": pubkey,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(smtp) = &services.config.smtp
|
||||
&& smtp.require_email_for_registration
|
||||
{
|
||||
// Email is required for untrusted registrations
|
||||
untrusted_flow.stages.push(AuthType::EmailIdentity);
|
||||
}
|
||||
|
||||
if !untrusted_flow.stages.is_empty() {
|
||||
flows.push(untrusted_flow);
|
||||
}
|
||||
|
||||
if flows.is_empty() {
|
||||
// No flows are configured. Bail out by default
|
||||
// unless open registration was explicitly enabled.
|
||||
if !services
|
||||
.config
|
||||
.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
|
||||
{
|
||||
return Err!(Request(Forbidden(
|
||||
"This server is not accepting registrations at this time."
|
||||
)));
|
||||
}
|
||||
|
||||
// We have open registration enabled (😧), provide a dummy flow
|
||||
flows.push(AuthFlow::new(vec![AuthType::Dummy]));
|
||||
}
|
||||
|
||||
flows
|
||||
};
|
||||
|
||||
let params = serde_json::value::to_raw_value(¶ms).expect("params should be valid JSON");
|
||||
|
||||
Ok((flows, params))
|
||||
}
|
||||
|
||||
async fn determine_registration_user_id(
|
||||
services: &Services,
|
||||
supplied_username: Option<String>,
|
||||
is_guest: bool,
|
||||
emergency_mode_enabled: bool,
|
||||
) -> Result<OwnedUserId> {
|
||||
if let Some(supplied_username) = supplied_username
|
||||
&& !is_guest
|
||||
{
|
||||
// The user gets to pick their username. Do some validation to make sure it's
|
||||
// acceptable.
|
||||
|
||||
// Don't allow registration with forbidden usernames.
|
||||
if services
|
||||
.globals
|
||||
.forbidden_usernames()
|
||||
.is_match(&supplied_username)
|
||||
&& !emergency_mode_enabled
|
||||
{
|
||||
return Err!(Request(Forbidden("Username is forbidden")));
|
||||
}
|
||||
|
||||
// Create and validate the user ID
|
||||
let user_id = match UserId::parse_with_server_name(
|
||||
&supplied_username,
|
||||
services.globals.server_name(),
|
||||
) {
|
||||
| Ok(user_id) => {
|
||||
if let Err(e) = user_id.validate_strict() {
|
||||
// Unless we are in emergency mode, we should follow synapse's behaviour on
|
||||
// not allowing things like spaces and UTF-8 characters in usernames
|
||||
if !emergency_mode_enabled {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {supplied_username} contains disallowed characters or \
|
||||
spaces: {e}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
||||
// Don't allow registration with user IDs that aren't local
|
||||
if !services.globals.user_is_local(&user_id) {
|
||||
return Err!(Request(InvalidUsername(
|
||||
"Username {supplied_username} is not local to this server"
|
||||
)));
|
||||
}
|
||||
|
||||
user_id
|
||||
},
|
||||
| Err(e) => {
|
||||
return Err!(Request(InvalidUsername(debug_warn!(
|
||||
"Username {supplied_username} is not valid: {e}"
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
if services.users.exists(&user_id).await {
|
||||
return Err!(Request(UserInUse("User ID is not available.")));
|
||||
}
|
||||
|
||||
Ok(user_id)
|
||||
} else {
|
||||
// The user is a guest or didn't specify a username. Generate a username for
|
||||
// them.
|
||||
|
||||
loop {
|
||||
let user_id = UserId::parse_with_server_name(
|
||||
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
||||
services.globals.server_name(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if !services.users.exists(&user_id).await {
|
||||
break Ok(user_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/register/email/requestToken`
|
||||
///
|
||||
/// Requests a validation email for the purpose of registering a new account.
|
||||
pub(crate) async fn request_registration_token_via_email_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<request_registration_token_via_email::v3::Request>,
|
||||
) -> Result<request_registration_token_via_email::v3::Response> {
|
||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
||||
};
|
||||
|
||||
if services
|
||||
.threepid
|
||||
.get_localpart_for_email(&email)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
return Err!(Request(ThreepidInUse("This email address is already in use.")));
|
||||
}
|
||||
|
||||
let session = services
|
||||
.threepid
|
||||
.send_validation_email(
|
||||
Mailbox::new(None, email),
|
||||
|verification_link| messages::NewAccount {
|
||||
server_name: services.config.server_name.as_ref(),
|
||||
verification_link,
|
||||
},
|
||||
&body.client_secret,
|
||||
body.send_attempt.try_into().unwrap(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(request_registration_token_via_email::v3::Response::new(session))
|
||||
}
|
||||
153
src/api/client/account/threepid.rs
Normal file
153
src/api/client/account/threepid.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result, err};
|
||||
use lettre::{Address, message::Mailbox};
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch,
|
||||
api::client::account::{
|
||||
ThirdPartyIdRemovalStatus, add_3pid, delete_3pid, get_3pids,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
},
|
||||
thirdparty::{Medium, ThirdPartyIdentifierInit},
|
||||
};
|
||||
use service::{mailer::messages, uiaa::Identity};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET _matrix/client/v3/account/3pid`
|
||||
///
|
||||
/// Get a list of third party identifiers associated with this account.
|
||||
pub(crate) async fn third_party_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_3pids::v3::Request>,
|
||||
) -> Result<get_3pids::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
let mut threepids = vec![];
|
||||
|
||||
if let Some(email) = services
|
||||
.threepid
|
||||
.get_email_for_localpart(sender_user.localpart())
|
||||
.await
|
||||
{
|
||||
threepids.push(
|
||||
ThirdPartyIdentifierInit {
|
||||
address: email.to_string(),
|
||||
medium: Medium::Email,
|
||||
// We don't currently track these, and they aren't used for much
|
||||
validated_at: MilliSecondsSinceUnixEpoch::now(),
|
||||
added_at: MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::UNIX_EPOCH)
|
||||
.unwrap(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(get_3pids::v3::Response::new(threepids))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/email/requestToken`
|
||||
///
|
||||
/// Requests a validation email for the purpose of changing an account's email.
|
||||
pub(crate) async fn request_3pid_management_token_via_email_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<request_3pid_management_token_via_email::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_email::v3::Response> {
|
||||
let Ok(email) = Address::try_from(body.email.clone()) else {
|
||||
return Err!(Request(InvalidParam("Invalid email address.")));
|
||||
};
|
||||
|
||||
if services
|
||||
.threepid
|
||||
.get_localpart_for_email(&email)
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
return Err!(Request(ThreepidInUse("This email address is already in use.")));
|
||||
}
|
||||
|
||||
let session = services
|
||||
.threepid
|
||||
.send_validation_email(
|
||||
Mailbox::new(None, email),
|
||||
|verification_link| messages::ChangeEmail {
|
||||
server_name: services.config.server_name.as_str(),
|
||||
user_id: body.sender_user.as_deref(),
|
||||
verification_link,
|
||||
},
|
||||
&body.client_secret,
|
||||
body.send_attempt.try_into().unwrap(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(request_3pid_management_token_via_email::v3::Response::new(session))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/msisdn/requestToken`
|
||||
///
|
||||
/// "This API should be used to request validation tokens when adding an email
|
||||
/// address to an account"
|
||||
///
|
||||
/// - 403 signals that The homeserver does not allow the third party identifier
|
||||
/// as a contact option.
|
||||
pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||
_body: Ruma<request_3pid_management_token_via_msisdn::v3::Request>,
|
||||
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
|
||||
Err!(Request(ThreepidMediumNotSupported(
|
||||
"MSISDN third-party identifiers are not supported."
|
||||
)))
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/add`
|
||||
pub(crate) async fn add_3pid_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<add_3pid::v3::Request>,
|
||||
) -> Result<add_3pid::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
// Require password auth to add an email
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
|
||||
let email = services
|
||||
.threepid
|
||||
.consume_valid_session(&body.sid, &body.client_secret)
|
||||
.await
|
||||
.map_err(|message| err!(Request(ThreepidAuthFailed("{message}"))))?;
|
||||
|
||||
services
|
||||
.threepid
|
||||
.associate_localpart_email(sender_user.localpart(), &email)
|
||||
.await?;
|
||||
|
||||
Ok(add_3pid::v3::Response::new())
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/account/3pid/delete`
|
||||
pub(crate) async fn delete_3pid_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_3pid::v3::Request>,
|
||||
) -> Result<delete_3pid::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if body.medium != Medium::Email {
|
||||
return Ok(delete_3pid::v3::Response {
|
||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
|
||||
});
|
||||
}
|
||||
|
||||
if services
|
||||
.threepid
|
||||
.disassociate_localpart_email(sender_user.localpart())
|
||||
.await
|
||||
.is_none()
|
||||
{
|
||||
return Err!(Request(ThreepidNotFound("Your account has no associated email.")));
|
||||
}
|
||||
|
||||
Ok(delete_3pid::v3::Response {
|
||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::Success,
|
||||
})
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
events::{
|
||||
AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType,
|
||||
RoomAccountDataEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
@@ -126,12 +126,6 @@ async fn set_account_data(
|
||||
)));
|
||||
}
|
||||
|
||||
if event_type_s == GlobalAccountDataEventType::PushRules.to_cow_str() {
|
||||
return Err!(Request(BadJson(
|
||||
"This endpoint cannot be used for setting/configuring push rules."
|
||||
)));
|
||||
}
|
||||
|
||||
let data: serde_json::Value = serde_json::from_str(data.get())
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Err, Result, debug};
|
||||
use conduwuit_service::Services;
|
||||
use futures::StreamExt;
|
||||
use rand::seq::SliceRandom;
|
||||
use ruma::{
|
||||
OwnedServerName, RoomAliasId, RoomId,
|
||||
api::client::alias::{create_alias, delete_alias, get_alias},
|
||||
};
|
||||
use conduwuit::{Err, Result};
|
||||
use ruma::api::client::alias::{create_alias, delete_alias, get_alias};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
@@ -96,65 +90,9 @@ pub(crate) async fn get_alias_route(
|
||||
) -> Result<get_alias::v3::Response> {
|
||||
let room_alias = body.body.room_alias;
|
||||
|
||||
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias, None).await
|
||||
else {
|
||||
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias).await else {
|
||||
return Err!(Request(NotFound("Room with alias not found.")));
|
||||
};
|
||||
|
||||
let servers = room_available_servers(&services, &room_id, &room_alias, servers).await;
|
||||
debug!(%room_alias, %room_id, "available servers: {servers:?}");
|
||||
|
||||
Ok(get_alias::v3::Response::new(room_id, servers))
|
||||
}
|
||||
|
||||
async fn room_available_servers(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
room_alias: &RoomAliasId,
|
||||
pre_servers: Vec<OwnedServerName>,
|
||||
) -> Vec<OwnedServerName> {
|
||||
// find active servers in room state cache to suggest
|
||||
let mut servers: Vec<OwnedServerName> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_servers(room_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
// push any servers we want in the list already (e.g. responded remote alias
|
||||
// servers, room alias server itself)
|
||||
servers.extend(pre_servers);
|
||||
|
||||
servers.sort_unstable();
|
||||
servers.dedup();
|
||||
|
||||
// shuffle list of servers randomly after sort and dedupe
|
||||
servers.shuffle(&mut rand::thread_rng());
|
||||
|
||||
// insert our server as the very first choice if in list, else check if we can
|
||||
// prefer the room alias server first
|
||||
match servers
|
||||
.iter()
|
||||
.position(|server_name| services.globals.server_is_ours(server_name))
|
||||
{
|
||||
| Some(server_index) => {
|
||||
servers.swap_remove(server_index);
|
||||
servers.insert(0, services.globals.server_name().to_owned());
|
||||
},
|
||||
| _ => {
|
||||
match servers
|
||||
.iter()
|
||||
.position(|server| server == room_alias.server_name())
|
||||
{
|
||||
| Some(alias_server_index) => {
|
||||
servers.swap_remove(alias_server_index);
|
||||
servers.insert(0, room_alias.server_name().into());
|
||||
},
|
||||
| _ => {},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
servers
|
||||
}
|
||||
|
||||
@@ -30,8 +30,10 @@ pub(crate) async fn get_capabilities_route(
|
||||
default: services.server.config.default_room_version.clone(),
|
||||
};
|
||||
|
||||
// we do not implement 3PID stuff
|
||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability { enabled: false };
|
||||
// Only allow 3pid changes if SMTP is configured
|
||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability {
|
||||
enabled: services.mailer.mailer().is_some(),
|
||||
};
|
||||
|
||||
capabilities.get_login_token = GetLoginTokenCapability {
|
||||
enabled: services.server.config.login_via_existing_session,
|
||||
@@ -51,7 +53,7 @@ pub(crate) async fn get_capabilities_route(
|
||||
.await
|
||||
{
|
||||
// Advertise suspension API
|
||||
capabilities.set("uk.timedout.msc4323", json!({"suspend":true, "lock": false}))?;
|
||||
capabilities.set("uk.timedout.msc4323", json!({"suspend": true, "lock": false}))?;
|
||||
}
|
||||
|
||||
Ok(get_capabilities::v3::Response { capabilities })
|
||||
|
||||
121
src/api/client/dehydrated_device.rs
Normal file
121
src/api/client/dehydrated_device.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{Err, Result, at};
|
||||
use futures::StreamExt;
|
||||
use ruma::api::client::dehydrated_device::{
|
||||
delete_dehydrated_device::unstable as delete_dehydrated_device,
|
||||
get_dehydrated_device::unstable as get_dehydrated_device, get_events::unstable as get_events,
|
||||
put_dehydrated_device::unstable as put_dehydrated_device,
|
||||
};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
const MAX_BATCH_EVENTS: usize = 50;
|
||||
|
||||
/// # `PUT /_matrix/client/../dehydrated_device`
|
||||
///
|
||||
/// Creates or overwrites the user's dehydrated device.
|
||||
#[tracing::instrument(skip_all, fields(%client))]
|
||||
pub(crate) async fn put_dehydrated_device_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<put_dehydrated_device::Request>,
|
||||
) -> Result<put_dehydrated_device::Response> {
|
||||
let sender_user = body
|
||||
.sender_user
|
||||
.as_deref()
|
||||
.expect("AccessToken authentication required");
|
||||
|
||||
let device_id = body.body.device_id.clone();
|
||||
|
||||
services
|
||||
.users
|
||||
.set_dehydrated_device(sender_user, body.body)
|
||||
.await?;
|
||||
|
||||
Ok(put_dehydrated_device::Response { device_id })
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/../dehydrated_device`
|
||||
///
|
||||
/// Deletes the user's dehydrated device without replacement.
|
||||
#[tracing::instrument(skip_all, fields(%client))]
|
||||
pub(crate) async fn delete_dehydrated_device_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<delete_dehydrated_device::Request>,
|
||||
) -> Result<delete_dehydrated_device::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
let device_id = services.users.get_dehydrated_device_id(sender_user).await?;
|
||||
|
||||
services.users.remove_device(sender_user, &device_id).await;
|
||||
|
||||
Ok(delete_dehydrated_device::Response { device_id })
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/../dehydrated_device`
|
||||
///
|
||||
/// Gets the user's dehydrated device
|
||||
#[tracing::instrument(skip_all, fields(%client))]
|
||||
pub(crate) async fn get_dehydrated_device_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_dehydrated_device::Request>,
|
||||
) -> Result<get_dehydrated_device::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
let device = services.users.get_dehydrated_device(sender_user).await?;
|
||||
|
||||
Ok(get_dehydrated_device::Response {
|
||||
device_id: device.device_id,
|
||||
device_data: device.device_data,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/../dehydrated_device/{device_id}/events`
|
||||
///
|
||||
/// Paginates the events of the dehydrated device.
|
||||
#[tracing::instrument(skip_all, fields(%client))]
|
||||
pub(crate) async fn get_dehydrated_events_route(
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_events::Request>,
|
||||
) -> Result<get_events::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
let device_id = &body.body.device_id;
|
||||
let existing_id = services.users.get_dehydrated_device_id(sender_user).await;
|
||||
|
||||
if existing_id.as_ref().is_err()
|
||||
|| existing_id
|
||||
.as_ref()
|
||||
.is_ok_and(|existing_id| existing_id != device_id)
|
||||
{
|
||||
return Err!(Request(Forbidden("Not the dehydrated device_id.")));
|
||||
}
|
||||
|
||||
let since: Option<u64> = body
|
||||
.body
|
||||
.next_batch
|
||||
.as_deref()
|
||||
.map(str::parse)
|
||||
.transpose()?;
|
||||
|
||||
let mut next_batch: Option<u64> = None;
|
||||
let events = services
|
||||
.users
|
||||
.get_to_device_events(sender_user, device_id, since, None)
|
||||
.take(MAX_BATCH_EVENTS)
|
||||
.inspect(|&(count, _)| {
|
||||
next_batch.replace(count);
|
||||
})
|
||||
.map(at!(1))
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
Ok(get_events::Response {
|
||||
events,
|
||||
next_batch: next_batch.as_ref().map(ToString::to_string),
|
||||
})
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{Err, Error, Result, debug, err, utils};
|
||||
use conduwuit::{Err, Result, debug, err, utils};
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
||||
api::client::{
|
||||
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
|
||||
error::ErrorKind,
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
api::client::device::{
|
||||
self, delete_device, delete_devices, get_device, get_devices, update_device,
|
||||
},
|
||||
};
|
||||
use service::uiaa::Identity;
|
||||
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::{Ruma, client::DEVICE_ID_LENGTH};
|
||||
|
||||
/// # `GET /_matrix/client/r0/devices`
|
||||
@@ -123,7 +121,7 @@ pub(crate) async fn delete_device_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_device::v3::Request>,
|
||||
) -> Result<delete_device::v3::Response> {
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
let sender_user = body.sender_user();
|
||||
let appservice = body.appservice_info.as_ref();
|
||||
|
||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||
@@ -139,41 +137,11 @@ pub(crate) async fn delete_device_route(
|
||||
return Ok(delete_device::v3::Response {});
|
||||
}
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, sender_device, &uiaainfo, json);
|
||||
|
||||
return Err!(Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err!(Request(NotJson("Not json.")));
|
||||
},
|
||||
},
|
||||
}
|
||||
// Prompt the user to confirm with their password using UIAA
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
|
||||
services
|
||||
.users
|
||||
@@ -200,7 +168,7 @@ pub(crate) async fn delete_devices_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_devices::v3::Request>,
|
||||
) -> Result<delete_devices::v3::Response> {
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
let sender_user = body.sender_user();
|
||||
let appservice = body.appservice_info.as_ref();
|
||||
|
||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||
@@ -215,41 +183,11 @@ pub(crate) async fn delete_devices_route(
|
||||
return Ok(delete_devices::v3::Response {});
|
||||
}
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body {
|
||||
| Some(ref json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, sender_device, &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
},
|
||||
},
|
||||
}
|
||||
// Prompt the user to confirm with their password using UIAA
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
|
||||
for device_id in &body.devices {
|
||||
services.users.remove_device(sender_user, device_id).await;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug, debug_warn, err,
|
||||
result::NotFound,
|
||||
utils,
|
||||
utils::{IterStream, stream::WidebandExt},
|
||||
};
|
||||
use conduwuit_service::{Services, users::parse_master_key};
|
||||
@@ -22,7 +21,6 @@
|
||||
upload_signatures::{self},
|
||||
upload_signing_keys,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
federation,
|
||||
},
|
||||
@@ -30,8 +28,8 @@
|
||||
serde::Raw,
|
||||
};
|
||||
use serde_json::json;
|
||||
use service::uiaa::Identity;
|
||||
|
||||
use super::SESSION_ID_LENGTH;
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/r0/keys/upload`
|
||||
@@ -174,16 +172,7 @@ pub(crate) async fn upload_signing_keys_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<upload_signing_keys::v3::Request>,
|
||||
) -> Result<upload_signing_keys::v3::Response> {
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
auth_error: None,
|
||||
};
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
match check_for_new_keys(
|
||||
services,
|
||||
@@ -207,32 +196,10 @@ pub(crate) async fn upload_signing_keys_route(
|
||||
// Some of the keys weren't found, so we let them upload
|
||||
},
|
||||
| _ => {
|
||||
match &body.auth {
|
||||
| Some(auth) => {
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
||||
.await?;
|
||||
|
||||
if !worked {
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
}
|
||||
// Success!
|
||||
},
|
||||
| _ => match body.json_body.as_ref() {
|
||||
| Some(json) => {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services
|
||||
.uiaa
|
||||
.create(sender_user, sender_device, &uiaainfo, json);
|
||||
|
||||
return Err(Error::Uiaa(uiaainfo));
|
||||
},
|
||||
| _ => {
|
||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
},
|
||||
},
|
||||
}
|
||||
let _ = services
|
||||
.uiaa
|
||||
.authenticate_password(&body.auth, Some(Identity::from_user_id(sender_user)))
|
||||
.await?;
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
Err, Result, err,
|
||||
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
|
||||
};
|
||||
use conduwuit_core::error;
|
||||
use conduwuit_service::{
|
||||
Services,
|
||||
media::{CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, Dim, FileMeta, MXC_LENGTH},
|
||||
@@ -113,7 +114,19 @@ pub(crate) async fn get_content_thumbnail_route(
|
||||
content,
|
||||
content_type,
|
||||
content_disposition,
|
||||
} = fetch_thumbnail(&services, &mxc, user, body.timeout_ms, &dim).await?;
|
||||
} = match fetch_thumbnail(&services, &mxc, user, body.timeout_ms, &dim).await {
|
||||
| Ok(meta) => meta,
|
||||
| Err(conduwuit::Error::Io(e)) => match e.kind() {
|
||||
| std::io::ErrorKind::NotFound =>
|
||||
return Err!(Request(NotFound("Thumbnail not found."))),
|
||||
| std::io::ErrorKind::PermissionDenied => {
|
||||
error!("Permission denied when trying to read file: {e:?}");
|
||||
return Err!(Request(Unknown("Unknown error when fetching thumbnail.")));
|
||||
},
|
||||
| _ => return Err!(Request(Unknown("Unknown error when fetching thumbnail."))),
|
||||
},
|
||||
| Err(_) => return Err!(Request(Unknown("Unknown error when fetching thumbnail."))),
|
||||
};
|
||||
|
||||
Ok(get_content_thumbnail::v1::Response {
|
||||
file: content.expect("entire file contents"),
|
||||
@@ -144,12 +157,22 @@ pub(crate) async fn get_content_route(
|
||||
server_name: &body.server_name,
|
||||
media_id: &body.media_id,
|
||||
};
|
||||
|
||||
let FileMeta {
|
||||
content,
|
||||
content_type,
|
||||
content_disposition,
|
||||
} = fetch_file(&services, &mxc, user, body.timeout_ms, None).await?;
|
||||
} = match fetch_file(&services, &mxc, user, body.timeout_ms, None).await {
|
||||
| Ok(meta) => meta,
|
||||
| Err(conduwuit::Error::Io(e)) => match e.kind() {
|
||||
| std::io::ErrorKind::NotFound => return Err!(Request(NotFound("Media not found."))),
|
||||
| std::io::ErrorKind::PermissionDenied => {
|
||||
error!("Permission denied when trying to read file: {e:?}");
|
||||
return Err!(Request(Unknown("Unknown error when fetching file.")));
|
||||
},
|
||||
| _ => return Err!(Request(Unknown("Unknown error when fetching file."))),
|
||||
},
|
||||
| Err(_) => return Err!(Request(Unknown("Unknown error when fetching file."))),
|
||||
};
|
||||
|
||||
Ok(get_content::v1::Response {
|
||||
file: content.expect("entire file contents"),
|
||||
@@ -185,7 +208,18 @@ pub(crate) async fn get_content_as_filename_route(
|
||||
content,
|
||||
content_type,
|
||||
content_disposition,
|
||||
} = fetch_file(&services, &mxc, user, body.timeout_ms, Some(&body.filename)).await?;
|
||||
} = match fetch_file(&services, &mxc, user, body.timeout_ms, None).await {
|
||||
| Ok(meta) => meta,
|
||||
| Err(conduwuit::Error::Io(e)) => match e.kind() {
|
||||
| std::io::ErrorKind::NotFound => return Err!(Request(NotFound("Media not found."))),
|
||||
| std::io::ErrorKind::PermissionDenied => {
|
||||
error!("Permission denied when trying to read file: {e:?}");
|
||||
return Err!(Request(Unknown("Unknown error when fetching file.")));
|
||||
},
|
||||
| _ => return Err!(Request(Unknown("Unknown error when fetching file."))),
|
||||
},
|
||||
| Err(_) => return Err!(Request(Unknown("Unknown error when fetching file."))),
|
||||
};
|
||||
|
||||
Ok(get_content_as_filename::v1::Response {
|
||||
file: content.expect("entire file contents"),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user