mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16014e1594 | |||
| 7e828440f9 | |||
| f6918833d7 | |||
| 4d7bbe9fb4 | |||
| 75be68fa61 | |||
| 0760150822 | |||
| 37a2ba59d0 | |||
| 724711218a | |||
| 359fb25262 | |||
| 9761e2f10c | |||
| 30e3e45f9f | |||
| e5efd55838 | |||
| 87734a074f | |||
| a7c4a7933d | |||
| 83becf013c | |||
| acb9eae707 | |||
| 2eee454a18 | |||
| e0b2595905 | |||
| 73afc1fd8f | |||
| 6acdd0d947 | |||
| e38c37d9e7 | |||
| 45254638b1 | |||
| 2d54264fbe | |||
| 6c1c7b35a5 | |||
| 8428e7cdf7 | |||
| e589464954 | |||
| 0413037246 | |||
| b9a8f8e6c7 | |||
| 032b199129 | |||
| e9e5fe2176 | |||
| 17fd34eb12 | |||
| 895b178720 | |||
| a65dd6dfb3 | |||
| e146c75279 | |||
| d75aebc373 | |||
| 80b72637e2 | |||
| a41e63b40e | |||
| cf9b72ce3f | |||
| 38552b36e9 | |||
| 9de780b56c | |||
| 55f71d3912 | |||
| 61347bee06 | |||
| 38cd88e1e8 | |||
| b44f7f5476 | |||
| e888810e67 | |||
| 02aee2f174 | |||
| 24c408f4c6 | |||
| 1c1f300efe | |||
| 8dccc04b40 | |||
| 96ab59b5b0 | |||
| c47337f3db | |||
| 3e0d404fb4 | |||
| 593d3bb321 | |||
| f14a253664 | |||
| b3974c569d | |||
| f163ebf3bb | |||
| 5ae9a5ff31 | |||
| 6f643a4b06 | |||
| 80698c0b17 | |||
| 909eeac5b0 | |||
| f521f88daf | |||
| 8f7ade4c22 | |||
| 8849a100fd | |||
| 5dfda2d300 | |||
| c13e9a7c2b | |||
| 393eef431b | |||
| 4bac9b33cc | |||
| 60605e9579 | |||
| 27bfb67d75 | |||
| fc1834d629 | |||
| 2fcedad2b1 | |||
| b362f0e0fa | |||
| 5530e7434a | |||
| bfb10cda26 | |||
| 5dbb868936 | |||
| 14b9511d2e | |||
| 7b852352e5 | |||
| b45df5f7bd | |||
| 4797183b43 | |||
| d68b71a0aa | |||
| 922875477f | |||
| 3a623dbdc3 | |||
| ae98610c50 | |||
| bceed3c829 | |||
| b89d2ceccd | |||
| eaa8997506 | |||
| 42a42b24a9 | |||
| 8d7e5ca2bb | |||
| 119cc2eec0 |
+141
-116
@@ -16,7 +16,6 @@ on:
|
||||
- 'docker/**'
|
||||
branches:
|
||||
- main
|
||||
- change-ci-cache
|
||||
tags:
|
||||
- '*'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
@@ -24,7 +23,7 @@ on:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
# sccache only on main repo
|
||||
@@ -51,8 +50,11 @@ env:
|
||||
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
|
||||
NIX_CONFIG: |
|
||||
show-trace = true
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
experimental-features = nix-command flakes
|
||||
extra-experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
# complement uses libolm
|
||||
NIXPKGS_ALLOW_INSECURE: 1
|
||||
|
||||
@@ -64,12 +66,23 @@ jobs:
|
||||
tests:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_PROFILE: "test"
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
|
||||
- name: Free up more runner space
|
||||
run: |
|
||||
set +o pipefail
|
||||
# large docker images
|
||||
sudo docker image prune --all --force || true
|
||||
# large packages
|
||||
sudo apt-get purge -y '^llvm-.*' 'php.*' '^mongodb-.*' '^mysql-.*' azure-cli google-cloud-cli google-chrome-stable firefox powershell microsoft-edge-stable || true
|
||||
sudo apt-get autoremove -y
|
||||
sudo apt-get clean
|
||||
# large folders
|
||||
sudo rm -rf /var/lib/apt/lists/* /usr/local/games /usr/local/sqlpackage /usr/local/.ghcup /usr/local/share/powershell /usr/local/share/edge_driver /usr/local/share/gecko_driver /usr/local/share/chromium /usr/local/share/chromedriver-linux64 /usr/local/share/vcpkg /usr/local/lib/python* /usr/local/lib/node_modules /usr/local/julia* /opt/mssql-tools /etc/skel /usr/share/vim /usr/share/postgresql /usr/share/man /usr/share/apache-maven-* /usr/share/R /usr/share/alsa /usr/share/miniconda /usr/share/grub /usr/share/gradle-* /usr/share/locale /usr/share/texinfo /usr/share/kotlinc /usr/share/swift /usr/share/doc /usr/share/az_9.3.0 /usr/share/sbt /usr/share/ri /usr/share/icons /usr/share/java /usr/share/fonts /usr/lib/google-cloud-sdk /usr/lib/jvm /usr/lib/mono /usr/lib/R /usr/lib/postgresql /usr/lib/heroku /usr/lib/gcc
|
||||
set -o pipefail
|
||||
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -85,13 +98,7 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: nixbuild/nix-quick-install-action@v28
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
- uses: nixbuild/nix-quick-install-action@master
|
||||
|
||||
- name: Restore and cache Nix store
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
@@ -114,11 +121,20 @@ jobs:
|
||||
# always save the cache
|
||||
save-always: true
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
experimental-features = nix-command flakes
|
||||
extra-experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
@@ -132,29 +148,16 @@ jobs:
|
||||
- name: Prepare build environment
|
||||
run: |
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
direnv allow
|
||||
nix develop .#all-features --command true --impure
|
||||
nix develop .#all-features --command true
|
||||
|
||||
- name: Cache CI dependencies
|
||||
run: |
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache ci
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
bin/nix-build-and-cache ci
|
||||
bin/nix-build-and-cache just '.#devShells.x86_64-linux.default'
|
||||
bin/nix-build-and-cache just '.#devShells.x86_64-linux.all-features'
|
||||
bin/nix-build-and-cache just '.#devShells.x86_64-linux.dynamic'
|
||||
|
||||
# use sccache for Rust
|
||||
- name: Run sccache-cache
|
||||
@@ -167,10 +170,14 @@ jobs:
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Run CI tests
|
||||
env:
|
||||
CARGO_PROFILE: "test"
|
||||
run: |
|
||||
direnv exec . engage > >(tee -a test_output.log)
|
||||
|
||||
- name: Run Complement tests
|
||||
env:
|
||||
CARGO_PROFILE: "test"
|
||||
run: |
|
||||
# the nix devshell sets $COMPLEMENT_SRC, so "/dev/null" is no-op
|
||||
direnv exec . bin/complement "/dev/null" complement_test_logs.jsonl complement_test_results.jsonl > >(tee -a test_output.log)
|
||||
@@ -218,7 +225,7 @@ jobs:
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Run cargo clean test artifacts
|
||||
- name: Run cargo clean test artifacts to free up space
|
||||
run: |
|
||||
cargo clean --profile test
|
||||
|
||||
@@ -229,8 +236,8 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
- target: x86_64-unknown-linux-musl
|
||||
- target: aarch64-linux-musl
|
||||
- target: x86_64-linux-musl
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
@@ -240,12 +247,6 @@ jobs:
|
||||
|
||||
- uses: nixbuild/nix-quick-install-action@v28
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Restore and cache Nix store
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
with:
|
||||
@@ -267,11 +268,20 @@ jobs:
|
||||
# always save the cache
|
||||
save-always: true
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
experimental-features = nix-command flakes
|
||||
extra-experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
@@ -301,26 +311,17 @@ jobs:
|
||||
|
||||
- name: Build static ${{ matrix.target }}
|
||||
run: |
|
||||
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||
if [[ ${{ matrix.target }} == "x86_64-linux-musl" ]]
|
||||
then
|
||||
CARGO_DEB_TARGET_TUPLE="x86_64-unknown-linux-musl"
|
||||
elif [[ ${{ matrix.target }} == "aarch64-linux-musl" ]]
|
||||
then
|
||||
CARGO_DEB_TARGET_TUPLE="aarch64-unknown-linux-musl"
|
||||
fi
|
||||
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features
|
||||
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
@@ -341,26 +342,17 @@ jobs:
|
||||
|
||||
- name: Build static debug ${{ matrix.target }}
|
||||
run: |
|
||||
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||
if [[ ${{ matrix.target }} == "x86_64-linux-musl" ]]
|
||||
then
|
||||
CARGO_DEB_TARGET_TUPLE="x86_64-unknown-linux-musl"
|
||||
elif [[ ${{ matrix.target }} == "aarch64-linux-musl" ]]
|
||||
then
|
||||
CARGO_DEB_TARGET_TUPLE="aarch64-unknown-linux-musl"
|
||||
fi
|
||||
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features-debug
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features-debug
|
||||
|
||||
# > warning: dev profile is not supported and will be a hard error in the future. cargo-deb is for making releases, and it doesn't make sense to use it with dev profiles.
|
||||
# so we need to coerce cargo-deb into thinking this is a release binary
|
||||
@@ -423,45 +415,13 @@ jobs:
|
||||
|
||||
- name: Build OCI image ${{ matrix.target }}
|
||||
run: |
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
|
||||
|
||||
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
|
||||
|
||||
- name: Build debug OCI image ${{ matrix.target }}
|
||||
run: |
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features-debug
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features-debug
|
||||
|
||||
cp -v -f result oci-image-${{ matrix.target }}-debug.tar.gz
|
||||
|
||||
@@ -481,6 +441,71 @@ jobs:
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
build_mac_binaries:
|
||||
name: Build MacOS Binaries
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, macos-13]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Tag comparison check
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
|
||||
run: |
|
||||
# Tag mismatch with latest repo tag check to prevent potential downgrades
|
||||
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
if [ $LATEST_TAG != ${{ github.ref_name }} ]; then
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.'
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
# use sccache for Rust
|
||||
- name: Run sccache-cache
|
||||
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
|
||||
uses: mozilla-actions/sccache-action@main
|
||||
# use rust-cache
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
# Nix can't do portable macOS builds yet
|
||||
- name: Build macOS x86_64 binary
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
run: |
|
||||
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short HEAD)" cargo build --release
|
||||
cp -v -f target/release/conduit conduwuit-macos-x86_64
|
||||
otool -L conduwuit-macos-x86_64
|
||||
# quick smoke test of the x86_64 macOS binary
|
||||
- name: Run x86_64 macOS release binary
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
run: |
|
||||
./conduwuit-macos-x86_64 --version
|
||||
- name: Build macOS arm64 binary
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short HEAD)" cargo build --release
|
||||
cp -v -f target/release/conduit conduwuit-macos-arm64
|
||||
otool -L conduwuit-macos-arm64
|
||||
# quick smoke test of the arm64 macOS binary
|
||||
- name: Run arm64 macOS release binary
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
./conduwuit-macos-arm64 --version
|
||||
- name: Upload macOS x86_64 binary
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: conduwuit-macos-x86_64
|
||||
path: conduwuit-macos-x86_64
|
||||
if-no-files-found: error
|
||||
- name: Upload macOS arm64 binary
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: conduwuit-macos-arm64
|
||||
path: conduwuit-macos-arm64
|
||||
if-no-files-found: error
|
||||
|
||||
docker:
|
||||
name: Docker publish
|
||||
runs-on: ubuntu-latest
|
||||
@@ -531,10 +556,10 @@ jobs:
|
||||
|
||||
- name: Move OCI images into position
|
||||
run: |
|
||||
mv -v oci-image-x86_64-unknown-linux-musl/*.tar.gz oci-image-amd64.tar.gz
|
||||
mv -v oci-image-aarch64-unknown-linux-musl/*.tar.gz oci-image-arm64v8.tar.gz
|
||||
mv -v oci-image-x86_64-unknown-linux-musl-debug/*.tar.gz oci-image-amd64-debug.tar.gz
|
||||
mv -v oci-image-aarch64-unknown-linux-musl-debug/*.tar.gz oci-image-arm64v8-debug.tar.gz
|
||||
mv -v oci-image-x86_64-linux-musl/*.tar.gz oci-image-amd64.tar.gz
|
||||
mv -v oci-image-aarch64-linux-musl/*.tar.gz oci-image-arm64v8.tar.gz
|
||||
mv -v oci-image-x86_64-linux-musl-debug/*.tar.gz oci-image-amd64-debug.tar.gz
|
||||
mv -v oci-image-aarch64-linux-musl-debug/*.tar.gz oci-image-arm64v8-debug.tar.gz
|
||||
|
||||
- name: Load and push amd64 image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
|
||||
@@ -24,8 +24,11 @@ env:
|
||||
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
|
||||
NIX_CONFIG: |
|
||||
show-trace = true
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-substituters = extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
experimental-features = nix-command flakes
|
||||
extra-experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
@@ -57,13 +60,7 @@ jobs:
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- uses: nixbuild/nix-quick-install-action@v28
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
- uses: nixbuild/nix-quick-install-action@master
|
||||
|
||||
- name: Restore and cache Nix store
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
@@ -86,11 +83,20 @@ jobs:
|
||||
# always save the cache
|
||||
save-always: true
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
experimental-features = nix-command flakes
|
||||
extra-experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
@@ -110,23 +116,7 @@ jobs:
|
||||
|
||||
- name: Cache CI dependencies
|
||||
run: |
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache ci
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
bin/nix-build-and-cache ci
|
||||
|
||||
- name: Run lychee and markdownlint
|
||||
run: |
|
||||
@@ -135,23 +125,7 @@ jobs:
|
||||
|
||||
- name: Build documentation (book)
|
||||
run: |
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#book
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
bin/nix-build-and-cache just .#book
|
||||
|
||||
cp -r --dereference result public
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy code and vulnerability scanner on repo
|
||||
uses: aquasecurity/trivy-action@0.24.0
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
scan-type: repo
|
||||
format: sarif
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
severity: CRITICAL,HIGH,MEDIUM,LOW
|
||||
|
||||
- name: Run Trivy code and vulnerability scanner on filesystem
|
||||
uses: aquasecurity/trivy-action@0.24.0
|
||||
uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
scan-type: fs
|
||||
format: sarif
|
||||
|
||||
+24
-12
@@ -10,6 +10,13 @@ variables:
|
||||
FF_USE_FASTZIP: true
|
||||
# Print progress reports for cache and artifact transfers
|
||||
TRANSFER_METER_FREQUENCY: 5s
|
||||
NIX_CONFIG: |
|
||||
show-trace = true
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
experimental-features = nix-command flakes
|
||||
extra-experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
|
||||
# Avoid duplicate pipelines
|
||||
# See: https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines
|
||||
@@ -23,6 +30,9 @@ workflow:
|
||||
before_script:
|
||||
# Enable nix-command and flakes
|
||||
- if command -v nix > /dev/null; then echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-experimental-features = nix-command flakes" >> /etc/nix/nix.conf; fi
|
||||
# Accept flake config from "untrusted" users
|
||||
- if command -v nix > /dev/null; then echo "accept-flake-config = true" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add conduwuit binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduwuit" >> /etc/nix/nix.conf; fi
|
||||
@@ -47,6 +57,8 @@ before_script:
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix-community.cachix.org" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://aseipp-nix-cache.freetls.fastly.net" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Install direnv and nix-direnv
|
||||
- if command -v nix > /dev/null; then nix-env -iA nixpkgs.direnv nixpkgs.nix-direnv; fi
|
||||
|
||||
@@ -58,7 +70,7 @@ before_script:
|
||||
|
||||
ci:
|
||||
stage: ci
|
||||
image: nixos/nix:2.23.3
|
||||
image: nixos/nix:2.24.9
|
||||
script:
|
||||
# Cache CI dependencies
|
||||
- ./bin/nix-build-and-cache ci
|
||||
@@ -83,31 +95,31 @@ ci:
|
||||
|
||||
artifacts:
|
||||
stage: artifacts
|
||||
image: nixos/nix:2.23.3
|
||||
image: nixos/nix:2.24.9
|
||||
script:
|
||||
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl
|
||||
- cp result/bin/conduit x86_64-unknown-linux-musl
|
||||
- ./bin/nix-build-and-cache just .#static-x86_64-linux-musl
|
||||
- cp result/bin/conduit x86_64-linux-musl
|
||||
|
||||
- mkdir -p target/release
|
||||
- cp result/bin/conduit target/release
|
||||
- direnv exec . cargo deb --no-build --no-strip
|
||||
- mv target/debian/*.deb x86_64-unknown-linux-musl.deb
|
||||
- mv target/debian/*.deb x86_64-linux-musl.deb
|
||||
|
||||
# Since the OCI image package is based on the binary package, this has the
|
||||
# fun side effect of uploading the normal binary too. Conduit users who are
|
||||
# deploying with Nix can leverage this fact by adding our binary cache to
|
||||
# their systems.
|
||||
#
|
||||
# Note that although we have an `oci-image-x86_64-unknown-linux-musl`
|
||||
# Note that although we have an `oci-image-x86_64-linux-musl`
|
||||
# output, we don't build it because it would be largely redundant to this
|
||||
# one since it's all containerized anyway.
|
||||
- ./bin/nix-build-and-cache just .#oci-image
|
||||
- cp result oci-image-amd64.tar.gz
|
||||
|
||||
- ./bin/nix-build-and-cache just .#static-aarch64-unknown-linux-musl
|
||||
- cp result/bin/conduit aarch64-unknown-linux-musl
|
||||
- ./bin/nix-build-and-cache just .#static-aarch64-linux-musl
|
||||
- cp result/bin/conduit aarch64-linux-musl
|
||||
|
||||
- ./bin/nix-build-and-cache just .#oci-image-aarch64-unknown-linux-musl
|
||||
- ./bin/nix-build-and-cache just .#oci-image-aarch64-linux-musl
|
||||
- cp result oci-image-arm64v8.tar.gz
|
||||
|
||||
- ./bin/nix-build-and-cache just .#book
|
||||
@@ -115,9 +127,9 @@ artifacts:
|
||||
- cp -r --dereference result public
|
||||
artifacts:
|
||||
paths:
|
||||
- x86_64-unknown-linux-musl
|
||||
- aarch64-unknown-linux-musl
|
||||
- x86_64-unknown-linux-musl.deb
|
||||
- x86_64-linux-musl
|
||||
- aarch64-linux-musl
|
||||
- x86_64-linux-musl.deb
|
||||
- oci-image-amd64.tar.gz
|
||||
- oci-image-arm64v8.tar.gz
|
||||
- public
|
||||
|
||||
+6
-3
@@ -1,7 +1,7 @@
|
||||
# Contributing guide
|
||||
|
||||
This page is for about contributing to conduwuit. The
|
||||
[development](development.md) page may be of interest for you as well.
|
||||
[development](./development.md) page may be of interest for you as well.
|
||||
|
||||
If you would like to work on an [issue][issues] that is not assigned, preferably
|
||||
ask in the Matrix room first at [#conduwuit:puppygock.gay][conduwuit-matrix],
|
||||
@@ -67,7 +67,7 @@ failing from your changes, please review the logs (they are uploaded as
|
||||
artifacts) and determine if they're intended or not.
|
||||
|
||||
If you'd like to run Complement locally using Nix, see the
|
||||
[testing](docs/development/testing.md) page.
|
||||
[testing](development/testing.md) page.
|
||||
|
||||
[Sytest][sytest] support will come soon.
|
||||
|
||||
@@ -128,7 +128,10 @@ 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.
|
||||
in line with the Contributor's Covenant, and conduwuit's Code of Conduct.
|
||||
|
||||
Contribution by users who violate either of these code of conducts will not have
|
||||
their contributions accepted.
|
||||
|
||||
[issues]: https://github.com/girlbossceo/conduwuit/issues
|
||||
[conduwuit-matrix]: https://matrix.to/#/#conduwuit:puppygock.gay
|
||||
|
||||
Generated
+341
-390
File diff suppressed because it is too large
Load Diff
+31
-22
@@ -19,8 +19,8 @@ license = "Apache-2.0"
|
||||
# See also `rust-toolchain.toml`
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/girlbossceo/conduwuit"
|
||||
rust-version = "1.80.1"
|
||||
version = "0.4.6"
|
||||
rust-version = "1.82.0"
|
||||
version = "0.4.7"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "conduit"
|
||||
@@ -69,7 +69,7 @@ version = "0.8.5"
|
||||
|
||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||
[workspace.dependencies.bytes]
|
||||
version = "1.7.1"
|
||||
version = "1.7.2"
|
||||
|
||||
[workspace.dependencies.http-body-util]
|
||||
version = "0.1.1"
|
||||
@@ -101,22 +101,21 @@ features = ["typed-header", "tracing"]
|
||||
[workspace.dependencies.axum-server]
|
||||
version = "0.7.1"
|
||||
default-features = false
|
||||
features = ["tls-rustls"]
|
||||
|
||||
# to listen on both HTTP and HTTPS if listening on TLS dierctly from conduwuit for complement or sytest
|
||||
[workspace.dependencies.axum-server-dual-protocol]
|
||||
version = "0.7"
|
||||
|
||||
[workspace.dependencies.axum-client-ip]
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
|
||||
[workspace.dependencies.tower]
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
default-features = false
|
||||
features = ["util"]
|
||||
|
||||
[workspace.dependencies.tower-http]
|
||||
version = "0.5.2"
|
||||
version = "0.6.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"add-extension",
|
||||
@@ -129,10 +128,10 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.rustls]
|
||||
version = "0.23.12"
|
||||
version = "0.23.13"
|
||||
|
||||
[workspace.dependencies.reqwest]
|
||||
version = "0.12.7"
|
||||
version = "0.12.8"
|
||||
default-features = false
|
||||
features = [
|
||||
"rustls-tls-native-roots",
|
||||
@@ -142,7 +141,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
version = "1.0.204"
|
||||
version = "1.0.209"
|
||||
default-features = false
|
||||
features = ["rc"]
|
||||
|
||||
@@ -200,7 +199,7 @@ default-features = false
|
||||
|
||||
# used for conduit's CLI and admin room command parsing
|
||||
[workspace.dependencies.clap]
|
||||
version = "4.5.15"
|
||||
version = "4.5.20"
|
||||
default-features = false
|
||||
features = [
|
||||
"std",
|
||||
@@ -216,7 +215,7 @@ version = "0.3.30"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
version = "1.39.2"
|
||||
version = "1.40.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"fs",
|
||||
@@ -248,7 +247,7 @@ features = ["alloc", "std"]
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.hyper]
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"server",
|
||||
@@ -257,7 +256,8 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.hyper-util]
|
||||
version = "0.1.6"
|
||||
# 0.1.9 causes DNS issues
|
||||
version = "=0.1.8"
|
||||
default-features = false
|
||||
features = [
|
||||
"client",
|
||||
@@ -314,7 +314,7 @@ version = "0.1.2"
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://github.com/girlbossceo/ruwuma"
|
||||
#branch = "conduwuit-changes"
|
||||
rev = "d7ddcd036f81edb257ab9371f9cadd46444e8a90"
|
||||
rev = "9900d0676564883cfade556d6e8da2a2c9061efd"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
@@ -329,18 +329,22 @@ features = [
|
||||
"ring-compat",
|
||||
"identifiers-validation",
|
||||
"unstable-unspecified",
|
||||
"unstable-msc2409",
|
||||
"unstable-msc2448",
|
||||
"unstable-msc2666",
|
||||
"unstable-msc2867",
|
||||
"unstable-msc2870",
|
||||
"unstable-msc3026",
|
||||
"unstable-msc3061",
|
||||
"unstable-msc3245",
|
||||
"unstable-msc3266",
|
||||
"unstable-msc3381", # polls
|
||||
"unstable-msc3489", # beacon / live location
|
||||
"unstable-msc3575",
|
||||
"unstable-msc4075",
|
||||
"unstable-msc4121",
|
||||
"unstable-msc4125",
|
||||
"unstable-msc4186",
|
||||
"unstable-extensible-events",
|
||||
]
|
||||
|
||||
@@ -403,17 +407,17 @@ version = "0.34.0"
|
||||
# jemalloc usage
|
||||
[workspace.dependencies.tikv-jemalloc-sys]
|
||||
git = "https://github.com/girlbossceo/jemallocator"
|
||||
rev = "c32af15f3b440ae5e46c3404f78b19093bbd5294"
|
||||
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
|
||||
default-features = false
|
||||
features = ["unprefixed_malloc_on_supported_platforms"]
|
||||
[workspace.dependencies.tikv-jemallocator]
|
||||
git = "https://github.com/girlbossceo/jemallocator"
|
||||
rev = "c32af15f3b440ae5e46c3404f78b19093bbd5294"
|
||||
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
|
||||
default-features = false
|
||||
features = ["unprefixed_malloc_on_supported_platforms"]
|
||||
[workspace.dependencies.tikv-jemalloc-ctl]
|
||||
git = "https://github.com/girlbossceo/jemallocator"
|
||||
rev = "c32af15f3b440ae5e46c3404f78b19093bbd5294"
|
||||
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
|
||||
default-features = false
|
||||
features = ["use_std"]
|
||||
|
||||
@@ -442,14 +446,14 @@ version = "0.4.3"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.termimad]
|
||||
version = "0.30.0"
|
||||
version = "0.30.1"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.checked_ops]
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.syn]
|
||||
version = "2.0.72"
|
||||
version = "2.0.76"
|
||||
default-features = false
|
||||
features = ["full", "extra-traits"]
|
||||
|
||||
@@ -457,7 +461,7 @@ features = ["full", "extra-traits"]
|
||||
version = "1.0.36"
|
||||
|
||||
[workspace.dependencies.proc-macro2]
|
||||
version = "1.0.86"
|
||||
version = "1.0.89"
|
||||
|
||||
|
||||
#
|
||||
@@ -608,7 +612,7 @@ inherits = "release"
|
||||
# and can be raised if build times are tolerable.
|
||||
|
||||
[profile.dev]
|
||||
debug = 1
|
||||
debug = "full"
|
||||
opt-level = 0
|
||||
panic = "unwind"
|
||||
debug-assertions = true
|
||||
@@ -715,12 +719,16 @@ opt-level = 'z'
|
||||
# primarily used for CI
|
||||
[profile.test]
|
||||
inherits = "dev"
|
||||
strip = false
|
||||
opt-level = 0
|
||||
codegen-units = 16
|
||||
incremental = false
|
||||
|
||||
[profile.test.package.'*']
|
||||
inherits = "dev"
|
||||
debug = 0
|
||||
strip = false
|
||||
opt-level = 0
|
||||
codegen-units = 16
|
||||
incremental = false
|
||||
|
||||
@@ -808,6 +816,7 @@ significant_drop_tightening = { level = "allow", priority = 1 } # TODO
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
|
||||
## some sadness
|
||||
too_long_first_doc_paragraph = { level = "allow", priority = 1 }
|
||||
doc_markdown = { level = "allow", priority = 1 }
|
||||
enum_glob_use = { level = "allow", priority = 1 }
|
||||
if_not_else = { level = "allow", priority = 1 }
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
# conduwuit
|
||||
|
||||
`main` / stable: [](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
|
||||
|
||||
<!-- ANCHOR: catchphrase --> ### a very cool, featureful fork of
|
||||
[Conduit](https://conduit.rs/) <!-- ANCHOR_END: catchphrase -->
|
||||
<!-- ANCHOR: catchphrase -->
|
||||
|
||||
Visit the [Conduwuit documentation](https://conduwuit.puppyirl.gay/) for more
|
||||
### a very cool, featureful fork of [Conduit](https://conduit.rs/)
|
||||
|
||||
<!-- ANCHOR_END: catchphrase -->
|
||||
|
||||
Visit the [conduwuit documentation](https://conduwuit.puppyirl.gay/) for more
|
||||
information.
|
||||
|
||||
<!-- ANCHOR: body --> #### What is Matrix?
|
||||
<!-- ANCHOR: body -->
|
||||
|
||||
#### What is Matrix?
|
||||
|
||||
[Matrix](https://matrix.org) is an open network for secure and decentralized
|
||||
communication. Users from every Matrix homeserver can chat with users from all
|
||||
@@ -18,9 +23,9 @@ to communicate with users outside of Matrix, like a community on Discord.
|
||||
|
||||
#### What is the goal?
|
||||
|
||||
An efficient Matrix homeserver that's easy to set up and just works. You can
|
||||
install it on a mini-computer like the Raspberry Pi to host Matrix for your
|
||||
family, friends or company.
|
||||
A high-performance and efficient Matrix homeserver that's easy to set up and
|
||||
just works. You can install it on a mini-computer like the Raspberry Pi to
|
||||
host Matrix for your family, friends or company.
|
||||
|
||||
#### Can I try it out?
|
||||
|
||||
@@ -37,13 +42,34 @@ transfem.dev is also listed at
|
||||
|
||||
#### What is the current status?
|
||||
|
||||
conduwuit is a hard fork of Conduit which is in beta, meaning you can join and
|
||||
participate in most Matrix rooms, but not all features are supported and you
|
||||
might run into bugs from time to time.
|
||||
conduwuit is technically a hard fork of Conduit, which is in Beta. The Beta status
|
||||
initially was inherited from Conduit, however overtime this Beta status is rapidly
|
||||
becoming less and less relevant as our codebase significantly diverges more and more.
|
||||
|
||||
conduwuit is quite stable and very usable as a daily driver and for a low-medium
|
||||
sized homeserver. There is still a lot of more work to be done, but it is in a far
|
||||
better place than the project was in early 2024.
|
||||
|
||||
#### How is conduwuit funded? Is conduwuit sustainable?
|
||||
|
||||
conduwuit has no external funding. This is made possible purely in my freetime with
|
||||
contributors, also in their free time, and only by user-curated donations.
|
||||
|
||||
conduwuit has existed since around November 2023, but [only became more publicly known
|
||||
in March/April 2024](https://matrix.org/blog/2024/04/26/this-week-in-matrix-2024-04-26/#conduwuit-website)
|
||||
and we have no plans in stopping or slowing down any time soon!
|
||||
|
||||
#### Can I migrate or switch from Conduit?
|
||||
|
||||
conduwuit is a complete drop-in replacement for Conduit. As long as you are using RocksDB,
|
||||
the only "migration" you need to do is replace the binary or container image. There
|
||||
is no harm or additional steps required for using conduwuit.
|
||||
|
||||
<!-- ANCHOR_END: body -->
|
||||
|
||||
<!-- ANCHOR: footer --> #### Contact
|
||||
<!-- ANCHOR: footer -->
|
||||
|
||||
#### Contact
|
||||
|
||||
If you run into any question, feel free to
|
||||
|
||||
@@ -52,8 +78,12 @@ If you run into any question, feel free to
|
||||
|
||||
#### Donate
|
||||
|
||||
conduwuit development is purely made possible by myself and contributors. I do
|
||||
not get paid to work on this, and I work on it in my free time. Donations are
|
||||
heavily appreciated! 💜🥺
|
||||
|
||||
- Liberapay: <https://liberapay.com/girlbossceo>
|
||||
- Ko-fi: <https://ko-fi.com/puppygock>
|
||||
- Ko-fi (note they take a fee): <https://ko-fi.com/puppygock>
|
||||
- GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
|
||||
|
||||
#### Logo
|
||||
@@ -73,5 +103,6 @@ Both, but I prefer conduwuit.
|
||||
- git.girlcock.ceo: <https://git.girlcock.ceo/strawberry/conduwuit>
|
||||
- git.gay: <https://git.gay/june/conduwuit>
|
||||
- Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
|
||||
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit> <!-- ANCHOR_END: footer
|
||||
-->
|
||||
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
|
||||
|
||||
<!-- ANCHOR_END: footer -->
|
||||
|
||||
@@ -15,7 +15,7 @@ DevicePolicy=closed
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
ProcSubset=pid
|
||||
#ProcSubset=pid
|
||||
ProtectClock=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ LOG_FILE="$2"
|
||||
# A `.jsonl` file to write test results to
|
||||
RESULTS_FILE="$3"
|
||||
|
||||
OCI_IMAGE="complement-conduit:main"
|
||||
OCI_IMAGE="complement-conduwuit:main"
|
||||
|
||||
# Complement tests that are skipped due to flakiness/reliability issues
|
||||
SKIPPED_COMPLEMENT_TESTS='-skip=TestClientSpacesSummary.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestJumpToDateEndpoint.*'
|
||||
@@ -34,7 +34,7 @@ toplevel="$(git rev-parse --show-toplevel)"
|
||||
|
||||
pushd "$toplevel" > /dev/null
|
||||
|
||||
bin/nix-build-and-cache just .#static-complement
|
||||
bin/nix-build-and-cache just .#linux-complement
|
||||
|
||||
docker load < result
|
||||
popd > /dev/null
|
||||
|
||||
+13
-7
@@ -26,7 +26,12 @@ just() {
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
# Find all output paths of the installables and their build dependencies
|
||||
readarray -t derivations < <(nix path-info --derivation "$@")
|
||||
#readarray -t derivations < <(nix path-info --derivation "$@")
|
||||
derivations=()
|
||||
while IFS=$'\n' read derivation; do
|
||||
derivations+=("$derivation")
|
||||
done < <(nix path-info --derivation "$@")
|
||||
|
||||
cache=()
|
||||
for derivation in "${derivations[@]}"; do
|
||||
cache+=(
|
||||
@@ -34,6 +39,9 @@ just() {
|
||||
)
|
||||
done
|
||||
|
||||
withattic() {
|
||||
nix shell --inputs-from "$toplevel" attic --command xargs attic push "$@" <<< "${cache[*]}"
|
||||
}
|
||||
# Upload them to Attic (conduit store)
|
||||
#
|
||||
# Use `xargs` and a here-string because something would probably explode if
|
||||
@@ -41,8 +49,7 @@ just() {
|
||||
# store paths include a newline in them.
|
||||
(
|
||||
IFS=$'\n'
|
||||
nix shell --inputs-from "$toplevel" attic -c xargs \
|
||||
attic push conduit <<< "${cache[*]}"
|
||||
withattic conduit || withattic conduit || withattic conduit || true
|
||||
)
|
||||
|
||||
# main "conduwuit" store
|
||||
@@ -59,8 +66,7 @@ just() {
|
||||
# store paths include a newline in them.
|
||||
(
|
||||
IFS=$'\n'
|
||||
nix shell --inputs-from "$toplevel" attic -c xargs \
|
||||
attic push conduwuit <<< "${cache[*]}"
|
||||
withattic conduwuit || withattic conduwuit || withattic conduwuit || true
|
||||
|
||||
# push to cachix if available
|
||||
if [ "$CACHIX_AUTH_TOKEN" ]; then
|
||||
@@ -76,8 +82,8 @@ ci() {
|
||||
--inputs-from "$toplevel"
|
||||
|
||||
# Keep sorted
|
||||
"$toplevel#devShells.x86_64-linux.default"
|
||||
"$toplevel#devShells.x86_64-linux.all-features"
|
||||
#"$toplevel#devShells.x86_64-linux.default"
|
||||
#"$toplevel#devShells.x86_64-linux.all-features"
|
||||
attic#default
|
||||
cachix#default
|
||||
nixpkgs#direnv
|
||||
|
||||
+78
-6
@@ -68,6 +68,10 @@
|
||||
# only effective in release-mode; forced to false in debug-mode.
|
||||
#sentry_send_error = true
|
||||
|
||||
# Controls the tracing log level for Sentry to send things like breadcrumbs and transactions
|
||||
# Defaults to "info"
|
||||
#sentry_filter = "info"
|
||||
|
||||
|
||||
### Database configuration
|
||||
|
||||
@@ -160,6 +164,19 @@ ip_range_denylist = [
|
||||
|
||||
### Moderation / Privacy / Security
|
||||
|
||||
# Config option to control whether the legacy unauthenticated Matrix media repository endpoints will be enabled.
|
||||
# These endpoints consist of:
|
||||
# - /_matrix/media/*/config
|
||||
# - /_matrix/media/*/upload
|
||||
# - /_matrix/media/*/preview_url
|
||||
# - /_matrix/media/*/download/*
|
||||
# - /_matrix/media/*/thumbnail/*
|
||||
#
|
||||
# The authenticated equivalent endpoints are always enabled.
|
||||
#
|
||||
# Defaults to true for now, but this is highly subject to change, likely in the next release.
|
||||
#allow_legacy_media = true
|
||||
|
||||
# Set to true to allow user type "guest" registrations. Element attempts to register guest users automatically.
|
||||
# Defaults to false
|
||||
allow_guest_registration = false
|
||||
@@ -207,11 +224,6 @@ registration_token = "change this token for something specific to your server"
|
||||
# defaults to false
|
||||
# block_non_admin_invites = false
|
||||
|
||||
# Allows admins to enter commands in rooms other than #admins by prefixing with \!admin. The reply
|
||||
# will be publicly visible to the room, originating from the sender.
|
||||
# defaults to true
|
||||
#admin_escape_commands = true
|
||||
|
||||
# List of forbidden username patterns/strings. Values in this list are matched as *contains*.
|
||||
# This is checked upon username availability check, registration, and startup as warnings if any local users in your database
|
||||
# have a forbidden username.
|
||||
@@ -305,6 +317,49 @@ allow_profile_lookup_federation_requests = true
|
||||
#auto_deactivate_banned_room_attempts = false
|
||||
|
||||
|
||||
### Admin Room and Console
|
||||
|
||||
# Controls whether the conduwuit admin room console / CLI will immediately activate on startup.
|
||||
# This option can also be enabled with `--console` conduwuit argument
|
||||
#
|
||||
# Defaults to false
|
||||
#admin_console_automatic = false
|
||||
|
||||
# Controls what admin commands will be executed on startup. This is a vector list of strings of admin commands to run.
|
||||
#
|
||||
# An example of this can be: `admin_execute = ["debug ping puppygock.gay", "debug echo hi"]`
|
||||
#
|
||||
# This option can also be configured with the `--execute` conduwuit argument and can take standard shell commands and environment variables
|
||||
#
|
||||
# Such example could be: `./conduwuit --execute "server admin-notice conduwuit has started up at $(date)"`
|
||||
#
|
||||
# Defaults to nothing.
|
||||
#admin_execute = [""]
|
||||
|
||||
# Controls whether conduwuit should error and fail to start if an admin execute command (`--execute` / `admin_execute`) fails
|
||||
#
|
||||
# Defaults to false
|
||||
#admin_execute_errors_ignore = false
|
||||
|
||||
# Controls the max log level for admin command log captures (logs generated from running admin commands)
|
||||
#
|
||||
# Defaults to "info" on release builds, else "debug" on debug builds
|
||||
#admin_log_capture = info
|
||||
|
||||
# Allows admins to enter commands in rooms other than #admins by prefixing with \!admin. The reply
|
||||
# will be publicly visible to the room, originating from the sender.
|
||||
# defaults to true
|
||||
#admin_escape_commands = true
|
||||
|
||||
# Controls whether admin room notices like account registrations, password changes, account deactivations,
|
||||
# room directory publications, etc will be sent to the admin room.
|
||||
#
|
||||
# Update notices and normal admin command responses will still be sent.
|
||||
#
|
||||
# defaults to true
|
||||
#admin_room_notices = true
|
||||
|
||||
|
||||
### Misc
|
||||
|
||||
# max log level for conduwuit. allows debug, info, warn, or error
|
||||
@@ -316,6 +371,11 @@ allow_profile_lookup_federation_requests = true
|
||||
# Defaults to "info"
|
||||
#log = "info"
|
||||
|
||||
# controls whether logs will be outputted with ANSI colours
|
||||
#
|
||||
# defaults to true
|
||||
#log_colors = true
|
||||
|
||||
# controls whether encrypted rooms and events are allowed (default true)
|
||||
#allow_encryption = false
|
||||
|
||||
@@ -434,6 +494,7 @@ allow_profile_lookup_federation_requests = true
|
||||
### Generic database options
|
||||
|
||||
# Set this to any float value to multiply conduwuit's in-memory LRU caches with.
|
||||
# By default, the caches scale automatically with cpu-core-count.
|
||||
# May be useful if you have significant memory to spare to increase performance.
|
||||
#
|
||||
# This was previously called `conduit_cache_capacity_modifier`
|
||||
@@ -443,7 +504,7 @@ allow_profile_lookup_federation_requests = true
|
||||
|
||||
# Set this to any float value in megabytes for conduwuit to tell the database engine that this much memory is available for database-related caches.
|
||||
# May be useful if you have significant memory to spare to increase performance.
|
||||
# Defaults to 256.0
|
||||
# Defaults to 128.0 + (64.0 * CPU core count).
|
||||
#db_cache_capacity_mb = 256.0
|
||||
|
||||
|
||||
@@ -797,9 +858,20 @@ allow_profile_lookup_federation_requests = true
|
||||
|
||||
# vector list of TURN URIs/servers to use
|
||||
#
|
||||
# replace "example.turn.uri" with your TURN domain, such as the coturn "realm".
|
||||
# if using TURN over TLS, replace "turn:" with "turns:"
|
||||
#
|
||||
# No default
|
||||
#turn_uris = ["turn:example.turn.uri?transport=udp", "turn:example.turn.uri?transport=tcp"]
|
||||
|
||||
# TURN secret to use that's read from the file path specified
|
||||
#
|
||||
# this takes priority over "turn_secret" first, and falls back to "turn_secret" if invalid or
|
||||
# failed to open.
|
||||
#
|
||||
# no default
|
||||
#turn_secret_file = "/path/to/secret.txt"
|
||||
|
||||
# TURN secret to use for generating the HMAC-SHA1 hash apart of username and password generation
|
||||
#
|
||||
# this is more secure, but if needed you can use traditional username/password below.
|
||||
|
||||
Vendored
+1
-1
@@ -22,7 +22,7 @@ DevicePolicy=closed
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
ProcSubset=pid
|
||||
#ProcSubset=pid
|
||||
ProtectClock=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
|
||||
Vendored
+1
-1
@@ -27,7 +27,7 @@ malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
|
||||
|
||||
[dependencies.rust-rocksdb]
|
||||
git = "https://github.com/girlbossceo/rust-rocksdb-zaidoon1"
|
||||
rev = "5383ca8173299066b516406e3a2cf945ead891cb"
|
||||
rev = "c1e5523eae095a893deaf9056128c7dbc2d5fd73"
|
||||
#branch = "master"
|
||||
default-features = false
|
||||
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
docs/development.md
|
||||
@@ -9,7 +9,7 @@ environment for everyone. This Code of Conduct applies to all conduwuit spaces,
|
||||
including any further community rooms that reference this CoC. Here are our
|
||||
guidelines to help maintain the welcoming atmosphere that sets conduwuit apart.
|
||||
|
||||
For the general foundational rules, please refer to the [Contributor's
|
||||
For the general foundational rules, please refer to the [Contributor's
|
||||
Covenant](https://github.com/girlbossceo/conduwuit/blob/main/CODE_OF_CONDUCT.md).
|
||||
Below are additional guidelines specific to the conduwuit community.
|
||||
|
||||
@@ -90,4 +90,4 @@ comfortable doing that, then please send a DM to one of the moderators directly.
|
||||
|
||||
Together, let’s build a community where everyone feels valued and respected.
|
||||
|
||||
- The conduwuit Moderation Team
|
||||
— The conduwuit Moderation Team
|
||||
|
||||
@@ -31,6 +31,22 @@ string. This does not apply to options that take booleans or numbers:
|
||||
- `--option log=\"debug\"` works ✅
|
||||
- `--option server_name='"example.com'"` works ✅
|
||||
|
||||
## Execute commandline flag
|
||||
|
||||
conduwuit supports running admin commands on startup using the commandline
|
||||
argument `--execute`. The most notable use for this is to create an admin user
|
||||
on first startup.
|
||||
|
||||
The syntax of this is a standard admin command without the prefix such as
|
||||
`./conduwuit --execute "users create_user june"`
|
||||
|
||||
An example output of a success is:
|
||||
```
|
||||
INFO conduit_service::admin::startup: Startup command #0 completed:
|
||||
Created user with user_id: @june:girlboss.ceo and password: `<redacted>`
|
||||
```
|
||||
|
||||
This commandline argument can be paired with the `--option` flag.
|
||||
|
||||
## Environment variables
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{{#include ../CONTRIBUTING.md}}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../CONTRIBUTING.md
|
||||
@@ -1,40 +1,44 @@
|
||||
# conduwuit - Behind Traefik Reverse Proxy
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
### If you already built the conduduwit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUWUIT_LOG: warn,state_res=warn
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
homeserver:
|
||||
### If you already built the conduduwit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
CONDUWUIT_SERVER_NAME: your.server.name.example # EDIT THIS
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUWUIT_PORT: 6167 # should match the loadbalancer traefik label
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUWUIT_LOG: warn,state_res=warn
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
|
||||
# We need some way to serve the client and server .well-known json. The simplest way is via the CONDUWUIT_WELL_KNOWN
|
||||
# variable / config option, there are multiple ways to do this, e.g. in the conduwuit.toml file, and in a seperate
|
||||
# see the override file for more information about delegation
|
||||
CONDUWUIT_WELL_KNOWN: |
|
||||
{
|
||||
client=https://your.server.name.example,
|
||||
server=your.server.name.example:443
|
||||
}
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
ulimits: # conduwuit uses quite a few file descriptors, and on some systems it defaults to 1024, so you can tell docker to increase it
|
||||
nofile:
|
||||
soft: 1048567
|
||||
hard: 1048567
|
||||
|
||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
|
||||
# and in the docker compose override file.
|
||||
well-known:
|
||||
image: nginx:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./nginx/matrix.conf:/etc/nginx/conf.d/matrix.conf # the config to serve the .well-known/matrix files
|
||||
- ./nginx/www:/var/www/ # location of the client and server .well-known-files
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and conduwuit
|
||||
@@ -50,10 +54,12 @@ services:
|
||||
# - homeserver
|
||||
|
||||
volumes:
|
||||
db:
|
||||
db:
|
||||
|
||||
networks:
|
||||
# This is the network Traefik listens to, if your network has a different
|
||||
# name, don't forget to change it here and in the docker-compose.override.yml
|
||||
proxy:
|
||||
external: true
|
||||
# This is the network Traefik listens to, if your network has a different
|
||||
# name, don't forget to change it here and in the docker-compose.override.yml
|
||||
proxy:
|
||||
external: true
|
||||
|
||||
# vim: ts=2:sw=2:expandtab
|
||||
|
||||
@@ -1,44 +1,37 @@
|
||||
# conduwuit - Traefik Reverse Proxy Labels
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
|
||||
homeserver:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
|
||||
|
||||
- "traefik.http.routers.to-conduwuit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which conduwuit is hosted
|
||||
- "traefik.http.routers.to-conduwuit.tls=true"
|
||||
- "traefik.http.routers.to-conduwuit.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.to-conduwuit.middlewares=cors-headers@docker"
|
||||
- "traefik.http.routers.to-conduwuit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which conduwuit is hosted
|
||||
- "traefik.http.routers.to-conduwuit.tls=true"
|
||||
- "traefik.http.routers.to-conduwuit.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.to-conduwuit.middlewares=cors-headers@docker"
|
||||
- "traefik.http.services.to_conduwuit.loadbalancer.server.port=6167"
|
||||
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowMethods=GET, POST, PUT, DELETE, OPTIONS"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowMethods=GET, POST, PUT, DELETE, OPTIONS"
|
||||
|
||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
|
||||
# and in the docker compose file.
|
||||
well-known:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=proxy"
|
||||
# If you want to have your account on <DOMAIN>, but host conduwuit on a subdomain,
|
||||
# you can let it only handle the well known file on that domain instead
|
||||
#- "traefik.http.routers.to-matrix-wellknown.rule=Host(`<DOMAIN>`) && PathPrefix(`/.well-known/matrix`)"
|
||||
#- "traefik.http.routers.to-matrix-wellknown.tls=true"
|
||||
#- "traefik.http.routers.to-matrix-wellknown.tls.certresolver=letsencrypt"
|
||||
#- "traefik.http.routers.to-matrix-wellknown.middlewares=cors-headers@docker"
|
||||
|
||||
- "traefik.http.routers.to-matrix-wellknown.rule=Host(`<SUBDOMAIN>.<DOMAIN>`) && PathPrefix(`/.well-known/matrix`)"
|
||||
- "traefik.http.routers.to-matrix-wellknown.tls=true"
|
||||
- "traefik.http.routers.to-matrix-wellknown.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.to-matrix-wellknown.middlewares=cors-headers@docker"
|
||||
### Uncomment this if you uncommented Element-Web App in the docker-compose.yml
|
||||
# element-web:
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
|
||||
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowMethods=GET, POST, PUT, DELETE, OPTIONS"
|
||||
# - "traefik.http.routers.to-element-web.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which Element-Web is hosted
|
||||
# - "traefik.http.routers.to-element-web.tls=true"
|
||||
# - "traefik.http.routers.to-element-web.tls.certresolver=letsencrypt"
|
||||
|
||||
# vim: ts=2:sw=2:expandtab
|
||||
|
||||
### Uncomment this if you uncommented Element-Web App in the docker-compose.yml
|
||||
# element-web:
|
||||
# labels:
|
||||
# - "traefik.enable=true"
|
||||
# - "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
|
||||
|
||||
# - "traefik.http.routers.to-element-web.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which Element-Web is hosted
|
||||
# - "traefik.http.routers.to-element-web.tls=true"
|
||||
# - "traefik.http.routers.to-element-web.tls.certresolver=letsencrypt"
|
||||
|
||||
@@ -1,42 +1,52 @@
|
||||
# conduwuit - Behind Traefik Reverse Proxy
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
### If you already built the conduwuit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/srv/conduwuit/.local/share/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
CONDUWUIT_ALLOW_REGISTRATION : 'true'
|
||||
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
### Uncomment and change values as desired
|
||||
# CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
# CONDUWUIT_PORT: 6167
|
||||
# CONDUWUIT_LOG: info # default is: "warn,state_res=warn"
|
||||
# CONDUWUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUWUIT_ALLOW_ENCRYPTION: 'true'
|
||||
# CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
# CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# CONDUWUIT_DATABASE_PATH: /srv/conduwuit/.local/share/conduwuit
|
||||
# CONDUWUIT_WORKERS: 10
|
||||
# CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
||||
homeserver:
|
||||
### If you already built the conduwuit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
CONDUWUIT_SERVER_NAME: your.server.name.example # EDIT THIS
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'false' # After setting a secure registration token, you can enable this
|
||||
CONDUWUIT_REGISTRATION_TOKEN: # This is a token you can use to register on the server
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
CONDUWUIT_PORT: 6167 # you need to match this with the traefik load balancer label if you're want to change it
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
#CONDUWUIT_CONFIG: '/etc/conduit.toml' # Uncomment if you mapped config toml above
|
||||
### Uncomment and change values as desired, note that conduwuit has plenty of config options, so you should check out the example example config too
|
||||
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
|
||||
# CONDUWUIT_LOG: info # default is: "warn,state_res=warn"
|
||||
# CONDUWUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUWUIT_ALLOW_ENCRYPTION: 'true'
|
||||
# CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
# CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# CONDUWUIT_ALLOW_INCOMING_PRESENCE: true
|
||||
# CONDUWUIT_ALLOW_OUTGOING_PRESENCE: true
|
||||
# CONDUWUIT_ALLOW_LOCAL_PRESENCE: true
|
||||
# CONDUWUIT_WORKERS: 10
|
||||
# CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
# CONDUWUIT_NEW_USER_DISPLAYNAME_SUFFIX = "🏳<200d>⚧"
|
||||
|
||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
|
||||
# and in the docker compose override file.
|
||||
well-known:
|
||||
image: nginx:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./nginx/matrix.conf:/etc/nginx/conf.d/matrix.conf # the config to serve the .well-known/matrix files
|
||||
- ./nginx/www:/var/www/ # location of the client and server .well-known-files
|
||||
# We need some way to serve the client and server .well-known json. The simplest way is via the CONDUWUIT_WELL_KNOWN
|
||||
# variable / config option, there are multiple ways to do this, e.g. in the conduwuit.toml file, and in a seperate
|
||||
# reverse proxy, but since you do not have a reverse proxy and following this guide, this example is included
|
||||
CONDUWUIT_WELL_KNOWN: |
|
||||
{
|
||||
client=https://your.server.name.example,
|
||||
server=your.server.name.example:443
|
||||
}
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
ulimits: # conduwuit uses quite a few file descriptors, and on some systems it defaults to 1024, so you can tell docker to increase it
|
||||
nofile:
|
||||
soft: 1048567
|
||||
hard: 1048567
|
||||
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
@@ -52,29 +62,79 @@ services:
|
||||
# depends_on:
|
||||
# - homeserver
|
||||
|
||||
traefik:
|
||||
image: "traefik:latest"
|
||||
container_name: "traefik"
|
||||
restart: "unless-stopped"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
# - "./traefik_config:/etc/traefik"
|
||||
- "acme:/etc/traefik/acme"
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
traefik:
|
||||
image: "traefik:latest"
|
||||
container_name: "traefik"
|
||||
restart: "unless-stopped"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- "/var/run/docker.sock:/var/run/docker.sock:z"
|
||||
- "acme:/etc/traefik/acme"
|
||||
#- "./traefik_config:/etc/traefik:z"
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
|
||||
# middleware redirect
|
||||
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
|
||||
# global redirect to https
|
||||
- "traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)"
|
||||
- "traefik.http.routers.redirs.entrypoints=http"
|
||||
- "traefik.http.routers.redirs.middlewares=redirect-to-https"
|
||||
# middleware redirect
|
||||
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
|
||||
# global redirect to https
|
||||
- "traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)"
|
||||
- "traefik.http.routers.redirs.entrypoints=web"
|
||||
- "traefik.http.routers.redirs.middlewares=redirect-to-https"
|
||||
|
||||
networks:
|
||||
- proxy
|
||||
configs:
|
||||
- source: dynamic.yml
|
||||
target: /etc/traefik/dynamic.yml
|
||||
|
||||
environment:
|
||||
TRAEFIK_LOG_LEVEL: DEBUG
|
||||
TRAEFIK_ENTRYPOINTS_WEB: true
|
||||
TRAEFIK_ENTRYPOINTS_WEB_ADDRESS: ":80"
|
||||
TRAEFIK_ENTRYPOINTS_WEB_HTTP_REDIRECTIONS_ENTRYPOINT_TO: websecure
|
||||
|
||||
TRAEFIK_ENTRYPOINTS_WEBSECURE: true
|
||||
TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS: ":443"
|
||||
TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_TLS_CERTRESOLVER: letsencrypt
|
||||
#TRAEFIK_ENTRYPOINTS_WEBSECURE_HTTP_MIDDLEWARES: secureHeaders@file # if you want to enabled STS
|
||||
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT: true
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL: # Set this to the email you want to receive certificate expiration emails for
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_KEYTYPE: EC384
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE: true
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_HTTPCHALLENGE_ENTRYPOINT: web
|
||||
TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_STORAGE: "/etc/traefik/acme/acme.json"
|
||||
|
||||
TRAEFIK_PROVIDERS_DOCKER: true
|
||||
TRAEFIK_PROVIDERS_DOCKER_ENDPOINT: "unix:///var/run/docker.sock"
|
||||
TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT: false
|
||||
|
||||
TRAEFIK_PROVIDERS_FILE: true
|
||||
TRAEFIK_PROVIDERS_FILE_FILENAME: "/etc/traefik/dynamic.yml"
|
||||
|
||||
configs:
|
||||
dynamic.yml:
|
||||
content: |
|
||||
# Optionally set STS headers, like in https://hstspreload.org
|
||||
# http:
|
||||
# middlewares:
|
||||
# secureHeaders:
|
||||
# headers:
|
||||
# forceSTSHeader: true
|
||||
# stsIncludeSubdomains: true
|
||||
# stsPreload: true
|
||||
# stsSeconds: 31536000
|
||||
tls:
|
||||
options:
|
||||
default:
|
||||
cipherSuites:
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
|
||||
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
|
||||
minVersion: VersionTLS12
|
||||
|
||||
volumes:
|
||||
db:
|
||||
@@ -82,3 +142,5 @@ volumes:
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
|
||||
# vim: ts=2:sw=2:expandtab
|
||||
|
||||
+21
-25
@@ -9,22 +9,14 @@ from a registry.
|
||||
|
||||
OCI images for conduwuit are available in the registries listed below.
|
||||
|
||||
| Registry | Image
|
||||
| Size | Notes | | --------------- |
|
||||
--------------------------------------------------------------- |
|
||||
----------------------------- | ---------------------- | | GitHub Registry |
|
||||
[ghcr.io/girlbossceo/conduwuit:latest][gh] | ![Image Size][shield-latest] |
|
||||
Stable tagged image. | | GitLab Registry |
|
||||
[registry.gitlab.com/conduwuit/conduwuit:latest][gl] | ![Image
|
||||
Size][shield-latest] | Stable tagged image. | | Docker Hub |
|
||||
[docker.io/girlbossceo/conduwuit:latest][dh] | ![Image
|
||||
Size][shield-latest] | Stable tagged image. | | GitHub Registry |
|
||||
[ghcr.io/girlbossceo/conduwuit:main][gh] | ![Image Size][shield-main] |
|
||||
Stable main branch. | | GitLab Registry |
|
||||
[registry.gitlab.com/conduwuit/conduwuit:main][gl] | ![Image
|
||||
Size][shield-main] | Stable main branch. | | Docker Hub |
|
||||
[docker.io/girlbossceo/conduwuit:main][dh] | ![Image
|
||||
Size][shield-main] | Stable main branch. |
|
||||
| Registry | Image | Size | Notes |
|
||||
| --------------- | --------------------------------------------------------------- | ----------------------------- | ---------------------- |
|
||||
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:latest][gh] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| GitLab Registry | [registry.gitlab.com/conduwuit/conduwuit:latest][gl] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| Docker Hub | [docker.io/girlbossceo/conduwuit:latest][dh] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:main][gh] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| GitLab Registry | [registry.gitlab.com/conduwuit/conduwuit:main][gl] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| Docker Hub | [docker.io/girlbossceo/conduwuit:main][dh] | ![Image Size][shield-main] | Stable main branch. |
|
||||
|
||||
[dh]: https://hub.docker.com/r/girlbossceo/conduwuit
|
||||
[gh]: https://github.com/girlbossceo/conduwuit/pkgs/container/conduwuit
|
||||
@@ -34,7 +26,9 @@ Size][shield-main] | Stable main branch. |
|
||||
|
||||
Use
|
||||
|
||||
```bash docker image pull <link> ```
|
||||
```bash
|
||||
docker image pull $LINK
|
||||
```
|
||||
|
||||
to pull it to your machine.
|
||||
|
||||
@@ -42,13 +36,13 @@ to pull it to your machine.
|
||||
|
||||
When you have the image you can simply run it with
|
||||
|
||||
```bash
|
||||
docker run -d -p 8448:6167 \
|
||||
-v db:/var/lib/conduwuit/ \
|
||||
-e CONDUWUIT_SERVER_NAME="your.server.name" \
|
||||
-e CONDUWUIT_DATABASE_BACKEND="rocksdb" \
|
||||
```bash
|
||||
docker run -d -p 8448:6167 \
|
||||
-v db:/var/lib/conduwuit/ \
|
||||
-e CONDUWUIT_SERVER_NAME="your.server.name" \
|
||||
-e CONDUWUIT_DATABASE_BACKEND="rocksdb" \
|
||||
-e CONDUWUIT_ALLOW_REGISTRATION=false \
|
||||
--name conduit <link>
|
||||
--name conduit $LINK
|
||||
```
|
||||
|
||||
or you can use [docker compose](#docker-compose).
|
||||
@@ -88,7 +82,9 @@ 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 ```
|
||||
```bash
|
||||
docker network create caddy
|
||||
```
|
||||
|
||||
After that, you can rename it so it matches `docker-compose.yml` and spin up the
|
||||
containers!
|
||||
@@ -101,7 +97,7 @@ To build the conduwuit image with docker-compose, you first need to open and
|
||||
modify the `docker-compose.yml` file. There you need to comment the `image:`
|
||||
option and uncomment the `build:` option. Then call docker compose with:
|
||||
|
||||
```bash
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
|
||||
+82
-24
@@ -13,18 +13,38 @@ what you need.
|
||||
|
||||
Prebuilt fully static musl binaries can be downloaded from the latest tagged
|
||||
release [here](https://github.com/girlbossceo/conduwuit/releases/latest) or
|
||||
`main` CI branch workflow artifact output. These also include Debian packages.
|
||||
`main` CI branch workflow artifact output. These also include Debian/Ubuntu packages.
|
||||
|
||||
These binaries have jemalloc and io_uring statically linked and included with
|
||||
them.
|
||||
them, so no additional dynamic dependencies need to be installed.
|
||||
|
||||
Alternatively, you may compile the binary yourself. We recommend using
|
||||
[Lix](https://lix.systems) to build conduwuit as this has the most guaranteed
|
||||
reproducibiltiy and easiest to get a build environment and output going.
|
||||
Nix (or [Lix](https://lix.systems)) to build conduwuit as this has the most guaranteed
|
||||
reproducibiltiy and easiest to get a build environment and output going. This also
|
||||
allows easy cross-compilation.
|
||||
|
||||
You can run the `nix build -L .#static-x86_64-linux-musl-all-features` or
|
||||
`nix build -L .#static-aarch64-linux-musl-all-features` commands based
|
||||
on architecture to cross-compile the necessary static binary located at
|
||||
`result/bin/conduit`. This is reproducible with the static binaries produced in our CI.
|
||||
|
||||
Otherwise, follow standard Rust project build guides (installing git and cloning
|
||||
the repo, getting the Rust toolchain via rustup, installing LLVM toolchain +
|
||||
libclang for RocksDB, installing liburing for io_uring and RocksDB, etc).
|
||||
|
||||
## Migrating from Conduit
|
||||
|
||||
As mentioned in the README, there is little to no steps needed to migrate
|
||||
from Conduit. As long as you are using the RocksDB database backend, just
|
||||
replace the binary / container image / etc.
|
||||
|
||||
**Note**: If you are relying on Conduit's "automatic delegation" feature,
|
||||
this will **NOT** work on conduwuit and you must configure delegation manually.
|
||||
This is not a mistake and no support for this feature will be added.
|
||||
|
||||
See the `[global.well_known]` config section, or configure your web server
|
||||
appropriately to send the delegation responses.
|
||||
|
||||
## Adding a conduwuit user
|
||||
|
||||
While conduwuit can run as any user it is better to use dedicated users for
|
||||
@@ -34,12 +54,14 @@ are correctly set up.
|
||||
In Debian or Fedora/RHEL, you can use this command to create a conduwuit user:
|
||||
|
||||
```bash
|
||||
sudo adduser --system conduwuit --group --disabled-login --no-create-home
|
||||
sudo adduser --system conduwuit --group --disabled-login --no-create-home
|
||||
```
|
||||
|
||||
For distros without `adduser`:
|
||||
|
||||
```bash sudo useradd -r --shell /usr/bin/nologin --no-create-home conduwuit ```
|
||||
```bash
|
||||
sudo useradd -r --shell /usr/bin/nologin --no-create-home conduwuit
|
||||
```
|
||||
|
||||
## Forwarding ports in the firewall or the router
|
||||
|
||||
@@ -56,12 +78,15 @@ The systemd unit for conduwuit can be found
|
||||
[here](../configuration/examples.md#example-systemd-unit-file). You may need to
|
||||
change the `ExecStart=` path to where you placed the conduwuit binary.
|
||||
|
||||
On systems where rsyslog is used alongside journald (i.e. Red Hat-based distros and OpenSUSE), put `$EscapeControlCharactersOnReceive off` inside `/etc/rsyslog.conf` to allow color in logs.
|
||||
|
||||
## Creating the conduwuit configuration file
|
||||
|
||||
Now we need to create the conduwuit's config file in
|
||||
`/etc/conduwuit/conduwuit.toml`. The example config can be found at
|
||||
[conduwuit-example.toml](../configuration/examples.md).**Please take a moment to
|
||||
read it. You need to change at least the server name.**
|
||||
[conduwuit-example.toml](../configuration/examples.md).
|
||||
|
||||
**Please take a moment to read the config. You need to change at least the server name.**
|
||||
|
||||
RocksDB is the only supported database backend.
|
||||
|
||||
@@ -71,53 +96,81 @@ If you are using a dedicated user for conduwuit, you will need to allow it to
|
||||
read the config. To do that you can run this:
|
||||
|
||||
```bash
|
||||
sudo chown -R root:root /etc/conduwuit sudo chmod -R 755 /etc/conduwuit
|
||||
sudo chown -R root:root /etc/conduwuit
|
||||
sudo chmod -R 755 /etc/conduwuit
|
||||
```
|
||||
|
||||
If you use the default database path you also need to run this:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/lib/conduwuit/ sudo chown -R conduwuit:conduwuit
|
||||
/var/lib/conduwuit/ sudo chmod 700 /var/lib/conduwuit/
|
||||
```bash
|
||||
sudo mkdir -p /var/lib/conduwuit/
|
||||
sudo chown -R conduwuit:conduwuit /var/lib/conduwuit/
|
||||
sudo chmod 700 /var/lib/conduwuit/
|
||||
```
|
||||
|
||||
## Setting up the Reverse Proxy
|
||||
|
||||
Refer to the documentation or various guides online of your chosen reverse proxy
|
||||
software. A [Caddy](https://caddyserver.com/) example will be provided as this
|
||||
software. There are many examples of basic Apache/Nginx reverse proxy setups
|
||||
out there.
|
||||
|
||||
A [Caddy](https://caddyserver.com/) example will be provided as this
|
||||
is the recommended reverse proxy for new users and is very trivial to use
|
||||
(handles TLS, reverse proxy headers, etc transparently with proper defaults).
|
||||
|
||||
Lighttpd is not supported as it seems to mess with the `X-Matrix` Authorization
|
||||
header, making federation non-functional. If using Apache, you need to use
|
||||
`nocanon` to prevent this.
|
||||
`nocanon` in your `ProxyPass` directive to prevent this (note that Apache
|
||||
isn't very good as a general reverse proxy).
|
||||
|
||||
Nginx users may need to set `proxy_buffering off;` if there are issues with
|
||||
uploading media like images.
|
||||
|
||||
You will need to reverse proxy everything under following routes:
|
||||
- `/_matrix/` - core Matrix C-S and S-S APIs
|
||||
- `/_conduwuit/` - ad-hoc conduwuit routes such as `/local_user_count` and
|
||||
`/server_version`
|
||||
|
||||
You can optionally reverse proxy the following individual routes:
|
||||
- `/.well-known/matrix/client` and `/.well-known/matrix/server` if using
|
||||
conduwuit to perform delegation
|
||||
- `/.well-known/matrix/support` if using conduwuit to send the homeserver admin
|
||||
contact and support page (formerly known as MSC1929)
|
||||
- `/` if you would like to see `hewwo from conduwuit woof!` at the root
|
||||
|
||||
### Caddy
|
||||
|
||||
Create `/etc/caddy/conf.d/conduwuit_caddyfile` and enter this (substitute for
|
||||
your server name).
|
||||
|
||||
```caddy
|
||||
your.server.name, your.server.name:8448 { # TCP reverse_proxy
|
||||
```caddyfile
|
||||
your.server.name, your.server.name:8448 {
|
||||
# TCP reverse_proxy
|
||||
127.0.0.1:6167
|
||||
# UNIX socket
|
||||
#reverse_proxy unix//run/conduwuit/conduwuit.sock
|
||||
# UNIX socket
|
||||
#reverse_proxy unix//run/conduwuit/conduwuit.sock
|
||||
}
|
||||
```
|
||||
|
||||
That's it! Just start and enable the service and you're set.
|
||||
|
||||
```bash sudo systemctl enable --now caddy ```
|
||||
```bash
|
||||
sudo systemctl enable --now caddy
|
||||
```
|
||||
|
||||
## You're done
|
||||
|
||||
Now you can start conduwuit with:
|
||||
|
||||
```bash sudo systemctl start conduwuit ```
|
||||
```bash
|
||||
sudo systemctl start conduwuit
|
||||
```
|
||||
|
||||
Set it to start automatically when your system boots with:
|
||||
|
||||
```bash sudo systemctl enable conduwuit ```
|
||||
```bash
|
||||
sudo systemctl enable conduwuit
|
||||
```
|
||||
|
||||
## How do I know it works?
|
||||
|
||||
@@ -127,10 +180,15 @@ homeserver and try to register.
|
||||
You can also use these commands as a quick health check (replace
|
||||
`your.server.name`).
|
||||
|
||||
```bash $ curl https://your.server.name/_conduwuit/server_version
|
||||
```bash
|
||||
curl https://your.server.name/_conduwuit/server_version
|
||||
|
||||
# If using port 8448 $ curl
|
||||
https://your.server.name:8448/_conduwuit/server_version ```
|
||||
# If using port 8448
|
||||
curl https://your.server.name:8448/_conduwuit/server_version
|
||||
|
||||
# If federation is enabled
|
||||
curl https://your.server.name:8448/_matrix/federation/v1/version
|
||||
```
|
||||
|
||||
- To check if your server can talk with other homeservers, you can use the
|
||||
[Matrix Federation Tester](https://federationtester.matrix.org/). If you can
|
||||
|
||||
+54
-15
@@ -1,38 +1,77 @@
|
||||
# conduwuit for NixOS
|
||||
|
||||
conduwuit can be acquired by [Lix][lix] from various places:
|
||||
conduwuit can be acquired by Nix (or [Lix][lix]) from various places:
|
||||
|
||||
* The `flake.nix` at the root of the repo
|
||||
* The `default.nix` at the root of the repo
|
||||
* From conduwuit's binary cache
|
||||
|
||||
A community maintained NixOS package is available at [`conduwuit`](https://search.nixos.org/packages?channel=unstable&show=conduwuit&from=0&size=50&sort=relevance&type=packages&query=conduwuit)
|
||||
|
||||
### Binary cache
|
||||
|
||||
A binary cache for conduwuit that the CI/CD publishes to is available at the
|
||||
following places (both are the same just different names):
|
||||
|
||||
``` https://attic.kennel.juneis.dog/conduit
|
||||
```
|
||||
https://attic.kennel.juneis.dog/conduit
|
||||
conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
|
||||
|
||||
https://attic.kennel.juneis.dog/conduwuit
|
||||
conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= ```
|
||||
conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=
|
||||
```
|
||||
|
||||
The binary caches have been recreated recently due to attic issues. The old
|
||||
public keys were:
|
||||
The binary caches were recreated some months ago due to attic issues. The old public
|
||||
keys were:
|
||||
|
||||
``` conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
```
|
||||
conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
```
|
||||
|
||||
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw= ```
|
||||
|
||||
If specifying a URL in your flake, please use the GitHub remote:
|
||||
`github:girlbossceo/conduwuit`
|
||||
If specifying a Git remote URL in your flake, you can use any remotes that
|
||||
are specified on the README (the mirrors), such as the GitHub: `github:girlbossceo/conduwuit`
|
||||
|
||||
The `flake.nix` and `default.nix` do not (currently) provide a NixOS module, so
|
||||
(for now) [`services.matrix-conduit`][module] from Nixpkgs should be used to
|
||||
configure conduwuit.
|
||||
### NixOS module
|
||||
|
||||
If you want to run the latest code, you should get conduwuit from the
|
||||
`flake.nix` or `default.nix` and set
|
||||
[`services.matrix-conduit.package`][package] appropriately.
|
||||
The `flake.nix` and `default.nix` do not currently provide a NixOS module (contributions
|
||||
welcome!), so [`services.matrix-conduit`][module] from Nixpkgs can be used to configure
|
||||
conduwuit.
|
||||
|
||||
If you want to run the latest code, you should get conduwuit from the `flake.nix`
|
||||
or `default.nix` and set [`services.matrix-conduit.package`][package]
|
||||
appropriately to use conduwuit instead of Conduit.
|
||||
|
||||
### UNIX sockets
|
||||
|
||||
Due to the lack of a conduwuit NixOS module, when using the `services.matrix-conduit` module
|
||||
it is not possible to use UNIX sockets. This is because the UNIX socket option does not exist
|
||||
in Conduit, and their module forces listening on `[::1]:6167` by default if unspecified.
|
||||
|
||||
Additionally, the [`matrix-conduit` systemd unit][systemd-unit] in the module does not allow
|
||||
the `AF_UNIX` socket address family in their systemd unit's `RestrictAddressFamilies=` which
|
||||
disallows the namespace from accessing or creating UNIX sockets.
|
||||
|
||||
There is no known workaround these. A conduwuit NixOS configuration module must be developed and
|
||||
published by the community.
|
||||
|
||||
### jemalloc and hardened profile
|
||||
|
||||
conduwuit uses jemalloc by default. This may interfere with the [`hardened.nix` profile][hardened.nix]
|
||||
due to them using `scudo` by default. You must either disable/hide `scudo` from conduwuit, or
|
||||
disable jemalloc like so:
|
||||
|
||||
```nix
|
||||
let
|
||||
conduwuit = pkgs.unstable.conduwuit.override {
|
||||
enableJemalloc = false;
|
||||
};
|
||||
in
|
||||
```
|
||||
|
||||
[lix]: https://lix.systems/
|
||||
[module]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit
|
||||
[package]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit.package
|
||||
[hardened.nix]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix#L22
|
||||
[systemd-unit]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/matrix/conduit.nix#L132
|
||||
|
||||
+80
-6
@@ -1,10 +1,74 @@
|
||||
# Development
|
||||
|
||||
Information about developing the project. If you are only interested in using
|
||||
it, you can safely ignore this section. If you plan on contributing, see the
|
||||
[contributor's guide](contributing.md).
|
||||
it, you can safely ignore this page. If you plan on contributing, see the
|
||||
[contributor's guide](./contributing.md).
|
||||
|
||||
## List of forked dependencies During conduwuit development, we have had to fork
|
||||
## conduwuit project layout
|
||||
|
||||
conduwuit uses a collection of sub-crates, packages, or workspace members
|
||||
that indicate what each general area of code is for. All of the workspace
|
||||
members are under `src/`. The workspace definition is at the top level / root
|
||||
`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 conduwuit 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 conduwuit 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`.
|
||||
- `service` is the high-level database definitions and functions for data,
|
||||
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.
|
||||
|
||||
The primary inspiration for this design was apart of hot reloadable development,
|
||||
to support "conduwuit 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.
|
||||
|
||||
## 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`.
|
||||
|
||||
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 = ["conduit-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.
|
||||
|
||||
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 conduwuit development, we have had to fork
|
||||
some dependencies to support our use-cases in some areas. This ranges from
|
||||
things said upstream project won't accept for any reason, faster-paced
|
||||
development (unresponsive or slow upstream), conduwuit-specific usecases, or
|
||||
@@ -38,9 +102,11 @@ 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 build \ --release \
|
||||
--no-default-features \
|
||||
--features=systemd,element_hacks,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
||||
```bash
|
||||
RUSTFLAGS="--cfg tokio_unstable" cargo build \
|
||||
--release \
|
||||
--no-default-features \
|
||||
--features=systemd,element_hacks,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
||||
```
|
||||
|
||||
[1]: https://github.com/ruma/ruma/
|
||||
@@ -51,3 +117,11 @@ this:
|
||||
[6]: 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
|
||||
[workspaces]: https://doc.rust-lang.org/cargo/reference/workspaces.html
|
||||
[macros]: https://doc.rust-lang.org/book/ch19-06-macros.html
|
||||
[syn]: https://docs.rs/syn/latest/syn/
|
||||
[proc-macro]: https://doc.rust-lang.org/reference/procedural-macros.html
|
||||
[clap]: https://docs.rs/clap/latest/clap/
|
||||
[features]: https://doc.rust-lang.org/cargo/reference/features.html
|
||||
[state]: https://docs.rs/axum/latest/axum/extract/struct.State.html
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
Have a look at [Complement's repository][complement] for an explanation of what
|
||||
it is.
|
||||
|
||||
To test against Complement, with [Lix][lix] and direnv installed and set up, you
|
||||
can:
|
||||
To test against Complement, with Nix (or [Lix](https://lix.systems) and direnv installed
|
||||
and set up, you can:
|
||||
|
||||
* Run `./bin/complement "$COMPLEMENT_SRC" ./path/to/logs.jsonl
|
||||
./path/to/results.jsonl` to build a Complement image, run the tests, and output
|
||||
@@ -18,6 +18,5 @@ Complement OCI image outputted to `result` (it's a `.tar.gz` file)
|
||||
output from the commit/revision you want to test (e.g. from main)
|
||||
[here][ci-workflows]
|
||||
|
||||
[lix]: https://lix.systems/
|
||||
[ci-workflows]: https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml?query=event%3Apush+is%3Asuccess+actor%3Agirlbossceo
|
||||
[complement]: https://github.com/matrix-org/complement
|
||||
|
||||
+12
-1
@@ -13,6 +13,17 @@
|
||||
> If there are things like Compose file issues or Dockerhub image issues, those
|
||||
> can still be mentioned as long as they're something we can fix.
|
||||
|
||||
## conduwuit and Matrix issues
|
||||
|
||||
#### Lost access to admin room
|
||||
|
||||
You can reinvite yourself to the admin room through the following methods:
|
||||
- Use the `--execute "users make_user_admin <username>"` conduwuit binary
|
||||
argument once to invite yourslf to the admin room on startup
|
||||
- Use the conduwuit console/CLI to run the `users make_user_admin` command
|
||||
- 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
|
||||
|
||||
#### Potential DNS issues when using Docker
|
||||
@@ -30,7 +41,7 @@ workarounds for this are:
|
||||
- Don't use Docker's default DNS setup and instead allow the container to use
|
||||
and communicate with your host's DNS servers (host's `/etc/resolv.conf`)
|
||||
|
||||
## Rocksdb / database issues
|
||||
## RocksDB / database issues
|
||||
|
||||
#### Direct IO
|
||||
|
||||
|
||||
@@ -21,6 +21,22 @@ These same values need to be set in conduwuit. See the [example
|
||||
config](configuration/examples.md) in the TURN section for configuring these and
|
||||
restart conduwuit after.
|
||||
|
||||
`turn_secret` or a path to `turn_secret_file` must have a value of your
|
||||
coturn `static-auth-secret`, or use `turn_username` and `turn_password`
|
||||
if using legacy username:password TURN authentication (not preferred).
|
||||
|
||||
`turn_uris` must be the list of TURN URIs you would like to send to the client.
|
||||
Typically you will just replace the example domain `example.turn.uri` with the
|
||||
`realm` you set from the example config.
|
||||
|
||||
If you are using TURN over TLS, you can replace `turn:` with `turns:` in the
|
||||
`turn_uris` config option to instruct clients to attempt to connect to
|
||||
TURN over TLS. This is highly recommended.
|
||||
|
||||
If you need unauthenticated access to the TURN URIs, or some clients may be
|
||||
having trouble, you can enable `turn_guest_access` in conduwuit which disables
|
||||
authentication for the TURN URI endpoint `/_matrix/client/v3/voip/turnServer`
|
||||
|
||||
### Run
|
||||
|
||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ cargo clippy \
|
||||
[[task]]
|
||||
name = "lychee"
|
||||
group = "lints"
|
||||
script = "lychee --verbose --offline docs *.md --exclude development.md"
|
||||
script = "lychee --verbose --offline docs *.md --exclude development.md --exclude contributing.md --exclude testing.md"
|
||||
|
||||
[[task]]
|
||||
name = "markdownlint"
|
||||
|
||||
Generated
+325
-81
@@ -4,16 +4,16 @@
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724226964,
|
||||
"narHash": "sha256-cltFh4su2vcFidxKp7LuEgX3ZGLfPy0DCdrQZ/QTe68=",
|
||||
"lastModified": 1729116596,
|
||||
"narHash": "sha256-NnLMLIXGZtAscUF4dCShksuQ1nOGF6Y2dEeyj0rBbUg=",
|
||||
"owner": "zhaofengli",
|
||||
"repo": "attic",
|
||||
"rev": "6d9aeaef0a067d664cb11bb7704f7ec373d47fb2",
|
||||
"rev": "2b05b7d986cf6009b1c1ef7daa4961cd1a658782",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -28,14 +28,14 @@
|
||||
"devenv": "devenv",
|
||||
"flake-compat": "flake-compat_3",
|
||||
"git-hooks": "git-hooks",
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724232775,
|
||||
"narHash": "sha256-6u2DycIEgrgNYlLxyGqdFVmBNiKIitnQKJ1pbRP5oko=",
|
||||
"lastModified": 1728672398,
|
||||
"narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "03b6cb3f953097bff378fb8b9ea094bd091a4ec7",
|
||||
"rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -53,12 +53,51 @@
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"git-hooks": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"pre-commit-hooks"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726520618,
|
||||
"narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "695525f9086542dfb09fde0871dbf4174abbf634",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"cachix_3": {
|
||||
"inputs": {
|
||||
"devenv": "devenv_3",
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"pre-commit-hooks"
|
||||
@@ -81,11 +120,11 @@
|
||||
"complement": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1722323564,
|
||||
"narHash": "sha256-6w6/N8walz4Ayc9zu7iySqJRmGFukhkaICLn4dweAcA=",
|
||||
"lastModified": 1724347376,
|
||||
"narHash": "sha256-y0e/ULDJ92IhNQZsS/06g0s+AYZ82aJfrIO9qEse94c=",
|
||||
"owner": "matrix-org",
|
||||
"repo": "complement",
|
||||
"rev": "6e4426a9e63233f9821a4d2382bfed145244183f",
|
||||
"rev": "39733c1b2f8314800776748cc7164f9a34650686",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -117,17 +156,12 @@
|
||||
}
|
||||
},
|
||||
"crane_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724006180,
|
||||
"narHash": "sha256-PVxPj0Ga2fMYMtcT9ARCthF+4U71YkOT7ZjgD/vf1Aw=",
|
||||
"lastModified": 1729741221,
|
||||
"narHash": "sha256-8AHZZXs1lFkERfBY0C8cZGElSo33D/et7NKEpLRmvzo=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "7ce92819802bc583b7e82ebc08013a530f22209f",
|
||||
"rev": "f235b656ee5b2bfd6d94c3bfd67896a575d4a6ed",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -144,7 +178,7 @@
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix_2",
|
||||
"nix": "nix_3",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"nixpkgs"
|
||||
@@ -154,6 +188,43 @@
|
||||
"git-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1727963652,
|
||||
"narHash": "sha256-os0EDjn7QVXL6RtHNb9TrZLXVm2Tc5/nZKk3KpbTzd8=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "cb0052e25dbcc8267b3026160dc73cddaac7d5fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv_2": {
|
||||
"inputs": {
|
||||
"cachix": "cachix_3",
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix_2",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"git-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1723156315,
|
||||
"narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=",
|
||||
@@ -168,9 +239,11 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv_2": {
|
||||
"devenv_3": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
@@ -180,6 +253,8 @@
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"poetry2nix": "poetry2nix",
|
||||
"pre-commit-hooks": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
@@ -209,11 +284,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724221791,
|
||||
"narHash": "sha256-mKX67QPnUybOopVph/LhOV1G/H4EvPxDIfSmbufrVdA=",
|
||||
"lastModified": 1729751566,
|
||||
"narHash": "sha256-99u/hrgBdi8bxSXZc9ZbNkR5EL1htrkbd3lsbKzS60g=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "e88b38a5a3834e039d413a88f8150a75ef6453ef",
|
||||
"rev": "f32a2d484091a6dc98220b1f4a2c2d60b7c97c64",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -288,27 +363,53 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
"nixpkgs-lib": [
|
||||
"attic",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"lastModified": 1722555600,
|
||||
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"flake-parts_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
"nixpkgs-lib": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
@@ -324,9 +425,24 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_3": {
|
||||
"inputs": {
|
||||
"systems": "systems_3"
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
@@ -357,11 +473,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1723202784,
|
||||
"narHash": "sha256-qbhjc/NEGaDbyy0ucycubq4N3//gDFFH3DOmp1D3u1Q=",
|
||||
"lastModified": 1727854478,
|
||||
"narHash": "sha256-/odH2nUMAwkMgOS2nG2z0exLQNJS4S2LfMW0teqU7co=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "c7012d0c18567c889b948781bc74a501e92275d1",
|
||||
"rev": "5f58871c9657b5fc0a7f65670fe2ba99c26c1d79",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -392,14 +508,30 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"libgit2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1697646580,
|
||||
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"liburing": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1724199144,
|
||||
"narHash": "sha256-MVjnwO6EbKzzSrU51dSseLarZ1fRp+6SagAf/nE/XZU=",
|
||||
"lastModified": 1725659644,
|
||||
"narHash": "sha256-WjnpmopfvFoUbubIu9bki+Y6P4YXDfvnW4+72hniq3g=",
|
||||
"owner": "axboe",
|
||||
"repo": "liburing",
|
||||
"rev": "2d4e799017d64cd2f8304503eef9064931bb3fbd",
|
||||
"rev": "0fe5c09195c0918f89582dd6ff098a58a0bdf62a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -417,6 +549,8 @@
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
@@ -459,6 +593,8 @@
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
@@ -480,11 +616,15 @@
|
||||
"nix_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
@@ -506,13 +646,42 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_3": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-parts": "flake-parts_2",
|
||||
"libgit2": "libgit2",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"nixpkgs-23-11": "nixpkgs-23-11",
|
||||
"nixpkgs-regression": "nixpkgs-regression_3",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1727438425,
|
||||
"narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.24",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1723827930,
|
||||
"narHash": "sha256-EU+W5F6y2CVNxGrGIMpY7nSVYq72WRChYxF4zpjx0y4=",
|
||||
"lastModified": 1726042813,
|
||||
"narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d4a7a4d0e066278bfb0d77bd2a7adde1c0ec9e3d",
|
||||
"rev": "159be5db480d1df880a0135ca0bfed84c2f88353",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -522,6 +691,22 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-23-11": {
|
||||
"locked": {
|
||||
"lastModified": 1717159533,
|
||||
"narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
@@ -554,18 +739,34 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"nixpkgs-regression_3": {
|
||||
"locked": {
|
||||
"lastModified": 1720535198,
|
||||
"narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=",
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1724316499,
|
||||
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -604,27 +805,27 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1722813957,
|
||||
"narHash": "sha256-IAoYyYnED7P8zrBFMnmp7ydaJfwTnwcnqxUElC1I26Y=",
|
||||
"lastModified": 1717432640,
|
||||
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cb9a96f23c491c081b38eab96d22fa958043c9fa",
|
||||
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"ref": "release-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1724271409,
|
||||
"narHash": "sha256-z4nw9HxkaXEn+5OT8ljLVL2oataHvAzUQ1LEi8Fp+SY=",
|
||||
"lastModified": 1727802920,
|
||||
"narHash": "sha256-HP89HZOT0ReIbI7IJZJQoJgxvB2Tn28V6XS3MNKnfLs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "36a9aeaaa17a2d4348498275f9fe530cd4f9e519",
|
||||
"rev": "27e30d177e57d912d614c88c622dcfdb2e6e6515",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -634,15 +835,33 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_5": {
|
||||
"locked": {
|
||||
"lastModified": 1725534445,
|
||||
"narHash": "sha256-Yd0FK9SkWy+ZPuNqUgmVPXokxDgMJoGuNpMEtkfcf84=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9bb1e7571aadf31ddb4af77fc64b2d59580f9a39",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
@@ -660,19 +879,59 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nix"
|
||||
],
|
||||
"flake-utils": "flake-utils_2",
|
||||
"gitignore": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nix"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712897695,
|
||||
"narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rocksdb": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1724285323,
|
||||
"narHash": "sha256-k60kreKQ0v+bQ16yBd2SfLYpuNjMw2qoRmZL/S3k6CU=",
|
||||
"lastModified": 1729712930,
|
||||
"narHash": "sha256-jlp4kPkRTpoJaUdobEoHd8rCGAQNBy4ZHZ6y5zL/ibw=",
|
||||
"owner": "girlbossceo",
|
||||
"repo": "rocksdb",
|
||||
"rev": "5a67ad7ce46328578ee5587fb0c23faa03d14e67",
|
||||
"rev": "871eda6953c3f399aae39808dcfccdd014885beb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "girlbossceo",
|
||||
"ref": "v9.5.2",
|
||||
"ref": "v9.7.3",
|
||||
"repo": "rocksdb",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -688,18 +947,18 @@
|
||||
"flake-utils": "flake-utils_3",
|
||||
"liburing": "liburing",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs_4",
|
||||
"nixpkgs": "nixpkgs_5",
|
||||
"rocksdb": "rocksdb"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1724153119,
|
||||
"narHash": "sha256-WxpvDJDttkINkXmUA/W5o11lwLPYhATAgu0QUAacZ2g=",
|
||||
"lastModified": 1729715509,
|
||||
"narHash": "sha256-jUDN4e1kObbksb4sc+57NEeujBEDRdLCOu9wiE3RZdM=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "3723e5910c14f0ffbd13de474b8a8fcc74db04ce",
|
||||
"rev": "40492e15d49b89cf409e2c5536444131fac49429",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -738,21 +997,6 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_3": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
attic.url = "github:zhaofengli/attic?ref=main";
|
||||
cachix.url = "github:cachix/cachix?ref=master";
|
||||
complement = { url = "github:matrix-org/complement?ref=main"; flake = false; };
|
||||
crane = { url = "github:ipetkov/crane?ref=master"; inputs.nixpkgs.follows = "nixpkgs"; };
|
||||
crane = { url = "github:ipetkov/crane?ref=master"; };
|
||||
fenix = { url = "github:nix-community/fenix?ref=main"; inputs.nixpkgs.follows = "nixpkgs"; };
|
||||
flake-compat = { url = "github:edolstra/flake-compat?ref=master"; flake = false; };
|
||||
flake-utils.url = "github:numtide/flake-utils?ref=main";
|
||||
nix-filter.url = "github:numtide/nix-filter?ref=main";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
||||
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.5.2"; flake = false; };
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixpkgs-unstable";
|
||||
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.7.3"; flake = false; };
|
||||
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
file = ./rust-toolchain.toml;
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-3jVIIf5XPnUU1CRaTyAiO0XHVbJl12MSx3eucTXCjtE=";
|
||||
sha256 = "sha256-yMuSb5eQPO/bHv+Bcf/US8LVMbf/G/0MSfiPwBhiPpk=";
|
||||
};
|
||||
|
||||
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
||||
@@ -38,7 +38,23 @@
|
||||
inherit inputs;
|
||||
main = self.callPackage ./nix/pkgs/main {};
|
||||
oci-image = self.callPackage ./nix/pkgs/oci-image {};
|
||||
rocksdb = pkgs.rocksdb.overrideAttrs (old: {
|
||||
tini = pkgs.tini.overrideAttrs {
|
||||
# newer clang/gcc is unhappy with tini-static: <https://3.dog/~strawberry/pb/c8y4>
|
||||
patches = [ (pkgs.fetchpatch {
|
||||
url = "https://patch-diff.githubusercontent.com/raw/krallin/tini/pull/224.patch";
|
||||
hash = "sha256-4bTfAhRyIT71VALhHY13hUgbjLEUyvgkIJMt3w9ag3k=";
|
||||
})
|
||||
];
|
||||
};
|
||||
liburing = pkgs.liburing.overrideAttrs {
|
||||
# Tests weren't building
|
||||
outputs = [ "out" "dev" "man" ];
|
||||
buildFlags = [ "library" ];
|
||||
src = inputs.liburing;
|
||||
};
|
||||
rocksdb = (pkgs.rocksdb.override {
|
||||
liburing = self.liburing;
|
||||
}).overrideAttrs (old: {
|
||||
src = inputs.rocksdb;
|
||||
version = pkgs.lib.removePrefix
|
||||
"v"
|
||||
@@ -76,18 +92,20 @@
|
||||
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
|
||||
preInstall = "";
|
||||
});
|
||||
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
|
||||
liburing = pkgs.liburing.overrideAttrs (old: {
|
||||
# the configure script doesn't support these, and unconditionally
|
||||
# builds both static and dynamic libraries.
|
||||
configureFlags = pkgs.lib.subtractLists
|
||||
[ "--enable-static" "--disable-shared" ]
|
||||
old.configureFlags;
|
||||
});
|
||||
});
|
||||
|
||||
scopeHost = mkScope pkgsHost;
|
||||
scopeHostStatic = mkScope pkgsHostStatic;
|
||||
scopeCrossLinux = mkScope pkgsHost.pkgsLinux.pkgsStatic;
|
||||
mkCrossScope = crossSystem:
|
||||
let pkgsCrossStatic = (import inputs.nixpkgs {
|
||||
inherit system;
|
||||
crossSystem = {
|
||||
config = crossSystem;
|
||||
};
|
||||
}).pkgsStatic;
|
||||
in
|
||||
mkScope pkgsCrossStatic;
|
||||
|
||||
mkDevShell = scope: scope.pkgs.mkShell {
|
||||
env = scope.main.env // {
|
||||
@@ -119,6 +137,9 @@
|
||||
engage
|
||||
cargo-audit
|
||||
|
||||
# Required by hardened-malloc.rs dep
|
||||
binutils
|
||||
|
||||
# Needed for producing Debian packages
|
||||
cargo-deb
|
||||
|
||||
@@ -145,12 +166,21 @@
|
||||
|
||||
# needed so we can get rid of gcc and other unused deps that bloat OCI images
|
||||
removeReferencesTo
|
||||
])
|
||||
]
|
||||
# liburing is Linux-exclusive
|
||||
++ lib.optional stdenv.hostPlatform.isLinux liburing
|
||||
# needed to build Rust applications on macOS
|
||||
++ lib.optionals stdenv.hostPlatform.isDarwin [
|
||||
# https://github.com/NixOS/nixpkgs/issues/206242
|
||||
# ld: library not found for -liconv
|
||||
libiconv
|
||||
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
|
||||
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
|
||||
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
|
||||
])
|
||||
++ scope.main.buildInputs
|
||||
++ scope.main.propagatedBuildInputs
|
||||
++ scope.main.nativeBuildInputs;
|
||||
|
||||
meta.broken = scope.main.meta.broken;
|
||||
};
|
||||
in
|
||||
{
|
||||
@@ -224,6 +254,8 @@
|
||||
|
||||
complement = scopeHost.complement;
|
||||
static-complement = scopeHostStatic.complement;
|
||||
# macOS containers don't exist, so the complement images must be forced to linux
|
||||
linux-complement = (mkCrossScope "${pkgsHost.hostPlatform.qemuArch}-linux-musl").complement;
|
||||
}
|
||||
//
|
||||
builtins.listToAttrs
|
||||
@@ -232,14 +264,7 @@
|
||||
(crossSystem:
|
||||
let
|
||||
binaryName = "static-${crossSystem}";
|
||||
pkgsCrossStatic =
|
||||
(import inputs.nixpkgs {
|
||||
inherit system;
|
||||
crossSystem = {
|
||||
config = crossSystem;
|
||||
};
|
||||
}).pkgsStatic;
|
||||
scopeCrossStatic = mkScope pkgsCrossStatic;
|
||||
scopeCrossStatic = mkCrossScope crossSystem;
|
||||
in
|
||||
[
|
||||
# An output for a statically-linked binary
|
||||
@@ -369,11 +394,20 @@
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a complement OCI image for the specified platform
|
||||
{
|
||||
name = "complement-${crossSystem}";
|
||||
value = scopeCrossStatic.complement;
|
||||
}
|
||||
]
|
||||
)
|
||||
[
|
||||
"x86_64-unknown-linux-musl"
|
||||
"aarch64-unknown-linux-musl"
|
||||
#"x86_64-apple-darwin"
|
||||
#"aarch64-apple-darwin"
|
||||
"x86_64-linux-gnu"
|
||||
"x86_64-linux-musl"
|
||||
"aarch64-linux-musl"
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
@@ -14,8 +14,10 @@ stdenv.mkDerivation {
|
||||
include = [
|
||||
"book.toml"
|
||||
"conduwuit-example.toml"
|
||||
"CODE_OF_CONDUCT.md"
|
||||
"CONTRIBUTING.md"
|
||||
"README.md"
|
||||
"development.md"
|
||||
"debian/conduwuit.service"
|
||||
"debian/README.md"
|
||||
"arch/conduwuit.service"
|
||||
|
||||
@@ -16,6 +16,7 @@ url_preview_domain_contains_allowlist = ["*"]
|
||||
media_compat_file_link = false
|
||||
media_startup_check = false
|
||||
rocksdb_direct_io = false
|
||||
log_colors = false
|
||||
|
||||
[global.tls]
|
||||
certs = "/certificate.crt"
|
||||
|
||||
@@ -18,6 +18,15 @@ let
|
||||
all_features = true;
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# no reason to use jemalloc for complement, just has compatibility/build issues
|
||||
"jemalloc"
|
||||
# console/CLI stuff isn't used or relevant for complement
|
||||
"console"
|
||||
"tokio_console"
|
||||
# sentry telemetry isn't useful for complement, disabled by default anyways
|
||||
"sentry_telemetry"
|
||||
# the containers don't use or need systemd signal support
|
||||
"systemd"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
@@ -57,7 +66,7 @@ let
|
||||
in
|
||||
|
||||
dockerTools.buildImage {
|
||||
name = "complement-${main.pname}";
|
||||
name = "complement-conduwuit";
|
||||
tag = "main";
|
||||
|
||||
copyToRoot = buildEnv {
|
||||
@@ -78,7 +87,7 @@ dockerTools.buildImage {
|
||||
"${lib.getExe start}"
|
||||
];
|
||||
|
||||
Entrypoint = if !stdenv.isDarwin
|
||||
Entrypoint = if !stdenv.hostPlatform.isDarwin
|
||||
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
|
||||
# are handled as expected
|
||||
then [ "${lib.getExe' tini "tini"}" "--" ]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{ lib
|
||||
, pkgsBuildHost
|
||||
, pkgsBuildTarget
|
||||
, rust
|
||||
, stdenv
|
||||
}:
|
||||
@@ -13,12 +14,6 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
||||
lib.concatStringsSep
|
||||
" "
|
||||
([]
|
||||
++ lib.optionals
|
||||
stdenv.targetPlatform.isx86_64
|
||||
[ "-C" "target-cpu=x86-64-v2" ]
|
||||
++ lib.optionals
|
||||
stdenv.targetPlatform.isAarch64
|
||||
[ "-C" "target-cpu=cortex-a55" ] # cortex-a55 == ARMv8.2-a
|
||||
# This disables PIE for static builds, which isn't great in terms
|
||||
# of security. Unfortunately, my hand is forced because nixpkgs'
|
||||
# `libstdc++.a` is built without `-fPIE`, which precludes us from
|
||||
@@ -41,7 +36,7 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
||||
# including it here. Linkers are weird.
|
||||
(stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isx86_64)
|
||||
&& stdenv.hostPlatform.isStatic
|
||||
&& !stdenv.isDarwin
|
||||
&& !stdenv.hostPlatform.isDarwin
|
||||
&& !stdenv.cc.bintools.isLLVM
|
||||
)
|
||||
[
|
||||
@@ -58,11 +53,12 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
||||
# even covers the case of build scripts that need native code compiled and
|
||||
# run on the build platform (I think).
|
||||
#
|
||||
# [0]: https://github.com/NixOS/nixpkgs/blob/5cdb38bb16c6d0a38779db14fcc766bc1b2394d6/pkgs/build-support/rust/lib/default.nix#L57-L80
|
||||
# [0]: https://github.com/NixOS/nixpkgs/blob/nixpkgs-unstable/pkgs/build-support/rust/lib/default.nix#L48-L68
|
||||
//
|
||||
(
|
||||
let
|
||||
inherit (rust.lib) envVars;
|
||||
shouldUseLLD = platform: platform.isAarch64 && platform.isStatic && !stdenv.hostPlatform.isDarwin;
|
||||
in
|
||||
lib.optionalAttrs
|
||||
(stdenv.targetPlatform.rust.rustcTarget
|
||||
@@ -70,23 +66,30 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
||||
(
|
||||
let
|
||||
inherit (stdenv.targetPlatform.rust) cargoEnvVarTarget;
|
||||
linkerForTarget = if shouldUseLLD stdenv.targetPlatform
|
||||
&& !stdenv.cc.bintools.isLLVM # whether stdenv's linker is lld already
|
||||
then "${pkgsBuildTarget.llvmPackages.bintools}/bin/${stdenv.cc.targetPrefix}ld.lld"
|
||||
else envVars.ccForTarget;
|
||||
in
|
||||
{
|
||||
"CC_${cargoEnvVarTarget}" = envVars.ccForTarget;
|
||||
"CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" =
|
||||
envVars.linkerForTarget;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = linkerForTarget;
|
||||
}
|
||||
)
|
||||
//
|
||||
(
|
||||
let
|
||||
inherit (stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget;
|
||||
linkerForHost = if shouldUseLLD stdenv.targetPlatform
|
||||
&& !stdenv.cc.bintools.isLLVM
|
||||
then "${pkgsBuildHost.llvmPackages.bintools}/bin/${stdenv.cc.targetPrefix}ld.lld"
|
||||
else envVars.ccForHost;
|
||||
in
|
||||
{
|
||||
"CC_${cargoEnvVarTarget}" = envVars.ccForHost;
|
||||
"CXX_${cargoEnvVarTarget}" = envVars.cxxForHost;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForHost;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = linkerForHost;
|
||||
CARGO_BUILD_TARGET = rustcTarget;
|
||||
}
|
||||
)
|
||||
@@ -98,7 +101,7 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
||||
{
|
||||
"CC_${cargoEnvVarTarget}" = envVars.ccForBuild;
|
||||
"CXX_${cargoEnvVarTarget}" = envVars.cxxForBuild;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForBuild;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForBuild;
|
||||
HOST_CC = "${pkgsBuildHost.stdenv.cc}/bin/cc";
|
||||
HOST_CXX = "${pkgsBuildHost.stdenv.cc}/bin/c++";
|
||||
}
|
||||
|
||||
+19
-32
@@ -6,6 +6,7 @@
|
||||
, libiconv
|
||||
, liburing
|
||||
, pkgsBuildHost
|
||||
, pkgsBuildTarget
|
||||
, rocksdb
|
||||
, removeReferencesTo
|
||||
, rust
|
||||
@@ -40,7 +41,7 @@ features'' = lib.subtractLists disable_features' features';
|
||||
|
||||
featureEnabled = feature : builtins.elem feature features'';
|
||||
|
||||
enableLiburing = featureEnabled "io_uring" && !stdenv.isDarwin;
|
||||
enableLiburing = featureEnabled "io_uring" && !stdenv.hostPlatform.isDarwin;
|
||||
|
||||
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
|
||||
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
|
||||
@@ -72,35 +73,13 @@ buildDepsOnlyEnv =
|
||||
# jemalloc symbols are prefixed.
|
||||
#
|
||||
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
|
||||
enableJemalloc = featureEnabled "jemalloc" && !stdenv.isDarwin;
|
||||
enableJemalloc = featureEnabled "jemalloc" && !stdenv.hostPlatform.isDarwin;
|
||||
|
||||
# for some reason enableLiburing in nixpkgs rocksdb is default true
|
||||
# which breaks Darwin entirely
|
||||
enableLiburing = enableLiburing;
|
||||
}).overrideAttrs (old: {
|
||||
# TODO: static rocksdb fails to build on darwin, also see <https://github.com/NixOS/nixpkgs/issues/320448>
|
||||
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
|
||||
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
|
||||
|
||||
enableLiburing = enableLiburing;
|
||||
|
||||
sse42Support = stdenv.targetPlatform.isx86_64;
|
||||
|
||||
cmakeFlags = if stdenv.targetPlatform.isx86_64
|
||||
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
|
||||
++ lib.optionals stdenv.targetPlatform.isx86_64 [
|
||||
"-DPORTABLE=x86-64-v2"
|
||||
"-DUSE_SSE=1"
|
||||
"-DHAVE_SSE=1"
|
||||
"-DHAVE_SSE42=1"
|
||||
]
|
||||
else if stdenv.targetPlatform.isAarch64
|
||||
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
|
||||
++ lib.optionals stdenv.targetPlatform.isAarch64 [
|
||||
# cortex-a73 == ARMv8-A
|
||||
"-DPORTABLE=armv8-a"
|
||||
]
|
||||
else old.cmakeFlags;
|
||||
});
|
||||
in
|
||||
{
|
||||
@@ -117,6 +96,7 @@ buildDepsOnlyEnv =
|
||||
inherit
|
||||
lib
|
||||
pkgsBuildHost
|
||||
pkgsBuildTarget
|
||||
rust
|
||||
stdenv;
|
||||
});
|
||||
@@ -127,11 +107,7 @@ buildPackageEnv = {
|
||||
# Only needed in static stdenv because these are transitive dependencies of rocksdb
|
||||
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
|
||||
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
|
||||
" -L${lib.getLib liburing}/lib -luring"
|
||||
+ lib.optionalString stdenv.targetPlatform.isx86_64
|
||||
" -Ctarget-cpu=x86-64-v2"
|
||||
+ lib.optionalString stdenv.targetPlatform.isAarch64
|
||||
" -Ctarget-cpu=cortex-a73"; # cortex-a73 == ARMv8-A
|
||||
" -L${lib.getLib liburing}/lib -luring";
|
||||
};
|
||||
|
||||
|
||||
@@ -159,7 +135,16 @@ commonAttrs = {
|
||||
dontStrip = profile == "dev" || profile == "test";
|
||||
dontPatchELF = profile == "dev" || profile == "test";
|
||||
|
||||
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
|
||||
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys'
|
||||
# needed to build Rust applications on macOS
|
||||
++ lib.optionals stdenv.hostPlatform.isDarwin [
|
||||
# https://github.com/NixOS/nixpkgs/issues/206242
|
||||
# ld: library not found for -liconv
|
||||
libiconv
|
||||
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
|
||||
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
|
||||
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
# bindgen needs the build platform's libclang. Apparently due to "splicing
|
||||
@@ -176,8 +161,10 @@ commonAttrs = {
|
||||
# needed so we can get rid of gcc and other unused deps that bloat OCI images
|
||||
removeReferencesTo
|
||||
]
|
||||
++ lib.optionals stdenv.isDarwin [
|
||||
# needed to build Rust applications on macOS
|
||||
++ lib.optionals stdenv.hostPlatform.isDarwin [
|
||||
# https://github.com/NixOS/nixpkgs/issues/206242
|
||||
# ld: library not found for -liconv
|
||||
libiconv
|
||||
|
||||
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
|
||||
@@ -189,7 +176,7 @@ commonAttrs = {
|
||||
#
|
||||
# <https://github.com/input-output-hk/haskell.nix/issues/829>
|
||||
postInstall = with pkgsBuildHost; ''
|
||||
find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${libgcc} -t ${linuxHeaders} -t ${libidn2} -t ${libunistring} '{}' +
|
||||
find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${rustc.unwrapped} -t ${rustc} -t ${libidn2} -t ${libunistring} '{}' +
|
||||
'';
|
||||
};
|
||||
in
|
||||
|
||||
@@ -16,7 +16,7 @@ dockerTools.buildLayeredImage {
|
||||
dockerTools.caCertificates
|
||||
];
|
||||
config = {
|
||||
Entrypoint = if !stdenv.isDarwin
|
||||
Entrypoint = if !stdenv.hostPlatform.isDarwin
|
||||
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
|
||||
# are handled as expected
|
||||
then [ "${lib.getExe' tini "tini"}" "--" ]
|
||||
|
||||
+11
-1
@@ -12,5 +12,15 @@
|
||||
"nix": {
|
||||
"enabled": true
|
||||
},
|
||||
"labels": ["dependencies", "github_actions"]
|
||||
"labels": [
|
||||
"dependencies",
|
||||
"github_actions"
|
||||
],
|
||||
"ignoreDeps": [
|
||||
"tikv-jemllocator",
|
||||
"tikv-jemalloc-sys",
|
||||
"tikv-jemalloc-ctl",
|
||||
"opentelemetry-rust",
|
||||
"tracing-opentelemetry"
|
||||
]
|
||||
}
|
||||
|
||||
+8
-3
@@ -2,8 +2,6 @@
|
||||
#
|
||||
# Other files that need upkeep when this changes:
|
||||
#
|
||||
# * `.gitlab-ci.yml`
|
||||
# * `.github/workflows/ci.yml`
|
||||
# * `Cargo.toml`
|
||||
# * `flake.nix`
|
||||
#
|
||||
@@ -11,13 +9,20 @@
|
||||
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||
|
||||
[toolchain]
|
||||
channel = "1.80.1"
|
||||
channel = "1.82.0"
|
||||
profile = "minimal"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
"rust-src",
|
||||
"rust-analyzer",
|
||||
# For CI and editors
|
||||
"rustfmt",
|
||||
"clippy",
|
||||
]
|
||||
targets = [
|
||||
#"x86_64-apple-darwin",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-unknown-linux-musl",
|
||||
"aarch64-unknown-linux-musl",
|
||||
#"aarch64-apple-darwin",
|
||||
]
|
||||
|
||||
+83
-50
@@ -1,6 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use conduit::{debug, info, trace, utils::time::parse_timepoint_ago, warn, Result};
|
||||
use conduit::{debug, debug_info, debug_warn, error, info, trace, utils::time::parse_timepoint_ago, Result};
|
||||
use conduit_service::media::Dim;
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, OwnedMxcUri, OwnedServerName, ServerName,
|
||||
@@ -19,7 +19,7 @@ pub(super) async fn delete(
|
||||
}
|
||||
|
||||
if let Some(mxc) = mxc {
|
||||
debug!("Got MXC URL: {mxc}");
|
||||
trace!("Got MXC URL: {mxc}");
|
||||
self.services
|
||||
.media
|
||||
.delete(&mxc.as_str().try_into()?)
|
||||
@@ -28,11 +28,12 @@ pub(super) async fn delete(
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Deleted the MXC from our database and on our filesystem.",
|
||||
));
|
||||
} else if let Some(event_id) = event_id {
|
||||
debug!("Got event ID to delete media from: {event_id}");
|
||||
}
|
||||
|
||||
let mut mxc_urls = vec![];
|
||||
let mut mxc_deletion_count: usize = 0;
|
||||
if let Some(event_id) = event_id {
|
||||
trace!("Got event ID to delete media from: {event_id}");
|
||||
|
||||
let mut mxc_urls = Vec::with_capacity(4);
|
||||
|
||||
// parsing the PDU for any MXC URLs begins here
|
||||
if let Some(event_json) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
|
||||
@@ -124,18 +125,28 @@ pub(super) async fn delete(
|
||||
}
|
||||
|
||||
if mxc_urls.is_empty() {
|
||||
// we shouldn't get here (should have errored earlier) but just in case for
|
||||
// whatever reason we do...
|
||||
info!("Parsed event ID {event_id} but did not contain any MXC URLs.");
|
||||
return Ok(RoomMessageEventContent::text_plain("Parsed event ID but found no MXC URLs."));
|
||||
}
|
||||
|
||||
let mut mxc_deletion_count: usize = 0;
|
||||
|
||||
for mxc_url in mxc_urls {
|
||||
self.services
|
||||
match self
|
||||
.services
|
||||
.media
|
||||
.delete(&mxc_url.as_str().try_into()?)
|
||||
.await?;
|
||||
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
debug_info!("Successfully deleted {mxc_url} from filesystem and database");
|
||||
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
debug_warn!("Failed to delete {mxc_url}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -158,34 +169,62 @@ pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
|
||||
));
|
||||
}
|
||||
|
||||
let mut failed_parsed_mxcs: usize = 0;
|
||||
|
||||
let mxc_list = self
|
||||
.body
|
||||
.to_vec()
|
||||
.drain(1..self.body.len().checked_sub(1).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
.filter_map(|mxc_s| {
|
||||
mxc_s
|
||||
.try_into()
|
||||
.inspect_err(|e| {
|
||||
debug_warn!("Failed to parse user-provided MXC URI: {e}");
|
||||
|
||||
failed_parsed_mxcs = failed_parsed_mxcs.saturating_add(1);
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.collect::<Vec<Mxc<'_>>>();
|
||||
|
||||
let mut mxc_deletion_count: usize = 0;
|
||||
|
||||
for mxc in mxc_list {
|
||||
debug!("Deleting MXC {mxc} in bulk");
|
||||
self.services.media.delete(&mxc.try_into()?).await?;
|
||||
mxc_deletion_count = mxc_deletion_count
|
||||
.checked_add(1)
|
||||
.expect("mxc_deletion_count should not get this high");
|
||||
for mxc in &mxc_list {
|
||||
trace!(%failed_parsed_mxcs, %mxc_deletion_count, "Deleting MXC {mxc} in bulk");
|
||||
match self.services.media.delete(mxc).await {
|
||||
Ok(()) => {
|
||||
debug_info!("Successfully deleted {mxc} from filesystem and database");
|
||||
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
debug_warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.",
|
||||
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem. \
|
||||
{failed_parsed_mxcs} MXCs failed to be parsed from the database.",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_past_remote_media(&self, duration: String, force: bool) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn delete_past_remote_media(
|
||||
&self, duration: String, before: bool, after: bool, yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if before && after {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Please only pick one argument, --before or --after.",
|
||||
));
|
||||
}
|
||||
assert!(!(before && after), "--before and --after should not be specified together");
|
||||
|
||||
let duration = parse_timepoint_ago(&duration)?;
|
||||
let deleted_count = self
|
||||
.services
|
||||
.media
|
||||
.delete_all_remote_media_at_after_time(duration, force)
|
||||
.delete_all_remote_media_at_after_time(duration, before, after, yes_i_want_to_delete_local_media)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -194,14 +233,10 @@ pub(super) async fn delete_past_remote_media(&self, duration: String, force: boo
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_all_from_user(&self, username: String, force: bool) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn delete_all_from_user(&self, username: String) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_local_user_id(self.services, &username)?;
|
||||
|
||||
let deleted_count = self
|
||||
.services
|
||||
.media
|
||||
.delete_from_user(&user_id, force)
|
||||
.await?;
|
||||
let deleted_count = self.services.media.delete_from_user(&user_id).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deleted {deleted_count} total files.",
|
||||
@@ -210,34 +245,36 @@ pub(super) async fn delete_all_from_user(&self, username: String, force: bool) -
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_all_from_server(
|
||||
&self, server_name: Box<ServerName>, force: bool,
|
||||
&self, server_name: Box<ServerName>, yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if server_name == self.services.globals.server_name() {
|
||||
return Ok(RoomMessageEventContent::text_plain("This command only works for remote media."));
|
||||
if server_name == self.services.globals.server_name() && !yes_i_want_to_delete_local_media {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"This command only works for remote media by default.",
|
||||
));
|
||||
}
|
||||
|
||||
let Ok(all_mxcs) = self.services.media.get_all_mxcs().await else {
|
||||
let Ok(all_mxcs) = self
|
||||
.services
|
||||
.media
|
||||
.get_all_mxcs()
|
||||
.await
|
||||
.inspect_err(|e| error!("Failed to get MXC URIs from our database: {e}"))
|
||||
else {
|
||||
return Ok(RoomMessageEventContent::text_plain("Failed to get MXC URIs from our database"));
|
||||
};
|
||||
|
||||
let mut deleted_count: usize = 0;
|
||||
|
||||
for mxc in all_mxcs {
|
||||
let mxc_server_name = match mxc.server_name() {
|
||||
Ok(server_name) => server_name,
|
||||
Err(e) => {
|
||||
if force {
|
||||
warn!("Failed to parse MXC {mxc} server name from database, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse MXC {mxc} server name from database: {e}",
|
||||
)));
|
||||
},
|
||||
let Ok(mxc_server_name) = mxc.server_name().inspect_err(|e| {
|
||||
debug_warn!("Failed to parse MXC {mxc} server name from database, ignoring error and skipping: {e}");
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if mxc_server_name != server_name || self.services.globals.server_is_ours(mxc_server_name) {
|
||||
if mxc_server_name != server_name
|
||||
|| (self.services.globals.server_is_ours(mxc_server_name) && !yes_i_want_to_delete_local_media)
|
||||
{
|
||||
trace!("skipping MXC URI {mxc}");
|
||||
continue;
|
||||
}
|
||||
@@ -249,12 +286,8 @@ pub(super) async fn delete_all_from_server(
|
||||
deleted_count = deleted_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
if force {
|
||||
warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!("Failed to delete MXC {mxc}: {e}")));
|
||||
debug_warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+27
-20
@@ -10,7 +10,7 @@ use crate::admin_command_dispatch;
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(super) enum MediaCommand {
|
||||
/// - Deletes a single media file from our database and on the filesystem
|
||||
/// via a single MXC URL
|
||||
/// via a single MXC URL or event ID (not redacted)
|
||||
Delete {
|
||||
/// The MXC URL to delete
|
||||
#[arg(long)]
|
||||
@@ -23,37 +23,44 @@ pub(super) enum MediaCommand {
|
||||
},
|
||||
|
||||
/// - Deletes a codeblock list of MXC URLs from our database and on the
|
||||
/// filesystem
|
||||
/// filesystem. This will always ignore errors.
|
||||
DeleteList,
|
||||
|
||||
/// - Deletes all remote media in the last X amount of time using filesystem
|
||||
/// metadata first created at date.
|
||||
/// - Deletes all remote media in the last/after "X" time using filesystem
|
||||
/// metadata first created at date, or fallback to last modified date.
|
||||
/// This will always ignore errors by default.
|
||||
///
|
||||
/// Synapse
|
||||
DeletePastRemoteMedia {
|
||||
/// - The duration (at or after), e.g. "5m" to delete all media in the
|
||||
/// past 5 minutes
|
||||
/// - The duration (at or after/before), e.g. "5m" to delete all media
|
||||
/// in the past or up to 5 minutes
|
||||
duration: String,
|
||||
|
||||
/// Continues deleting remote media if an undeletable object is found
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
#[arg(long, short)]
|
||||
before: bool,
|
||||
|
||||
#[arg(long, short)]
|
||||
after: bool,
|
||||
|
||||
/// Long argument to delete local media
|
||||
#[arg(long)]
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
},
|
||||
|
||||
/// - Deletes all the local media from a local user on our server
|
||||
/// - Deletes all the local media from a local user on our server. This will
|
||||
/// always ignore errors by default.
|
||||
DeleteAllFromUser {
|
||||
username: String,
|
||||
|
||||
/// Continues deleting media if an undeletable object is found
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
|
||||
/// - Deletes all remote media from the specified remote server
|
||||
/// - Deletes all remote media from the specified remote server. This will
|
||||
/// always ignore errors by default.
|
||||
DeleteAllFromServer {
|
||||
server_name: Box<ServerName>,
|
||||
|
||||
/// Continues deleting media if an undeletable object is found
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
/// Long argument to delete local media
|
||||
#[arg(long)]
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
},
|
||||
|
||||
GetFileInfo {
|
||||
@@ -82,10 +89,10 @@ pub(super) enum MediaCommand {
|
||||
#[arg(short, long, default_value("10000"))]
|
||||
timeout: u32,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(short, long, default_value("800"))]
|
||||
width: u32,
|
||||
|
||||
#[arg(short, long)]
|
||||
#[arg(short, long, default_value("800"))]
|
||||
height: u32,
|
||||
},
|
||||
}
|
||||
|
||||
+10
-2
@@ -15,7 +15,7 @@ use conduit::{
|
||||
},
|
||||
trace,
|
||||
utils::string::{collect_stream, common_prefix},
|
||||
Error, Result,
|
||||
warn, Error, Result,
|
||||
};
|
||||
use futures_util::future::FutureExt;
|
||||
use ruma::{
|
||||
@@ -114,7 +114,15 @@ async fn process(context: &Command<'_>, command: AdminCommand, args: &[String])
|
||||
|
||||
fn capture_create(context: &Command<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
|
||||
let env_config = &context.services.server.config.admin_log_capture;
|
||||
let env_filter = EnvFilter::try_new(env_config).unwrap_or_else(|_| "debug".into());
|
||||
let env_filter = EnvFilter::try_new(env_config).unwrap_or_else(|e| {
|
||||
warn!("admin_log_capture filter invalid: {e:?}");
|
||||
cfg!(debug_assertions)
|
||||
.then_some("debug")
|
||||
.or(Some("info"))
|
||||
.map(Into::into)
|
||||
.expect("default capture EnvFilter")
|
||||
});
|
||||
|
||||
let log_level = env_filter
|
||||
.max_level_hint()
|
||||
.and_then(LevelFilter::into_level)
|
||||
|
||||
@@ -2,6 +2,7 @@ mod account_data;
|
||||
mod appservice;
|
||||
mod globals;
|
||||
mod presence;
|
||||
mod pusher;
|
||||
mod resolver;
|
||||
mod room_alias;
|
||||
mod room_state_cache;
|
||||
@@ -13,7 +14,7 @@ use conduit::Result;
|
||||
|
||||
use self::{
|
||||
account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
|
||||
presence::PresenceCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
|
||||
presence::PresenceCommand, pusher::PusherCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
|
||||
room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand,
|
||||
};
|
||||
use crate::admin_command_dispatch;
|
||||
@@ -57,4 +58,8 @@ pub(super) enum QueryCommand {
|
||||
/// - resolver service
|
||||
#[command(subcommand)]
|
||||
Resolver(ResolverCommand),
|
||||
|
||||
/// - pusher service
|
||||
#[command(subcommand)]
|
||||
Pusher(PusherCommand),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, UserId};
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(crate) enum PusherCommand {
|
||||
/// - Returns all the pushers for the user.
|
||||
GetPushers {
|
||||
/// Full user ID
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) async fn process(subcommand: PusherCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
PusherCommand::GetPushers {
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.pusher.get_pushers(&user_id)?;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
+11
-24
@@ -1,13 +1,11 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::{admin_command, escape_html, get_room_info, PAGE_SIZE};
|
||||
use crate::{admin_command, get_room_info, PAGE_SIZE};
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_rooms(
|
||||
&self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
|
||||
&self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool, no_details: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
// TODO: i know there's a way to do this with clap, but i can't seem to find it
|
||||
let page = page.unwrap_or(1);
|
||||
@@ -61,29 +59,18 @@ pub(super) async fn list_rooms(
|
||||
};
|
||||
|
||||
let output_plain = format!(
|
||||
"Rooms:\n{}",
|
||||
"Rooms ({}):\n```\n{}\n```",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.map(|(id, members, name)| format!("{id}\tMembers: {members}\tName: {name}"))
|
||||
.map(|(id, members, name)| if no_details {
|
||||
format!("{id}")
|
||||
} else {
|
||||
format!("{id}\tMembers: {members}\tName: {name}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
let output_html = format!(
|
||||
"<table><caption>Room list - page \
|
||||
{page}</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
|
||||
rooms
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (id, members, name)| {
|
||||
writeln!(
|
||||
output,
|
||||
"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
|
||||
escape_html(id.as_ref()),
|
||||
members,
|
||||
escape_html(name)
|
||||
)
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(output_plain))
|
||||
}
|
||||
|
||||
+14
-2
@@ -10,6 +10,10 @@ pub(crate) enum RoomInfoCommand {
|
||||
/// - List joined members in a room
|
||||
ListJoinedMembers {
|
||||
room_id: Box<RoomId>,
|
||||
|
||||
/// Lists only our local users in the specified room
|
||||
#[arg(long)]
|
||||
local_only: bool,
|
||||
},
|
||||
|
||||
/// - Displays room topic
|
||||
@@ -22,7 +26,7 @@ pub(crate) enum RoomInfoCommand {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
async fn list_joined_members(&self, room_id: Box<RoomId>, local_only: bool) -> Result<RoomMessageEventContent> {
|
||||
let room_name = self
|
||||
.services
|
||||
.rooms
|
||||
@@ -37,7 +41,15 @@ async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageE
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(Result::ok);
|
||||
.filter_map(|member| {
|
||||
if local_only {
|
||||
member
|
||||
.ok()
|
||||
.filter(|user| self.services.globals.user_is_local(user))
|
||||
} else {
|
||||
member.ok()
|
||||
}
|
||||
});
|
||||
|
||||
let member_info = members
|
||||
.into_iter()
|
||||
|
||||
@@ -27,6 +27,11 @@ pub(super) enum RoomCommand {
|
||||
/// Excludes rooms that we have banned
|
||||
#[arg(long)]
|
||||
exclude_banned: bool,
|
||||
|
||||
#[arg(long)]
|
||||
/// Whether to only output room IDs without supplementary room
|
||||
/// information
|
||||
no_details: bool,
|
||||
},
|
||||
|
||||
#[command(subcommand)]
|
||||
|
||||
+118
-41
@@ -1,19 +1,23 @@
|
||||
use std::{collections::BTreeMap, fmt::Write as _};
|
||||
|
||||
use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname};
|
||||
use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room};
|
||||
use conduit::{error, info, utils, warn, PduBuilder, Result};
|
||||
use ruma::{
|
||||
events::{
|
||||
room::{message::RoomMessageEventContent, redaction::RoomRedactionEventContent},
|
||||
room::{
|
||||
message::RoomMessageEventContent,
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
redaction::RoomRedactionEventContent,
|
||||
},
|
||||
tag::{TagEvent, TagEventContent, TagInfo},
|
||||
RoomAccountDataEventType, TimelineEventType,
|
||||
RoomAccountDataEventType, StateEventType, TimelineEventType,
|
||||
},
|
||||
EventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
|
||||
use crate::{
|
||||
admin_command, escape_html, get_room_info,
|
||||
admin_command, get_room_info,
|
||||
utils::{parse_active_local_user_id, parse_local_user_id},
|
||||
};
|
||||
|
||||
@@ -111,6 +115,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[room_id_server_name.to_owned(), self.services.globals.server_name().to_owned()],
|
||||
None,
|
||||
&None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -128,6 +133,22 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
|
||||
// we dont add a device since we're not the user, just the creator
|
||||
|
||||
// if this account creation is from the CLI / --execute, invite the first user
|
||||
// to admin room
|
||||
if let Some(admin_room) = self.services.admin.get_admin_room()? {
|
||||
if self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_joined_count(&admin_room)?
|
||||
== Some(1)
|
||||
{
|
||||
self.services.admin.make_user_admin(&user_id).await?;
|
||||
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
}
|
||||
|
||||
// Inhibit login does not work for guests
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Created user with user_id: {user_id} and password: `{password}`"
|
||||
@@ -163,9 +184,8 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
.rooms_joined(&user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
|
||||
update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
|
||||
leave_all_rooms(self.services, &user_id).await;
|
||||
|
||||
full_user_deactivate(self.services, &user_id, all_joined_rooms).await?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -271,9 +291,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
.rooms_joined(&user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
|
||||
update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
|
||||
leave_all_rooms(self.services, &user_id).await;
|
||||
full_user_deactivate(self.services, &user_id, all_joined_rooms).await?;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
@@ -320,7 +338,7 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result<RoomMess
|
||||
rooms.reverse();
|
||||
|
||||
let output_plain = format!(
|
||||
"Rooms {user_id} Joined ({}):\n{}",
|
||||
"Rooms {user_id} Joined ({}):\n```\n{}\n```",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
@@ -329,26 +347,7 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result<RoomMess
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
let output_html = format!(
|
||||
"<table><caption>Rooms {user_id} Joined \
|
||||
({})</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (id, members, name)| {
|
||||
writeln!(
|
||||
output,
|
||||
"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
|
||||
escape_html(id.as_ref()),
|
||||
members,
|
||||
escape_html(name)
|
||||
)
|
||||
.unwrap();
|
||||
output
|
||||
})
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
|
||||
Ok(RoomMessageEventContent::notice_markdown(output_plain))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
@@ -362,7 +361,7 @@ pub(super) async fn force_join_room(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
"Parsed user_id must be a local user"
|
||||
);
|
||||
join_room_by_id_helper(self.services, &user_id, &room_id, None, &[], None).await?;
|
||||
join_room_by_id_helper(self.services, &user_id, &room_id, None, &[], None, &None).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{user_id} has been joined to {room_id}.",
|
||||
@@ -370,23 +369,101 @@ pub(super) async fn force_join_room(
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn force_leave_room(
|
||||
&self, user_id: String, room_id: OwnedRoomOrAliasId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let displayname = self
|
||||
.services
|
||||
.users
|
||||
.displayname(&user_id)?
|
||||
.unwrap_or_else(|| user_id.to_string());
|
||||
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
|
||||
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
"Parsed user_id must be a local user"
|
||||
);
|
||||
self.services
|
||||
.admin
|
||||
.make_user_admin(&user_id, displayname)
|
||||
leave_room(self.services, &user_id, &room_id, None).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{user_id} has left {room_id}.",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_demote(
|
||||
&self, user_id: String, room_id: OwnedRoomOrAliasId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
|
||||
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
"Parsed user_id must be a local user"
|
||||
);
|
||||
|
||||
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
let room_power_levels = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomPowerLevels, "")?
|
||||
.as_ref()
|
||||
.and_then(|event| serde_json::from_str(event.content.get()).ok()?)
|
||||
.and_then(|content: RoomPowerLevelsEventContent| content.into());
|
||||
|
||||
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)
|
||||
}) || self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomCreate, "")?
|
||||
.as_ref()
|
||||
.is_some_and(|event| event.sender == user_id);
|
||||
|
||||
if !user_can_demote_self {
|
||||
return Ok(RoomMessageEventContent::notice_markdown(
|
||||
"User is not allowed to modify their own power levels in the room.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||
power_levels_content.users.remove(&user_id);
|
||||
|
||||
let event_id = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&power_levels_content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(String::new()),
|
||||
redacts: None,
|
||||
timestamp: None,
|
||||
},
|
||||
&user_id,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"User {user_id} demoted themselves to the room default power level in {room_id} - {event_id}"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
|
||||
assert!(
|
||||
self.services.globals.user_is_local(&user_id),
|
||||
"Parsed user_id must be a local user"
|
||||
);
|
||||
self.services.admin.make_user_admin(&user_id).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{user_id} has been granted admin privileges.",
|
||||
)))
|
||||
|
||||
@@ -73,6 +73,19 @@ pub(super) enum UserCommand {
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
},
|
||||
|
||||
/// - Manually leave a local user from a room.
|
||||
ForceLeaveRoom {
|
||||
user_id: String,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
},
|
||||
|
||||
/// - Forces the specified user to drop their power levels to the room
|
||||
/// default, if their permissions allow and the auth check permits
|
||||
ForceDemote {
|
||||
user_id: String,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
},
|
||||
|
||||
/// - Grant server-admin privileges to a user.
|
||||
MakeUserAdmin {
|
||||
user_id: String,
|
||||
|
||||
+154
-54
@@ -2,7 +2,7 @@ use std::fmt::Write;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::{debug_info, error, info, utils, warn, Error, Result};
|
||||
use conduit::{debug_info, error, info, utils, warn, Error, PduBuilder, Result};
|
||||
use register::RegistrationKind;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
@@ -15,9 +15,17 @@ use ruma::{
|
||||
error::ErrorKind,
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
|
||||
events::{
|
||||
room::{
|
||||
message::RoomMessageEventContent,
|
||||
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||
},
|
||||
GlobalAccountDataEventType, StateEventType, TimelineEventType,
|
||||
},
|
||||
push, OwnedRoomId, UserId,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use service::Services;
|
||||
|
||||
use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
||||
use crate::Ruma;
|
||||
@@ -296,50 +304,61 @@ pub(crate) async fn register_route(
|
||||
|
||||
debug_info!(%user_id, %device_id, "User account was created");
|
||||
|
||||
let device_display_name = body.initial_device_display_name.clone().unwrap_or_default();
|
||||
|
||||
// log in conduit admin channel if a non-guest user registered
|
||||
if body.appservice_info.is_none() && !is_guest {
|
||||
info!("New user \"{user_id}\" registered on this server.");
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"New user \"{user_id}\" registered on this server from IP {client}."
|
||||
)))
|
||||
.await;
|
||||
if !device_display_name.is_empty() {
|
||||
info!("New user \"{user_id}\" registered on this server with device display name: {device_display_name}");
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"New user \"{user_id}\" registered on this server from IP {client} and device display name \
|
||||
\"{device_display_name}\""
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
info!("New user \"{user_id}\" registered on this server.");
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"New user \"{user_id}\" registered on this server from IP {client}"
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log in conduit admin channel if a guest registered
|
||||
if body.appservice_info.is_none() && is_guest && services.globals.log_guest_registrations() {
|
||||
info!("New guest user \"{user_id}\" registered on this server.");
|
||||
|
||||
if let Some(device_display_name) = &body.initial_device_display_name {
|
||||
if body
|
||||
.initial_device_display_name
|
||||
.as_ref()
|
||||
.is_some_and(|device_display_name| !device_display_name.is_empty())
|
||||
{
|
||||
if !device_display_name.is_empty() {
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \
|
||||
server from IP {client}."
|
||||
)))
|
||||
.await;
|
||||
} else {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
|
||||
{client}.",
|
||||
"Guest user \"{user_id}\" with device display name \"{device_display_name}\" registered on \
|
||||
this server from IP {client}"
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on this server from IP {client}.",
|
||||
)))
|
||||
.await;
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
|
||||
{client}",
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,10 +367,7 @@ pub(crate) async fn register_route(
|
||||
if !is_guest {
|
||||
if let Some(admin_room) = services.admin.get_admin_room()? {
|
||||
if services.rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
|
||||
services
|
||||
.admin
|
||||
.make_user_admin(&user_id, displayname)
|
||||
.await?;
|
||||
services.admin.make_user_admin(&user_id).await?;
|
||||
|
||||
warn!("Granting {user_id} admin privileges as the first user");
|
||||
}
|
||||
@@ -380,6 +396,7 @@ pub(crate) async fn register_route(
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[room_id_server_name.to_owned(), services.globals.server_name().to_owned()],
|
||||
None,
|
||||
&body.appservice_info,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -476,12 +493,15 @@ pub(crate) async fn change_password_route(
|
||||
}
|
||||
|
||||
info!("User {sender_user} changed their password.");
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"User {sender_user} changed their password."
|
||||
)))
|
||||
.await;
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"User {sender_user} changed their password."
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(change_password::v3::Response {})
|
||||
}
|
||||
@@ -556,9 +576,6 @@ pub(crate) async fn deactivate_route(
|
||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||
}
|
||||
|
||||
// Remove devices and mark account as deactivated
|
||||
services.users.deactivate_account(sender_user)?;
|
||||
|
||||
// Remove profile pictures and display name
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
@@ -566,19 +583,19 @@ pub(crate) async fn deactivate_route(
|
||||
.rooms_joined(sender_user)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
super::update_displayname(&services, sender_user.clone(), None, all_joined_rooms.clone()).await?;
|
||||
super::update_avatar_url(&services, sender_user.clone(), None, None, all_joined_rooms).await?;
|
||||
|
||||
// Make the user leave all rooms before deactivation
|
||||
super::leave_all_rooms(&services, sender_user).await;
|
||||
full_user_deactivate(&services, sender_user, all_joined_rooms).await?;
|
||||
|
||||
info!("User {sender_user} deactivated their account.");
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"User {sender_user} deactivated their account."
|
||||
)))
|
||||
.await;
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
"User {sender_user} deactivated their account."
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(deactivate::v3::Response {
|
||||
id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
|
||||
@@ -648,3 +665,86 @@ pub(crate) async fn check_registration_token_validity(
|
||||
valid: reg_token == body.token,
|
||||
})
|
||||
}
|
||||
|
||||
/// Runs through all the deactivation steps:
|
||||
///
|
||||
/// - Mark as deactivated
|
||||
/// - Removing display name
|
||||
/// - Removing avatar URL and blurhash
|
||||
/// - Removing all profile data
|
||||
/// - Leaving all rooms (and forgets all of them)
|
||||
pub async fn full_user_deactivate(
|
||||
services: &Services, user_id: &UserId, all_joined_rooms: Vec<OwnedRoomId>,
|
||||
) -> Result<()> {
|
||||
services.users.deactivate_account(user_id)?;
|
||||
|
||||
super::update_displayname(services, user_id, None, all_joined_rooms.clone()).await?;
|
||||
super::update_avatar_url(services, user_id, None, None, all_joined_rooms.clone()).await?;
|
||||
|
||||
let all_profile_keys = services
|
||||
.users
|
||||
.all_profile_keys(user_id)
|
||||
.filter_map(Result::ok);
|
||||
|
||||
for (profile_key, _profile_value) in all_profile_keys {
|
||||
if let Err(e) = services.users.set_profile_key(user_id, &profile_key, None) {
|
||||
warn!("Failed removing {user_id} profile key {profile_key}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
for room_id in all_joined_rooms {
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
let room_power_levels = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(&room_id, &StateEventType::RoomPowerLevels, "")?
|
||||
.as_ref()
|
||||
.and_then(|event| serde_json::from_str(event.content.get()).ok()?)
|
||||
.and_then(|content: RoomPowerLevelsEventContent| content.into());
|
||||
|
||||
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, "")?
|
||||
.as_ref()
|
||||
.is_some_and(|event| event.sender == user_id);
|
||||
|
||||
if user_can_demote_self {
|
||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||
power_levels_content.users.remove(user_id);
|
||||
|
||||
// ignore errors so deactivation doesn't fail
|
||||
if let Err(e) = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomPowerLevels,
|
||||
content: to_raw_value(&power_levels_content).expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
state_key: Some(String::new()),
|
||||
redacts: None,
|
||||
timestamp: None,
|
||||
},
|
||||
user_id,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(%room_id, %user_id, "Failed to demote user's own power level: {e}");
|
||||
} else {
|
||||
info!("Demoted {user_id} in {room_id} as part of account deactivation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super::leave_all_rooms(services, user_id).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
use axum::extract::State;
|
||||
use conduit::{err, Err, Result};
|
||||
use ruma::api::{appservice::ping, client::appservice::request_ping};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `POST /_matrix/client/v1/appservice/{appserviceId}/ping`
|
||||
///
|
||||
/// Ask the homeserver to ping the application service to ensure the connection
|
||||
/// works.
|
||||
pub(crate) async fn appservice_ping(
|
||||
State(services): State<crate::State>, body: Ruma<request_ping::v1::Request>,
|
||||
) -> Result<request_ping::v1::Response> {
|
||||
let appservice_info = body
|
||||
.appservice_info
|
||||
.as_ref()
|
||||
.ok_or_else(|| err!(Request(Forbidden("This endpoint can only be called by appservices."))))?;
|
||||
|
||||
if body.appservice_id != appservice_info.registration.id {
|
||||
return Err!(Request(Forbidden(
|
||||
"Appservices can only ping themselves (wrong appservice ID)."
|
||||
)));
|
||||
}
|
||||
|
||||
if appservice_info.registration.url.is_none() {
|
||||
return Err!(Request(UrlNotSet(
|
||||
"Appservice does not have a URL set, there is nothing to ping."
|
||||
)));
|
||||
}
|
||||
|
||||
let timer = tokio::time::Instant::now();
|
||||
|
||||
let _response = services
|
||||
.sending
|
||||
.send_appservice_request(
|
||||
appservice_info.registration.clone(),
|
||||
ping::send_ping::v1::Request {
|
||||
transaction_id: body.transaction_id.clone(),
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.expect("We already validated if an appservice URL exists above");
|
||||
|
||||
Ok(request_ping::v1::Response {
|
||||
duration: timer.elapsed(),
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use ruma::api::client::discovery::get_capabilities::{
|
||||
self, Capabilities, RoomVersionStability, RoomVersionsCapability, ThirdPartyIdChangesCapability,
|
||||
use ruma::{
|
||||
api::client::discovery::get_capabilities::{
|
||||
self, Capabilities, RoomVersionStability, RoomVersionsCapability, ThirdPartyIdChangesCapability,
|
||||
},
|
||||
RoomVersionId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{Result, Ruma};
|
||||
|
||||
@@ -14,13 +18,19 @@ use crate::{Result, Ruma};
|
||||
pub(crate) async fn get_capabilities_route(
|
||||
State(services): State<crate::State>, _body: Ruma<get_capabilities::v3::Request>,
|
||||
) -> Result<get_capabilities::v3::Response> {
|
||||
let mut available = BTreeMap::new();
|
||||
for room_version in &services.globals.unstable_room_versions {
|
||||
available.insert(room_version.clone(), RoomVersionStability::Unstable);
|
||||
}
|
||||
for room_version in &services.globals.stable_room_versions {
|
||||
available.insert(room_version.clone(), RoomVersionStability::Stable);
|
||||
}
|
||||
let available: BTreeMap<RoomVersionId, RoomVersionStability> = services
|
||||
.globals
|
||||
.unstable_room_versions
|
||||
.iter()
|
||||
.map(|unstable_room_version| (unstable_room_version.clone(), RoomVersionStability::Unstable))
|
||||
.chain(
|
||||
services
|
||||
.globals
|
||||
.stable_room_versions
|
||||
.iter()
|
||||
.map(|stable_room_version| (stable_room_version.clone(), RoomVersionStability::Stable)),
|
||||
)
|
||||
.collect();
|
||||
|
||||
let mut capabilities = Capabilities::default();
|
||||
capabilities.room_versions = RoomVersionsCapability {
|
||||
@@ -28,11 +38,16 @@ pub(crate) async fn get_capabilities_route(
|
||||
available,
|
||||
};
|
||||
|
||||
// conduit does not implement 3PID stuff
|
||||
// we do not implement 3PID stuff
|
||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
// MSC4133 capability
|
||||
capabilities
|
||||
.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))
|
||||
.expect("this is valid JSON we created");
|
||||
|
||||
Ok(get_capabilities::v3::Response {
|
||||
capabilities,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::{err, info, warn, Error, Result};
|
||||
use conduit::{err, info, warn, Err, Error, Result};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::{
|
||||
@@ -124,6 +124,10 @@ pub(crate) async fn set_room_visibility_route(
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
|
||||
}
|
||||
|
||||
if services.users.is_deactivated(sender_user).unwrap_or(false) && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("Guests cannot publish to room directories")));
|
||||
}
|
||||
|
||||
if !user_can_publish_room(&services, sender_user, &body.room_id)? {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::forbidden(),
|
||||
@@ -133,13 +137,27 @@ pub(crate) async fn set_room_visibility_route(
|
||||
|
||||
match &body.visibility {
|
||||
room::Visibility::Public => {
|
||||
if services.globals.config.lockdown_public_room_directory && !services.users.is_admin(sender_user)? {
|
||||
if services.globals.config.lockdown_public_room_directory
|
||||
&& !services.users.is_admin(sender_user)?
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
info!(
|
||||
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
|
||||
\"lockdown_public_room_directory\" is enabled",
|
||||
body.room_id
|
||||
);
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!(
|
||||
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
|
||||
\"lockdown_public_room_directory\" is enabled",
|
||||
body.room_id
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::forbidden(),
|
||||
"Publishing rooms to the room directory is not allowed",
|
||||
@@ -147,7 +165,14 @@ pub(crate) async fn set_room_visibility_route(
|
||||
}
|
||||
|
||||
services.rooms.directory.set_public(&body.room_id)?;
|
||||
info!("{sender_user} made {0} public", body.room_id);
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!("{sender_user} made {} public to the room directory", body.room_id))
|
||||
.await;
|
||||
}
|
||||
info!("{sender_user} made {0} public to the room directory", body.room_id);
|
||||
},
|
||||
room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id)?,
|
||||
_ => {
|
||||
|
||||
@@ -20,7 +20,8 @@ use ruma::{
|
||||
error::ErrorKind,
|
||||
membership::{
|
||||
ban_user, forget_room, get_member_events, invite_user, join_room_by_id, join_room_by_id_or_alias,
|
||||
joined_members, joined_rooms, kick_user, leave_room, unban_user, ThirdPartySigned,
|
||||
joined_members::{self, v3::RoomMember},
|
||||
joined_rooms, kick_user, leave_room, unban_user, ThirdPartySigned,
|
||||
},
|
||||
},
|
||||
federation::{self, membership::create_invite},
|
||||
@@ -39,13 +40,10 @@ use ruma::{
|
||||
OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
|
||||
};
|
||||
use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
|
||||
use service::{rooms::state::RoomMutexGuard, Services};
|
||||
use service::{appservice::RegistrationInfo, rooms::state::RoomMutexGuard, Services};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
client::{update_avatar_url, update_displayname},
|
||||
Ruma,
|
||||
};
|
||||
use crate::{client::full_user_deactivate, Ruma};
|
||||
|
||||
/// Checks if the room is banned in any way possible and the sender user is not
|
||||
/// an admin.
|
||||
@@ -73,16 +71,15 @@ async fn banned_room_check(
|
||||
|
||||
if services.globals.config.auto_deactivate_banned_room_attempts {
|
||||
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
|
||||
{client_ip}"
|
||||
)))
|
||||
.await;
|
||||
|
||||
if let Err(e) = services.users.deactivate_account(user_id) {
|
||||
warn!(%user_id, %e, "Failed to deactivate account");
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
|
||||
{client_ip}"
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
@@ -92,9 +89,7 @@ async fn banned_room_check(
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_displayname(services, user_id.into(), None, all_joined_rooms.clone()).await?;
|
||||
update_avatar_url(services, user_id.into(), None, None, all_joined_rooms).await?;
|
||||
leave_all_rooms(services, user_id).await;
|
||||
full_user_deactivate(services, user_id, all_joined_rooms).await?;
|
||||
}
|
||||
|
||||
return Err(Error::BadRequest(
|
||||
@@ -116,16 +111,15 @@ async fn banned_room_check(
|
||||
|
||||
if services.globals.config.auto_deactivate_banned_room_attempts {
|
||||
warn!("Automatically deactivating user {user_id} due to attempted banned room join");
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
|
||||
{client_ip}"
|
||||
)))
|
||||
.await;
|
||||
|
||||
if let Err(e) = services.users.deactivate_account(user_id) {
|
||||
warn!(%user_id, %e, "Failed to deactivate account");
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
|
||||
{client_ip}"
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
@@ -135,9 +129,7 @@ async fn banned_room_check(
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_displayname(services, user_id.into(), None, all_joined_rooms.clone()).await?;
|
||||
update_avatar_url(services, user_id.into(), None, None, all_joined_rooms).await?;
|
||||
leave_all_rooms(services, user_id).await;
|
||||
full_user_deactivate(services, user_id, all_joined_rooms).await?;
|
||||
}
|
||||
|
||||
return Err(Error::BadRequest(
|
||||
@@ -208,6 +200,7 @@ pub(crate) async fn join_room_by_id_route(
|
||||
body.reason.clone(),
|
||||
&servers,
|
||||
body.third_party_signed.as_ref(),
|
||||
&body.appservice_info,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -227,13 +220,14 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
body: Ruma<join_room_by_id_or_alias::v3::Request>,
|
||||
) -> Result<join_room_by_id_or_alias::v3::Response> {
|
||||
let sender_user = body.sender_user.as_deref().expect("user is authenticated");
|
||||
let appservice_info = &body.appservice_info;
|
||||
let body = body.body;
|
||||
|
||||
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) {
|
||||
Ok(room_id) => {
|
||||
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client).await?;
|
||||
|
||||
let mut servers = body.server_name.clone();
|
||||
let mut servers = body.via.clone();
|
||||
servers.extend(
|
||||
services
|
||||
.rooms
|
||||
@@ -266,13 +260,13 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
let response = services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, Some(&body.server_name.clone()))
|
||||
.resolve_alias(&room_alias, Some(&body.via.clone()))
|
||||
.await?;
|
||||
let (room_id, mut pre_servers) = response;
|
||||
|
||||
banned_room_check(&services, sender_user, Some(&room_id), Some(room_alias.server_name()), client).await?;
|
||||
|
||||
let mut servers = body.server_name;
|
||||
let mut servers = body.via;
|
||||
if let Some(pre_servers) = &mut pre_servers {
|
||||
servers.append(pre_servers);
|
||||
}
|
||||
@@ -309,6 +303,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
|
||||
body.reason.clone(),
|
||||
&servers,
|
||||
body.third_party_signed.as_ref(),
|
||||
appservice_info,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -635,24 +630,22 @@ pub(crate) async fn joined_members_route(
|
||||
));
|
||||
}
|
||||
|
||||
let mut joined = BTreeMap::new();
|
||||
for user_id in services
|
||||
let joined: BTreeMap<OwnedUserId, RoomMember> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&body.room_id)
|
||||
.filter_map(Result::ok)
|
||||
{
|
||||
let display_name = services.users.displayname(&user_id)?;
|
||||
let avatar_url = services.users.avatar_url(&user_id)?;
|
||||
.filter_map(|user| {
|
||||
let user = user.ok()?;
|
||||
|
||||
joined.insert(
|
||||
user_id,
|
||||
joined_members::v3::RoomMember {
|
||||
display_name,
|
||||
avatar_url,
|
||||
},
|
||||
);
|
||||
}
|
||||
Some((
|
||||
user.clone(),
|
||||
RoomMember {
|
||||
display_name: services.users.displayname(&user).unwrap_or_default(),
|
||||
avatar_url: services.users.avatar_url(&user).unwrap_or_default(),
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(joined_members::v3::Response {
|
||||
joined,
|
||||
@@ -661,11 +654,11 @@ pub(crate) async fn joined_members_route(
|
||||
|
||||
pub async fn join_room_by_id_helper(
|
||||
services: &Services, sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
|
||||
third_party_signed: Option<&ThirdPartySigned>,
|
||||
third_party_signed: Option<&ThirdPartySigned>, appservice_info: &Option<RegistrationInfo>,
|
||||
) -> Result<join_room_by_id::v3::Response> {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
|
||||
let user_is_guest = services.users.is_deactivated(sender_user).unwrap_or(false);
|
||||
let user_is_guest = services.users.is_deactivated(sender_user).unwrap_or(false) && appservice_info.is_none();
|
||||
|
||||
if matches!(services.rooms.state_accessor.guest_can_join(room_id), Ok(false)) && user_is_guest {
|
||||
return Err!(Request(Forbidden("Guests are not allowed to join this room")));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub(super) mod account;
|
||||
pub(super) mod alias;
|
||||
pub(super) mod appservice;
|
||||
pub(super) mod backup;
|
||||
pub(super) mod capabilities;
|
||||
pub(super) mod config;
|
||||
@@ -36,8 +37,10 @@ pub(super) mod unversioned;
|
||||
pub(super) mod user_directory;
|
||||
pub(super) mod voip;
|
||||
|
||||
pub use account::full_user_deactivate;
|
||||
pub(super) use account::*;
|
||||
pub(super) use alias::*;
|
||||
pub(super) use appservice::*;
|
||||
pub(super) use backup::*;
|
||||
pub(super) use capabilities::*;
|
||||
pub(super) use config::*;
|
||||
|
||||
+47
-19
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use conduit::{pdu::PduBuilder, warn, Error, Result};
|
||||
use conduit::{pdu::PduBuilder, warn, Err, Error, Result};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::{
|
||||
@@ -10,7 +10,7 @@ use ruma::{
|
||||
},
|
||||
events::{room::member::RoomMemberEventContent, StateEventType, TimelineEventType},
|
||||
presence::PresenceState,
|
||||
OwnedMxcUri, OwnedRoomId, OwnedUserId,
|
||||
OwnedMxcUri, OwnedRoomId, UserId,
|
||||
};
|
||||
use serde_json::value::to_raw_value;
|
||||
use service::Services;
|
||||
@@ -26,20 +26,25 @@ pub(crate) async fn set_displayname_route(
|
||||
State(services): State<crate::State>, body: Ruma<set_display_name::v3::Request>,
|
||||
) -> Result<set_display_name::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_displayname(&services, sender_user.clone(), body.displayname.clone(), all_joined_rooms).await?;
|
||||
update_displayname(&services, &body.user_id, body.displayname.clone(), all_joined_rooms).await?;
|
||||
|
||||
if services.globals.allow_local_presence() {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(sender_user, &PresenceState::Online)?;
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||
}
|
||||
|
||||
Ok(set_display_name::v3::Response {})
|
||||
@@ -110,16 +115,21 @@ pub(crate) async fn set_avatar_url_route(
|
||||
State(services): State<crate::State>, body: Ruma<set_avatar_url::v3::Request>,
|
||||
) -> Result<set_avatar_url::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(sender_user)
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_avatar_url(
|
||||
&services,
|
||||
sender_user.clone(),
|
||||
&body.user_id,
|
||||
body.avatar_url.clone(),
|
||||
body.blurhash.clone(),
|
||||
all_joined_rooms,
|
||||
@@ -130,7 +140,7 @@ pub(crate) async fn set_avatar_url_route(
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(sender_user, &PresenceState::Online)?;
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||
}
|
||||
|
||||
Ok(set_avatar_url::v3::Response {})
|
||||
@@ -196,7 +206,7 @@ pub(crate) async fn get_avatar_url_route(
|
||||
|
||||
/// # `GET /_matrix/client/v3/profile/{userId}`
|
||||
///
|
||||
/// Returns the displayname, avatar_url and blurhash of the user.
|
||||
/// Returns the displayname, avatar_url, blurhash, and tz of the user.
|
||||
///
|
||||
/// - If user is on another server and we do not have a local copy already,
|
||||
/// fetch profile over federation.
|
||||
@@ -232,11 +242,23 @@ pub(crate) async fn get_profile_route(
|
||||
.users
|
||||
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_timezone(&body.user_id, response.tz.clone())
|
||||
.await?;
|
||||
|
||||
for (profile_key, profile_key_value) in &response.custom_profile_fields {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, profile_key, Some(profile_key_value.clone()))?;
|
||||
}
|
||||
|
||||
return Ok(get_profile::v3::Response {
|
||||
displayname: response.displayname,
|
||||
avatar_url: response.avatar_url,
|
||||
blurhash: response.blurhash,
|
||||
tz: response.tz,
|
||||
custom_profile_fields: response.custom_profile_fields,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -251,13 +273,19 @@ pub(crate) async fn get_profile_route(
|
||||
avatar_url: services.users.avatar_url(&body.user_id)?,
|
||||
blurhash: services.users.blurhash(&body.user_id)?,
|
||||
displayname: services.users.displayname(&body.user_id)?,
|
||||
tz: services.users.timezone(&body.user_id)?,
|
||||
custom_profile_fields: services
|
||||
.users
|
||||
.all_profile_keys(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn update_displayname(
|
||||
services: &Services, user_id: OwnedUserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>,
|
||||
services: &Services, user_id: &UserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>,
|
||||
) -> Result<()> {
|
||||
let current_display_name = services.users.displayname(&user_id).unwrap_or_default();
|
||||
let current_display_name = services.users.displayname(user_id).unwrap_or_default();
|
||||
|
||||
if displayname == current_display_name {
|
||||
return Ok(());
|
||||
@@ -265,7 +293,7 @@ pub async fn update_displayname(
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(&user_id, displayname.clone())
|
||||
.set_displayname(user_id, displayname.clone())
|
||||
.await?;
|
||||
|
||||
// Send a new join membership event into all joined rooms
|
||||
@@ -309,11 +337,11 @@ pub async fn update_displayname(
|
||||
}
|
||||
|
||||
pub async fn update_avatar_url(
|
||||
services: &Services, user_id: OwnedUserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>,
|
||||
services: &Services, user_id: &UserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>,
|
||||
all_joined_rooms: Vec<OwnedRoomId>,
|
||||
) -> Result<()> {
|
||||
let current_avatar_url = services.users.avatar_url(&user_id).unwrap_or_default();
|
||||
let current_blurhash = services.users.blurhash(&user_id).unwrap_or_default();
|
||||
let current_avatar_url = services.users.avatar_url(user_id).unwrap_or_default();
|
||||
let current_blurhash = services.users.blurhash(user_id).unwrap_or_default();
|
||||
|
||||
if current_avatar_url == avatar_url && current_blurhash == blurhash {
|
||||
return Ok(());
|
||||
@@ -321,11 +349,11 @@ pub async fn update_avatar_url(
|
||||
|
||||
services
|
||||
.users
|
||||
.set_avatar_url(&user_id, avatar_url.clone())
|
||||
.set_avatar_url(user_id, avatar_url.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_blurhash(&user_id, blurhash.clone())
|
||||
.set_blurhash(user_id, blurhash.clone())
|
||||
.await?;
|
||||
|
||||
// Send a new join membership event into all joined rooms
|
||||
@@ -370,14 +398,14 @@ pub async fn update_avatar_url(
|
||||
}
|
||||
|
||||
pub async fn update_all_rooms(
|
||||
services: &Services, all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: OwnedUserId,
|
||||
services: &Services, all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: &UserId,
|
||||
) {
|
||||
for (pdu_builder, room_id) in all_joined_rooms {
|
||||
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||
if let Err(e) = services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(pdu_builder, &user_id, room_id, &state_lock)
|
||||
.build_and_append_pdu(pdu_builder, user_id, room_id, &state_lock)
|
||||
.await
|
||||
{
|
||||
warn!(%user_id, %room_id, %e, "Failed to update/send new profile join membership update in room");
|
||||
|
||||
+23
-25
@@ -31,6 +31,8 @@ pub(crate) async fn report_event_route(
|
||||
body.room_id, body.event_id
|
||||
);
|
||||
|
||||
delay_response().await;
|
||||
|
||||
// check if we know about the reported event ID or if it's invalid
|
||||
let Some(pdu) = services.rooms.timeline.get_pdu(&body.event_id)? else {
|
||||
return Err(Error::BadRequest(
|
||||
@@ -81,21 +83,19 @@ pub(crate) async fn report_event_route(
|
||||
))
|
||||
.await;
|
||||
|
||||
delay_response().await?;
|
||||
|
||||
Ok(report_content::v3::Response {})
|
||||
}
|
||||
|
||||
/// in the following order:
|
||||
///
|
||||
/// check if the room ID from the URI matches the PDU's room ID
|
||||
/// check if reporting user is in the reporting room
|
||||
/// check if score is in valid range
|
||||
/// check if report reasoning is less than or equal to 750 characters
|
||||
/// check if reporting user is in the reporting room
|
||||
fn is_report_valid(
|
||||
services: &Services, event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: &Option<String>,
|
||||
score: Option<ruma::Int>, pdu: &std::sync::Arc<PduEvent>,
|
||||
) -> Result<bool> {
|
||||
) -> Result<()> {
|
||||
debug_info!("Checking if report from user {sender_user} for event {event_id} in room {room_id} is valid");
|
||||
|
||||
if room_id != pdu.room_id {
|
||||
@@ -105,10 +105,24 @@ fn is_report_valid(
|
||||
));
|
||||
}
|
||||
|
||||
if score.is_some_and(|s| s > int!(0) || s < int!(-100)) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid score, must be within 0 to -100",
|
||||
));
|
||||
};
|
||||
|
||||
if reason.as_ref().is_some_and(|s| s.len() > 750) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Reason too long, should be 750 characters or fewer",
|
||||
));
|
||||
};
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&pdu.room_id)
|
||||
.room_members(room_id)
|
||||
.filter_map(Result::ok)
|
||||
.any(|user_id| user_id == *sender_user)
|
||||
{
|
||||
@@ -118,30 +132,14 @@ fn is_report_valid(
|
||||
));
|
||||
}
|
||||
|
||||
if score.map(|s| s > int!(0) || s < int!(-100)) == Some(true) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid score, must be within 0 to -100",
|
||||
));
|
||||
};
|
||||
|
||||
if reason.clone().map(|s| s.len() >= 750) == Some(true) {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Reason too long, should be 750 characters or fewer",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(true)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// even though this is kinda security by obscurity, let's still make a small
|
||||
/// random delay sending a successful response per spec suggestion regarding
|
||||
/// random delay sending a response per spec suggestion regarding
|
||||
/// enumerating for potential events existing in our server.
|
||||
async fn delay_response() -> Result<()> {
|
||||
let time_to_wait = rand::thread_rng().gen_range(8..21);
|
||||
async fn delay_response() {
|
||||
let time_to_wait = rand::thread_rng().gen_range(3..10);
|
||||
debug_info!("Got successful /report request, waiting {time_to_wait} seconds before sending successful response.");
|
||||
sleep(Duration::from_secs(time_to_wait)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+45
-3
@@ -1,7 +1,7 @@
|
||||
use std::{cmp::max, collections::BTreeMap};
|
||||
|
||||
use axum::extract::State;
|
||||
use conduit::{debug_info, debug_warn, err};
|
||||
use conduit::{debug_info, debug_warn, err, Err};
|
||||
use ruma::{
|
||||
api::client::{
|
||||
error::ErrorKind,
|
||||
@@ -64,6 +64,7 @@ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
|
||||
/// - Send events listed in initial state
|
||||
/// - Send events implied by `name` and `topic`
|
||||
/// - Send invite events
|
||||
#[allow(clippy::large_stack_frames)]
|
||||
pub(crate) async fn create_room_route(
|
||||
State(services): State<crate::State>, body: Ruma<create_room::v3::Request>,
|
||||
) -> Result<create_room::v3::Response> {
|
||||
@@ -92,6 +93,31 @@ pub(crate) async fn create_room_route(
|
||||
));
|
||||
}
|
||||
|
||||
if body.visibility == room::Visibility::Public
|
||||
&& services.globals.config.lockdown_public_room_directory
|
||||
&& !services.users.is_admin(sender_user)?
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
info!(
|
||||
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
|
||||
\"lockdown_public_room_directory\" is enabled",
|
||||
&room_id
|
||||
);
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!(
|
||||
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
|
||||
\"lockdown_public_room_directory\" is enabled",
|
||||
&room_id
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
|
||||
}
|
||||
|
||||
let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?;
|
||||
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
@@ -119,6 +145,7 @@ pub(crate) async fn create_room_route(
|
||||
None => services.globals.default_room_version(),
|
||||
};
|
||||
|
||||
#[allow(clippy::single_match_else)]
|
||||
let content = match &body.creation_content {
|
||||
Some(content) => {
|
||||
use RoomVersionId::*;
|
||||
@@ -230,8 +257,7 @@ pub(crate) async fn create_room_route(
|
||||
_ => RoomPreset::PrivateChat, // Room visibility should not be custom
|
||||
});
|
||||
|
||||
let mut users = BTreeMap::new();
|
||||
users.insert(sender_user.clone(), int!(100));
|
||||
let mut users = BTreeMap::from_iter([(sender_user.clone(), int!(100))]);
|
||||
|
||||
if preset == RoomPreset::TrustedPrivateChat {
|
||||
for invite_ in &body.invite {
|
||||
@@ -450,6 +476,14 @@ pub(crate) async fn create_room_route(
|
||||
|
||||
if body.visibility == room::Visibility::Public {
|
||||
services.rooms.directory.set_public(&room_id)?;
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!("{sender_user} made {} public to the room directory", &room_id))
|
||||
.await;
|
||||
}
|
||||
info!("{sender_user} made {0} public to the room directory", &room_id);
|
||||
}
|
||||
|
||||
info!("{sender_user} created a room with room ID {room_id}");
|
||||
@@ -821,10 +855,18 @@ fn default_power_levels_content(
|
||||
power_levels_content["events"]["m.room.history_visibility"] =
|
||||
serde_json::to_value(100).expect("100 is valid Value");
|
||||
|
||||
// always allow users to respond (not post new) to polls. this is primarily
|
||||
// useful in read-only announcement rooms that post a public poll.
|
||||
power_levels_content["events"]["org.matrix.msc3381.poll.response"] =
|
||||
serde_json::to_value(0).expect("0 is valid Value");
|
||||
power_levels_content["events"]["m.poll.response"] = serde_json::to_value(0).expect("0 is valid Value");
|
||||
|
||||
// synapse does this too. clients do not expose these permissions. it prevents
|
||||
// default users from calling public rooms, for obvious reasons.
|
||||
if *visibility == room::Visibility::Public {
|
||||
power_levels_content["events"]["m.call.invite"] = serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["m.call"] = serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["m.call.member"] = serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["org.matrix.msc3401.call"] =
|
||||
serde_json::to_value(50).expect("50 is valid Value");
|
||||
power_levels_content["events"]["org.matrix.msc3401.call.member"] =
|
||||
|
||||
+38
-39
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
cmp,
|
||||
cmp::Ordering,
|
||||
cmp::{self, Ordering},
|
||||
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -26,12 +25,12 @@ use ruma::{
|
||||
},
|
||||
uiaa::UiaaResponse,
|
||||
},
|
||||
directory::RoomTypeFilter,
|
||||
events::{
|
||||
presence::PresenceEvent,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
AnyRawAccountDataEvent, StateEventType, TimelineEventType,
|
||||
},
|
||||
room::RoomType,
|
||||
serde::Raw,
|
||||
state_res::Event,
|
||||
uint, DeviceId, EventId, MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId,
|
||||
@@ -1501,8 +1500,28 @@ pub(crate) async fn sync_events_v4_route(
|
||||
for (room_id, (required_state_request, timeline_limit, roomsince)) in &todo_rooms {
|
||||
let roomsincecount = PduCount::Normal(*roomsince);
|
||||
|
||||
let (timeline_pdus, limited) =
|
||||
load_timeline(&services, &sender_user, room_id, roomsincecount, *timeline_limit)?;
|
||||
let mut timestamp: Option<_> = None;
|
||||
let mut invite_state = None;
|
||||
let (timeline_pdus, limited);
|
||||
if all_invited_rooms.contains(room_id) {
|
||||
// TODO: figure out a timestamp we can use for remote invites
|
||||
invite_state = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(&sender_user, room_id)
|
||||
.unwrap_or(None);
|
||||
|
||||
(timeline_pdus, limited) = (Vec::new(), true);
|
||||
} else {
|
||||
(timeline_pdus, limited) =
|
||||
match load_timeline(&services, &sender_user, room_id, roomsincecount, *timeline_limit) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
warn!("Encountered missing timeline in {}, error {}", room_id, err);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
account_data.rooms.insert(
|
||||
room_id.clone(),
|
||||
@@ -1556,17 +1575,6 @@ pub(crate) async fn sync_events_v4_route(
|
||||
.map(|(_, pdu)| pdu.to_sync_room_event())
|
||||
.collect();
|
||||
|
||||
let invite_state = if all_invited_rooms.contains(room_id) {
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(&sender_user, room_id)
|
||||
.unwrap_or(None)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut timestamp: Option<_> = None;
|
||||
for (_, pdu) in timeline_pdus {
|
||||
let ts = MilliSecondsSinceUnixEpoch(pdu.origin_server_ts);
|
||||
if DEFAULT_BUMP_TYPES.contains(pdu.event_type()) && !timestamp.is_some_and(|time| time > ts) {
|
||||
@@ -1752,32 +1760,23 @@ pub(crate) async fn sync_events_v4_route(
|
||||
}
|
||||
|
||||
fn filter_rooms(
|
||||
rooms: &[OwnedRoomId], State(services): State<crate::State>, filter: &[Option<RoomType>], negate: bool,
|
||||
rooms: &[OwnedRoomId], State(services): State<crate::State>, filter: &[RoomTypeFilter], negate: bool,
|
||||
) -> Vec<OwnedRoomId> {
|
||||
return rooms
|
||||
.iter()
|
||||
.filter(|r| {
|
||||
match services.rooms.state_accessor.get_room_type(r) {
|
||||
Err(e) => {
|
||||
warn!("Requested room type for {}, but could not retrieve with error {}", r, e);
|
||||
false
|
||||
},
|
||||
Ok(None) => {
|
||||
// For rooms which do not have a room type, use 'null' to include them
|
||||
if negate {
|
||||
!filter.contains(&None)
|
||||
} else {
|
||||
filter.contains(&None)
|
||||
}
|
||||
},
|
||||
Ok(Some(room_type)) => {
|
||||
if negate {
|
||||
!filter.contains(&Some(room_type))
|
||||
} else {
|
||||
filter.is_empty() || filter.contains(&Some(room_type))
|
||||
}
|
||||
},
|
||||
}
|
||||
.filter(|r| match services.rooms.state_accessor.get_room_type(r) {
|
||||
Err(e) => {
|
||||
warn!("Requested room type for {}, but could not retrieve with error {}", r, e);
|
||||
false
|
||||
},
|
||||
Ok(result) => {
|
||||
let result = RoomTypeFilter::from(result);
|
||||
if negate {
|
||||
!filter.contains(&result)
|
||||
} else {
|
||||
filter.is_empty() || filter.contains(&result)
|
||||
}
|
||||
},
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
+341
-2
@@ -1,12 +1,27 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::warn;
|
||||
use conduit::{warn, Err};
|
||||
use ruma::{
|
||||
api::client::{error::ErrorKind, membership::mutual_rooms, room::get_summary},
|
||||
api::{
|
||||
client::{
|
||||
error::ErrorKind,
|
||||
membership::mutual_rooms,
|
||||
profile::{
|
||||
delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key, set_profile_key,
|
||||
set_timezone_key,
|
||||
},
|
||||
room::get_summary,
|
||||
},
|
||||
federation,
|
||||
},
|
||||
events::room::member::MembershipState,
|
||||
presence::PresenceState,
|
||||
OwnedRoomId,
|
||||
};
|
||||
|
||||
use super::{update_avatar_url, update_displayname};
|
||||
use crate::{Error, Result, Ruma, RumaResponse};
|
||||
|
||||
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
|
||||
@@ -161,3 +176,327 @@ pub(crate) async fn get_room_summary(
|
||||
.unwrap_or_else(|_e| None),
|
||||
})
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
||||
///
|
||||
/// Deletes the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
|
||||
///
|
||||
/// - Also makes sure other users receive the update using presence EDUs
|
||||
pub(crate) async fn delete_timezone_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_timezone_key::unstable::Request>,
|
||||
) -> Result<delete_timezone_key::unstable::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
services.users.set_timezone(&body.user_id, None).await?;
|
||||
|
||||
if services.globals.allow_local_presence() {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||
}
|
||||
|
||||
Ok(delete_timezone_key::unstable::Response {})
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
||||
///
|
||||
/// Updates the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
|
||||
///
|
||||
/// - Also makes sure other users receive the update using presence EDUs
|
||||
pub(crate) async fn set_timezone_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<set_timezone_key::unstable::Request>,
|
||||
) -> Result<set_timezone_key::unstable::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_timezone(&body.user_id, body.tz.clone())
|
||||
.await?;
|
||||
|
||||
if services.globals.allow_local_presence() {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||
}
|
||||
|
||||
Ok(set_timezone_key::unstable::Response {})
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
|
||||
///
|
||||
/// Updates the profile key-value field of a user, as per MSC4133.
|
||||
///
|
||||
/// This also handles the avatar_url and displayname being updated.
|
||||
pub(crate) async fn set_profile_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<set_profile_key::unstable::Request>,
|
||||
) -> Result<set_profile_key::unstable::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
if body.kv_pair.is_empty() {
|
||||
return Err!(Request(BadJson(
|
||||
"The key-value pair JSON body is empty. Use DELETE to delete a key"
|
||||
)));
|
||||
}
|
||||
|
||||
if body.kv_pair.len() > 1 {
|
||||
// TODO: support PATCH or "recursively" adding keys in some sort
|
||||
return Err!(Request(BadJson("This endpoint can only take one key-value pair at a time")));
|
||||
}
|
||||
|
||||
let Some(profile_key_value) = body.kv_pair.get(&body.key) else {
|
||||
return Err!(Request(BadJson(
|
||||
"The key does not match the URL field key, or JSON body is empty (use DELETE)"
|
||||
)));
|
||||
};
|
||||
|
||||
if body
|
||||
.kv_pair
|
||||
.keys()
|
||||
.any(|key| key.starts_with("u.") && !profile_key_value.is_string())
|
||||
{
|
||||
return Err!(Request(BadJson("u.* profile key fields must be strings")));
|
||||
}
|
||||
|
||||
if body.kv_pair.keys().any(|key| key.len() > 128) {
|
||||
return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
|
||||
}
|
||||
|
||||
if body.key == "displayname" {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_displayname(&services, &body.user_id, Some(profile_key_value.to_string()), all_joined_rooms).await?;
|
||||
} else if body.key == "avatar_url" {
|
||||
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
|
||||
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_avatar_url(&services, &body.user_id, Some(mxc), None, all_joined_rooms).await?;
|
||||
} else {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, &body.key, Some(profile_key_value.clone()))?;
|
||||
}
|
||||
|
||||
if services.globals.allow_local_presence() {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||
}
|
||||
|
||||
Ok(set_profile_key::unstable::Response {})
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
|
||||
///
|
||||
/// Deletes the profile key-value field of a user, as per MSC4133.
|
||||
///
|
||||
/// This also handles the avatar_url and displayname being updated.
|
||||
pub(crate) async fn delete_profile_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_profile_key::unstable::Request>,
|
||||
) -> Result<delete_profile_key::unstable::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||
}
|
||||
|
||||
if body.kv_pair.len() > 1 {
|
||||
// TODO: support PATCH or "recursively" adding keys in some sort
|
||||
return Err!(Request(BadJson("This endpoint can only take one key-value pair at a time")));
|
||||
}
|
||||
|
||||
if body.key == "displayname" {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_displayname(&services, &body.user_id, None, all_joined_rooms).await?;
|
||||
} else if body.key == "avatar_url" {
|
||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
update_avatar_url(&services, &body.user_id, None, None, all_joined_rooms).await?;
|
||||
} else {
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, &body.key, None)?;
|
||||
}
|
||||
|
||||
if services.globals.allow_local_presence() {
|
||||
// Presence update
|
||||
services
|
||||
.presence
|
||||
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||
}
|
||||
|
||||
Ok(delete_profile_key::unstable::Response {})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
||||
///
|
||||
/// Returns the `timezone` of the user as per MSC4133 and MSC4175.
|
||||
///
|
||||
/// - If user is on another server and we do not have a local copy already fetch
|
||||
/// `timezone` over federation
|
||||
pub(crate) async fn get_timezone_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_timezone_key::unstable::Request>,
|
||||
) -> Result<get_timezone_key::unstable::Response> {
|
||||
if !services.globals.user_is_local(&body.user_id) {
|
||||
// Create and update our local copy of the user
|
||||
if let Ok(response) = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
field: None, // we want the full user's profile to update locally as well
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
if !services.users.exists(&body.user_id)? {
|
||||
services.users.create(&body.user_id, None)?;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(&body.user_id, response.displayname.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_timezone(&body.user_id, response.tz.clone())
|
||||
.await?;
|
||||
|
||||
return Ok(get_timezone_key::unstable::Response {
|
||||
tz: response.tz,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !services.users.exists(&body.user_id)? {
|
||||
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
||||
// federation
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
||||
}
|
||||
|
||||
Ok(get_timezone_key::unstable::Response {
|
||||
tz: services.users.timezone(&body.user_id)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{userId}/{field}}`
|
||||
///
|
||||
/// Gets the profile key-value field of a user, as per MSC4133.
|
||||
///
|
||||
/// - If user is on another server and we do not have a local copy already fetch
|
||||
/// `timezone` over federation
|
||||
pub(crate) async fn get_profile_key_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_profile_key::unstable::Request>,
|
||||
) -> Result<get_profile_key::unstable::Response> {
|
||||
let mut profile_key_value: BTreeMap<String, serde_json::Value> = BTreeMap::new();
|
||||
|
||||
if !services.globals.user_is_local(&body.user_id) {
|
||||
// Create and update our local copy of the user
|
||||
if let Ok(response) = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
body.user_id.server_name(),
|
||||
federation::query::get_profile_information::v1::Request {
|
||||
user_id: body.user_id.clone(),
|
||||
field: None, // we want the full user's profile to update locally as well
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
if !services.users.exists(&body.user_id)? {
|
||||
services.users.create(&body.user_id, None)?;
|
||||
}
|
||||
|
||||
services
|
||||
.users
|
||||
.set_displayname(&body.user_id, response.displayname.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||
.await?;
|
||||
services
|
||||
.users
|
||||
.set_timezone(&body.user_id, response.tz.clone())
|
||||
.await?;
|
||||
|
||||
if let Some(value) = response.custom_profile_fields.get(&body.key) {
|
||||
profile_key_value.insert(body.key.clone(), value.clone());
|
||||
services
|
||||
.users
|
||||
.set_profile_key(&body.user_id, &body.key, Some(value.clone()))?;
|
||||
} else {
|
||||
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||
}
|
||||
|
||||
return Ok(get_profile_key::unstable::Response {
|
||||
value: profile_key_value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !services.users.exists(&body.user_id)? {
|
||||
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
||||
// federation
|
||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
||||
}
|
||||
|
||||
if let Some(value) = services.users.profile_key(&body.user_id, &body.key)? {
|
||||
profile_key_value.insert(body.key.clone(), value);
|
||||
} else {
|
||||
return Err!(Request(NotFound("The requested profile key does not exist.")));
|
||||
}
|
||||
|
||||
Ok(get_profile_key::unstable::Response {
|
||||
value: profile_key_value,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ pub(crate) async fn get_supported_versions_route(
|
||||
("org.matrix.msc3575".to_owned(), true), /* sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1588877046) */
|
||||
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
|
||||
("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */
|
||||
("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */
|
||||
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
|
||||
]),
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ pub(crate) async fn turn_server_route(
|
||||
return Err!(Request(NotFound("Not Found")));
|
||||
}
|
||||
|
||||
let turn_secret = services.globals.turn_secret().clone();
|
||||
let turn_secret = services.globals.turn_secret.clone();
|
||||
|
||||
let (username, password) = if !turn_secret.is_empty() {
|
||||
let expiry = SecondsSinceUnixEpoch::from_system_time(
|
||||
|
||||
+12
-1
@@ -22,6 +22,13 @@ use crate::{client, server};
|
||||
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
let config = &server.config;
|
||||
let mut router = router
|
||||
.ruma_route(client::get_timezone_key_route)
|
||||
.ruma_route(client::get_profile_key_route)
|
||||
.ruma_route(client::set_profile_key_route)
|
||||
.ruma_route(client::delete_profile_key_route)
|
||||
.ruma_route(client::set_timezone_key_route)
|
||||
.ruma_route(client::delete_timezone_key_route)
|
||||
.ruma_route(client::appservice_ping)
|
||||
.ruma_route(client::get_supported_versions_route)
|
||||
.ruma_route(client::get_register_available_route)
|
||||
.ruma_route(client::register_route)
|
||||
@@ -249,7 +256,11 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||
.route("/_matrix/media/v3/config", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/download/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/thumbnail/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/v3/preview_url", any(redirect_legacy_preview));
|
||||
.route("/_matrix/media/v3/preview_url", any(redirect_legacy_preview))
|
||||
.route("/_matrix/media/r0/config", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/r0/download/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/r0/thumbnail/*path", any(legacy_media_disabled))
|
||||
.route("/_matrix/media/r0/preview_url", any(redirect_legacy_preview));
|
||||
}
|
||||
|
||||
router
|
||||
|
||||
@@ -236,6 +236,7 @@ async fn auth_server(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::or_fun_call)]
|
||||
let signature_uri = CanonicalJsonValue::String(
|
||||
request
|
||||
.parts
|
||||
|
||||
+20
-2
@@ -1,3 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduit::{Error, Result};
|
||||
use get_profile_information::v1::ProfileField;
|
||||
@@ -75,6 +77,8 @@ pub(crate) async fn get_profile_information_route(
|
||||
let mut displayname = None;
|
||||
let mut avatar_url = None;
|
||||
let mut blurhash = None;
|
||||
let mut tz = None;
|
||||
let mut custom_profile_fields = BTreeMap::new();
|
||||
|
||||
match &body.field {
|
||||
Some(ProfileField::DisplayName) => {
|
||||
@@ -84,12 +88,24 @@ pub(crate) async fn get_profile_information_route(
|
||||
avatar_url = services.users.avatar_url(&body.user_id)?;
|
||||
blurhash = services.users.blurhash(&body.user_id)?;
|
||||
},
|
||||
// TODO: what to do with custom
|
||||
Some(_) => {},
|
||||
Some(custom_field) => {
|
||||
if let Some(value) = services
|
||||
.users
|
||||
.profile_key(&body.user_id, custom_field.as_str())?
|
||||
{
|
||||
custom_profile_fields.insert(custom_field.to_string(), value);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
displayname = services.users.displayname(&body.user_id)?;
|
||||
avatar_url = services.users.avatar_url(&body.user_id)?;
|
||||
blurhash = services.users.blurhash(&body.user_id)?;
|
||||
tz = services.users.timezone(&body.user_id)?;
|
||||
custom_profile_fields = services
|
||||
.users
|
||||
.all_profile_keys(&body.user_id)
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
},
|
||||
}
|
||||
|
||||
@@ -97,5 +113,7 @@ pub(crate) async fn get_profile_information_route(
|
||||
displayname,
|
||||
avatar_url,
|
||||
blurhash,
|
||||
tz,
|
||||
custom_profile_fields,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,10 +52,6 @@ zstd_compression = [
|
||||
perf_measurements = []
|
||||
sentry_telemetry = []
|
||||
|
||||
# these do nothing, these are purely for informing users to update their build scripts if they use one
|
||||
rocksdb = []
|
||||
sha256_media = []
|
||||
|
||||
[dependencies]
|
||||
argon2.workspace = true
|
||||
arrayvec.workspace = true
|
||||
@@ -83,7 +79,6 @@ regex.workspace = true
|
||||
reqwest.workspace = true
|
||||
ring.workspace = true
|
||||
ruma.workspace = true
|
||||
rustls.workspace = true
|
||||
sanitize-filename.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_regex.workspace = true
|
||||
|
||||
@@ -9,22 +9,6 @@ pub fn check(config: &Config) -> Result<()> {
|
||||
info!("Note: conduwuit was built without optimisations (i.e. debug build)");
|
||||
}
|
||||
|
||||
// prevents catching this in `--all-features`
|
||||
if cfg!(all(feature = "rocksdb", not(feature = "sha256_media"))) {
|
||||
warn!(
|
||||
"Note the rocksdb feature was deleted from conduwuit. SQLite support was removed and RocksDB is the only \
|
||||
supported backend now. Please update your build script to remove this feature."
|
||||
);
|
||||
}
|
||||
|
||||
// prevents catching this in `--all-features`
|
||||
if cfg!(all(feature = "sha256_media", not(feature = "rocksdb"))) {
|
||||
warn!(
|
||||
"Note the sha256_media feature was deleted from conduwuit, it is now fully integrated in a \
|
||||
forwards-compatible way. Please update your build script to remove this feature."
|
||||
);
|
||||
}
|
||||
|
||||
warn_deprecated(config);
|
||||
warn_unknown_key(config);
|
||||
|
||||
|
||||
+71
-39
@@ -21,7 +21,7 @@ use url::Url;
|
||||
|
||||
pub use self::check::check;
|
||||
use self::proxy::ProxyConfig;
|
||||
use crate::{error::Error, Err, Result};
|
||||
use crate::{error::Error, utils::sys, Err, Result};
|
||||
|
||||
pub mod check;
|
||||
pub mod proxy;
|
||||
@@ -184,6 +184,8 @@ pub struct Config {
|
||||
pub query_trusted_key_servers_first: bool,
|
||||
#[serde(default = "default_log")]
|
||||
pub log: String,
|
||||
#[serde(default = "true_fn", alias = "log_colours")]
|
||||
pub log_colors: bool,
|
||||
#[serde(default = "default_openid_token_ttl")]
|
||||
pub openid_token_ttl: u64,
|
||||
#[serde(default)]
|
||||
@@ -194,6 +196,7 @@ pub struct Config {
|
||||
pub turn_uris: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub turn_secret: String,
|
||||
pub turn_secret_file: Option<PathBuf>,
|
||||
#[serde(default = "default_turn_ttl")]
|
||||
pub turn_ttl: u64,
|
||||
|
||||
@@ -374,6 +377,13 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub test: BTreeSet<String>,
|
||||
|
||||
/// Controls whether admin room notices like account registrations, password
|
||||
/// changes, account deactivations, room directory publications, etc will
|
||||
/// be sent to the admin room. Update notices and normal admin command
|
||||
/// responses will still be sent.
|
||||
#[serde(default = "true_fn")]
|
||||
pub admin_room_notices: bool,
|
||||
|
||||
#[serde(flatten)]
|
||||
#[allow(clippy::zero_sized_map_values)] // this is a catchall, the map shouldn't be zero at runtime
|
||||
catchall: BTreeMap<String, IgnoredAny>,
|
||||
@@ -426,29 +436,26 @@ const DEPRECATED_KEYS: &[&str; 9] = &[
|
||||
|
||||
impl Config {
|
||||
/// Pre-initialize config
|
||||
pub fn load(path: &Option<PathBuf>) -> Result<Figment> {
|
||||
pub fn load(paths: &Option<Vec<PathBuf>>) -> Result<Figment> {
|
||||
let raw_config = if let Some(config_file_env) = Env::var("CONDUIT_CONFIG") {
|
||||
Figment::new()
|
||||
.merge(Toml::file(config_file_env).nested())
|
||||
.merge(Env::prefixed("CONDUIT_").global().split("__"))
|
||||
.merge(Env::prefixed("CONDUWUIT_").global().split("__"))
|
||||
Figment::new().merge(Toml::file(config_file_env).nested())
|
||||
} else if let Some(config_file_arg) = Env::var("CONDUWUIT_CONFIG") {
|
||||
Figment::new()
|
||||
.merge(Toml::file(config_file_arg).nested())
|
||||
.merge(Env::prefixed("CONDUIT_").global().split("__"))
|
||||
.merge(Env::prefixed("CONDUWUIT_").global().split("__"))
|
||||
} else if let Some(config_file_arg) = path {
|
||||
Figment::new()
|
||||
.merge(Toml::file(config_file_arg).nested())
|
||||
.merge(Env::prefixed("CONDUIT_").global().split("__"))
|
||||
.merge(Env::prefixed("CONDUWUIT_").global().split("__"))
|
||||
Figment::new().merge(Toml::file(config_file_arg).nested())
|
||||
} else if let Some(config_file_args) = paths {
|
||||
let mut figment = Figment::new();
|
||||
|
||||
for config in config_file_args {
|
||||
figment = figment.merge(Toml::file(config).nested());
|
||||
}
|
||||
|
||||
figment
|
||||
} else {
|
||||
Figment::new()
|
||||
.merge(Env::prefixed("CONDUIT_").global().split("__"))
|
||||
.merge(Env::prefixed("CONDUWUIT_").global().split("__"))
|
||||
};
|
||||
|
||||
Ok(raw_config)
|
||||
Ok(raw_config
|
||||
.merge(Env::prefixed("CONDUIT_").global().split("__"))
|
||||
.merge(Env::prefixed("CONDUWUIT_").global().split("__")))
|
||||
}
|
||||
|
||||
/// Finalize config
|
||||
@@ -466,7 +473,11 @@ impl Config {
|
||||
|
||||
#[must_use]
|
||||
pub fn get_bind_addrs(&self) -> Vec<SocketAddr> {
|
||||
let mut addrs = Vec::new();
|
||||
let mut addrs = Vec::with_capacity(
|
||||
self.get_bind_hosts()
|
||||
.len()
|
||||
.saturating_add(self.get_bind_ports().len()),
|
||||
);
|
||||
for host in &self.get_bind_hosts() {
|
||||
for port in &self.get_bind_ports() {
|
||||
addrs.push(SocketAddr::new(*host, *port));
|
||||
@@ -684,15 +695,20 @@ impl fmt::Display for Config {
|
||||
}
|
||||
});
|
||||
line("TURN secret", {
|
||||
if self.turn_secret.is_empty() {
|
||||
if self.turn_secret.is_empty() && self.turn_secret_file.is_none() {
|
||||
"not set"
|
||||
} else {
|
||||
"set"
|
||||
}
|
||||
});
|
||||
line("TURN secret file path", {
|
||||
self.turn_secret_file
|
||||
.as_ref()
|
||||
.map_or("", |path| path.to_str().unwrap_or_default())
|
||||
});
|
||||
line("Turn TTL", &self.turn_ttl.to_string());
|
||||
line("Turn URIs", {
|
||||
let mut lst = vec![];
|
||||
let mut lst = Vec::with_capacity(self.turn_uris.len());
|
||||
for item in self.turn_uris.iter().cloned().enumerate() {
|
||||
let (_, uri): (usize, String) = item;
|
||||
lst.push(uri);
|
||||
@@ -700,7 +716,7 @@ impl fmt::Display for Config {
|
||||
&lst.join(", ")
|
||||
});
|
||||
line("Auto Join Rooms", {
|
||||
let mut lst = vec![];
|
||||
let mut lst = Vec::with_capacity(self.auto_join_rooms.len());
|
||||
for room in &self.auto_join_rooms {
|
||||
lst.push(room);
|
||||
}
|
||||
@@ -752,28 +768,28 @@ impl fmt::Display for Config {
|
||||
line("Allow legacy (unauthenticated) media", &self.allow_legacy_media.to_string());
|
||||
line("Freeze legacy (unauthenticated) media", &self.freeze_legacy_media.to_string());
|
||||
line("Prevent Media Downloads From", {
|
||||
let mut lst = vec![];
|
||||
let mut lst = Vec::with_capacity(self.prevent_media_downloads_from.len());
|
||||
for domain in &self.prevent_media_downloads_from {
|
||||
lst.push(domain.host());
|
||||
}
|
||||
&lst.join(", ")
|
||||
});
|
||||
line("Forbidden Remote Server Names (\"Global\" ACLs)", {
|
||||
let mut lst = vec![];
|
||||
let mut lst = Vec::with_capacity(self.forbidden_remote_server_names.len());
|
||||
for domain in &self.forbidden_remote_server_names {
|
||||
lst.push(domain.host());
|
||||
}
|
||||
&lst.join(", ")
|
||||
});
|
||||
line("Forbidden Remote Room Directory Server Names", {
|
||||
let mut lst = vec![];
|
||||
let mut lst = Vec::with_capacity(self.forbidden_remote_room_directory_server_names.len());
|
||||
for domain in &self.forbidden_remote_room_directory_server_names {
|
||||
lst.push(domain.host());
|
||||
}
|
||||
&lst.join(", ")
|
||||
});
|
||||
line("Outbound Request IP Range Denylist", {
|
||||
let mut lst = vec![];
|
||||
line("Outbound Request IP Range (CIDR) Denylist", {
|
||||
let mut lst = Vec::with_capacity(self.ip_range_denylist.len());
|
||||
for item in self.ip_range_denylist.iter().cloned().enumerate() {
|
||||
let (_, ip): (usize, String) = item;
|
||||
lst.push(ip);
|
||||
@@ -862,6 +878,7 @@ impl fmt::Display for Config {
|
||||
.map_or("", |url| url.as_str()),
|
||||
);
|
||||
line("Enable the tokio-console", &self.tokio_console.to_string());
|
||||
line("Admin room notices", &self.admin_room_notices.to_string());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -887,29 +904,29 @@ fn default_database_backups_to_keep() -> i16 { 1 }
|
||||
|
||||
fn default_database_backend() -> String { "rocksdb".to_owned() }
|
||||
|
||||
fn default_db_cache_capacity_mb() -> f64 { 256.0 }
|
||||
fn default_db_cache_capacity_mb() -> f64 { 128.0 + parallelism_scaled_f64(64.0) }
|
||||
|
||||
fn default_pdu_cache_capacity() -> u32 { 150_000 }
|
||||
fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
|
||||
|
||||
fn default_cache_capacity_modifier() -> f64 { 1.0 }
|
||||
|
||||
fn default_auth_chain_cache_capacity() -> u32 { 100_000 }
|
||||
fn default_auth_chain_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
|
||||
|
||||
fn default_shorteventid_cache_capacity() -> u32 { 500_000 }
|
||||
fn default_shorteventid_cache_capacity() -> u32 { parallelism_scaled_u32(50_000).saturating_add(100_000) }
|
||||
|
||||
fn default_eventidshort_cache_capacity() -> u32 { 100_000 }
|
||||
fn default_eventidshort_cache_capacity() -> u32 { parallelism_scaled_u32(25_000).saturating_add(100_000) }
|
||||
|
||||
fn default_shortstatekey_cache_capacity() -> u32 { 100_000 }
|
||||
fn default_shortstatekey_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
|
||||
|
||||
fn default_statekeyshort_cache_capacity() -> u32 { 100_000 }
|
||||
fn default_statekeyshort_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
|
||||
|
||||
fn default_server_visibility_cache_capacity() -> u32 { 100 }
|
||||
fn default_server_visibility_cache_capacity() -> u32 { parallelism_scaled_u32(500) }
|
||||
|
||||
fn default_user_visibility_cache_capacity() -> u32 { 100 }
|
||||
fn default_user_visibility_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
|
||||
|
||||
fn default_stateinfo_cache_capacity() -> u32 { 100 }
|
||||
fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
|
||||
|
||||
fn default_roomid_spacehierarchy_cache_capacity() -> u32 { 100 }
|
||||
fn default_roomid_spacehierarchy_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
|
||||
|
||||
fn default_dns_cache_entries() -> u32 { 32768 }
|
||||
|
||||
@@ -1079,6 +1096,21 @@ fn default_sentry_filter() -> String { "info".to_owned() }
|
||||
|
||||
fn default_startup_netburst_keep() -> i64 { 50 }
|
||||
|
||||
fn default_admin_log_capture() -> String { "debug".to_owned() }
|
||||
fn default_admin_log_capture() -> String {
|
||||
cfg!(debug_assertions)
|
||||
.then_some("debug")
|
||||
.unwrap_or("info")
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn default_admin_room_tag() -> String { "m.server_notice".to_owned() }
|
||||
|
||||
#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
|
||||
fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
|
||||
|
||||
fn parallelism_scaled_u32(val: u32) -> u32 {
|
||||
let val = val.try_into().expect("failed to cast u32 to usize");
|
||||
parallelism_scaled(val).try_into().unwrap_or(u32::MAX)
|
||||
}
|
||||
|
||||
fn parallelism_scaled(val: usize) -> usize { val.saturating_mul(sys::available_parallelism()) }
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ pub fn set_panic_trap() {
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(deprecated_in_future)]
|
||||
fn panic_handler(info: &panic::PanicInfo<'_>, next: &dyn Fn(&panic::PanicInfo<'_>)) {
|
||||
fn panic_handler(info: &panic::PanicHookInfo<'_>, next: &dyn Fn(&panic::PanicHookInfo<'_>)) {
|
||||
trap();
|
||||
next(info);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ pub const MAPS: &[&str] = &[
|
||||
"userid_presenceid",
|
||||
"userid_selfsigningkeyid",
|
||||
"userid_usersigningkeyid",
|
||||
"useridprofilekey_value",
|
||||
"openidtoken_expiresatuserid",
|
||||
"userroomid_highlightcount",
|
||||
"userroomid_invitestate",
|
||||
|
||||
@@ -42,6 +42,7 @@ default = [
|
||||
"gzip_compression",
|
||||
"io_uring",
|
||||
"jemalloc",
|
||||
"jemalloc_stats",
|
||||
"release_max_log_level",
|
||||
"sentry_telemetry",
|
||||
"systemd",
|
||||
@@ -65,6 +66,9 @@ console = [
|
||||
# "conduit-router/dev_release_log_level",
|
||||
# "conduit-service/dev_release_log_level",
|
||||
#]
|
||||
direct_tls = [
|
||||
"conduit-router/direct_tls"
|
||||
]
|
||||
element_hacks = [
|
||||
"conduit-api/element_hacks",
|
||||
"conduit-service/element_hacks",
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ use conduit::{
|
||||
pub(crate) struct Args {
|
||||
#[arg(short, long)]
|
||||
/// Path to the config TOML file (optional)
|
||||
pub(crate) config: Option<PathBuf>,
|
||||
pub(crate) config: Option<Vec<PathBuf>>,
|
||||
|
||||
/// Override a configuration variable using TOML 'key=value' syntax
|
||||
#[arg(long, short('O'))]
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ pub(crate) fn init(config: &Config) -> Result<(LogLevelReloadHandles, TracingFla
|
||||
let reload_handles = LogLevelReloadHandles::default();
|
||||
|
||||
let console_filter = EnvFilter::try_new(&config.log).map_err(|e| err!(Config("log", "{e}.")))?;
|
||||
let console_layer = tracing_subscriber::fmt::Layer::new();
|
||||
let console_layer = tracing_subscriber::fmt::Layer::new().with_ansi(config.log_colors);
|
||||
let (console_reload_filter, console_reload_handle) = reload::Layer::new(console_filter.clone());
|
||||
reload_handles.add("console", Box::new(console_reload_handle));
|
||||
|
||||
|
||||
@@ -42,9 +42,16 @@ systemd = [
|
||||
"dep:sd-notify",
|
||||
]
|
||||
|
||||
direct_tls = [
|
||||
"axum-server/tls-rustls",
|
||||
"dep:rustls",
|
||||
"dep:axum-server-dual-protocol",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
axum-client-ip.workspace = true
|
||||
axum-server-dual-protocol.workspace = true
|
||||
axum-server-dual-protocol.optional = true
|
||||
axum-server.workspace = true
|
||||
axum.workspace = true
|
||||
conduit-admin.workspace = true
|
||||
@@ -63,6 +70,7 @@ hyper.workspace = true
|
||||
hyper-util.workspace = true
|
||||
ruma.workspace = true
|
||||
rustls.workspace = true
|
||||
rustls.optional = true
|
||||
sentry.optional = true
|
||||
sentry-tower.optional = true
|
||||
sentry-tower.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod plain;
|
||||
#[cfg(feature = "direct_tls")]
|
||||
mod tls;
|
||||
mod unix;
|
||||
|
||||
@@ -23,7 +24,14 @@ pub(super) async fn serve(
|
||||
if cfg!(unix) && config.unix_socket_path.is_some() {
|
||||
unix::serve(server, app, shutdown).await
|
||||
} else if config.tls.is_some() {
|
||||
tls::serve(server, app, handle, addrs).await
|
||||
#[cfg(feature = "direct_tls")]
|
||||
return tls::serve(server, app, handle, addrs).await;
|
||||
|
||||
#[cfg(not(feature = "direct_tls"))]
|
||||
return conduit::Err!(Config(
|
||||
"tls",
|
||||
"conduwuit was not built with direct TLS support (\"direct_tls\")"
|
||||
));
|
||||
} else {
|
||||
plain::serve(server, app, handle, addrs).await
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ pub(super) async fn serve(
|
||||
|
||||
// we use ring for ruma and hashing state, but aws-lc-rs is the new default.
|
||||
// without this, TLS mode will panic.
|
||||
_ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
rustls::crypto::aws_lc_rs::default_provider()
|
||||
.install_default()
|
||||
.expect("failed to initialise aws-lc-rs rustls crypto provider");
|
||||
|
||||
debug!("Using direct TLS. Certificate path {certs} and certificate private key path {key}",);
|
||||
info!(
|
||||
|
||||
@@ -101,8 +101,7 @@ pub async fn create_admin_room(services: &Services) -> Result<()> {
|
||||
.await?;
|
||||
|
||||
// 3. Power levels
|
||||
let mut users = BTreeMap::new();
|
||||
users.insert(server_user.clone(), 100.into());
|
||||
let users = BTreeMap::from_iter([(server_user.clone(), 100.into())]);
|
||||
|
||||
services
|
||||
.rooms
|
||||
|
||||
@@ -21,7 +21,7 @@ impl super::Service {
|
||||
/// Invite the user to the conduit admin room.
|
||||
///
|
||||
/// In conduit, this is equivalent to granting admin privileges.
|
||||
pub async fn make_user_admin(&self, user_id: &UserId, displayname: String) -> Result<()> {
|
||||
pub async fn make_user_admin(&self, user_id: &UserId) -> Result<()> {
|
||||
let Some(room_id) = self.get_admin_room()? else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -65,7 +65,7 @@ impl super::Service {
|
||||
event_type: TimelineEventType::RoomMember,
|
||||
content: to_raw_value(&RoomMemberEventContent {
|
||||
membership: MembershipState::Join,
|
||||
displayname: Some(displayname),
|
||||
displayname: None,
|
||||
avatar_url: None,
|
||||
is_direct: None,
|
||||
third_party_invite: None,
|
||||
@@ -86,9 +86,7 @@ impl super::Service {
|
||||
.await?;
|
||||
|
||||
// Set power level
|
||||
let mut users = BTreeMap::new();
|
||||
users.insert(server_user.clone(), 100.into());
|
||||
users.insert(user_id.to_owned(), 100.into());
|
||||
let users = BTreeMap::from_iter([(server_user.clone(), 100.into()), (user_id.to_owned(), 100.into())]);
|
||||
|
||||
self.services
|
||||
.timeline
|
||||
@@ -123,9 +121,8 @@ impl super::Service {
|
||||
self.services.timeline.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
event_type: TimelineEventType::RoomMessage,
|
||||
content: to_raw_value(&RoomMessageEventContent::text_html(
|
||||
format!("## Thank you for trying out conduwuit!\n\nconduwuit is a fork of upstream Conduit which is in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Git and Documentation: https://github.com/girlbossceo/conduwuit\n> Report issues: https://github.com/girlbossceo/conduwuit/issues\n\nFor a list of available commands, send the following message in this room: `@conduit:{}: --help`\n\nHere are some rooms you can join (by typing the command):\n\nconduwuit room (Ask questions and get notified on updates):\n`/join #conduwuit:puppygock.gay`", self.services.globals.server_name()),
|
||||
format!("<h2>Thank you for trying out conduwuit!</h2>\n<p>conduwuit is a fork of upstream Conduit which is in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.</p>\n<p>Helpful links:</p>\n<blockquote>\n<p>Git and Documentation: https://github.com/girlbossceo/conduwuit<br>Report issues: https://github.com/girlbossceo/conduwuit/issues</p>\n</blockquote>\n<p>For a list of available commands, send the following message in this room: <code>@conduit:{}: --help</code></p>\n<p>Here are some rooms you can join (by typing the command):</p>\n<p>conduwuit room (Ask questions and get notified on updates):<br><code>/join #conduwuit:puppygock.gay</code></p>\n", self.services.globals.server_name()),
|
||||
content: to_raw_value(&RoomMessageEventContent::text_markdown(
|
||||
String::from("## Thank you for trying out conduwuit!\n\nconduwuit is a fork of upstream Conduit which is in Beta. This means you can join and participate in most Matrix rooms, but not all features are supported and you might run into bugs from time to time.\n\nHelpful links:\n> Git and Documentation: https://github.com/girlbossceo/conduwuit\n> Report issues: https://github.com/girlbossceo/conduwuit/issues\n\nFor a list of available commands, send the following message in this room: `!admin --help`\n\nHere are some rooms you can join (by typing the command):\n\nconduwuit room (Ask questions and get notified on updates):\n`/join #conduwuit:puppygock.gay`"),
|
||||
))
|
||||
.expect("event is valid, we just created it"),
|
||||
unsigned: None,
|
||||
|
||||
@@ -92,12 +92,12 @@ fn startup_command_error(i: usize, content: &RoomMessageEventContent) -> Result<
|
||||
#[cfg(not(feature = "console"))]
|
||||
#[implement(super::Service)]
|
||||
fn startup_command_output(i: usize, content: &RoomMessageEventContent) -> Result<()> {
|
||||
info!("Startup command #{i} completed:\n{:#?}", content.body());
|
||||
info!("Startup command #{i} completed:\n{:#}", content.body());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "console"))]
|
||||
#[implement(super::Service)]
|
||||
fn startup_command_error(i: usize, content: &RoomMessageEventContent) -> Result<()> {
|
||||
Err!(error!("Startup command #{i} failed:\n{:#?}", content.body()))
|
||||
Err!(error!("Startup command #{i} failed:\n{:#}", content.body()))
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ fn base(config: &Config) -> Result<reqwest::ClientBuilder> {
|
||||
builder = if config.zstd_compression {
|
||||
builder.zstd(true)
|
||||
} else {
|
||||
builder.zstd(false).no_brotli()
|
||||
builder.zstd(false).no_zstd()
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@ use crate::{media, Services};
|
||||
/// equal or lesser version. These are expected to be backward-compatible.
|
||||
pub(crate) const DATABASE_VERSION: u64 = 13;
|
||||
|
||||
/// Conduit's database version.
|
||||
///
|
||||
/// Conduit bumped the database version to 16, but did not introduce any
|
||||
/// breaking changes. Their database migrations are extremely fragile and risky,
|
||||
/// and also do not really apply to us, so just to retain Conduit -> conduwuit
|
||||
/// compatibility we'll check for both versions.
|
||||
pub(crate) const CONDUIT_DATABASE_VERSION: u64 = 16;
|
||||
|
||||
pub(crate) async fn migrations(services: &Services) -> Result<()> {
|
||||
// Matrix resource ownership is based on the server name; changing it
|
||||
// requires recreating the database from scratch.
|
||||
@@ -54,6 +62,7 @@ async fn fresh(services: &Services) -> Result<()> {
|
||||
.db
|
||||
.bump_database_version(DATABASE_VERSION)?;
|
||||
|
||||
db["global"].insert(b"feat_sha256_media", &[])?;
|
||||
db["global"].insert(b"fix_bad_double_separator_in_state_cache", &[])?;
|
||||
db["global"].insert(b"retroactively_fix_bad_data_from_roomuserid_joined", &[])?;
|
||||
|
||||
@@ -147,9 +156,11 @@ async fn migrate(services: &Services) -> Result<()> {
|
||||
retroactively_fix_bad_data_from_roomuserid_joined(services).await?;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
services.globals.db.database_version().unwrap(),
|
||||
DATABASE_VERSION,
|
||||
let version_match = services.globals.db.database_version().unwrap() == DATABASE_VERSION
|
||||
|| services.globals.db.database_version().unwrap() == CONDUIT_DATABASE_VERSION;
|
||||
|
||||
assert!(
|
||||
version_match,
|
||||
"Failed asserting local database version {} is equal to known latest conduwuit database version {}",
|
||||
services.globals.db.database_version().unwrap(),
|
||||
DATABASE_VERSION,
|
||||
|
||||
+30
-15
@@ -8,7 +8,7 @@ use std::{
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use conduit::{error, trace, Config, Result};
|
||||
use conduit::{err, error, trace, Config, Result};
|
||||
use data::Data;
|
||||
use ipaddress::IPAddress;
|
||||
use regex::RegexSet;
|
||||
@@ -40,6 +40,7 @@ pub struct Service {
|
||||
pub stateres_mutex: Arc<Mutex<()>>,
|
||||
pub server_user: OwnedUserId,
|
||||
pub admin_alias: OwnedRoomAliasId,
|
||||
pub turn_secret: String,
|
||||
}
|
||||
|
||||
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
|
||||
@@ -76,12 +77,24 @@ impl crate::Service for Service {
|
||||
// Experimental, partially supported room versions
|
||||
let unstable_room_versions = vec![RoomVersionId::V2, RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5];
|
||||
|
||||
let mut cidr_range_denylist = Vec::new();
|
||||
for cidr in config.ip_range_denylist.clone() {
|
||||
let cidr = IPAddress::parse(cidr).expect("valid cidr range");
|
||||
trace!("Denied CIDR range: {:?}", cidr);
|
||||
cidr_range_denylist.push(cidr);
|
||||
}
|
||||
let cidr_range_denylist: Vec<_> = config
|
||||
.ip_range_denylist
|
||||
.iter()
|
||||
.map(IPAddress::parse)
|
||||
.inspect(|cidr| trace!("Denied CIDR range: {cidr:?}"))
|
||||
.collect::<Result<_, String>>()
|
||||
.map_err(|e| err!(Config("ip_range_denylist", e)))?;
|
||||
|
||||
let turn_secret = config
|
||||
.turn_secret_file
|
||||
.as_ref()
|
||||
.map_or(config.turn_secret.clone(), |path| {
|
||||
std::fs::read_to_string(path).unwrap_or_else(|e| {
|
||||
error!("Failed to read the TURN secret file: {e}");
|
||||
|
||||
config.turn_secret.clone()
|
||||
})
|
||||
});
|
||||
|
||||
let mut s = Self {
|
||||
db,
|
||||
@@ -98,6 +111,7 @@ impl crate::Service for Service {
|
||||
.expect("#admins:server_name is valid alias name"),
|
||||
server_user: UserId::parse_with_server_name(String::from("conduit"), &config.server_name)
|
||||
.expect("@conduit:server_name is valid"),
|
||||
turn_secret,
|
||||
};
|
||||
|
||||
if !s
|
||||
@@ -206,8 +220,6 @@ impl Service {
|
||||
|
||||
pub fn turn_username(&self) -> &String { &self.config.turn_username }
|
||||
|
||||
pub fn turn_secret(&self) -> &String { &self.config.turn_secret }
|
||||
|
||||
pub fn allow_profile_lookup_federation_requests(&self) -> bool {
|
||||
self.config.allow_profile_lookup_federation_requests
|
||||
}
|
||||
@@ -263,12 +275,15 @@ impl Service {
|
||||
pub fn block_non_admin_invites(&self) -> bool { self.config.block_non_admin_invites }
|
||||
|
||||
pub fn supported_room_versions(&self) -> Vec<RoomVersionId> {
|
||||
let mut room_versions: Vec<RoomVersionId> = Vec::with_capacity(self.stable_room_versions.len());
|
||||
room_versions.extend(self.stable_room_versions.clone());
|
||||
if self.allow_unstable_room_versions() {
|
||||
room_versions.extend(self.unstable_room_versions.clone());
|
||||
};
|
||||
room_versions
|
||||
if self.config.allow_unstable_room_versions {
|
||||
self.stable_room_versions
|
||||
.clone()
|
||||
.into_iter()
|
||||
.chain(self.unstable_room_versions.clone())
|
||||
.collect()
|
||||
} else {
|
||||
self.stable_room_versions.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
|
||||
|
||||
+61
-67
@@ -10,7 +10,7 @@ use std::{path::PathBuf, sync::Arc, time::SystemTime};
|
||||
use async_trait::async_trait;
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use conduit::{
|
||||
debug, debug_error, debug_info, err, error, trace,
|
||||
debug, debug_error, debug_info, debug_warn, err, error, trace,
|
||||
utils::{self, MutexMap},
|
||||
warn, Err, Result, Server,
|
||||
};
|
||||
@@ -99,45 +99,46 @@ impl Service {
|
||||
pub async fn delete(&self, mxc: &Mxc<'_>) -> Result<()> {
|
||||
if let Ok(keys) = self.db.search_mxc_metadata_prefix(mxc) {
|
||||
for key in keys {
|
||||
trace!(?mxc, ?key, "Deleting from filesystem");
|
||||
trace!(?mxc, "MXC Key: {key:?}");
|
||||
debug_info!(?mxc, "Deleting from filesystem");
|
||||
|
||||
if let Err(e) = self.remove_media_file(&key).await {
|
||||
error!(?mxc, ?key, "Failed to remove media file: {e}");
|
||||
debug_error!(?mxc, "Failed to remove media file: {e}");
|
||||
}
|
||||
|
||||
trace!(?mxc, ?key, "Deleting from database");
|
||||
if let Err(e) = self.db.delete_file_mxc(mxc) {
|
||||
error!(?mxc, ?key, "Failed to remove media from database: {e}");
|
||||
}
|
||||
debug_info!(?mxc, "Deleting from database");
|
||||
_ = self.db.delete_file_mxc(mxc);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err!(Database(error!(
|
||||
"Failed to find any media keys for MXC {mxc:?} in our database."
|
||||
)))
|
||||
Err!(Database(error!("Failed to find any media keys for MXC {mxc} in our database.")))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes all media by the specified user
|
||||
///
|
||||
/// currently, this is only practical for local users
|
||||
pub async fn delete_from_user(&self, user: &UserId, force: bool) -> Result<usize> {
|
||||
pub async fn delete_from_user(&self, user: &UserId) -> Result<usize> {
|
||||
let mxcs = self.db.get_all_user_mxcs(user);
|
||||
let mut deletion_count: usize = 0;
|
||||
|
||||
for mxc in mxcs {
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
debug_info!("Deleting MXC {mxc} by user {user} from database and filesystem");
|
||||
if force {
|
||||
_ = self
|
||||
.delete(&mxc)
|
||||
.await
|
||||
.inspect_err(|e| warn!("Failed to delete {mxc} from user {user}, ignoring error: {e}"));
|
||||
} else {
|
||||
self.delete(&mxc).await?;
|
||||
}
|
||||
let Ok(mxc) = mxc.as_str().try_into().inspect_err(|e| {
|
||||
debug_error!(?mxc, "Failed to parse MXC URI from database: {e}");
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
deletion_count = deletion_count.saturating_add(1);
|
||||
debug_info!(%deletion_count, "Deleting MXC {mxc} by user {user} from database and filesystem");
|
||||
match self.delete(&mxc).await {
|
||||
Ok(()) => {
|
||||
deletion_count = deletion_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
debug_error!(%deletion_count, "Failed to delete {mxc} from user {user}, ignoring error: {e}");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(deletion_count)
|
||||
@@ -176,9 +177,6 @@ impl Service {
|
||||
for key in all_keys {
|
||||
trace!("Full MXC key from database: {key:?}");
|
||||
|
||||
// we need to get the MXC URL from the first part of the key (the first 0xff /
|
||||
// 255 push). this is all necessary because of conduit using magic keys for
|
||||
// media
|
||||
let mut parts = key.split(|&b| b == 0xFF);
|
||||
let mxc = parts
|
||||
.next()
|
||||
@@ -189,31 +187,33 @@ impl Service {
|
||||
.transpose()?;
|
||||
|
||||
let Some(mxc_s) = mxc else {
|
||||
return Err!(Database("Parsed MXC URL unicode bytes from database but still is None"));
|
||||
debug_warn!(?mxc, "Parsed MXC URL unicode bytes from database but is still invalid");
|
||||
continue;
|
||||
};
|
||||
|
||||
trace!("Parsed MXC key to URL: {mxc_s}");
|
||||
let mxc = OwnedMxcUri::from(mxc_s);
|
||||
|
||||
mxcs.push(mxc);
|
||||
if mxc.is_valid() {
|
||||
mxcs.push(mxc);
|
||||
} else {
|
||||
debug_warn!("{mxc:?} from database was found to not be valid");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mxcs)
|
||||
}
|
||||
|
||||
/// Deletes all remote only media files in the given at or after
|
||||
/// time/duration. Returns a u32 with the amount of media files deleted.
|
||||
pub async fn delete_all_remote_media_at_after_time(&self, time: SystemTime, force: bool) -> Result<usize> {
|
||||
/// time/duration. Returns a usize with the amount of media files deleted.
|
||||
pub async fn delete_all_remote_media_at_after_time(
|
||||
&self, time: SystemTime, before: bool, after: bool, yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result<usize> {
|
||||
let all_keys = self.db.get_all_media_keys();
|
||||
|
||||
let mut remote_mxcs = Vec::with_capacity(all_keys.len());
|
||||
|
||||
for key in all_keys {
|
||||
trace!("Full MXC key from database: {key:?}");
|
||||
|
||||
// we need to get the MXC URL from the first part of the key (the first 0xff /
|
||||
// 255 push). this is all necessary because of conduit using magic keys for
|
||||
// media
|
||||
let mut parts = key.split(|&b| b == 0xFF);
|
||||
let mxc = parts
|
||||
.next()
|
||||
@@ -224,35 +224,30 @@ impl Service {
|
||||
.transpose()?;
|
||||
|
||||
let Some(mxc_s) = mxc else {
|
||||
return Err!(Database("Parsed MXC URL unicode bytes from database but still is None"));
|
||||
debug_warn!(?mxc, "Parsed MXC URL unicode bytes from database but is still invalid");
|
||||
continue;
|
||||
};
|
||||
|
||||
trace!("Parsed MXC key to URL: {mxc_s}");
|
||||
let mxc = OwnedMxcUri::from(mxc_s);
|
||||
if mxc.server_name() == Ok(self.services.globals.server_name()) {
|
||||
debug!("Ignoring local media MXC: {mxc}");
|
||||
// ignore our own MXC URLs as this would be local media.
|
||||
if (mxc.server_name() == Ok(self.services.globals.server_name()) && !yes_i_want_to_delete_local_media)
|
||||
|| !mxc.is_valid()
|
||||
{
|
||||
debug!("Ignoring local or broken media MXC: {mxc}");
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = self.get_media_file(&key);
|
||||
debug!("MXC path: {path:?}");
|
||||
|
||||
let file_metadata = match fs::metadata(path.clone()).await {
|
||||
Ok(file_metadata) => file_metadata,
|
||||
Err(e) => {
|
||||
if force {
|
||||
error!("Failed to obtain file metadata for MXC {mxc} at file path \"{path:?}\", skipping: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err!(Database(
|
||||
"Failed to obtain file metadata for MXC {mxc} at file path \"{path:?}\": {e}"
|
||||
));
|
||||
error!("Failed to obtain file metadata for MXC {mxc} at file path \"{path:?}\", skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
debug!("File metadata: {file_metadata:?}");
|
||||
trace!(%mxc, ?path, "File metadata: {file_metadata:?}");
|
||||
|
||||
let file_created_at = match file_metadata.created() {
|
||||
Ok(value) => value,
|
||||
@@ -261,33 +256,36 @@ impl Service {
|
||||
file_metadata.modified()?
|
||||
},
|
||||
Err(err) => {
|
||||
if force {
|
||||
error!("Could not delete MXC {mxc} at path {path:?}: {err:?}. Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(err.into());
|
||||
error!("Could not delete MXC {mxc} at path {path:?}: {err:?}. Skipping...");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
debug!("File created at: {file_created_at:?}");
|
||||
if file_created_at <= time {
|
||||
debug!("File is within user duration, pushing to list of file paths and keys to delete.");
|
||||
|
||||
if file_created_at >= time && before {
|
||||
debug!("File is within (before) user duration, pushing to list of file paths and keys to delete.");
|
||||
remote_mxcs.push(mxc.to_string());
|
||||
} else if file_created_at <= time && after {
|
||||
debug!("File is not within (after) user duration, pushing to list of file paths and keys to delete.");
|
||||
remote_mxcs.push(mxc.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Finished going through all our media in database for eligible keys to delete, checking if these are empty"
|
||||
);
|
||||
if remote_mxcs.is_empty() {
|
||||
return Err!(Database("Did not found any eligible MXCs to delete."));
|
||||
}
|
||||
|
||||
debug_info!("Deleting media now in the past {time:?}.");
|
||||
debug_info!("Deleting media now in the past {time:?}");
|
||||
|
||||
let mut deletion_count: usize = 0;
|
||||
|
||||
for mxc in remote_mxcs {
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
let Ok(mxc) = mxc.as_str().try_into() else {
|
||||
debug_warn!("Invalid MXC in database, skipping");
|
||||
continue;
|
||||
};
|
||||
|
||||
debug_info!("Deleting MXC {mxc} from database and filesystem");
|
||||
|
||||
match self.delete(&mxc).await {
|
||||
@@ -295,12 +293,8 @@ impl Service {
|
||||
deletion_count = deletion_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
if force {
|
||||
warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err!(Database(warn!("Failed to delete MXC {mxc}: {e}")));
|
||||
warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ impl Data {
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
#[allow(clippy::single_match_else)]
|
||||
match short {
|
||||
Some(short) => ret.push(
|
||||
utils::u64_from_bytes(short).map_err(|_| Error::bad_database("Invalid shorteventid in db."))?,
|
||||
|
||||
@@ -15,7 +15,7 @@ pub(crate) async fn send_request<T>(
|
||||
where
|
||||
T: OutgoingRequest + Debug + Send,
|
||||
{
|
||||
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_0];
|
||||
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_7];
|
||||
|
||||
let Some(dest) = registration.url else {
|
||||
return Ok(None);
|
||||
|
||||
@@ -479,6 +479,8 @@ impl Service {
|
||||
.collect::<Vec<_>>(),
|
||||
)))
|
||||
.into(),
|
||||
ephemeral: Vec::new(),
|
||||
to_device: Vec::new(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -636,7 +638,9 @@ impl Service {
|
||||
.pdus
|
||||
.iter()
|
||||
.filter(|(_, res)| res.is_err())
|
||||
.for_each(|(pdu_id, res)| warn!("error for {pdu_id} from remote: {res:?}"));
|
||||
.for_each(
|
||||
|(pdu_id, res)| warn!(%transaction_id, %server, "error sending PDU {pdu_id} to remote server: {res:?}"),
|
||||
);
|
||||
})
|
||||
.map(|_| dest.clone())
|
||||
.map_err(|e| (dest.clone(), e))
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use conduit::{err, info, utils, warn, Error, Result};
|
||||
use conduit::{debug, err, info, utils, warn, Error, Result};
|
||||
use database::Map;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use serde::Deserialize;
|
||||
use tokio::{sync::Notify, time::interval};
|
||||
|
||||
use crate::{admin, client, Dep};
|
||||
use crate::{admin, client, globals, Dep};
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
@@ -19,6 +19,7 @@ pub struct Service {
|
||||
struct Services {
|
||||
admin: Dep<admin::Service>,
|
||||
client: Dep<client::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -42,6 +43,7 @@ impl crate::Service for Service {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
services: Services {
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
admin: args.depend::<admin::Service>("admin"),
|
||||
client: args.depend::<client::Service>("client"),
|
||||
},
|
||||
@@ -52,6 +54,10 @@ impl crate::Service for Service {
|
||||
}
|
||||
|
||||
async fn worker(self: Arc<Self>) -> Result<()> {
|
||||
if !self.services.globals.allow_check_for_updates() {
|
||||
debug!("Disabling update check");
|
||||
return Ok(());
|
||||
}
|
||||
let mut i = interval(self.interval);
|
||||
loop {
|
||||
tokio::select! {
|
||||
|
||||
@@ -32,6 +32,7 @@ pub struct Data {
|
||||
userid_password: Arc<Map>,
|
||||
userid_selfsigningkeyid: Arc<Map>,
|
||||
userid_usersigningkeyid: Arc<Map>,
|
||||
useridprofilekey_value: Arc<Map>,
|
||||
services: Services,
|
||||
}
|
||||
|
||||
@@ -64,6 +65,7 @@ impl Data {
|
||||
userid_password: db["userid_password"].clone(),
|
||||
userid_selfsigningkeyid: db["userid_selfsigningkeyid"].clone(),
|
||||
userid_usersigningkeyid: db["userid_usersigningkeyid"].clone(),
|
||||
useridprofilekey_value: db["useridprofilekey_value"].clone(),
|
||||
services: Services {
|
||||
server: args.server.clone(),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
@@ -231,6 +233,111 @@ impl Data {
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Gets a specific user profile key
|
||||
pub(super) fn profile_key(&self, user_id: &UserId, profile_key: &str) -> Result<Option<serde_json::Value>> {
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xFF);
|
||||
key.extend_from_slice(profile_key.as_bytes());
|
||||
|
||||
self.useridprofilekey_value
|
||||
.get(&key)?
|
||||
.map_or(Ok(None), |bytes| Ok(Some(serde_json::from_slice(&bytes).unwrap())))
|
||||
}
|
||||
|
||||
/// Gets all the user's profile keys and values in an iterator
|
||||
pub(super) fn all_profile_keys<'a>(
|
||||
&'a self, user_id: &UserId,
|
||||
) -> Box<dyn Iterator<Item = Result<(String, serde_json::Value)>> + 'a + Send> {
|
||||
let prefix = user_id.as_bytes().to_vec();
|
||||
|
||||
Box::new(
|
||||
self.useridprofilekey_value
|
||||
.scan_prefix(prefix)
|
||||
.map(|(key, value)| {
|
||||
let profile_key_name = utils::string_from_bytes(
|
||||
key.rsplit(|&b| b == 0xFF)
|
||||
.next()
|
||||
.ok_or_else(|| err!(Database("Profile key in db is invalid")))?,
|
||||
)
|
||||
.map_err(|e| err!(Database("Profile key in db is invalid. {e}")))?;
|
||||
|
||||
let profile_key_value = serde_json::from_slice(&value)
|
||||
.map_err(|e| err!(Database("Profile key in db is invalid. {e}")))?;
|
||||
|
||||
Ok((profile_key_name, profile_key_value))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Sets a new profile key value, removes the key if value is None
|
||||
pub(super) fn set_profile_key(
|
||||
&self, user_id: &UserId, profile_key: &str, profile_key_value: Option<serde_json::Value>,
|
||||
) -> Result<()> {
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xFF);
|
||||
key.extend_from_slice(profile_key.as_bytes());
|
||||
|
||||
// TODO: insert to the stable MSC4175 key when it's stable
|
||||
if let Some(value) = profile_key_value {
|
||||
let value = serde_json::to_vec(&value).unwrap();
|
||||
|
||||
self.useridprofilekey_value.insert(&key, &value)
|
||||
} else {
|
||||
self.useridprofilekey_value.remove(&key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the timezone of a user.
|
||||
pub(super) fn timezone(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||
// first check the unstable prefix
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xFF);
|
||||
key.extend_from_slice(b"us.cloke.msc4175.tz");
|
||||
|
||||
let value = self
|
||||
.useridprofilekey_value
|
||||
.get(&key)?
|
||||
.map(|bytes| utils::string_from_bytes(&bytes).map_err(|e| err!(Database("Timezone in db is invalid. {e}"))))
|
||||
.transpose()
|
||||
.unwrap();
|
||||
|
||||
// TODO: transparently migrate unstable key usage to the stable key once MSC4133
|
||||
// and MSC4175 are stable, likely a remove/insert in this block
|
||||
if value.is_none() || value.as_ref().is_some_and(String::is_empty) {
|
||||
// check the stable prefix
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xFF);
|
||||
key.extend_from_slice(b"m.tz");
|
||||
|
||||
return self
|
||||
.useridprofilekey_value
|
||||
.get(&key)?
|
||||
.map(|bytes| {
|
||||
utils::string_from_bytes(&bytes).map_err(|e| err!(Database("Timezone in db is invalid. {e}")))
|
||||
})
|
||||
.transpose();
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Sets a new timezone or removes it if timezone is None.
|
||||
pub(super) fn set_timezone(&self, user_id: &UserId, timezone: Option<String>) -> Result<()> {
|
||||
let mut key = user_id.as_bytes().to_vec();
|
||||
key.push(0xFF);
|
||||
key.extend_from_slice(b"us.cloke.msc4175.tz");
|
||||
|
||||
// TODO: insert to the stable MSC4175 key when it's stable
|
||||
if let Some(timezone) = timezone {
|
||||
self.useridprofilekey_value
|
||||
.insert(&key, timezone.as_bytes())?;
|
||||
} else {
|
||||
self.useridprofilekey_value.remove(&key)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets a new avatar_url or removes it if avatar_url is None.
|
||||
pub(super) fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
|
||||
if let Some(blurhash) = blurhash {
|
||||
|
||||
@@ -327,6 +327,33 @@ impl Service {
|
||||
/// Get the blurhash of a user.
|
||||
pub fn blurhash(&self, user_id: &UserId) -> Result<Option<String>> { self.db.blurhash(user_id) }
|
||||
|
||||
pub fn timezone(&self, user_id: &UserId) -> Result<Option<String>> { self.db.timezone(user_id) }
|
||||
|
||||
/// Gets a specific user profile key
|
||||
pub fn profile_key(&self, user_id: &UserId, profile_key: &str) -> Result<Option<serde_json::Value>> {
|
||||
self.db.profile_key(user_id, profile_key)
|
||||
}
|
||||
|
||||
/// Gets all the user's profile keys and values in an iterator
|
||||
pub fn all_profile_keys<'a>(
|
||||
&'a self, user_id: &UserId,
|
||||
) -> Box<dyn Iterator<Item = Result<(String, serde_json::Value)>> + 'a + Send> {
|
||||
self.db.all_profile_keys(user_id)
|
||||
}
|
||||
|
||||
/// Sets a new profile key value, removes the key if value is None
|
||||
pub fn set_profile_key(
|
||||
&self, user_id: &UserId, profile_key: &str, profile_key_value: Option<serde_json::Value>,
|
||||
) -> Result<()> {
|
||||
self.db
|
||||
.set_profile_key(user_id, profile_key, profile_key_value)
|
||||
}
|
||||
|
||||
/// Sets a new tz or removes it if tz is None.
|
||||
pub async fn set_timezone(&self, user_id: &UserId, tz: Option<String>) -> Result<()> {
|
||||
self.db.set_timezone(user_id, tz)
|
||||
}
|
||||
|
||||
/// Sets a new blurhash or removes it if blurhash is None.
|
||||
pub async fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
|
||||
self.db.set_blurhash(user_id, blurhash)
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
{"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Invited_user_can_reject_invite_over_federation_several_times"}
|
||||
{"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Invited_user_has_'is_direct'_flag_in_prev_content_after_joining"}
|
||||
{"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Remote_invited_user_can_see_room_metadata"}
|
||||
{"Action":"pass","Test":"TestFederationThumbnail"}
|
||||
{"Action":"fail","Test":"TestGetMissingEventsGapFilling"}
|
||||
{"Action":"fail","Test":"TestInboundCanReturnMissingEvents"}
|
||||
{"Action":"fail","Test":"TestInboundCanReturnMissingEvents/Inbound_federation_can_return_missing_events_for_invited_visibility"}
|
||||
|
||||
Reference in New Issue
Block a user