mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
328 Commits
v0.5.0-rc
...
v0.5.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
| b6e9dc3d98 | |||
| cfcd6eb1a6 | |||
| 88e7e50daf | |||
| 8345ea2cd3 | |||
| add2e0e9ee | |||
| 43e6c27bb7 | |||
| c7c9f0e4a6 | |||
| ef2d307c15 | |||
| f761d4d5c9 | |||
| 16b07ae3ec | |||
| 62d80b97e6 | |||
| fda8b36809 | |||
| f6dfc9538f | |||
| f80d85e107 | |||
| 9158edfb7c | |||
| 04656a7886 | |||
| 442bb9889c | |||
| 62180897c0 | |||
| 80277f6aa2 | |||
| d32534164c | |||
| b3271e0d65 | |||
| 106bcd30b7 | |||
| da4b94d80d | |||
| 32f990fc72 | |||
| 5e59ce37c4 | |||
| a774afe837 | |||
| ffe3b0faf2 | |||
| bd6d4bc58f | |||
| b4d22bd05e | |||
| 7ce782ddf4 | |||
| 4add39d0fe | |||
| ea49b60273 | |||
| 2fa9621f3a | |||
| 09bc71caab | |||
| 6983798487 | |||
| a4ef04cd14 | |||
| 4e0cedbe51 | |||
| 4ff1155bf0 | |||
| e161e5dd61 | |||
| f698254c41 | |||
| 69837671bb | |||
| ff8bbd4cfa | |||
| 1a8482b3b4 | |||
| 31c2968bb2 | |||
| 3c8376d897 | |||
| 50acfe7832 | |||
| eb7d893c86 | |||
| 936161d89e | |||
| 329925c661 | |||
| af399fd517 | |||
| ad0b0af955 | |||
| 2c5af902a3 | |||
| 2f449ba47d | |||
| a567e314e9 | |||
| ed3cd99781 | |||
| 99fe88c21e | |||
| ffd0fd4242 | |||
| b2a565b0b4 | |||
| c516a8df3e | |||
| 94d786ac12 | |||
| 677316631a | |||
| 2b730a30ad | |||
| 98f9570547 | |||
| 13335042b7 | |||
| 6db8df5e23 | |||
| d0b4a619af | |||
| 4a2d0d35bc | |||
| 3e0ff2dc84 | |||
| 71a3855af6 | |||
| db7d23e780 | |||
| 1c585ab1b6 | |||
| 24e6086f12 | |||
| ee63f720c9 | |||
| 4b3c54bbfa | |||
| 68856645ee | |||
| 9ad4f20da4 | |||
| 186c459584 | |||
| 29a19ba437 | |||
| 3b0195e6b3 | |||
| 4b331fe50e | |||
| c323894497 | |||
| 5b5ccba64e | |||
| 9dcf289c7a | |||
| d86061084c | |||
| 1d26eec82d | |||
| 9514064c1c | |||
| 2abf15b9e9 | |||
| cd5d4f48be | |||
| eed3291625 | |||
| 6a7fe3ab7c | |||
| 72daf7ea68 | |||
| 94f2384fb0 | |||
| d59f68a51a | |||
| b1b6dc0479 | |||
| 184a3b0f0c | |||
| b5c167de12 | |||
| 5be07ebc0f | |||
| 7c6b8b132a | |||
| 1351d07735 | |||
| 6e7c73336c | |||
| 52adae7553 | |||
| a5520e8b1b | |||
| 265802d546 | |||
| da9f1ae5d7 | |||
| 607e338ac2 | |||
| f75d9fa79e | |||
| 7c0c029a4a | |||
| 49023aa295 | |||
| 0c96891008 | |||
| 1f31e74024 | |||
| 9ab381e4eb | |||
| dda27ffcb1 | |||
| 8ab825b12c | |||
| 19f6d9d0e1 | |||
| 277b4951e8 | |||
| 610129d162 | |||
| 4c0ae8c2f7 | |||
| ea25dc04b2 | |||
| 388730d6dd | |||
| ac944496c1 | |||
| 3dae02b886 | |||
| 3eed408b29 | |||
| 4fbbfe5d30 | |||
| df3eb95d4f | |||
| 7045481fae | |||
| c6ae6adc80 | |||
| afdf5a07b5 | |||
| f9e76d6239 | |||
| 8141ca3444 | |||
| abf33013e3 | |||
| 96e85adc32 | |||
| fc1170e12a | |||
| 819e35f81f | |||
| bab40a3747 | |||
| aad42bdaa0 | |||
| 3759d1be6c | |||
| 77d8e26efe | |||
| 7a8ca8842a | |||
| 80832cb0bb | |||
| 98d8e5c63c | |||
| 5167e1f06d | |||
| e56d3c6cb3 | |||
| afcd0bfeef | |||
| 5b8464252c | |||
| 2cc6ad8df3 | |||
| afe9e5536b | |||
| 9ebb39ca4f | |||
| f59e3d8850 | |||
| 6cb3275be0 | |||
| be16f84410 | |||
| 9dd058de60 | |||
| 5a1c41e66b | |||
| fabd3cf567 | |||
| 5e21b43f25 | |||
| 9bda5a43e5 | |||
| 8c18481d1d | |||
| fde1b94e26 | |||
| b71201cf19 | |||
| 8451ea3bc3 | |||
| 6f15c9b3f4 | |||
| 0074f903d8 | |||
| 1852eeebf2 | |||
| 5b6279b1c5 | |||
| 4c2999ccd1 | |||
| 53d03bbb1f | |||
| 66231676f1 | |||
| 16fa2eca87 | |||
| 6a0f9add0c | |||
| 02f19cf951 | |||
| 685b127f99 | |||
| cc1889d135 | |||
| 0238f27605 | |||
| 5dae086197 | |||
| 44e6b1af3c | |||
| 94c8683836 | |||
| d36167ab64 | |||
| 925061b92d | |||
| 27328cbc01 | |||
| a3f9432da8 | |||
| 82168b972a | |||
| 7526ba9d6f | |||
| 8c74e35e76 | |||
| e5049cae4a | |||
| f1af7ec08c | |||
| 3b8a90ad13 | |||
| 7a349fdc58 | |||
| 6c8a1b5e9b | |||
| a5d70f7356 | |||
| 50cadbee96 | |||
| b1182fe8a4 | |||
| 77d7e8a3ad | |||
| 72797532b6 | |||
| b4ef646485 | |||
| b8f8f68634 | |||
| 33635e11d1 | |||
| 1a71798859 | |||
| 7e4453620e | |||
| 2259e2c82f | |||
| 1792711d09 | |||
| 0b2fca5ad9 | |||
| 0e110bb48b | |||
| 3ffe2cd56e | |||
| c5b94be5b8 | |||
| 2bbb28bb88 | |||
| 203cf57fdf | |||
| 9c6b5b4407 | |||
| a1fc4d49ac | |||
| b56e480b3c | |||
| 7c8eeaf4ea | |||
| 9eb99f8070 | |||
| 0a9b6c136f | |||
| d91570d0e6 | |||
| 3a2c5be4f4 | |||
| e21403a4d4 | |||
| 74eb30c106 | |||
| 6458f4b195 | |||
| 5a335933b8 | |||
| 3b95af9a18 | |||
| a407909d09 | |||
| 7a6d657558 | |||
| b195107053 | |||
| e5a1309583 | |||
| 03f2ac9caf | |||
| 5fdb8895b1 | |||
| 749f29aaab | |||
| a3d87be22f | |||
| aefc4b4e69 | |||
| 503210c3bf | |||
| 674acc8657 | |||
| af3d6a2e37 | |||
| 98e6c81e49 | |||
| 14341bb906 | |||
| 5bce0a3a46 | |||
| c53e9e07f2 | |||
| 7f645ff0e9 | |||
| 5b5735f653 | |||
| 025950139e | |||
| 0602e38ada | |||
| 8f73caae0b | |||
| 802395bdb7 | |||
| 60a952508e | |||
| 4d46df2af5 | |||
| 7b8320e0eb | |||
| 6c42ac2726 | |||
| 71673b2a88 | |||
| 5f7636f177 | |||
| f78104a959 | |||
| ad8cbcaac1 | |||
| f54a62dda0 | |||
| 9040ad054e | |||
| 4dd809fdc4 | |||
| 3675c941f8 | |||
| 5b6ff3869c | |||
| 9ec35cf684 | |||
| 63c4975483 | |||
| 5813419f4b | |||
| 8dcdb4b09a | |||
| 7ad710d96a | |||
| c8c9d73b20 | |||
| 004671b437 | |||
| aeae67a7ee | |||
| 74c5bfd311 | |||
| 2ba0400758 | |||
| 468071336b | |||
| 9c6908b8c1 | |||
| c8349988aa | |||
| 6c96acc482 | |||
| 52693db477 | |||
| 77e0b76408 | |||
| 0317cc8cc5 | |||
| c6bf8f5ea1 | |||
| e4489a5d20 | |||
| a9f6bb6169 | |||
| 1b9c331049 | |||
| 17273b1cea | |||
| c5aca8036d | |||
| b73d558cba | |||
| 76a5a67b6f | |||
| e0446181c5 | |||
| a26b1c5722 | |||
| c9bb943a34 | |||
| 353dc17af4 | |||
| 66a82447da | |||
| 3e64a5c907 | |||
| 5d51adc29d | |||
| 0740d7259a | |||
| af08d3f7bd | |||
| 9ca964a97e | |||
| 39abd9a827 | |||
| ac0545f773 | |||
| 80f9536d21 | |||
| ef8392cbbe | |||
| 1e3bb02ced | |||
| badb83484f | |||
| 34f9e3260f | |||
| aba88ccead | |||
| 5fc8e90e02 | |||
| cd67ca6c57 | |||
| 9cb71e212a | |||
| 0ff37b829c | |||
| b6ac3649ee | |||
| e1bd6a7c2d | |||
| faf48405aa | |||
| ab4a283870 | |||
| 9d59f777d2 | |||
| 61670370ed | |||
| 1606441d09 | |||
| cf71aeef0b | |||
| a1b0369033 | |||
| c070edc189 | |||
| 35e9d9b02e | |||
| a752fb9943 | |||
| f70fdca828 | |||
| 82133ee2ea | |||
| ff56ee7413 | |||
| 6aa35260e6 | |||
| 139a1ac504 | |||
| 65a370836c | |||
| aa6d0fcaa7 | |||
| d921b82376 | |||
| da984d49cf | |||
| b7a41f283f | |||
| b77a1eb079 | |||
| 23cf2b2236 | |||
| de3b137df8 | |||
| 8e8c6bfe07 | |||
| f0a1aaf7bc | |||
| 52cee65748 |
@@ -21,3 +21,4 @@ indent_size = 2
|
||||
|
||||
[*.rs]
|
||||
indent_style = tab
|
||||
max_line_length = 98
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# taken from https://github.com/gitattributes/gitattributes/blob/46a8961ad73f5bd4d8d193708840fbc9e851d702/Rust.gitattributes
|
||||
# Auto detect text files and perform normalization
|
||||
* text=auto
|
||||
|
||||
*.rs text diff=rust
|
||||
*.toml text diff=toml
|
||||
Cargo.lock text
|
||||
|
||||
# taken from https://github.com/gitattributes/gitattributes/blob/46a8961ad73f5bd4d8d193708840fbc9e851d702/Common.gitattributes
|
||||
# Documents
|
||||
*.bibtex text diff=bibtex
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.md text diff=markdown
|
||||
*.mdx text diff=markdown
|
||||
*.tex text diff=tex
|
||||
*.adoc text
|
||||
*.textile text
|
||||
*.mustache text
|
||||
*.csv text eol=crlf
|
||||
*.tab text
|
||||
*.tsv text
|
||||
*.txt text
|
||||
*.sql text
|
||||
*.epub diff=astextplain
|
||||
|
||||
# Graphics
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.tif binary
|
||||
*.tiff binary
|
||||
*.ico binary
|
||||
# SVG treated as text by default.
|
||||
*.svg text
|
||||
*.eps binary
|
||||
|
||||
# Scripts
|
||||
*.bash text eol=lf
|
||||
*.fish text eol=lf
|
||||
*.ksh text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.zsh text eol=lf
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.ps1 text eol=crlf
|
||||
|
||||
# Serialisation
|
||||
*.json text
|
||||
*.toml text
|
||||
*.xml text
|
||||
*.yaml text
|
||||
*.yml text
|
||||
|
||||
# Archives
|
||||
*.7z binary
|
||||
*.bz binary
|
||||
*.bz2 binary
|
||||
*.bzip2 binary
|
||||
*.gz binary
|
||||
*.lz binary
|
||||
*.lzma binary
|
||||
*.rar binary
|
||||
*.tar binary
|
||||
*.taz binary
|
||||
*.tbz binary
|
||||
*.tbz2 binary
|
||||
*.tgz binary
|
||||
*.tlz binary
|
||||
*.txz binary
|
||||
*.xz binary
|
||||
*.Z binary
|
||||
*.zip binary
|
||||
*.zst binary
|
||||
|
||||
# Text files where line endings should be preserved
|
||||
*.patch -text
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
<!-- Please describe your changes here -->
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
- [ ] I ran `cargo fmt`, `cargo clippy`, and `cargo test`
|
||||
- [ ] I agree to release my code and all other changes of this MR under the Apache-2.0 license
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
name: CI and Artifacts
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
# documentation workflow deals with this or is not relevant for this workflow
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- 'conduwuit-example.toml'
|
||||
- 'book.toml'
|
||||
- '.gitlab-ci.yml'
|
||||
- '.gitignore'
|
||||
- 'renovate.json'
|
||||
- 'docs/**'
|
||||
- 'debian/**'
|
||||
- 'docker/**'
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
#workflow_dispatch:
|
||||
|
||||
#concurrency:
|
||||
# group: ${{ gitea.head_ref || gitea.ref_name }}
|
||||
# cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Required to make some things output color
|
||||
TERM: ansi
|
||||
# Publishing to my nix binary cache
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
# conduwuit.cachix.org
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
# Just in case incremental is still being set to true, speeds up CI
|
||||
CARGO_INCREMENTAL: 0
|
||||
# Custom nix binary cache if fork is being used
|
||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||
# Get error output from nix that we can actually use
|
||||
NIX_CONFIG: show-trace = true
|
||||
|
||||
#permissions:
|
||||
# packages: write
|
||||
# contents: read
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Sync repository
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Tag comparison check
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
run: |
|
||||
# Tag mismatch with latest repo tag check to prevent potential downgrades
|
||||
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
if [ $LATEST_TAG != ${{ gitea.ref_name }} ]; then
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.'
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install Nix
|
||||
uses: https://github.com/DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
diagnostic-endpoint: ""
|
||||
extra-conf: |
|
||||
experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Configure Magic Nix Cache
|
||||
uses: https://github.com/DeterminateSystems/magic-nix-cache-action@main
|
||||
with:
|
||||
diagnostic-endpoint: ""
|
||||
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit 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=
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
EOF
|
||||
|
||||
- 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
|
||||
direnv allow
|
||||
nix develop .#all-features --command true
|
||||
|
||||
- name: Cache CI dependencies
|
||||
run: |
|
||||
bin/nix-build-and-cache ci
|
||||
|
||||
- name: Run CI tests
|
||||
run: |
|
||||
direnv exec . engage > >(tee -a test_output.log)
|
||||
|
||||
- name: Sync Complement repository
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
with:
|
||||
repository: 'matrix-org/complement'
|
||||
path: complement_src
|
||||
|
||||
- name: Run Complement tests
|
||||
run: |
|
||||
direnv exec . bin/complement 'complement_src' 'complement_test_logs.jsonl' 'complement_test_results.jsonl'
|
||||
cp -v -f result complement_oci_image.tar.gz
|
||||
|
||||
- name: Upload Complement OCI image
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_oci_image.tar.gz
|
||||
path: complement_oci_image.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Complement logs
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_test_logs.jsonl
|
||||
path: complement_test_logs.jsonl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Complement results
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_test_results.jsonl
|
||||
path: complement_test_results.jsonl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Diff Complement results with checked-in repo results
|
||||
run: |
|
||||
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_test_output.log)
|
||||
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 100 complement_test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Update Job Summary
|
||||
if: success() || failure()
|
||||
run: |
|
||||
if [ ${{ job.status }} == 'success' ]; then
|
||||
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 40 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: tests
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
- target: x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Sync repository
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: https://github.com/DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
diagnostic-endpoint: ""
|
||||
extra-conf: |
|
||||
experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
|
||||
- name: Install and enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Configure Magic Nix Cache
|
||||
uses: https://github.com/DeterminateSystems/magic-nix-cache-action@main
|
||||
with:
|
||||
diagnostic-endpoint: ""
|
||||
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit 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=
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
EOF
|
||||
|
||||
- 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
|
||||
direnv allow
|
||||
nix develop .#all-features --command true
|
||||
|
||||
- name: Build static ${{ matrix.target }}
|
||||
run: |
|
||||
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
cp -v -f result/bin/conduit target/release/conduwuit
|
||||
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
# -p conduit is the main crate name
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}.deb
|
||||
mv -v target/release/conduwuit static-${{ matrix.target }}
|
||||
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
|
||||
|
||||
- name: Upload static-${{ matrix.target }}
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-${{ matrix.target }}
|
||||
path: static-${{ matrix.target }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload deb ${{ matrix.target }}
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: deb-${{ matrix.target }}
|
||||
path: ${{ matrix.target }}.deb
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Build OCI image ${{ matrix.target }}
|
||||
run: |
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}
|
||||
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
|
||||
|
||||
- name: Upload OCI image ${{ matrix.target }}
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: oci-image-${{ matrix.target }}
|
||||
path: oci-image-${{ matrix.target }}.tar.gz
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
+464
-125
@@ -22,8 +22,8 @@ concurrency:
|
||||
|
||||
env:
|
||||
# sccache only on main repo
|
||||
SCCACHE_GHA_ENABLED: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'true' || 'false' }}"
|
||||
RUSTC_WRAPPER: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
|
||||
SCCACHE_GHA_ENABLED: "${{ !startsWith(github.ref, 'refs/tags/') && (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'true' || 'false' }}"
|
||||
RUSTC_WRAPPER: "${{ !startsWith(github.ref, 'refs/tags/') && (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
|
||||
SCCACHE_BUCKET: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
|
||||
SCCACHE_S3_USE_SSL: ${{ vars.SCCACHE_S3_USE_SSL }}
|
||||
SCCACHE_REGION: ${{ vars.SCCACHE_REGION }}
|
||||
@@ -45,23 +45,54 @@ 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/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=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= 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
|
||||
WEB_UPLOAD_SSH_USERNAME: ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
|
||||
GH_REF_NAME: ${{ github.ref_name }}
|
||||
WEBSERVER_DIR_NAME: ${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Test
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Setup SSH web publish
|
||||
env:
|
||||
web_upload_ssh_private_key: ${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}
|
||||
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
|
||||
run: |
|
||||
mkdir -p -v ~/.ssh
|
||||
|
||||
echo "${{ secrets.WEB_UPLOAD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
|
||||
echo "${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519
|
||||
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
cat >>~/.ssh/config <<END
|
||||
Host website
|
||||
HostName ${{ secrets.WEB_UPLOAD_SSH_HOSTNAME }}
|
||||
User ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
StrictHostKeyChecking yes
|
||||
AddKeysToAgent no
|
||||
ForwardX11 no
|
||||
BatchMode yes
|
||||
END
|
||||
|
||||
echo "Checking connection"
|
||||
ssh -q website "echo test" || ssh -q website "echo test"
|
||||
|
||||
echo "Creating commit rev directory on web server"
|
||||
ssh -q website "rm -rf /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/" || ssh -q website "rm -rf /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/"
|
||||
ssh -q website "mkdir -v /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/" || ssh -q website "mkdir -v /var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/"
|
||||
|
||||
echo "SSH_WEBSITE=1" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install liburing
|
||||
run: |
|
||||
sudo apt install liburing-dev -y
|
||||
@@ -72,11 +103,13 @@ jobs:
|
||||
sudo docker image prune --all --force || true
|
||||
sudo apt purge -y 'php.*' '^mongodb-.*' '^mysql-.*' azure-cli google-cloud-cli google-chrome-stable firefox powershell microsoft-edge-stable || true
|
||||
sudo apt clean
|
||||
sudo rm -v -rf /usr/local/games /usr/local/sqlpackage /usr/local/share/powershell /usr/local/share/edge_driver /usr/local/share/gecko_driver /usr/local/share/chromium /usr/local/share/chromedriver-linux64 /usr/lib/google-cloud-sdk /usr/lib/jvm /usr/lib/mono /usr/lib/heroku
|
||||
sudo rm -rf /usr/local/lib/android /usr/local/julia* /usr/local/games /usr/local/sqlpackage /usr/local/share/powershell /usr/local/share/edge_driver /usr/local/share/gecko_driver /usr/local/share/chromium /usr/local/share/chromedriver-linux64 /usr/lib/google-cloud-sdk /usr/lib/jvm /usr/lib/mono /usr/local/lib/heroku /usr/lib/heroku /usr/local/share/boost /usr/share/dotnet /usr/local/bin/cmake* /usr/local/bin/stack /usr/local/bin/terraform /opt/microsoft/powershell /opt/hostedtoolcache/CodeQL /opt/hostedtoolcache/go /opt/hostedtoolcache/PyPy /usr/local/bin/sam || true
|
||||
set -o pipefail
|
||||
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Tag comparison check
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
|
||||
@@ -84,7 +117,7 @@ jobs:
|
||||
# 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
|
||||
if [ ${LATEST_TAG} != ${GH_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
|
||||
@@ -93,6 +126,9 @@ jobs:
|
||||
- uses: nixbuild/nix-quick-install-action@master
|
||||
|
||||
- name: Restore and cache Nix store
|
||||
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
|
||||
# releases and tags
|
||||
#if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
with:
|
||||
# restore and save a cache using this key
|
||||
@@ -122,7 +158,7 @@ jobs:
|
||||
- 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/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit 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
|
||||
@@ -133,8 +169,8 @@ jobs:
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
extra-substituters = ${ATTIC_ENDPOINT}
|
||||
extra-trusted-public-keys = ${ATTIC_PUBLIC_KEY}
|
||||
EOF
|
||||
|
||||
- name: Prepare build environment
|
||||
@@ -153,13 +189,20 @@ jobs:
|
||||
|
||||
# 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]')
|
||||
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
|
||||
# releases and tags
|
||||
#if: ${{ (env.SCCACHE_GHA_ENABLED == 'true') && !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: mozilla-actions/sccache-action@main
|
||||
|
||||
# use rust-cache
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
|
||||
# releases and tags
|
||||
#if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
cache-on-failure: "true"
|
||||
cache-targets: "true"
|
||||
|
||||
- name: Run CI tests
|
||||
env:
|
||||
@@ -181,6 +224,7 @@ jobs:
|
||||
name: complement_oci_image.tar.gz
|
||||
path: complement_oci_image.tar.gz
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Upload Complement logs
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -201,9 +245,11 @@ jobs:
|
||||
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_diff_output.log)
|
||||
|
||||
- name: Update Job Summary
|
||||
env:
|
||||
GH_JOB_STATUS: ${{ job.status }}
|
||||
if: success() || failure()
|
||||
run: |
|
||||
if [ ${{ job.status }} == 'success' ]; then
|
||||
if [ ${GH_JOB_STATUS} == 'success' ]; then
|
||||
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo '# CI failure' >> $GITHUB_STEP_SUMMARY
|
||||
@@ -224,19 +270,60 @@ jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-24.04
|
||||
needs: tests
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-linux-musl
|
||||
- target: x86_64-linux-musl
|
||||
steps:
|
||||
- name: Free up a bit of runner space
|
||||
run: |
|
||||
set +o pipefail
|
||||
sudo docker image prune --all --force || true
|
||||
sudo apt purge -y 'php.*' '^mongodb-.*' '^mysql-.*' azure-cli google-cloud-cli google-chrome-stable firefox powershell microsoft-edge-stable || true
|
||||
sudo apt clean
|
||||
sudo rm -rf /usr/local/lib/android /usr/local/julia* /usr/local/games /usr/local/sqlpackage /usr/local/share/powershell /usr/local/share/edge_driver /usr/local/share/gecko_driver /usr/local/share/chromium /usr/local/share/chromedriver-linux64 /usr/lib/google-cloud-sdk /usr/lib/jvm /usr/lib/mono /usr/local/lib/heroku /usr/lib/heroku /usr/local/share/boost /usr/share/dotnet /usr/local/bin/cmake* /usr/local/bin/stack /usr/local/bin/terraform /opt/microsoft/powershell /opt/hostedtoolcache/CodeQL /opt/hostedtoolcache/go /opt/hostedtoolcache/PyPy /usr/local/bin/sam || true
|
||||
set -o pipefail
|
||||
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup SSH web publish
|
||||
env:
|
||||
web_upload_ssh_private_key: ${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}
|
||||
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
|
||||
run: |
|
||||
mkdir -p -v ~/.ssh
|
||||
|
||||
echo "${{ secrets.WEB_UPLOAD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
|
||||
echo "${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519
|
||||
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
cat >>~/.ssh/config <<END
|
||||
Host website
|
||||
HostName ${{ secrets.WEB_UPLOAD_SSH_HOSTNAME }}
|
||||
User ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
StrictHostKeyChecking yes
|
||||
AddKeysToAgent no
|
||||
ForwardX11 no
|
||||
BatchMode yes
|
||||
END
|
||||
|
||||
echo "Checking connection"
|
||||
ssh -q website "echo test" || ssh -q website "echo test"
|
||||
|
||||
echo "SSH_WEBSITE=1" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: nixbuild/nix-quick-install-action@master
|
||||
|
||||
- name: Restore and cache Nix store
|
||||
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
|
||||
# releases and tags
|
||||
#if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
with:
|
||||
# restore and save a cache using this key
|
||||
@@ -266,7 +353,7 @@ jobs:
|
||||
- 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/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit 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
|
||||
@@ -277,8 +364,8 @@ jobs:
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
extra-substituters = ${ATTIC_ENDPOINT}
|
||||
extra-trusted-public-keys = ${ATTIC_PUBLIC_KEY}
|
||||
EOF
|
||||
|
||||
- name: Prepare build environment
|
||||
@@ -290,15 +377,22 @@ jobs:
|
||||
|
||||
# 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]')
|
||||
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
|
||||
# releases and tags
|
||||
#if: ${{ (env.SCCACHE_GHA_ENABLED == 'true') && !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: mozilla-actions/sccache-action@main
|
||||
|
||||
# use rust-cache
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
|
||||
# releases and tags
|
||||
#if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
cache-on-failure: "true"
|
||||
cache-targets: "true"
|
||||
|
||||
- name: Build static ${{ matrix.target }}
|
||||
- name: Build static ${{ matrix.target }}-all-features
|
||||
run: |
|
||||
if [[ ${{ matrix.target }} == "x86_64-linux-musl" ]]
|
||||
then
|
||||
@@ -314,22 +408,40 @@ jobs:
|
||||
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
cp -v -f result/bin/conduit target/release/conduwuit
|
||||
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
# -p conduit is the main crate name
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}.deb
|
||||
cp -v -f result/bin/conduwuit target/release/conduwuit
|
||||
cp -v -f result/bin/conduwuit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduwuit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}.deb
|
||||
mv -v target/release/conduwuit static-${{ matrix.target }}
|
||||
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
|
||||
|
||||
- name: Build static x86_64-linux-musl-all-features-x86_64-haswell-optimised
|
||||
if: ${{ matrix.target == 'x86_64-linux-musl' }}
|
||||
run: |
|
||||
CARGO_DEB_TARGET_TUPLE="x86_64-unknown-linux-musl"
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
|
||||
bin/nix-build-and-cache just .#static-x86_64-linux-musl-all-features-x86_64-haswell-optimised
|
||||
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
cp -v -f result/bin/conduwuit target/release/conduwuit
|
||||
cp -v -f result/bin/conduwuit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduwuit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/x86_64-linux-musl-x86_64-haswell-optimised.deb
|
||||
mv -v target/release/conduwuit static-x86_64-linux-musl-x86_64-haswell-optimised
|
||||
mv -v target/release/x86_64-linux-musl-x86_64-haswell-optimised.deb x86_64-linux-musl-x86_64-haswell-optimised.deb
|
||||
|
||||
# quick smoke test of the x86_64 static release binary
|
||||
- name: Run x86_64 static release binary
|
||||
- name: Quick smoke test the x86_64 static release binary
|
||||
if: ${{ matrix.target == 'x86_64-linux-musl' }}
|
||||
run: |
|
||||
# GH actions default runners are x86_64 only
|
||||
if file result/bin/conduit | grep x86-64; then
|
||||
result/bin/conduit --version
|
||||
if file result/bin/conduwuit | grep x86-64; then
|
||||
result/bin/conduwuit --version
|
||||
result/bin/conduwuit --help
|
||||
result/bin/conduwuit -Oserver_name="'$(date -u +%s).local'" -Odatabase_path="'/tmp/$(date -u +%s)'" --execute "server admin-notice awawawawawawawawawawa" --execute "server memory-usage" --execute "server shutdown"
|
||||
fi
|
||||
|
||||
- name: Build static debug ${{ matrix.target }}
|
||||
- name: Build static debug ${{ matrix.target }}-all-features
|
||||
run: |
|
||||
if [[ ${{ matrix.target }} == "x86_64-linux-musl" ]]
|
||||
then
|
||||
@@ -347,10 +459,9 @@ jobs:
|
||||
# so we need to coerce cargo-deb into thinking this is a release binary
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
cp -v -f result/bin/conduit target/release/conduwuit
|
||||
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
# -p conduit is the main crate name
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}-debug.deb
|
||||
cp -v -f result/bin/conduwuit target/release/conduwuit
|
||||
cp -v -f result/bin/conduwuit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduwuit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}-debug.deb
|
||||
mv -v target/release/conduwuit static-${{ matrix.target }}-debug
|
||||
mv -v target/release/${{ matrix.target }}-debug.deb ${{ matrix.target }}-debug.deb
|
||||
|
||||
@@ -358,8 +469,8 @@ jobs:
|
||||
- name: Run x86_64 static debug binary
|
||||
run: |
|
||||
# GH actions default runners are x86_64 only
|
||||
if file result/bin/conduit | grep x86-64; then
|
||||
result/bin/conduit --version
|
||||
if file result/bin/conduwuit | grep x86-64; then
|
||||
result/bin/conduwuit --version
|
||||
fi
|
||||
|
||||
# check validity of produced deb package, invalid debs will error on these commands
|
||||
@@ -372,14 +483,22 @@ jobs:
|
||||
dpkg-deb --info ${{ matrix.target }}.deb
|
||||
dpkg-deb --info ${{ matrix.target }}-debug.deb
|
||||
|
||||
- name: Upload static-${{ matrix.target }}
|
||||
- name: Upload static-x86_64-linux-musl-all-features-x86_64-haswell-optimised to GitHub
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.target == 'x86_64-linux-musl' }}
|
||||
with:
|
||||
name: static-x86_64-linux-musl-x86_64-haswell-optimised
|
||||
path: static-x86_64-linux-musl-x86_64-haswell-optimised
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload static-${{ matrix.target }}-all-features to GitHub
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-${{ matrix.target }}
|
||||
path: static-${{ matrix.target }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload deb ${{ matrix.target }}
|
||||
- name: Upload static deb ${{ matrix.target }}-all-features to GitHub
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deb-${{ matrix.target }}
|
||||
@@ -387,14 +506,42 @@ jobs:
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Upload static-${{ matrix.target }}-debug
|
||||
- name: Upload static-x86_64-linux-musl-all-features-x86_64-haswell-optimised to webserver
|
||||
if: ${{ matrix.target == 'x86_64-linux-musl' }}
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
chmod +x static-x86_64-linux-musl-x86_64-haswell-optimised
|
||||
scp static-x86_64-linux-musl-x86_64-haswell-optimised website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/static-x86_64-linux-musl-x86_64-haswell-optimised
|
||||
fi
|
||||
|
||||
- name: Upload static-${{ matrix.target }}-all-features to webserver
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
chmod +x static-${{ matrix.target }}
|
||||
scp static-${{ matrix.target }} website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/static-${{ matrix.target }}
|
||||
fi
|
||||
|
||||
- name: Upload static deb x86_64-linux-musl-all-features-x86_64-haswell-optimised to webserver
|
||||
if: ${{ matrix.target == 'x86_64-linux-musl' }}
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
scp x86_64-linux-musl-x86_64-haswell-optimised.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/x86_64-linux-musl-x86_64-haswell-optimised.deb
|
||||
fi
|
||||
|
||||
- name: Upload static deb ${{ matrix.target }}-all-features to webserver
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
scp ${{ matrix.target }}.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/${{ matrix.target }}.deb
|
||||
fi
|
||||
|
||||
- name: Upload static-${{ matrix.target }}-debug-all-features to GitHub
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-${{ matrix.target }}-debug
|
||||
path: static-${{ matrix.target }}-debug
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload deb ${{ matrix.target }}-debug
|
||||
- name: Upload static deb ${{ matrix.target }}-debug-all-features to GitHub
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deb-${{ matrix.target }}-debug
|
||||
@@ -402,19 +549,46 @@ jobs:
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Build OCI image ${{ matrix.target }}
|
||||
- name: Upload static-${{ matrix.target }}-debug-all-features to webserver
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
scp static-${{ matrix.target }}-debug website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/static-${{ matrix.target }}-debug
|
||||
fi
|
||||
|
||||
- name: Upload static deb ${{ matrix.target }}-debug-all-features to webserver
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
scp ${{ matrix.target }}-debug.deb website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/${{ matrix.target }}-debug.deb
|
||||
fi
|
||||
|
||||
- name: Build OCI image ${{ matrix.target }}-all-features
|
||||
run: |
|
||||
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 }}
|
||||
- name: Build OCI image x86_64-linux-musl-all-features-x86_64-haswell-optimised
|
||||
if: ${{ matrix.target == 'x86_64-linux-musl' }}
|
||||
run: |
|
||||
bin/nix-build-and-cache just .#oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised
|
||||
|
||||
cp -v -f result oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz
|
||||
|
||||
- name: Build debug OCI image ${{ matrix.target }}-all-features
|
||||
run: |
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features-debug
|
||||
|
||||
cp -v -f result oci-image-${{ matrix.target }}-debug.tar.gz
|
||||
|
||||
- name: Upload OCI image ${{ matrix.target }}
|
||||
- name: Upload OCI image x86_64-linux-musl-all-features-x86_64-haswell-optimised to GitHub
|
||||
if: ${{ matrix.target == 'x86_64-linux-musl' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised
|
||||
path: oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
- name: Upload OCI image ${{ matrix.target }}-all-features to GitHub
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oci-image-${{ matrix.target }}
|
||||
@@ -422,7 +596,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Upload OCI image ${{ matrix.target }}-debug
|
||||
- name: Upload OCI image ${{ matrix.target }}-debug-all-features to GitHub
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oci-image-${{ matrix.target }}-debug
|
||||
@@ -430,6 +604,25 @@ jobs:
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Upload OCI image x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz to webserver
|
||||
if: ${{ matrix.target == 'x86_64-linux-musl' }}
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
scp oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised.tar.gz
|
||||
fi
|
||||
|
||||
- name: Upload OCI image ${{ matrix.target }}-all-features to webserver
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
scp oci-image-${{ matrix.target }}.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/oci-image-${{ matrix.target }}.tar.gz
|
||||
fi
|
||||
|
||||
- name: Upload OCI image ${{ matrix.target }}-debug-all-features to webserver
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
scp oci-image-${{ matrix.target }}-debug.tar.gz website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/oci-image-${{ matrix.target }}-debug.tar.gz
|
||||
fi
|
||||
|
||||
build_mac_binaries:
|
||||
name: Build MacOS Binaries
|
||||
strategy:
|
||||
@@ -439,13 +632,44 @@ jobs:
|
||||
steps:
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup SSH web publish
|
||||
env:
|
||||
web_upload_ssh_private_key: ${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}
|
||||
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (env.web_upload_ssh_private_key != '') && github.event.pull_request.user.login != 'renovate[bot]'
|
||||
run: |
|
||||
mkdir -p -v ~/.ssh
|
||||
|
||||
echo "${{ secrets.WEB_UPLOAD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
|
||||
echo "${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519
|
||||
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
cat >>~/.ssh/config <<END
|
||||
Host website
|
||||
HostName ${{ secrets.WEB_UPLOAD_SSH_HOSTNAME }}
|
||||
User ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
StrictHostKeyChecking yes
|
||||
AddKeysToAgent no
|
||||
ForwardX11 no
|
||||
BatchMode yes
|
||||
END
|
||||
|
||||
echo "Checking connection"
|
||||
ssh -q website "echo test" || ssh -q website "echo test"
|
||||
|
||||
echo "SSH_WEBSITE=1" >> "$GITHUB_ENV"
|
||||
|
||||
- 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
|
||||
|
||||
if [ ${LATEST_TAG} != ${GH_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
|
||||
@@ -453,41 +677,63 @@ jobs:
|
||||
|
||||
# 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]')
|
||||
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
|
||||
# releases and tags
|
||||
#if: ${{ (env.SCCACHE_GHA_ENABLED == 'true') && !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: mozilla-actions/sccache-action@main
|
||||
|
||||
# use rust-cache
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
cache-on-failure: "true"
|
||||
cache-targets: "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
|
||||
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short ${{ github.sha }})" cargo build --release --locked --features=perf_measurements,sentry_telemetry,direct_tls
|
||||
cp -v -f target/release/conduwuit 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 --help
|
||||
./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
|
||||
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short ${{ github.sha }})" cargo build --release --locked --features=perf_measurements,sentry_telemetry,direct_tls
|
||||
cp -v -f target/release/conduwuit 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 --help
|
||||
./conduwuit-macos-arm64 --version
|
||||
|
||||
- name: Upload macOS x86_64 binary to webserver
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
chmod +x conduwuit-macos-x86_64
|
||||
scp conduwuit-macos-x86_64 website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/conduwuit-macos-x86_64
|
||||
fi
|
||||
|
||||
- name: Upload macOS arm64 binary to webserver
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
if [ ! -z $SSH_WEBSITE ]; then
|
||||
chmod +x conduwuit-macos-arm64
|
||||
scp conduwuit-macos-arm64 website:/var/www/girlboss.ceo/~strawberry/conduwuit/ci-bins/${WEBSERVER_DIR_NAME}/conduwuit-macos-arm64
|
||||
fi
|
||||
|
||||
- name: Upload macOS x86_64 binary
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -503,28 +749,35 @@ jobs:
|
||||
name: conduwuit-macos-arm64
|
||||
path: conduwuit-macos-arm64
|
||||
if-no-files-found: error
|
||||
|
||||
variables:
|
||||
outputs:
|
||||
github_repository: ${{ steps.var.outputs.github_repository }}
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Setting global variables
|
||||
uses: actions/github-script@v7
|
||||
id: var
|
||||
with:
|
||||
script: |
|
||||
core.setOutput('github_repository', '${{ github.repository }}'.toLowerCase())
|
||||
docker:
|
||||
name: Docker publish
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && github.event.pull_request.user.login != 'renovate[bot]'
|
||||
needs: [build, variables, tests]
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && github.event.pull_request.user.login != 'renovate[bot]'
|
||||
env:
|
||||
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
DOCKER_TAG: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
GHCR_ARM64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GHCR_AMD64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
GHCR_TAG: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
GLCR_ARM64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GLCR_AMD64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
GLCR_TAG: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
GLCR_BRANCH: registry.gitlab.com/conduwuit/conduwuit:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
DOCKER_HUB_REPO: docker.io/${{ needs.variables.outputs.github_repository }}
|
||||
GHCR_REPO: ghcr.io/${{ needs.variables.outputs.github_repository }}
|
||||
GLCR_REPO: registry.gitlab.com/conduwuit/conduwuit
|
||||
UNIQUE_TAG: ${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
BRANCH_TAG: ${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
|
||||
GHCR_ENABLED: "${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && 'true' || 'false' }}"
|
||||
steps:
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
@@ -554,101 +807,187 @@ jobs:
|
||||
|
||||
- name: Move OCI images into position
|
||||
run: |
|
||||
mv -v oci-image-x86_64-linux-musl-all-features-x86_64-haswell-optimised/*.tar.gz oci-image-amd64-haswell-optimised.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 haswell image
|
||||
run: |
|
||||
docker load -i oci-image-amd64-haswell-optimised.tar.gz
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
|
||||
fi
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker push ${GHCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
fi
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker push ${GLCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
fi
|
||||
|
||||
- name: Load and push amd64 image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
docker load -i oci-image-amd64.tar.gz
|
||||
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_AMD64 }}
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GHCR_AMD64 }}
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GLCR_AMD64 }}
|
||||
docker push ${{ env.DOCKER_AMD64 }}
|
||||
docker push ${{ env.GHCR_AMD64 }}
|
||||
docker push ${{ env.GLCR_AMD64 }}
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64
|
||||
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64
|
||||
fi
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-amd64
|
||||
docker push ${GHCR_REPO}:${UNIQUE_TAG}-amd64
|
||||
fi
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-amd64
|
||||
docker push ${GLCR_REPO}:${UNIQUE_TAG}-amd64
|
||||
fi
|
||||
|
||||
- name: Load and push arm64 image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
docker load -i oci-image-arm64v8.tar.gz
|
||||
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_ARM64 }}
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GHCR_ARM64 }}
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GLCR_ARM64 }}
|
||||
docker push ${{ env.DOCKER_ARM64 }}
|
||||
docker push ${{ env.GHCR_ARM64 }}
|
||||
docker push ${{ env.GLCR_ARM64 }}
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8
|
||||
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8
|
||||
fi
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8
|
||||
docker push ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8
|
||||
fi
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8
|
||||
docker push ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8
|
||||
fi
|
||||
|
||||
- name: Load and push amd64 debug image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
docker load -i oci-image-amd64-debug.tar.gz
|
||||
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_AMD64 }}-debug
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GHCR_AMD64 }}-debug
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GLCR_AMD64 }}-debug
|
||||
docker push ${{ env.DOCKER_AMD64 }}-debug
|
||||
docker push ${{ env.GHCR_AMD64 }}-debug
|
||||
docker push ${{ env.GLCR_AMD64 }}-debug
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
fi
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
docker push ${GHCR_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
fi
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
docker push ${GLCR_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
fi
|
||||
|
||||
- name: Load and push arm64 debug image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
docker load -i oci-image-arm64v8-debug.tar.gz
|
||||
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_ARM64 }}-debug
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GHCR_ARM64 }}-debug
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GLCR_ARM64 }}-debug
|
||||
docker push ${{ env.DOCKER_ARM64 }}-debug
|
||||
docker push ${{ env.GHCR_ARM64 }}-debug
|
||||
docker push ${{ env.GLCR_ARM64 }}-debug
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8-debug
|
||||
docker push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8-debug
|
||||
fi
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8-debug
|
||||
docker push ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8-debug
|
||||
fi
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker tag $(docker images -q conduwuit:main) ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8-debug
|
||||
docker push ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8-debug
|
||||
fi
|
||||
|
||||
- name: Create Docker haswell manifests
|
||||
run: |
|
||||
# Dockerhub Container Registry
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker manifest create ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker manifest create ${DOCKER_HUB_REPO}:${BRANCH_TAG}-haswell --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
|
||||
fi
|
||||
# GitHub Container Registry
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker manifest create ${GHCR_REPO}:${UNIQUE_TAG}-haswell --amend ${GHCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker manifest create ${GHCR_REPO}:${BRANCH_TAG}-haswell --amend ${GHCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
fi
|
||||
# GitLab Container Registry
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker manifest create ${GLCR_REPO}:${UNIQUE_TAG}-haswell --amend ${GLCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker manifest create ${GLCR_REPO}:${BRANCH_TAG}-haswell --amend ${GLCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
fi
|
||||
|
||||
- name: Create Docker combined manifests
|
||||
run: |
|
||||
# Dockerhub Container Registry
|
||||
docker manifest create ${{ env.DOCKER_TAG }} --amend ${{ env.DOCKER_ARM64 }} --amend ${{ env.DOCKER_AMD64 }}
|
||||
docker manifest create ${{ env.DOCKER_BRANCH }} --amend ${{ env.DOCKER_ARM64 }} --amend ${{ env.DOCKER_AMD64 }}
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker manifest create ${DOCKER_HUB_REPO}:${UNIQUE_TAG} --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64
|
||||
docker manifest create ${DOCKER_HUB_REPO}:${BRANCH_TAG} --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64
|
||||
fi
|
||||
# GitHub Container Registry
|
||||
docker manifest create ${{ env.GHCR_TAG }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
|
||||
docker manifest create ${{ env.GHCR_BRANCH }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker manifest create ${GHCR_REPO}:${UNIQUE_TAG} --amend ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${GHCR_REPO}:${UNIQUE_TAG}-amd64
|
||||
docker manifest create ${GHCR_REPO}:${BRANCH_TAG} --amend ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${GHCR_REPO}:${UNIQUE_TAG}-amd64
|
||||
fi
|
||||
# GitLab Container Registry
|
||||
docker manifest create ${{ env.GLCR_TAG }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
|
||||
docker manifest create ${{ env.GLCR_BRANCH }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker manifest create ${GLCR_REPO}:${UNIQUE_TAG} --amend ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${GLCR_REPO}:${UNIQUE_TAG}-amd64
|
||||
docker manifest create ${GLCR_REPO}:${BRANCH_TAG} --amend ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8 --amend ${GLCR_REPO}:${UNIQUE_TAG}-amd64
|
||||
fi
|
||||
|
||||
- name: Create Docker combined debug manifests
|
||||
run: |
|
||||
# Dockerhub Container Registry
|
||||
docker manifest create ${{ env.DOCKER_TAG }}-debug --amend ${{ env.DOCKER_ARM64 }}-debug --amend ${{ env.DOCKER_AMD64 }}-debug
|
||||
docker manifest create ${{ env.DOCKER_BRANCH }}-debug --amend ${{ env.DOCKER_ARM64 }}-debug --amend ${{ env.DOCKER_AMD64 }}-debug
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker manifest create ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-debug --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
docker manifest create ${DOCKER_HUB_REPO}:${BRANCH_TAG}-debug --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
fi
|
||||
# GitHub Container Registry
|
||||
docker manifest create ${{ env.GHCR_TAG }}-debug --amend ${{ env.GHCR_ARM64 }}-debug --amend ${{ env.GHCR_AMD64 }}-debug
|
||||
docker manifest create ${{ env.GHCR_BRANCH }}-debug --amend ${{ env.GHCR_ARM64 }}-debug --amend ${{ env.GHCR_AMD64 }}-debug
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker manifest create ${GHCR_REPO}:${UNIQUE_TAG}-debug --amend ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${GHCR_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
docker manifest create ${GHCR_REPO}:${BRANCH_TAG}-debug --amend ${GHCR_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${GHCR_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
fi
|
||||
# GitLab Container Registry
|
||||
docker manifest create ${{ env.GLCR_TAG }}-debug --amend ${{ env.GLCR_ARM64 }}-debug --amend ${{ env.GLCR_AMD64 }}-debug
|
||||
docker manifest create ${{ env.GLCR_BRANCH }}-debug --amend ${{ env.GLCR_ARM64 }}-debug --amend ${{ env.GLCR_AMD64 }}-debug
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker manifest create ${GLCR_REPO}:${UNIQUE_TAG}-debug --amend ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${GLCR_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
docker manifest create ${GLCR_REPO}:${BRANCH_TAG}-debug --amend ${GLCR_REPO}:${UNIQUE_TAG}-arm64v8-debug --amend ${GLCR_REPO}:${UNIQUE_TAG}-amd64-debug
|
||||
fi
|
||||
|
||||
- name: Push manifests to Docker registries
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
docker manifest push ${{ env.DOCKER_TAG }}
|
||||
docker manifest push ${{ env.DOCKER_BRANCH }}
|
||||
docker manifest push ${{ env.GHCR_TAG }}
|
||||
docker manifest push ${{ env.GHCR_BRANCH }}
|
||||
docker manifest push ${{ env.GLCR_TAG }}
|
||||
docker manifest push ${{ env.GLCR_BRANCH }}
|
||||
docker manifest push ${{ env.DOCKER_TAG }}-debug
|
||||
docker manifest push ${{ env.DOCKER_BRANCH }}-debug
|
||||
docker manifest push ${{ env.GHCR_TAG }}-debug
|
||||
docker manifest push ${{ env.GHCR_BRANCH }}-debug
|
||||
docker manifest push ${{ env.GLCR_TAG }}-debug
|
||||
docker manifest push ${{ env.GLCR_BRANCH }}-debug
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
docker manifest push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}
|
||||
docker manifest push ${DOCKER_HUB_REPO}:${BRANCH_TAG}
|
||||
docker manifest push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-debug
|
||||
docker manifest push ${DOCKER_HUB_REPO}:${BRANCH_TAG}-debug
|
||||
docker manifest push ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker manifest push ${DOCKER_HUB_REPO}:${BRANCH_TAG}-haswell
|
||||
fi
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
docker manifest push ${GHCR_REPO}:${UNIQUE_TAG}
|
||||
docker manifest push ${GHCR_REPO}:${BRANCH_TAG}
|
||||
docker manifest push ${GHCR_REPO}:${UNIQUE_TAG}-debug
|
||||
docker manifest push ${GHCR_REPO}:${BRANCH_TAG}-debug
|
||||
docker manifest push ${GHCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker manifest push ${GHCR_REPO}:${BRANCH_TAG}-haswell
|
||||
fi
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
docker manifest push ${GLCR_REPO}:${UNIQUE_TAG}
|
||||
docker manifest push ${GLCR_REPO}:${BRANCH_TAG}
|
||||
docker manifest push ${GLCR_REPO}:${UNIQUE_TAG}-debug
|
||||
docker manifest push ${GLCR_REPO}:${BRANCH_TAG}-debug
|
||||
docker manifest push ${GLCR_REPO}:${UNIQUE_TAG}-haswell
|
||||
docker manifest push ${GLCR_REPO}:${BRANCH_TAG}-haswell
|
||||
fi
|
||||
|
||||
- name: Add Image Links to Job Summary
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
echo "- \`docker pull ${{ env.DOCKER_TAG }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.GHCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.GLCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.DOCKER_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.GHCR_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.GLCR_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
if [ ! -z $DOCKERHUB_TOKEN ]; then
|
||||
echo "- \`docker pull ${DOCKER_HUB_REPO}:${UNIQUE_TAG}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${DOCKER_HUB_REPO}:${UNIQUE_TAG}-haswell\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
if [ $GHCR_ENABLED = "true" ]; then
|
||||
echo "- \`docker pull ${GHCR_REPO}:${UNIQUE_TAG}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${GHCR_REPO}:${UNIQUE_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${GHCR_REPO}:${UNIQUE_TAG}-haswell\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
if [ ! -z $GITLAB_TOKEN ]; then
|
||||
echo "- \`docker pull ${GLCR_REPO}:${UNIQUE_TAG}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${GLCR_REPO}:${UNIQUE_TAG}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${GLCR_REPO}:${UNIQUE_TAG}-haswell\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Update Docker Hub Description
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- README.md
|
||||
- .github/workflows/docker-hub-description.yml
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
dockerHubDescription:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && github.event.pull_request.user.login != 'renovate[bot]' && (vars.DOCKER_USERNAME != '') }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setting variables
|
||||
uses: actions/github-script@v7
|
||||
id: var
|
||||
with:
|
||||
script: |
|
||||
const githubRepo = '${{ github.repository }}'.toLowerCase()
|
||||
const repoId = githubRepo.split('/')[1]
|
||||
|
||||
core.setOutput('github_repository', githubRepo)
|
||||
const dockerRepo = '${{ vars.DOCKER_USERNAME }}'.toLowerCase() + '/' + repoId
|
||||
core.setOutput('docker_repo', dockerRepo)
|
||||
|
||||
- name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v4
|
||||
with:
|
||||
username: ${{ vars.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
repository: ${{ steps.var.outputs.docker_repo }}
|
||||
short-description: ${{ github.event.repository.description }}
|
||||
enable-url-completion: true
|
||||
@@ -24,7 +24,7 @@ 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 = 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-substituters = extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit 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
|
||||
@@ -36,6 +36,8 @@ concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
name: Documentation and GitHub Pages
|
||||
@@ -61,6 +63,8 @@ jobs:
|
||||
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup GitHub Pages
|
||||
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') && (github.event_name != 'pull_request')
|
||||
@@ -69,6 +73,9 @@ jobs:
|
||||
- uses: nixbuild/nix-quick-install-action@master
|
||||
|
||||
- name: Restore and cache Nix store
|
||||
# we want a fresh-state when we do releases/tags to avoid potential cache poisoning attacks impacting
|
||||
# releases and tags
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
with:
|
||||
# restore and save a cache using this key
|
||||
@@ -98,7 +105,7 @@ jobs:
|
||||
- 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/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit 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
|
||||
@@ -109,8 +116,8 @@ jobs:
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
extra-substituters = ${ATTIC_ENDPOINT}
|
||||
extra-trusted-public-keys = ${ATTIC_PUBLIC_KEY}
|
||||
EOF
|
||||
|
||||
- name: Prepare build environment
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
name: Upload Release Assets
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to release'
|
||||
required: true
|
||||
type: string
|
||||
action_id:
|
||||
description: 'Action ID of the CI run'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
GH_EVENT_NAME: ${{ github.event_name }}
|
||||
GH_EVENT_INPUTS_ACTION_ID: ${{ github.event.inputs.action_id }}
|
||||
GH_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }}
|
||||
GH_REPOSITORY: ${{ github.repository }}
|
||||
GH_SHA: ${{ github.sha }}
|
||||
GH_TAG: ${{ github.event.release.tag_name }}
|
||||
|
||||
steps:
|
||||
- name: get latest ci id
|
||||
id: get_ci_id
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if [ "${GH_EVENT_NAME}" == "workflow_dispatch" ]; then
|
||||
id="${GH_EVENT_INPUTS_ACTION_ID}"
|
||||
tag="${GH_EVENT_INPUTS_TAG}"
|
||||
else
|
||||
# get all runs of the ci workflow
|
||||
json=$(gh api "repos/${GH_REPOSITORY}/actions/workflows/ci.yml/runs")
|
||||
|
||||
# find first run that is github sha and status is completed
|
||||
id=$(echo "$json" | jq ".workflow_runs[] | select(.head_sha == \"${GH_SHA}\" and .status == \"completed\") | .id" | head -n 1)
|
||||
|
||||
if [ ! "$id" ]; then
|
||||
echo "No completed runs found"
|
||||
echo "ci_id=0" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tag="${GH_TAG}"
|
||||
fi
|
||||
|
||||
echo "ci_id=$id" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: get latest ci artifacts
|
||||
if: steps.get_ci_id.outputs.ci_id != 0
|
||||
uses: actions/download-artifact@v4
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
merge-multiple: true
|
||||
run-id: ${{ steps.get_ci_id.outputs.ci_id }}
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- run: |
|
||||
ls
|
||||
|
||||
- name: upload release assets
|
||||
if: steps.get_ci_id.outputs.ci_id != 0
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ steps.get_ci_id.outputs.tag }}
|
||||
run: |
|
||||
for file in $(find . -type f); do
|
||||
case "$file" in
|
||||
*json*) echo "Skipping $file...";;
|
||||
*) echo "Uploading $file..."; gh release upload $TAG "$file" --clobber --repo="${GH_REPOSITORY}" || echo "Something went wrong, skipping.";;
|
||||
esac
|
||||
done
|
||||
|
||||
- name: upload release assets to website
|
||||
if: steps.get_ci_id.outputs.ci_id != 0
|
||||
env:
|
||||
TAG: ${{ steps.get_ci_id.outputs.tag }}
|
||||
run: |
|
||||
mkdir -p -v ~/.ssh
|
||||
|
||||
echo "${{ secrets.WEB_UPLOAD_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
|
||||
echo "${{ secrets.WEB_UPLOAD_SSH_PRIVATE_KEY }}" >> ~/.ssh/id_ed25519
|
||||
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
cat >>~/.ssh/config <<END
|
||||
Host website
|
||||
HostName ${{ secrets.WEB_UPLOAD_SSH_HOSTNAME }}
|
||||
User ${{ secrets.WEB_UPLOAD_SSH_USERNAME }}
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
StrictHostKeyChecking yes
|
||||
AddKeysToAgent no
|
||||
ForwardX11 no
|
||||
BatchMode yes
|
||||
END
|
||||
|
||||
echo "Creating tag directory on web server"
|
||||
ssh -q website "rm -rf /var/www/girlboss.ceo/~strawberry/conduwuit/releases/$TAG/"
|
||||
ssh -q website "mkdir -v /var/www/girlboss.ceo/~strawberry/conduwuit/releases/$TAG/"
|
||||
|
||||
for file in $(find . -type f); do
|
||||
case "$file" in
|
||||
*json*) echo "Skipping $file...";;
|
||||
*) echo "Uploading $file to website"; scp $file website:/var/www/girlboss.ceo/~strawberry/conduwuit/releases/$TAG/$file;;
|
||||
esac
|
||||
done
|
||||
+1
-1
@@ -30,7 +30,7 @@ modules.xml
|
||||
.nfs*
|
||||
|
||||
# Rust
|
||||
/target/
|
||||
/target
|
||||
|
||||
### vscode ###
|
||||
.vscode/*
|
||||
|
||||
+2
-6
@@ -12,8 +12,8 @@ variables:
|
||||
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=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
experimental-features = nix-command flakes
|
||||
extra-experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
@@ -45,10 +45,6 @@ before_script:
|
||||
- if command -v nix > /dev/null && [ -n "$ATTIC_ENDPOINT" ]; then echo "extra-substituters = $ATTIC_ENDPOINT" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null && [ -n "$ATTIC_PUBLIC_KEY" ]; then echo "extra-trusted-public-keys = $ATTIC_PUBLIC_KEY" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add Lix binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://cache.lix.systems" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add crane binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
Generated
+820
-457
File diff suppressed because it is too large
Load Diff
+107
-63
@@ -7,23 +7,24 @@ default-members = ["src/*"]
|
||||
|
||||
[workspace.package]
|
||||
authors = [
|
||||
"strawberry <strawberry@puppygock.gay>",
|
||||
"timokoesters <timo@koesters.xyz>",
|
||||
"June Clementine Strawberry <june@girlboss.ceo>",
|
||||
"strawberry <strawberry@puppygock.gay>", # woof
|
||||
"Jason Volk <jason@zemos.net>",
|
||||
]
|
||||
categories = ["network-programming"]
|
||||
description = "a very cool fork of Conduit, a Matrix homeserver written in Rust"
|
||||
description = "a very cool Matrix chat homeserver written in Rust"
|
||||
edition = "2021"
|
||||
homepage = "https://conduwuit.puppyirl.gay/"
|
||||
keywords = ["chat", "matrix", "server", "uwu"]
|
||||
keywords = ["chat", "matrix", "networking", "server", "uwu"]
|
||||
license = "Apache-2.0"
|
||||
# See also `rust-toolchain.toml`
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/girlbossceo/conduwuit"
|
||||
rust-version = "1.83.0"
|
||||
rust-version = "1.84.0"
|
||||
version = "0.5.0"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "conduit"
|
||||
name = "conduwuit"
|
||||
|
||||
[workspace.dependencies.arrayvec]
|
||||
version = "0.7.4"
|
||||
@@ -35,6 +36,7 @@ features = [
|
||||
"const_generics",
|
||||
"const_new",
|
||||
"serde",
|
||||
"union",
|
||||
"write",
|
||||
]
|
||||
|
||||
@@ -45,7 +47,7 @@ version = "0.5.7"
|
||||
version = "0.2.9"
|
||||
|
||||
[workspace.dependencies.cargo_toml]
|
||||
version = "0.20"
|
||||
version = "0.21"
|
||||
default-features = false
|
||||
features = ["features"]
|
||||
|
||||
@@ -57,10 +59,6 @@ features = ["parse"]
|
||||
[workspace.dependencies.sanitize-filename]
|
||||
version = "0.6.0"
|
||||
|
||||
[workspace.dependencies.jsonwebtoken]
|
||||
version = "9.3.0"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.base64]
|
||||
version = "0.22.1"
|
||||
default-features = false
|
||||
@@ -79,13 +77,13 @@ version = "0.8.5"
|
||||
|
||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||
[workspace.dependencies.bytes]
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
|
||||
[workspace.dependencies.http-body-util]
|
||||
version = "0.1.2"
|
||||
|
||||
[workspace.dependencies.http]
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
|
||||
[workspace.dependencies.regex]
|
||||
version = "1.11.1"
|
||||
@@ -129,16 +127,17 @@ version = "0.6.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"add-extension",
|
||||
"catch-panic",
|
||||
"cors",
|
||||
"sensitive-headers",
|
||||
"set-header",
|
||||
"timeout",
|
||||
"trace",
|
||||
"util",
|
||||
"catch-panic",
|
||||
]
|
||||
|
||||
[workspace.dependencies.rustls]
|
||||
version = "0.23.16"
|
||||
version = "0.23.19"
|
||||
default-features = false
|
||||
features = ["aws_lc_rs"]
|
||||
|
||||
@@ -153,7 +152,7 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
default-features = false
|
||||
features = ["rc"]
|
||||
|
||||
@@ -180,7 +179,7 @@ version = "0.5.3"
|
||||
features = ["alloc", "rand"]
|
||||
default-features = false
|
||||
|
||||
# Used to generate thumbnails for images
|
||||
# Used to generate thumbnails for images & blurhashes
|
||||
[workspace.dependencies.image]
|
||||
version = "0.25.5"
|
||||
default-features = false
|
||||
@@ -191,6 +190,14 @@ features = [
|
||||
"webp",
|
||||
]
|
||||
|
||||
[workspace.dependencies.blurhash]
|
||||
version = "0.2.3"
|
||||
default-features = false
|
||||
features = [
|
||||
"fast-linear-to-srgb",
|
||||
"image",
|
||||
]
|
||||
|
||||
# logging
|
||||
[workspace.dependencies.log]
|
||||
version = "0.4.22"
|
||||
@@ -199,7 +206,7 @@ default-features = false
|
||||
version = "0.1.41"
|
||||
default-features = false
|
||||
[workspace.dependencies.tracing-subscriber]
|
||||
version = "0.3.18"
|
||||
version = "=0.3.18"
|
||||
default-features = false
|
||||
features = ["env-filter", "std", "tracing", "tracing-log", "ansi", "fmt"]
|
||||
[workspace.dependencies.tracing-core]
|
||||
@@ -211,9 +218,9 @@ default-features = false
|
||||
version = "2.0.1"
|
||||
default-features = false
|
||||
|
||||
# used for conduit's CLI and admin room command parsing
|
||||
# used for conduwuit's CLI and admin room command parsing
|
||||
[workspace.dependencies.clap]
|
||||
version = "4.5.21"
|
||||
version = "4.5.23"
|
||||
default-features = false
|
||||
features = [
|
||||
"derive",
|
||||
@@ -231,7 +238,7 @@ default-features = false
|
||||
features = ["std", "async-await"]
|
||||
|
||||
[workspace.dependencies.tokio]
|
||||
version = "1.41.1"
|
||||
version = "1.42.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"fs",
|
||||
@@ -242,13 +249,14 @@ features = [
|
||||
"time",
|
||||
"rt-multi-thread",
|
||||
"io-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[workspace.dependencies.tokio-metrics]
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
|
||||
[workspace.dependencies.libloading]
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
|
||||
# Validating urls in config, was already a transitive dependency
|
||||
[workspace.dependencies.url]
|
||||
@@ -294,12 +302,12 @@ default-features = false
|
||||
features = ["env", "toml"]
|
||||
|
||||
[workspace.dependencies.hickory-resolver]
|
||||
version = "0.24.1"
|
||||
version = "0.24.2"
|
||||
default-features = false
|
||||
|
||||
# Used for conduit::Error type
|
||||
# Used for conduwuit::Error type
|
||||
[workspace.dependencies.thiserror]
|
||||
version = "2.0.3"
|
||||
version = "2.0.7"
|
||||
default-features = false
|
||||
|
||||
# Used when hashing the state
|
||||
@@ -334,7 +342,7 @@ version = "0.1.2"
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://github.com/girlbossceo/ruwuma"
|
||||
#branch = "conduwuit-changes"
|
||||
rev = "1a550585bf025cce48ef8b734339245092bc986e"
|
||||
rev = "f5667c6292adb43fbe4725d31d6b5127a0cf60ce"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
@@ -350,7 +358,6 @@ features = [
|
||||
"compat-upload-signatures",
|
||||
"identifiers-validation",
|
||||
"unstable-unspecified",
|
||||
"unstable-msc2409",
|
||||
"unstable-msc2448",
|
||||
"unstable-msc2666",
|
||||
"unstable-msc2867",
|
||||
@@ -366,6 +373,7 @@ features = [
|
||||
"unstable-msc4121",
|
||||
"unstable-msc4125",
|
||||
"unstable-msc4186",
|
||||
"unstable-msc4203", # sending to-device events to appservices
|
||||
"unstable-msc4210", # remove legacy mentions
|
||||
"unstable-extensible-events",
|
||||
]
|
||||
@@ -410,7 +418,7 @@ features = ["rt-tokio"]
|
||||
|
||||
# optional sentry metrics for crash/panic reporting
|
||||
[workspace.dependencies.sentry]
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"backtrace",
|
||||
@@ -426,24 +434,30 @@ features = [
|
||||
]
|
||||
|
||||
[workspace.dependencies.sentry-tracing]
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
[workspace.dependencies.sentry-tower]
|
||||
version = "0.34.0"
|
||||
version = "0.35.0"
|
||||
|
||||
# jemalloc usage
|
||||
[workspace.dependencies.tikv-jemalloc-sys]
|
||||
git = "https://github.com/girlbossceo/jemallocator"
|
||||
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
default-features = false
|
||||
features = ["unprefixed_malloc_on_supported_platforms"]
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
]
|
||||
[workspace.dependencies.tikv-jemallocator]
|
||||
git = "https://github.com/girlbossceo/jemallocator"
|
||||
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
default-features = false
|
||||
features = ["unprefixed_malloc_on_supported_platforms"]
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
]
|
||||
[workspace.dependencies.tikv-jemalloc-ctl]
|
||||
git = "https://github.com/girlbossceo/jemallocator"
|
||||
rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
default-features = false
|
||||
features = ["use_std"]
|
||||
|
||||
@@ -480,7 +494,7 @@ default-features = false
|
||||
version = "0.1"
|
||||
|
||||
[workspace.dependencies.syn]
|
||||
version = "2.0.87"
|
||||
version = "2.0.90"
|
||||
default-features = false
|
||||
features = ["full", "extra-traits"]
|
||||
|
||||
@@ -493,6 +507,23 @@ version = "1.0.89"
|
||||
[workspace.dependencies.bytesize]
|
||||
version = "1.3.0"
|
||||
|
||||
[workspace.dependencies.core_affinity]
|
||||
version = "0.8.1"
|
||||
|
||||
[workspace.dependencies.libc]
|
||||
version = "0.2"
|
||||
|
||||
[workspace.dependencies.num-traits]
|
||||
version = "0.2"
|
||||
|
||||
[workspace.dependencies.minicbor]
|
||||
version = "0.25.1"
|
||||
features = ["std"]
|
||||
|
||||
[workspace.dependencies.minicbor-serde]
|
||||
version = "0.3.2"
|
||||
features = ["std"]
|
||||
|
||||
#
|
||||
# Patches
|
||||
#
|
||||
@@ -502,16 +533,16 @@ version = "1.3.0"
|
||||
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
|
||||
[patch.crates-io.tracing-subscriber]
|
||||
git = "https://github.com/girlbossceo/tracing"
|
||||
rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
|
||||
rev = "05825066a6d0e9ad6b80dcf29457eb179ff4768c"
|
||||
[patch.crates-io.tracing]
|
||||
git = "https://github.com/girlbossceo/tracing"
|
||||
rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
|
||||
rev = "05825066a6d0e9ad6b80dcf29457eb179ff4768c"
|
||||
[patch.crates-io.tracing-core]
|
||||
git = "https://github.com/girlbossceo/tracing"
|
||||
rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
|
||||
rev = "05825066a6d0e9ad6b80dcf29457eb179ff4768c"
|
||||
[patch.crates-io.tracing-log]
|
||||
git = "https://github.com/girlbossceo/tracing"
|
||||
rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
|
||||
rev = "05825066a6d0e9ad6b80dcf29457eb179ff4768c"
|
||||
|
||||
# adds a tab completion callback: https://github.com/girlbossceo/rustyline-async/commit/de26100b0db03e419a3d8e1dd26895d170d1fe50
|
||||
# adds event for CTRL+\: https://github.com/girlbossceo/rustyline-async/commit/67d8c49aeac03a5ef4e818f663eaa94dd7bf339b
|
||||
@@ -519,42 +550,54 @@ rev = "ccc4fbd8238c2d5ba354e61ec17ac610af11401d"
|
||||
git = "https://github.com/girlbossceo/rustyline-async"
|
||||
rev = "deaeb0694e2083f53d363b648da06e10fc13900c"
|
||||
|
||||
# adds LIFO queue scheduling; this should be updated with PR progress.
|
||||
[patch.crates-io.event-listener]
|
||||
git = "https://github.com/girlbossceo/event-listener"
|
||||
rev = "fe4aebeeaae435af60087ddd56b573a2e0be671d"
|
||||
[patch.crates-io.async-channel]
|
||||
git = "https://github.com/girlbossceo/async-channel"
|
||||
rev = "92e5e74063bf2a3b10414bcc8a0d68b235644280"
|
||||
|
||||
[patch.crates-io.core_affinity]
|
||||
git = "https://github.com/girlbossceo/core_affinity_rs"
|
||||
rev = "9c8e51510c35077df888ee72a36b4b05637147da"
|
||||
|
||||
#
|
||||
# Our crates
|
||||
#
|
||||
|
||||
[workspace.dependencies.conduit-router]
|
||||
package = "conduit_router"
|
||||
[workspace.dependencies.conduwuit-router]
|
||||
package = "conduwuit_router"
|
||||
path = "src/router"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.conduit-admin]
|
||||
package = "conduit_admin"
|
||||
[workspace.dependencies.conduwuit-admin]
|
||||
package = "conduwuit_admin"
|
||||
path = "src/admin"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.conduit-api]
|
||||
package = "conduit_api"
|
||||
[workspace.dependencies.conduwuit-api]
|
||||
package = "conduwuit_api"
|
||||
path = "src/api"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.conduit-service]
|
||||
package = "conduit_service"
|
||||
[workspace.dependencies.conduwuit-service]
|
||||
package = "conduwuit_service"
|
||||
path = "src/service"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.conduit-database]
|
||||
package = "conduit_database"
|
||||
[workspace.dependencies.conduwuit-database]
|
||||
package = "conduwuit_database"
|
||||
path = "src/database"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.conduit-core]
|
||||
package = "conduit_core"
|
||||
[workspace.dependencies.conduwuit-core]
|
||||
package = "conduwuit_core"
|
||||
path = "src/core"
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.conduit-macros]
|
||||
package = "conduit_macros"
|
||||
[workspace.dependencies.conduwuit-macros]
|
||||
package = "conduwuit_macros"
|
||||
path = "src/macros"
|
||||
default-features = false
|
||||
|
||||
@@ -613,7 +656,7 @@ codegen-units = 32
|
||||
# '-Clink-arg=-Wl,--no-gc-sections',
|
||||
#]
|
||||
|
||||
[profile.release-max-perf.package.conduit_macros]
|
||||
[profile.release-max-perf.package.conduwuit_macros]
|
||||
inherits = "release-max-perf.build-override"
|
||||
#rustflags = [
|
||||
# '-Crelocation-model=pic',
|
||||
@@ -647,7 +690,7 @@ panic = "unwind"
|
||||
debug-assertions = true
|
||||
incremental = true
|
||||
#rustflags = [
|
||||
# '--cfg', 'conduit_mods',
|
||||
# '--cfg', 'conduwuit_mods',
|
||||
# '-Ztime-passes',
|
||||
# '-Zmir-opt-level=0',
|
||||
# '-Zvalidate-mir=false',
|
||||
@@ -664,11 +707,11 @@ incremental = true
|
||||
# '-Clink-arg=-Wl,-z,lazy',
|
||||
#]
|
||||
|
||||
[profile.dev.package.conduit_core]
|
||||
[profile.dev.package.conduwuit_core]
|
||||
inherits = "dev"
|
||||
incremental = false
|
||||
#rustflags = [
|
||||
# '--cfg', 'conduit_mods',
|
||||
# '--cfg', 'conduwuit_mods',
|
||||
# '-Ztime-passes',
|
||||
# '-Zmir-opt-level=0',
|
||||
# '-Ztls-model=initial-exec',
|
||||
@@ -685,10 +728,10 @@ incremental = false
|
||||
# '-Clink-arg=-Wl,-z,nodelete',
|
||||
#]
|
||||
|
||||
[profile.dev.package.conduit]
|
||||
[profile.dev.package.conduwuit]
|
||||
inherits = "dev"
|
||||
#rustflags = [
|
||||
# '--cfg', 'conduit_mods',
|
||||
# '--cfg', 'conduwuit_mods',
|
||||
# '-Ztime-passes',
|
||||
# '-Zmir-opt-level=0',
|
||||
# '-Zvalidate-mir=false',
|
||||
@@ -710,7 +753,7 @@ incremental = false
|
||||
codegen-units = 1
|
||||
opt-level = 'z'
|
||||
#rustflags = [
|
||||
# '--cfg', 'conduit_mods',
|
||||
# '--cfg', 'conduwuit_mods',
|
||||
# '-Ztls-model=initial-exec',
|
||||
# '-Cprefer-dynamic=true',
|
||||
# '-Zstaticlib-prefer-dynamic=true',
|
||||
@@ -731,7 +774,7 @@ incremental = false
|
||||
codegen-units = 1
|
||||
opt-level = 'z'
|
||||
#rustflags = [
|
||||
# '--cfg', 'conduit_mods',
|
||||
# '--cfg', 'conduwuit_mods',
|
||||
# '-Ztls-model=global-dynamic',
|
||||
# '-Cprefer-dynamic=true',
|
||||
# '-Zstaticlib-prefer-dynamic=true',
|
||||
@@ -850,6 +893,7 @@ enum_glob_use = { level = "allow", priority = 1 }
|
||||
if_not_else = { level = "allow", priority = 1 }
|
||||
if_then_some_else_none = { level = "allow", priority = 1 }
|
||||
inline_always = { level = "allow", priority = 1 }
|
||||
match_bool = { level = "allow", priority = 1 }
|
||||
missing_docs_in_private_items = { level = "allow", priority = 1 }
|
||||
missing_errors_doc = { level = "allow", priority = 1 }
|
||||
missing_panics_doc = { level = "allow", priority = 1 }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<!-- ANCHOR: catchphrase -->
|
||||
|
||||
### a very cool, featureful fork of [Conduit](https://conduit.rs/)
|
||||
### a very cool [Matrix](https://matrix.org/) chat homeserver written in Rust
|
||||
|
||||
<!-- ANCHOR_END: catchphrase -->
|
||||
|
||||
@@ -15,16 +15,15 @@ information and how to deploy/setup conduwuit.
|
||||
|
||||
#### 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
|
||||
other Matrix servers. You can even use bridges (also called Matrix Appservices)
|
||||
to communicate with users outside of Matrix, like a community on Discord.
|
||||
[Matrix](https://matrix.org) is an open, federated, and extensible network for
|
||||
decentralised communication. Users from any Matrix homeserver can chat with users from all
|
||||
other homeservers over federation. Matrix is designed to be extensible and built on top of.
|
||||
You can even use bridges such as Matrix Appservices to communicate with users outside of Matrix, like a community on Discord.
|
||||
|
||||
#### What is the goal?
|
||||
|
||||
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.
|
||||
A high-performance, efficient, low-cost, and featureful Matrix homeserver that's
|
||||
easy to set up and just works with minimal configuration needed.
|
||||
|
||||
#### Can I try it out?
|
||||
|
||||
@@ -37,17 +36,22 @@ homeserver". This means there are rules, so please read the rules:
|
||||
[https://transfem.dev/homeserver_rules.txt](https://transfem.dev/homeserver_rules.txt)
|
||||
|
||||
transfem.dev is also listed at
|
||||
[servers.joinmatrix.org](https://servers.joinmatrix.org/)
|
||||
[servers.joinmatrix.org](https://servers.joinmatrix.org/), which is a list of
|
||||
popular public Matrix homeservers, including some others that run conduwuit.
|
||||
|
||||
#### What is the current status?
|
||||
|
||||
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 technically a hard fork of [Conduit](https://conduit.rs/), which is in beta.
|
||||
The beta status initially was inherited from Conduit, however the huge amount of
|
||||
codebase divergance, changes, fixes, and improvements have effectively made this
|
||||
beta status not entirely applicable to us anymore.
|
||||
|
||||
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.
|
||||
conduwuit is very stable based on our rapidly growing userbase, has lots of features that users
|
||||
expect, and very usable as a daily driver for small, medium, and upper-end medium sized homeservers.
|
||||
|
||||
A lot of critical stability and performance issues have been fixed, and a lot of
|
||||
necessary groundwork has finished; making this project way better than it was
|
||||
back in the start at ~early 2024.
|
||||
|
||||
#### How is conduwuit funded? Is conduwuit sustainable?
|
||||
|
||||
@@ -66,16 +70,43 @@ is no harm or additional steps required for using conduwuit. See the
|
||||
[Migrating from Conduit](https://conduwuit.puppyirl.gay/deploying/generic.html#migrating-from-conduit) section
|
||||
on the generic deploying guide.
|
||||
|
||||
Note that as of conduwuit version 0.5.0, backwards compatibility with Conduit is
|
||||
no longer supported. We only support migrating *from* Conduit, not back to
|
||||
Conduit like before. If you are truly finding yourself wanting to migrate back
|
||||
to Conduit, we would appreciate all your feedback and if we can assist with
|
||||
any issues or concerns.
|
||||
|
||||
#### Can I migrate from Synapse or Dendrite?
|
||||
|
||||
Currently there is no known way to seamlessly migrate all user data from the old
|
||||
homeserver to conduwuit. However it is perfectly acceptable to replace the old
|
||||
homeserver software with conduwuit using the same server name and there will not
|
||||
be any issues with federation.
|
||||
|
||||
There is an interest in developing a built-in seamless user data migration
|
||||
method into conduwuit, however there is no concrete ETA or timeline for this.
|
||||
|
||||
|
||||
<!-- ANCHOR_END: body -->
|
||||
|
||||
<!-- ANCHOR: footer -->
|
||||
|
||||
#### Contact
|
||||
|
||||
If you run into any question, feel free to
|
||||
[`#conduwuit:puppygock.gay`](https://matrix.to/#/#conduwuit:puppygock.gay)
|
||||
is the official project Matrix room. You can get support here, ask questions or
|
||||
concerns, get assistance setting up conduwuit, etc.
|
||||
|
||||
- Ask us in `#conduwuit:puppygock.gay` on Matrix
|
||||
- [Open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new)
|
||||
This room should stay relevant and focused on conduwuit. An offtopic general
|
||||
chatter room can be found there as well.
|
||||
|
||||
Please keep the issue trackers focused on bug reports and enhancement requests.
|
||||
General support is extremely difficult to be offered over an issue tracker, and
|
||||
simple questions should be asked directly in an interactive platform like our
|
||||
Matrix room above as they can turn into a relevant discussion and/or may not be
|
||||
simple to answer. If you're not sure, just ask in the Matrix room.
|
||||
|
||||
If you have a bug or feature to request: [Open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new)
|
||||
|
||||
#### Donate
|
||||
|
||||
@@ -83,15 +114,17 @@ 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 (note they take a fee): <https://ko-fi.com/puppygock>
|
||||
- GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
|
||||
- Liberapay (preferred): <https://liberapay.com/girlbossceo>
|
||||
- GitHub Sponsors (preferred): <https://github.com/sponsors/girlbossceo>
|
||||
- Ko-fi: <https://ko-fi.com/puppygock>
|
||||
|
||||
I do not and will not accept cryptocurrency donations, including things related.
|
||||
|
||||
#### Logo
|
||||
|
||||
Original repo and Matrix room picture was from bran (<3). Current banner image
|
||||
and logo is directly from [this cohost
|
||||
post](https://cohost.org/RatBaby/post/1028290-finally-a-flag-for).
|
||||
post](https://web.archive.org/web/20241126004041/https://cohost.org/RatBaby/post/1028290-finally-a-flag-for).
|
||||
|
||||
#### Is it conduwuit or Conduwuit?
|
||||
|
||||
@@ -99,11 +132,15 @@ Both, but I prefer conduwuit.
|
||||
|
||||
#### Mirrors of conduwuit
|
||||
|
||||
If GitHub is unavailable in your country, or has poor connectivity, conduwuit's
|
||||
source code is mirrored onto the following additional platforms I maintain:
|
||||
|
||||
- GitHub: <https://github.com/girlbossceo/conduwuit>
|
||||
- GitLab: <https://gitlab.com/conduwuit/conduwuit>
|
||||
- git.girlcock.ceo: <https://git.girlcock.ceo/strawberry/conduwuit>
|
||||
- git.gay: <https://git.gay/june/conduwuit>
|
||||
- Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
|
||||
- mau.dev: <https://mau.dev/june/conduwuit>
|
||||
- Codeberg: <https://codeberg.org/arf/conduwuit>
|
||||
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
|
||||
|
||||
<!-- ANCHOR_END: footer -->
|
||||
|
||||
+16
-2
@@ -1,12 +1,26 @@
|
||||
[Unit]
|
||||
Description=conduwuit Matrix homeserver
|
||||
After=network.target
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
Documentation=https://conduwuit.puppyirl.gay/
|
||||
RequiresMountsFor=/var/lib/private/conduwuit
|
||||
|
||||
[Service]
|
||||
DynamicUser=yes
|
||||
Type=notify
|
||||
Type=notify-reload
|
||||
ReloadSignal=SIGUSR1
|
||||
|
||||
TTYPath=/dev/tty25
|
||||
DeviceAllow=char-tty
|
||||
StandardInput=tty-force
|
||||
StandardOutput=tty
|
||||
StandardError=journal+console
|
||||
TTYReset=yes
|
||||
# uncomment to allow buffer to be cleared every restart
|
||||
TTYVTDisallocate=no
|
||||
|
||||
TTYColumns=120
|
||||
TTYRows=40
|
||||
|
||||
AmbientCapabilities=
|
||||
CapabilityBoundingSet=
|
||||
|
||||
+2
-1
@@ -34,7 +34,8 @@ toplevel="$(git rev-parse --show-toplevel)"
|
||||
|
||||
pushd "$toplevel" > /dev/null
|
||||
|
||||
bin/nix-build-and-cache just .#linux-complement
|
||||
#bin/nix-build-and-cache just .#linux-complement
|
||||
bin/nix-build-and-cache just .#complement
|
||||
|
||||
docker load < result
|
||||
popd > /dev/null
|
||||
|
||||
+10
-6
@@ -3,13 +3,17 @@ cognitive-complexity-threshold = 94 # TODO reduce me ALARA
|
||||
excessive-nesting-threshold = 11 # TODO reduce me to 4 or 5
|
||||
future-size-threshold = 7745 # TODO reduce me ALARA
|
||||
stack-size-threshold = 196608 # reduce me ALARA
|
||||
too-many-lines-threshold = 700 # TODO reduce me to <= 100
|
||||
too-many-lines-threshold = 780 # TODO reduce me to <= 100
|
||||
type-complexity-threshold = 250 # reduce me to ~200
|
||||
|
||||
disallowed-macros = [
|
||||
{ path = "log::error", reason = "use conduit_core::error" },
|
||||
{ path = "log::warn", reason = "use conduit_core::warn" },
|
||||
{ path = "log::info", reason = "use conduit_core::info" },
|
||||
{ path = "log::debug", reason = "use conduit_core::debug" },
|
||||
{ path = "log::trace", reason = "use conduit_core::trace" },
|
||||
{ path = "log::error", reason = "use conduwuit_core::error" },
|
||||
{ path = "log::warn", reason = "use conduwuit_core::warn" },
|
||||
{ path = "log::info", reason = "use conduwuit_core::info" },
|
||||
{ path = "log::debug", reason = "use conduwuit_core::debug" },
|
||||
{ path = "log::trace", reason = "use conduwuit_core::trace" },
|
||||
]
|
||||
|
||||
disallowed-methods = [
|
||||
{ path = "tokio::spawn", reason = "use and pass conduuwit_core::server::Server::runtime() to spawn from" },
|
||||
]
|
||||
|
||||
+488
-264
File diff suppressed because it is too large
Load Diff
Vendored
+2
@@ -9,6 +9,8 @@ It is recommended to see the [generic deployment guide](../deploying/generic.md)
|
||||
for further information if needed as usage of the Debian package is generally
|
||||
related.
|
||||
|
||||
No `apt` repository is currently offered yet, it is in the works/development.
|
||||
|
||||
### Configuration
|
||||
|
||||
When installed, the example config is placed at `/etc/conduwuit/conduwuit.toml`
|
||||
|
||||
Vendored
+16
-2
@@ -1,13 +1,27 @@
|
||||
[Unit]
|
||||
Description=conduwuit Matrix homeserver
|
||||
Documentation=https://conduwuit.puppyirl.gay/
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
Documentation=https://conduwuit.puppyirl.gay/
|
||||
|
||||
[Service]
|
||||
DynamicUser=yes
|
||||
User=conduwuit
|
||||
Group=conduwuit
|
||||
Type=notify
|
||||
Type=notify-reload
|
||||
ReloadSignal=SIGUSR1
|
||||
|
||||
TTYPath=/dev/tty25
|
||||
DeviceAllow=char-tty
|
||||
StandardInput=tty-force
|
||||
StandardOutput=tty
|
||||
StandardError=journal+console
|
||||
TTYReset=yes
|
||||
# uncomment to allow buffer to be cleared every restart
|
||||
TTYVTDisallocate=no
|
||||
|
||||
TTYColumns=120
|
||||
TTYRows=40
|
||||
|
||||
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||
|
||||
|
||||
Vendored
+16
-4
@@ -10,21 +10,33 @@ CONDUWUIT_DATABASE_PATH_SYMLINK=/var/lib/matrix-conduit
|
||||
case $1 in
|
||||
purge)
|
||||
# Remove debconf changes from the db
|
||||
db_purge
|
||||
#db_purge
|
||||
|
||||
# Per https://www.debian.org/doc/debian-policy/ch-files.html#behavior
|
||||
# "configuration files must be preserved when the package is removed, and
|
||||
# only deleted when the package is purged."
|
||||
|
||||
#
|
||||
|
||||
if [ -d "$CONDUWUIT_CONFIG_PATH" ]; then
|
||||
rm -v -r "$CONDUWUIT_CONFIG_PATH"
|
||||
if test -L "$CONDUWUIT_CONFIG_PATH"; then
|
||||
echo "Deleting conduwuit configuration files"
|
||||
rm -v -r "$CONDUWUIT_CONFIG_PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d "$CONDUWUIT_DATABASE_PATH" ]; then
|
||||
rm -v -r "$CONDUWUIT_DATABASE_PATH"
|
||||
if test -L "$CONDUWUIT_DATABASE_PATH"; then
|
||||
echo "Deleting conduwuit database directory"
|
||||
rm -r "$CONDUWUIT_DATABASE_PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d "$CONDUWUIT_DATABASE_PATH_SYMLINK" ]; then
|
||||
rm -v -r "$CONDUWUIT_DATABASE_PATH_SYMLINK"
|
||||
if test -L "$CONDUWUIT_DATABASE_SYMLINK"; then
|
||||
echo "Removing matrix-conduit symlink"
|
||||
rm -r "$CONDUWUIT_DATABASE_PATH_SYMLINK"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
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 = "4bce1bb97d8be6f0d47245c99d465ca9cef33aad"
|
||||
rev = "7b0e1bbe395a41ba8a11347a4921da590e3ad0d9"
|
||||
#branch = "master"
|
||||
default-features = false
|
||||
|
||||
|
||||
Vendored
+59
-58
@@ -1,61 +1,62 @@
|
||||
pub use rust_rocksdb::*;
|
||||
|
||||
#[cfg_attr(not(conduit_mods), link(name = "rocksdb"))]
|
||||
#[cfg_attr(conduit_mods, link(name = "rocksdb", kind = "static"))]
|
||||
extern "C" {
|
||||
pub fn rocksdb_list_column_families();
|
||||
pub fn rocksdb_logger_create_stderr_logger();
|
||||
pub fn rocksdb_options_set_info_log();
|
||||
pub fn rocksdb_get_options_from_string();
|
||||
pub fn rocksdb_writebatch_create();
|
||||
pub fn rocksdb_writebatch_destroy();
|
||||
pub fn rocksdb_writebatch_put_cf();
|
||||
pub fn rocksdb_writebatch_delete_cf();
|
||||
pub fn rocksdb_iter_value();
|
||||
pub fn rocksdb_iter_seek_to_last();
|
||||
pub fn rocksdb_iter_seek_for_prev();
|
||||
pub fn rocksdb_iter_seek_to_first();
|
||||
pub fn rocksdb_iter_next();
|
||||
pub fn rocksdb_iter_prev();
|
||||
pub fn rocksdb_iter_seek();
|
||||
pub fn rocksdb_iter_valid();
|
||||
pub fn rocksdb_iter_get_error();
|
||||
pub fn rocksdb_iter_key();
|
||||
pub fn rocksdb_iter_destroy();
|
||||
pub fn rocksdb_livefiles();
|
||||
pub fn rocksdb_livefiles_count();
|
||||
pub fn rocksdb_livefiles_destroy();
|
||||
pub fn rocksdb_livefiles_column_family_name();
|
||||
pub fn rocksdb_livefiles_name();
|
||||
pub fn rocksdb_livefiles_size();
|
||||
pub fn rocksdb_livefiles_level();
|
||||
pub fn rocksdb_livefiles_smallestkey();
|
||||
pub fn rocksdb_livefiles_largestkey();
|
||||
pub fn rocksdb_livefiles_entries();
|
||||
pub fn rocksdb_livefiles_deletions();
|
||||
pub fn rocksdb_put_cf();
|
||||
pub fn rocksdb_delete_cf();
|
||||
pub fn rocksdb_get_pinned_cf();
|
||||
pub fn rocksdb_create_column_family();
|
||||
pub fn rocksdb_get_latest_sequence_number();
|
||||
pub fn rocksdb_batched_multi_get_cf();
|
||||
pub fn rocksdb_cancel_all_background_work();
|
||||
pub fn rocksdb_repair_db();
|
||||
pub fn rocksdb_list_column_families_destroy();
|
||||
pub fn rocksdb_flush();
|
||||
pub fn rocksdb_flush_wal();
|
||||
pub fn rocksdb_open_column_families();
|
||||
pub fn rocksdb_open_for_read_only_column_families();
|
||||
pub fn rocksdb_open_as_secondary_column_families();
|
||||
pub fn rocksdb_open_column_families_with_ttl();
|
||||
pub fn rocksdb_open();
|
||||
pub fn rocksdb_open_for_read_only();
|
||||
pub fn rocksdb_open_with_ttl();
|
||||
pub fn rocksdb_open_as_secondary();
|
||||
pub fn rocksdb_write();
|
||||
pub fn rocksdb_create_iterator_cf();
|
||||
pub fn rocksdb_backup_engine_create_new_backup_flush();
|
||||
pub fn rocksdb_backup_engine_options_create();
|
||||
pub fn rocksdb_write_buffer_manager_destroy();
|
||||
pub fn rocksdb_options_set_ttl();
|
||||
#[cfg_attr(not(conduwuit_mods), link(name = "rocksdb"))]
|
||||
#[cfg_attr(conduwuit_mods, link(name = "rocksdb", kind = "static"))]
|
||||
unsafe extern "C" {
|
||||
pub unsafe fn rocksdb_list_column_families();
|
||||
pub unsafe fn rocksdb_logger_create_stderr_logger();
|
||||
pub unsafe fn rocksdb_logger_create_callback_logger();
|
||||
pub unsafe fn rocksdb_options_set_info_log();
|
||||
pub unsafe fn rocksdb_get_options_from_string();
|
||||
pub unsafe fn rocksdb_writebatch_create();
|
||||
pub unsafe fn rocksdb_writebatch_destroy();
|
||||
pub unsafe fn rocksdb_writebatch_put_cf();
|
||||
pub unsafe fn rocksdb_writebatch_delete_cf();
|
||||
pub unsafe fn rocksdb_iter_value();
|
||||
pub unsafe fn rocksdb_iter_seek_to_last();
|
||||
pub unsafe fn rocksdb_iter_seek_for_prev();
|
||||
pub unsafe fn rocksdb_iter_seek_to_first();
|
||||
pub unsafe fn rocksdb_iter_next();
|
||||
pub unsafe fn rocksdb_iter_prev();
|
||||
pub unsafe fn rocksdb_iter_seek();
|
||||
pub unsafe fn rocksdb_iter_valid();
|
||||
pub unsafe fn rocksdb_iter_get_error();
|
||||
pub unsafe fn rocksdb_iter_key();
|
||||
pub unsafe fn rocksdb_iter_destroy();
|
||||
pub unsafe fn rocksdb_livefiles();
|
||||
pub unsafe fn rocksdb_livefiles_count();
|
||||
pub unsafe fn rocksdb_livefiles_destroy();
|
||||
pub unsafe fn rocksdb_livefiles_column_family_name();
|
||||
pub unsafe fn rocksdb_livefiles_name();
|
||||
pub unsafe fn rocksdb_livefiles_size();
|
||||
pub unsafe fn rocksdb_livefiles_level();
|
||||
pub unsafe fn rocksdb_livefiles_smallestkey();
|
||||
pub unsafe fn rocksdb_livefiles_largestkey();
|
||||
pub unsafe fn rocksdb_livefiles_entries();
|
||||
pub unsafe fn rocksdb_livefiles_deletions();
|
||||
pub unsafe fn rocksdb_put_cf();
|
||||
pub unsafe fn rocksdb_delete_cf();
|
||||
pub unsafe fn rocksdb_get_pinned_cf();
|
||||
pub unsafe fn rocksdb_create_column_family();
|
||||
pub unsafe fn rocksdb_get_latest_sequence_number();
|
||||
pub unsafe fn rocksdb_batched_multi_get_cf();
|
||||
pub unsafe fn rocksdb_cancel_all_background_work();
|
||||
pub unsafe fn rocksdb_repair_db();
|
||||
pub unsafe fn rocksdb_list_column_families_destroy();
|
||||
pub unsafe fn rocksdb_flush();
|
||||
pub unsafe fn rocksdb_flush_wal();
|
||||
pub unsafe fn rocksdb_open_column_families();
|
||||
pub unsafe fn rocksdb_open_for_read_only_column_families();
|
||||
pub unsafe fn rocksdb_open_as_secondary_column_families();
|
||||
pub unsafe fn rocksdb_open_column_families_with_ttl();
|
||||
pub unsafe fn rocksdb_open();
|
||||
pub unsafe fn rocksdb_open_for_read_only();
|
||||
pub unsafe fn rocksdb_open_with_ttl();
|
||||
pub unsafe fn rocksdb_open_as_secondary();
|
||||
pub unsafe fn rocksdb_write();
|
||||
pub unsafe fn rocksdb_create_iterator_cf();
|
||||
pub unsafe fn rocksdb_backup_engine_create_new_backup_flush();
|
||||
pub unsafe fn rocksdb_backup_engine_options_create();
|
||||
pub unsafe fn rocksdb_write_buffer_manager_destroy();
|
||||
pub unsafe fn rocksdb_options_set_ttl();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ The syntax of this is a standard admin command without the prefix such as
|
||||
|
||||
An example output of a success is:
|
||||
```
|
||||
INFO conduit_service::admin::startup: Startup command #0 completed:
|
||||
INFO conduwuit_service::admin::startup: Startup command #0 completed:
|
||||
Created user with user_id: @june:girlboss.ceo and password: `<redacted>`
|
||||
```
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ services:
|
||||
CONDUWUIT_PORT: 6167 # should match the loadbalancer traefik label
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
|
||||
#CONDUWUIT_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
|
||||
@@ -33,6 +33,8 @@ services:
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
|
||||
#CONDUWUIT_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
|
||||
@@ -17,6 +17,8 @@ services:
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_REGISTRATION_TOKEN: 'YOUR_TOKEN' # A registration token is required when registration is allowed.
|
||||
#CONDUWUIT_YES_I_AM_VERY_VERY_SURE_I_WANT_AN_OPEN_REGISTRATION_SERVER_PRONE_TO_ABUSE: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
|
||||
@@ -24,6 +24,9 @@ OCI images for conduwuit are available in the registries listed below.
|
||||
[shield-latest]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/latest
|
||||
[shield-main]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/main
|
||||
|
||||
OCI image `.tar.gz` files are also hosted directly at when uploaded by CI with a
|
||||
commit hash/revision or a tagged release: <https://pup.systems/~strawberry/conduwuit/>
|
||||
|
||||
Use
|
||||
|
||||
```bash
|
||||
@@ -41,7 +44,7 @@ docker run -d -p 8448:6167 \
|
||||
-v db:/var/lib/conduwuit/ \
|
||||
-e CONDUWUIT_SERVER_NAME="your.server.name" \
|
||||
-e CONDUWUIT_ALLOW_REGISTRATION=false \
|
||||
--name conduit $LINK
|
||||
--name conduwuit $LINK
|
||||
```
|
||||
|
||||
or you can use [docker compose](#docker-compose).
|
||||
|
||||
+58
-16
@@ -1,6 +1,6 @@
|
||||
# Generic deployment documentation
|
||||
|
||||
> ## Getting help
|
||||
> ### Getting help
|
||||
>
|
||||
> If you run into any problems while setting up conduwuit, ask us in
|
||||
> `#conduwuit:puppygock.gay` or [open an issue on
|
||||
@@ -8,29 +8,50 @@
|
||||
|
||||
## Installing conduwuit
|
||||
|
||||
You may simply download the binary that fits your machine. Run `uname -m` to see
|
||||
what you need.
|
||||
### Static prebuilt binary
|
||||
|
||||
You may simply download the binary that fits your machine architecture (x86_64
|
||||
or aarch64). Run `uname -m` to see what you need.
|
||||
|
||||
Prebuilt fully static musl binaries can be downloaded from the latest tagged
|
||||
release [here](https://github.com/girlbossceo/conduwuit/releases/latest) or
|
||||
`main` CI branch workflow artifact output. These also include Debian/Ubuntu packages.
|
||||
`main` CI branch workflow artifact output. These also include Debian/Ubuntu
|
||||
packages.
|
||||
|
||||
Binaries are also available on my website directly at: <https://pup.systems/~strawberry/conduwuit/>
|
||||
|
||||
These can be curl'd directly from. `ci-bins` are CI workflow binaries by commit
|
||||
hash/revision, and `releases` are tagged releases. Sort by descending last
|
||||
modified for the latest.
|
||||
|
||||
These binaries have jemalloc and io_uring statically linked and included with
|
||||
them, so no additional dynamic dependencies need to be installed.
|
||||
|
||||
For the **best** performance; if using an `x86_64` CPU made in the last ~15 years,
|
||||
we recommend using the `-haswell-` optimised binaries. This sets
|
||||
`-march=haswell` which is the most compatible and highest performance with
|
||||
optimised binaries. The database backend, RocksDB, most benefits from this as it
|
||||
will then use hardware accelerated CRC32 hashing/checksumming which is critical
|
||||
for performance.
|
||||
|
||||
### Compiling
|
||||
|
||||
Alternatively, you may compile the binary yourself. We recommend using
|
||||
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.
|
||||
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.
|
||||
`result/bin/conduwuit`. 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).
|
||||
If wanting to build using standard Rust toolchains, make sure you install:
|
||||
- `liburing-dev` on the compiling machine, and `liburing` on the target host
|
||||
- LLVM and libclang for RocksDB
|
||||
|
||||
You can build conduwuit using `cargo build --release --all-features`
|
||||
|
||||
## Migrating from Conduit
|
||||
|
||||
@@ -38,6 +59,12 @@ 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.
|
||||
|
||||
**WARNING**: As of conduwuit 0.5.0, all database and backwards compatibility
|
||||
with Conduit is no longer supported. We only support migrating *from* Conduit,
|
||||
not back to Conduit like before. If you are truly finding yourself wanting to
|
||||
migrate back to Conduit, we would appreciate all your feedback and if we can
|
||||
assist with any issues or concerns.
|
||||
|
||||
**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.
|
||||
@@ -89,11 +116,25 @@ on the network level, consider something like NextDNS or Pi-Hole.
|
||||
|
||||
## Setting up a systemd service
|
||||
|
||||
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.
|
||||
Two example systemd units for conduwuit can be found
|
||||
[on the configuration page](../configuration/examples.md#debian-systemd-unit-file).
|
||||
You may need to change the `ExecStart=` path to where you placed the conduwuit
|
||||
binary if it is not `/usr/bin/conduwuit`.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
If you are using a different `database_path` other than the systemd unit
|
||||
configured default `/var/lib/conduwuit`, you need to add your path to the
|
||||
systemd unit's `ReadWritePaths=`. This can be done by either directly editing
|
||||
`conduwuit.service` and reloading systemd, or running `systemctl edit conduwuit.service`
|
||||
and entering the following:
|
||||
|
||||
```
|
||||
[Service]
|
||||
ReadWritePaths=/path/to/custom/database/path
|
||||
```
|
||||
|
||||
## Creating the conduwuit configuration file
|
||||
|
||||
@@ -101,7 +142,8 @@ 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 the config. You need to change at least the server name.**
|
||||
**Please take a moment to read the config. You need to change at least the
|
||||
server name.**
|
||||
|
||||
RocksDB is the only supported database backend.
|
||||
|
||||
|
||||
@@ -29,6 +29,12 @@ conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
```
|
||||
|
||||
If needed, we have a binary cache on Cachix but it is only limited to 5GB:
|
||||
|
||||
```
|
||||
https://conduwuit.cachix.org
|
||||
conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
```
|
||||
|
||||
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`
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ 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"]`.
|
||||
`Cargo.toml` will be `woof = ["conduwuit-api/woof"]`.
|
||||
|
||||
The rationale for this is due to Rust / Cargo not supporting
|
||||
["workspace level features"][9], we must make a choice of; either scattering
|
||||
|
||||
+50
-12
@@ -22,23 +22,59 @@ conduwuit has moderation admin commands for:
|
||||
Any commands with `-list` in them will require a codeblock in the message with
|
||||
each object being newline delimited. An example of doing this is:
|
||||
|
||||
```` !admin rooms moderation ban-list-of-rooms ``` !roomid1:server.name
|
||||
!roomid2:server.name !roomid3:server.name ``` ````
|
||||
````
|
||||
!admin rooms moderation ban-list-of-rooms
|
||||
```
|
||||
!roomid1:server.name
|
||||
#badroomalias1:server.name
|
||||
!roomid2:server.name
|
||||
!roomid3:server.name
|
||||
#badroomalias2:server.name
|
||||
```
|
||||
````
|
||||
|
||||
## Database
|
||||
## Database (RocksDB)
|
||||
|
||||
If using RocksDB, there's very little you need to do. Compaction is ran
|
||||
automatically based on various defined thresholds tuned for conduwuit to be high
|
||||
performance with the least I/O amplifcation or overhead. Manually running
|
||||
compaction is not recommended, or compaction via a timer. RocksDB is built with
|
||||
io_uring support via liburing for async read I/O.
|
||||
Generally there is very little you need to do. [Compaction][rocksdb-compaction]
|
||||
is ran automatically based on various defined thresholds tuned for conduwuit to
|
||||
be high performance with the least I/O amplifcation or overhead. Manually
|
||||
running compaction is not recommended, or compaction via a timer, due to
|
||||
creating unnecessary I/O amplification. RocksDB is built with io_uring support
|
||||
via liburing for improved read performance.
|
||||
|
||||
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](troubleshooting.md).
|
||||
|
||||
### Compression
|
||||
|
||||
Some RocksDB settings can be adjusted such as the compression method chosen. See
|
||||
the RocksDB section in the [example config](configuration/examples.md). btrfs
|
||||
users may benefit from disabling compression on RocksDB if CoW is in use.
|
||||
the RocksDB section in the [example config](configuration/examples.md).
|
||||
|
||||
RocksDB troubleshooting can be found [in the RocksDB section of
|
||||
troubleshooting](troubleshooting.md).
|
||||
btrfs users have reported that database compression does not need to be disabled
|
||||
on conduwuit as the filesystem already does not attempt to compress. This can be
|
||||
validated by using `filefrag -v` on a `.SST` file in your database, and ensure
|
||||
the `physical_offset` matches (no filesystem compression). It is very important
|
||||
to ensure no additional filesystem compression takes place as this can render
|
||||
unbuffered Direct IO inoperable, significantly slowing down read and write
|
||||
performance. See <https://btrfs.readthedocs.io/en/latest/Compression.html#compatibility>
|
||||
|
||||
> Compression is done using the COW mechanism so it’s incompatible with
|
||||
> nodatacow. Direct IO read works on compressed files but will fall back to
|
||||
> buffered writes and leads to no compression even if force compression is set.
|
||||
> Currently nodatasum and compression don’t work together.
|
||||
|
||||
### Files in database
|
||||
|
||||
Do not touch any of the files in the database directory. This must be said due
|
||||
to users being mislead by the `.log` files in the RocksDB directory, thinking
|
||||
they're server logs or database logs, however they are critical RocksDB files
|
||||
related to WAL tracking.
|
||||
|
||||
The only safe files that can be deleted are the `LOG` files (all caps). These
|
||||
are the real RocksDB telemetry/log files, however conduwuit has already
|
||||
configured to only store up to 3 RocksDB `LOG` files due to generall being
|
||||
useless for average users unless troubleshooting something low-level. If you
|
||||
would like to store nearly none at all, see the `rocksdb_max_log_files`
|
||||
config option.
|
||||
|
||||
## Backups
|
||||
|
||||
@@ -95,3 +131,5 @@ Built-in S3 support is also planned, but for now using a "S3 filesystem" on
|
||||
`media/` works. conduwuit also sends a `Cache-Control` header of 1 year and
|
||||
immutable for all media requests (download and thumbnail) to reduce unnecessary
|
||||
media requests from browsers, reduce bandwidth usage, and reduce load.
|
||||
|
||||
[rocksdb-compaction]: https://github.com/facebook/rocksdb/wiki/Compaction
|
||||
|
||||
+51
-10
@@ -41,18 +41,56 @@ 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`)
|
||||
|
||||
#### DNS No connections available error message
|
||||
|
||||
If you receive spurious amounts of error logs saying "DNS No connections
|
||||
available", this is due to your DNS server (servers from `/etc/resolv.conf`)
|
||||
being overloaded and unable to handle typical Matrix federation volume. Some
|
||||
users have reported that the upstream servers are rate-limiting them as well
|
||||
when they get this error (e.g. popular upstreams like Google DNS).
|
||||
|
||||
Matrix federation is extremely heavy and sends wild amounts of DNS requests.
|
||||
Unfortunately this is by design and has only gotten worse with more
|
||||
server/destination resolution steps. Synapse also expects a very perfect DNS
|
||||
setup.
|
||||
|
||||
There are some ways you can reduce the amount of DNS queries, but ultimately
|
||||
the best solution/fix is selfhosting a high quality caching DNS server like
|
||||
[Unbound][unbound-arch] without any upstream resolvers, and without DNSSEC
|
||||
validation enabled.
|
||||
|
||||
DNSSEC validation is highly recommended to be **disabled** due to DNSSEC being
|
||||
very computationally expensive, and is extremely susceptible to denial of
|
||||
service, especially on Matrix. Many servers also strangely have broken DNSSEC
|
||||
setups and will result in non-functional federation.
|
||||
|
||||
conduwuit cannot provide a "works-for-everyone" Unbound DNS setup guide, but
|
||||
the [official Unbound tuning guide][unbound-tuning] and the [Unbound Arch Linux wiki page][unbound-arch]
|
||||
may be of interest. Disabling DNSSEC on Unbound is commenting out trust-anchors
|
||||
config options and removing the `validator` module.
|
||||
|
||||
**Avoid** using `systemd-resolved` as it does **not** perform very well under
|
||||
high load, and we have identified its DNS caching to not be very effective.
|
||||
|
||||
dnsmasq can possibly work, but it does **not** support TCP fallback which can be
|
||||
problematic when receiving large DNS responses such as from large SRV records.
|
||||
If you still want to use dnsmasq, make sure you **disable** `dns_tcp_fallback`
|
||||
in conduwuit config.
|
||||
|
||||
Raising `dns_cache_entries` in conduwuit config from the default can also assist
|
||||
in DNS caching, but a full-fledged external caching resolver is better and more
|
||||
reliable.
|
||||
|
||||
If you don't have IPv6 connectivity, changing `ip_lookup_strategy` to match
|
||||
your setup can help reduce unnecessary AAAA queries
|
||||
(`1 - Ipv4Only (Only query for A records, no AAAA/IPv6)`).
|
||||
|
||||
If your DNS server supports it, some users have reported enabling
|
||||
`query_over_tcp_only` to force only TCP querying by default has improved DNS
|
||||
reliability at a slight performance cost due to TCP overhead.
|
||||
|
||||
## RocksDB / database issues
|
||||
|
||||
#### Direct IO
|
||||
|
||||
Some filesystems may not like RocksDB using [Direct
|
||||
IO](https://github.com/facebook/rocksdb/wiki/Direct-IO). Direct IO is for
|
||||
non-buffered I/O which improves conduwuit performance and reduces system CPU
|
||||
usage, but at least FUSE and possibly ZFS are filesystems potentially known
|
||||
to not like this. See the [example config](configuration/examples.md) for
|
||||
disabling it if needed. Issues from Direct IO on unsupported filesystems are
|
||||
usually shown as startup errors.
|
||||
|
||||
#### Database corruption
|
||||
|
||||
If your database is corrupted *and* is failing to start (e.g. checksum
|
||||
@@ -149,3 +187,6 @@ If you are a developer, you can also view the raw jemalloc statistics with
|
||||
`!admin debug memory-stats`. Please note that this output is extremely large
|
||||
which may only be visible in the conduwuit console CLI due to PDU size limits,
|
||||
and is not easy for non-developers to understand.
|
||||
|
||||
[unbound-tuning]: https://unbound.docs.nlnetlabs.nl/en/latest/topics/core/performance.html
|
||||
[unbound-arch]: https://wiki.archlinux.org/title/Unbound
|
||||
|
||||
+56
-25
@@ -86,6 +86,7 @@ env DIRENV_DEVSHELL=all-features \
|
||||
direnv exec . \
|
||||
cargo doc \
|
||||
--workspace \
|
||||
--locked \
|
||||
--profile test \
|
||||
--all-features \
|
||||
--no-deps \
|
||||
@@ -97,10 +98,11 @@ env DIRENV_DEVSHELL=all-features \
|
||||
name = "clippy/default"
|
||||
group = "lints"
|
||||
script = """
|
||||
direnv exec . \
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--locked \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
@@ -114,8 +116,8 @@ env DIRENV_DEVSHELL=all-features \
|
||||
direnv exec . \
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--locked \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
@@ -123,32 +125,37 @@ env DIRENV_DEVSHELL=all-features \
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "clippy/jemalloc"
|
||||
name = "clippy/no-features"
|
||||
group = "lints"
|
||||
script = """
|
||||
env DIRENV_DEVSHELL=no-features \
|
||||
direnv exec . \
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--locked \
|
||||
--profile test \
|
||||
--no-default-features \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "clippy/other-features"
|
||||
group = "lints"
|
||||
script = """
|
||||
direnv exec . \
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--locked \
|
||||
--profile test \
|
||||
--features jemalloc \
|
||||
--all-targets \
|
||||
--no-default-features \
|
||||
--features=console,systemd,element_hacks,direct_tls,perf_measurements,brotli_compression,blurhashing \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
"""
|
||||
|
||||
#[[task]]
|
||||
#name = "clippy/hardened_malloc"
|
||||
#group = "lints"
|
||||
#script = """
|
||||
#cargo clippy \
|
||||
# --workspace \
|
||||
# --features hardened_malloc \
|
||||
# --all-targets \
|
||||
# --color=always \
|
||||
# -- \
|
||||
# -D warnings
|
||||
#"""
|
||||
|
||||
[[task]]
|
||||
name = "lychee"
|
||||
group = "lints"
|
||||
@@ -167,8 +174,10 @@ env DIRENV_DEVSHELL=all-features \
|
||||
direnv exec . \
|
||||
cargo test \
|
||||
--workspace \
|
||||
--locked \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--no-fail-fast \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
@@ -179,13 +188,35 @@ env DIRENV_DEVSHELL=all-features \
|
||||
name = "cargo/default"
|
||||
group = "tests"
|
||||
script = """
|
||||
cargo test \
|
||||
--workspace \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
env DIRENV_DEVSHELL=default \
|
||||
direnv exec . \
|
||||
cargo test \
|
||||
--workspace \
|
||||
--locked \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--no-fail-fast \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "cargo/no-features"
|
||||
group = "tests"
|
||||
script = """
|
||||
env DIRENV_DEVSHELL=no-features \
|
||||
direnv exec . \
|
||||
cargo test \
|
||||
--workspace \
|
||||
--locked \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--no-fail-fast \
|
||||
--no-default-features \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
"""
|
||||
|
||||
# Checks if the generated example config differs from the checked in repo's
|
||||
|
||||
Generated
+123
-493
@@ -5,15 +5,16 @@
|
||||
"crane": "crane",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-parts": "flake-parts",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729116596,
|
||||
"narHash": "sha256-NnLMLIXGZtAscUF4dCShksuQ1nOGF6Y2dEeyj0rBbUg=",
|
||||
"lastModified": 1731270564,
|
||||
"narHash": "sha256-6KMC/NH/VWP5Eb+hA56hz0urel3jP6Y6cF2PX6xaTkk=",
|
||||
"owner": "zhaofengli",
|
||||
"repo": "attic",
|
||||
"rev": "2b05b7d986cf6009b1c1ef7daa4961cd1a658782",
|
||||
"rev": "47752427561f1c34debb16728a210d378f0ece36",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -26,16 +27,16 @@
|
||||
"cachix": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"flake-compat": "flake-compat_3",
|
||||
"flake-compat": "flake-compat_2",
|
||||
"git-hooks": "git-hooks",
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728672398,
|
||||
"narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
|
||||
"lastModified": 1737621947,
|
||||
"narHash": "sha256-8HFvG7fvIFbgtaYAY2628Tb89fA55nPm2jSiNs0/Cws=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
|
||||
"rev": "f65a3cd5e339c223471e64c051434616e18cc4f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -47,72 +48,31 @@
|
||||
},
|
||||
"cachix_2": {
|
||||
"inputs": {
|
||||
"devenv": "devenv_2",
|
||||
"devenv": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
],
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
"devenv"
|
||||
],
|
||||
"git-hooks": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"pre-commit-hooks"
|
||||
"devenv"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726520618,
|
||||
"narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=",
|
||||
"lastModified": 1728672398,
|
||||
"narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712055811,
|
||||
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
|
||||
"rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "latest",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -120,15 +80,15 @@
|
||||
"complement": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1724347376,
|
||||
"narHash": "sha256-y0e/ULDJ92IhNQZsS/06g0s+AYZ82aJfrIO9qEse94c=",
|
||||
"owner": "matrix-org",
|
||||
"lastModified": 1734303596,
|
||||
"narHash": "sha256-HjDRyLR4MBqQ3IjfMM6eE+8ayztXlbz3gXdyDmFla68=",
|
||||
"owner": "girlbossceo",
|
||||
"repo": "complement",
|
||||
"rev": "39733c1b2f8314800776748cc7164f9a34650686",
|
||||
"rev": "14cc5be797b774f1a2b9f826f38181066d4952b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "matrix-org",
|
||||
"owner": "girlbossceo",
|
||||
"ref": "main",
|
||||
"repo": "complement",
|
||||
"type": "github"
|
||||
@@ -157,11 +117,11 @@
|
||||
},
|
||||
"crane_2": {
|
||||
"locked": {
|
||||
"lastModified": 1729741221,
|
||||
"narHash": "sha256-8AHZZXs1lFkERfBY0C8cZGElSo33D/et7NKEpLRmvzo=",
|
||||
"lastModified": 1737689766,
|
||||
"narHash": "sha256-ivVXYaYlShxYoKfSo5+y5930qMKKJ8CLcAoIBPQfJ6s=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "f235b656ee5b2bfd6d94c3bfd67896a575d4a6ed",
|
||||
"rev": "6fe74265bbb6d016d663b1091f015e2976c4a527",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -178,100 +138,26 @@
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix_3",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"git-hooks": [
|
||||
"cachix",
|
||||
"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=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv_3": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"poetry2nix": "poetry2nix",
|
||||
"pre-commit-hooks": [
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"pre-commit-hooks"
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1708704632,
|
||||
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
|
||||
"lastModified": 1733323168,
|
||||
"narHash": "sha256-d5DwB4MZvlaQpN6OQ4SLYxb5jA4UH5EtV5t5WOtjLPU=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
|
||||
"rev": "efa9010b8b1cfd5dd3c7ed1e172a470c3b84a064",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "python-rewrite",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -284,11 +170,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729751566,
|
||||
"narHash": "sha256-99u/hrgBdi8bxSXZc9ZbNkR5EL1htrkbd3lsbKzS60g=",
|
||||
"lastModified": 1737786656,
|
||||
"narHash": "sha256-ubCW9Jy7ZUOF354bWxTgLDpVnTvIpNr6qR4H/j7I0oo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "f32a2d484091a6dc98220b1f4a2c2d60b7c97c64",
|
||||
"rev": "2f721f527886f801403f389a9cabafda8f1e3b7f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -317,11 +203,11 @@
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -333,27 +219,11 @@
|
||||
"flake-compat_3": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_4": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -412,44 +282,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"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_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -473,11 +310,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1727854478,
|
||||
"narHash": "sha256-/odH2nUMAwkMgOS2nG2z0exLQNJS4S2LfMW0teqU7co=",
|
||||
"lastModified": 1733318908,
|
||||
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "5f58871c9657b5fc0a7f65670fe2ba99c26c1d79",
|
||||
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -527,11 +364,11 @@
|
||||
"liburing": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1725659644,
|
||||
"narHash": "sha256-WjnpmopfvFoUbubIu9bki+Y6P4YXDfvnW4+72hniq3g=",
|
||||
"lastModified": 1737600516,
|
||||
"narHash": "sha256-EKyLQ3pbcjoU5jH5atge59F4fzuhTsb6yalUj6Ve2t8=",
|
||||
"owner": "axboe",
|
||||
"repo": "liburing",
|
||||
"rev": "0fe5c09195c0918f89582dd6ff098a58a0bdf62a",
|
||||
"rev": "6c509e2b0c881a13b83b259a221bf15fc9b3f681",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -542,123 +379,26 @@
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_2",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1710156097,
|
||||
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"ref": "main",
|
||||
"repo": "nix-filter",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688870561,
|
||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_3": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
"devenv"
|
||||
],
|
||||
"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"
|
||||
"nixpkgs-23-11": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
],
|
||||
"nixpkgs-regression": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"cachix",
|
||||
"devenv"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1727438425,
|
||||
@@ -675,6 +415,43 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1731533336,
|
||||
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"ref": "main",
|
||||
"repo": "nix-filter",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"attic",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729742964,
|
||||
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1726042813,
|
||||
@@ -691,70 +468,6 @@
|
||||
"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,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression_2": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression_3": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1724316499,
|
||||
@@ -773,11 +486,11 @@
|
||||
},
|
||||
"nixpkgs-stable_2": {
|
||||
"locked": {
|
||||
"lastModified": 1720386169,
|
||||
"narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
|
||||
"lastModified": 1730741070,
|
||||
"narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
|
||||
"rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -789,16 +502,16 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1692808169,
|
||||
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
|
||||
"lastModified": 1730531603,
|
||||
"narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
|
||||
"rev": "7ffd9ae656aec493492b44d0ddfb28e79a1ea25d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -821,11 +534,11 @@
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1727802920,
|
||||
"narHash": "sha256-HP89HZOT0ReIbI7IJZJQoJgxvB2Tn28V6XS3MNKnfLs=",
|
||||
"lastModified": 1733212471,
|
||||
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "27e30d177e57d912d614c88c622dcfdb2e6e6515",
|
||||
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -837,11 +550,11 @@
|
||||
},
|
||||
"nixpkgs_5": {
|
||||
"locked": {
|
||||
"lastModified": 1725534445,
|
||||
"narHash": "sha256-Yd0FK9SkWy+ZPuNqUgmVPXokxDgMJoGuNpMEtkfcf84=",
|
||||
"lastModified": 1737717945,
|
||||
"narHash": "sha256-ET91TMkab3PmOZnqiJQYOtSGvSTvGeHoegAv4zcTefM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9bb1e7571aadf31ddb4af77fc64b2d59580f9a39",
|
||||
"rev": "ecd26a469ac56357fd333946a99086e992452b6a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -851,87 +564,19 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692876271,
|
||||
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"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": 1731690620,
|
||||
"narHash": "sha256-Xd4TJYqPERMJLXaGa6r6Ny1Wlw8Uy5Cyf/8q7nS58QM=",
|
||||
"lastModified": 1737828695,
|
||||
"narHash": "sha256-8Ev6zzhNPU798JNvU27a7gj5X+6SDG3jBweUkQ59DbA=",
|
||||
"owner": "girlbossceo",
|
||||
"repo": "rocksdb",
|
||||
"rev": "292446aa2bc41699204d817a1e4b091679a886eb",
|
||||
"rev": "a4d9230dcc9d03be428b9a728133f8f646c0065c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "girlbossceo",
|
||||
"ref": "v9.7.4",
|
||||
"ref": "v9.9.3",
|
||||
"repo": "rocksdb",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -943,8 +588,8 @@
|
||||
"complement": "complement",
|
||||
"crane": "crane_2",
|
||||
"fenix": "fenix",
|
||||
"flake-compat": "flake-compat_4",
|
||||
"flake-utils": "flake-utils_3",
|
||||
"flake-compat": "flake-compat_3",
|
||||
"flake-utils": "flake-utils",
|
||||
"liburing": "liburing",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs_5",
|
||||
@@ -954,11 +599,11 @@
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1729715509,
|
||||
"narHash": "sha256-jUDN4e1kObbksb4sc+57NEeujBEDRdLCOu9wiE3RZdM=",
|
||||
"lastModified": 1737728869,
|
||||
"narHash": "sha256-U4pl3Hi0lT6GP4ecN3q9wdD2sdaKMbmD/5NJ1NdJ9AM=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "40492e15d49b89cf409e2c5536444131fac49429",
|
||||
"rev": "6e4c29f7ce18cea7d3d31237a4661ab932eab636",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -982,21 +627,6 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"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",
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
inputs = {
|
||||
attic.url = "github:zhaofengli/attic?ref=main";
|
||||
cachix.url = "github:cachix/cachix?ref=master";
|
||||
complement = { url = "github:matrix-org/complement?ref=main"; flake = false; };
|
||||
complement = { url = "github:girlbossceo/complement?ref=main"; flake = false; };
|
||||
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=nixpkgs-unstable";
|
||||
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.7.4"; flake = false; };
|
||||
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.9.3"; flake = false; };
|
||||
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
|
||||
};
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
let
|
||||
pkgsHost = import inputs.nixpkgs{
|
||||
inherit system;
|
||||
config.permittedInsecurePackages = [ "olm-3.2.16" ];
|
||||
};
|
||||
pkgsHostStatic = pkgsHost.pkgsStatic;
|
||||
|
||||
@@ -27,7 +26,7 @@
|
||||
file = ./rust-toolchain.toml;
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-s1RPtyvDGJaX/BisLT+ifVfuhDT1nZkZ1NcK8sbwELM=";
|
||||
sha256 = "sha256-lMLAupxng4Fd9F1oDw8gx+qA0RuF7ou7xhNU8wgs0PU=";
|
||||
};
|
||||
|
||||
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
||||
@@ -73,9 +72,16 @@
|
||||
"-DWITH_TESTS=1"
|
||||
# we use rust-rocksdb via C interface and dont need C++ RTTI
|
||||
"-DUSE_RTTI=1"
|
||||
# this doesn't exist in RocksDB, and USE_SSE is deprecated for
|
||||
# PORTABLE=$(march)
|
||||
"-DFORCE_SSE42=1"
|
||||
# PORTABLE will get set in main/default.nix
|
||||
"-DPORTABLE=1"
|
||||
]
|
||||
old.cmakeFlags
|
||||
++ [
|
||||
# no real reason to have snappy, no one uses this
|
||||
"-DWITH_SNAPPY=0"
|
||||
# we dont need to use ldb or sst_dump (core_tools)
|
||||
"-DWITH_CORE_TOOLS=0"
|
||||
# we dont need trace tools
|
||||
@@ -118,9 +124,9 @@
|
||||
# code.
|
||||
COMPLEMENT_SRC = inputs.complement.outPath;
|
||||
|
||||
# Needed for Complement
|
||||
CGO_CFLAGS = "-I${scope.pkgs.olm}/include";
|
||||
CGO_LDFLAGS = "-L${scope.pkgs.olm}/lib";
|
||||
# Needed for Complement: <https://github.com/golang/go/issues/52690>
|
||||
CGO_CFLAGS = "-Wl,--no-gc-sections";
|
||||
CGO_LDFLAGS = "-Wl,--no-gc-sections";
|
||||
};
|
||||
|
||||
# Development tools
|
||||
@@ -163,21 +169,9 @@
|
||||
|
||||
# used for rust caching in CI to speed it up
|
||||
sccache
|
||||
|
||||
# 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
|
||||
])
|
||||
++ lib.optional stdenv.hostPlatform.isLinux liburing)
|
||||
++ scope.main.buildInputs
|
||||
++ scope.main.propagatedBuildInputs
|
||||
++ scope.main.nativeBuildInputs;
|
||||
@@ -185,23 +179,59 @@
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
default = scopeHost.main;
|
||||
default = scopeHost.main.override {
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
default-debug = scopeHost.main.override {
|
||||
profile = "dev";
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
# just a test profile used for things like CI and complement
|
||||
default-test = scopeHost.main.override {
|
||||
profile = "test";
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
all-features = scopeHost.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
all-features-debug = scopeHost.main.override {
|
||||
@@ -210,10 +240,12 @@
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
|
||||
@@ -223,10 +255,16 @@
|
||||
main = scopeHost.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
};
|
||||
@@ -237,10 +275,12 @@
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
};
|
||||
@@ -273,6 +313,15 @@
|
||||
value = scopeCrossStatic.main;
|
||||
}
|
||||
|
||||
# An output for a statically-linked binary with x86_64 haswell
|
||||
# target optimisations
|
||||
{
|
||||
name = "${binaryName}-x86_64-haswell-optimised";
|
||||
value = scopeCrossStatic.main.override {
|
||||
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a statically-linked unstripped debug ("dev") binary
|
||||
{
|
||||
name = "${binaryName}-debug";
|
||||
@@ -290,6 +339,14 @@
|
||||
value = scopeCrossStatic.main.override {
|
||||
profile = "test";
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -299,14 +356,42 @@
|
||||
value = scopeCrossStatic.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a statically-linked binary with `--all-features` and with x86_64 haswell
|
||||
# target optimisations
|
||||
{
|
||||
name = "${binaryName}-all-features-x86_64-haswell-optimised";
|
||||
value = scopeCrossStatic.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a statically-linked unstripped debug ("dev") binary with `--all-features`
|
||||
{
|
||||
name = "${binaryName}-all-features-debug";
|
||||
@@ -316,10 +401,12 @@
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -338,6 +425,17 @@
|
||||
value = scopeCrossStatic.oci-image;
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary with x86_64 haswell
|
||||
# target optimisations
|
||||
{
|
||||
name = "oci-image-${crossSystem}-x86_64-haswell-optimised";
|
||||
value = scopeCrossStatic.oci-image.override {
|
||||
main = scopeCrossStatic.main.override {
|
||||
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that unstripped debug ("dev") binary
|
||||
{
|
||||
name = "oci-image-${crossSystem}-debug";
|
||||
@@ -357,30 +455,62 @@
|
||||
main = scopeCrossStatic.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary with `--all-features` and with x86_64 haswell
|
||||
# target optimisations
|
||||
{
|
||||
name = "oci-image-${crossSystem}-all-features-x86_64-haswell-optimised";
|
||||
value = scopeCrossStatic.oci-image.override {
|
||||
main = scopeCrossStatic.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that unstripped debug ("dev") binary with `--all-features`
|
||||
{
|
||||
name = "oci-image-${crossSystem}-all-features-debug";
|
||||
value = scopeCrossStatic.oci-image.override {
|
||||
main = scopeCrossStatic.main.override {
|
||||
profile = "dev";
|
||||
all_features = true;
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
profile = "dev";
|
||||
all_features = true;
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -418,10 +548,16 @@
|
||||
main = prev.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
];
|
||||
};
|
||||
}));
|
||||
|
||||
@@ -9,14 +9,40 @@ database_path = "/database"
|
||||
log = "trace,h2=warn,hyper=warn"
|
||||
port = [8008, 8448]
|
||||
trusted_servers = []
|
||||
only_query_trusted_key_servers = false
|
||||
query_trusted_key_servers_first = false
|
||||
query_trusted_key_servers_first_on_join = false
|
||||
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
|
||||
ip_range_denylist = []
|
||||
url_preview_domain_contains_allowlist = ["*"]
|
||||
url_preview_domain_explicit_denylist = ["*"]
|
||||
media_compat_file_link = false
|
||||
media_startup_check = false
|
||||
rocksdb_direct_io = false
|
||||
media_startup_check = true
|
||||
prune_missing_media = true
|
||||
log_colors = false
|
||||
admin_room_notices = false
|
||||
allow_check_for_updates = false
|
||||
intentionally_unknown_config_option_for_testing = true
|
||||
rocksdb_log_level = "debug"
|
||||
rocksdb_max_log_files = 1
|
||||
rocksdb_recovery_mode = 0
|
||||
rocksdb_paranoid_file_checks = true
|
||||
log_guest_registrations = false
|
||||
allow_legacy_media = true
|
||||
startup_netburst = true
|
||||
startup_netburst_keep = -1
|
||||
|
||||
# valgrind makes things so slow
|
||||
dns_timeout = 60
|
||||
dns_attempts = 20
|
||||
request_conn_timeout = 60
|
||||
request_timeout = 120
|
||||
well_known_conn_timeout = 60
|
||||
well_known_timeout = 60
|
||||
federation_idle_timeout = 300
|
||||
sender_timeout = 300
|
||||
sender_idle_timeout = 300
|
||||
sender_retry_backoff_limit = 300
|
||||
|
||||
[global.tls]
|
||||
certs = "/certificate.crt"
|
||||
|
||||
@@ -18,20 +18,24 @@ 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"
|
||||
"perf_measurements"
|
||||
# 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
|
||||
"experimental"
|
||||
# compression isn't needed for complement
|
||||
"brotli_compression"
|
||||
"gzip_compression"
|
||||
"zstd_compression"
|
||||
# complement doesn't need hot reloading
|
||||
"conduwuit_mods"
|
||||
# complement doesn't have URL preview media tests
|
||||
"url_preview"
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{ lib
|
||||
, pkgsBuildHost
|
||||
, pkgsBuildTarget
|
||||
, rust
|
||||
, stdenv
|
||||
}:
|
||||
@@ -23,25 +22,13 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
||||
[ "-C" "relocation-model=static" ]
|
||||
++ lib.optionals
|
||||
(stdenv.buildPlatform.config != stdenv.hostPlatform.config)
|
||||
[ "-l" "c" ]
|
||||
++ lib.optionals
|
||||
# This check has to match the one [here][0]. We only need to set
|
||||
# these flags when using a different linker. Don't ask me why,
|
||||
# though, because I don't know. All I know is it breaks otherwise.
|
||||
#
|
||||
# [0]: https://github.com/NixOS/nixpkgs/blob/5cdb38bb16c6d0a38779db14fcc766bc1b2394d6/pkgs/build-support/rust/lib/default.nix#L37-L40
|
||||
(
|
||||
# Nixpkgs doesn't check for x86_64 here but we do, because I
|
||||
# observed a failure building statically for x86_64 without
|
||||
# including it here. Linkers are weird.
|
||||
(stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isx86_64)
|
||||
&& stdenv.hostPlatform.isStatic
|
||||
&& !stdenv.hostPlatform.isDarwin
|
||||
&& !stdenv.cc.bintools.isLLVM
|
||||
)
|
||||
[
|
||||
"-l"
|
||||
"c"
|
||||
|
||||
"-l"
|
||||
"stdc++"
|
||||
|
||||
"-L"
|
||||
"${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib"
|
||||
]
|
||||
@@ -58,7 +45,6 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
||||
(
|
||||
let
|
||||
inherit (rust.lib) envVars;
|
||||
shouldUseLLD = platform: platform.isAarch64 && platform.isStatic && !stdenv.hostPlatform.isDarwin;
|
||||
in
|
||||
lib.optionalAttrs
|
||||
(stdenv.targetPlatform.rust.rustcTarget
|
||||
@@ -66,30 +52,22 @@ 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" = linkerForTarget;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForTarget;
|
||||
}
|
||||
)
|
||||
//
|
||||
(
|
||||
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" = linkerForHost;
|
||||
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForHost;
|
||||
CARGO_BUILD_TARGET = rustcTarget;
|
||||
}
|
||||
)
|
||||
|
||||
+60
-34
@@ -6,7 +6,6 @@
|
||||
, libiconv
|
||||
, liburing
|
||||
, pkgsBuildHost
|
||||
, pkgsBuildTarget
|
||||
, rocksdb
|
||||
, removeReferencesTo
|
||||
, rust
|
||||
@@ -14,12 +13,29 @@
|
||||
, stdenv
|
||||
|
||||
# Options (keep sorted)
|
||||
, default_features ? true
|
||||
, disable_release_max_log_level ? false
|
||||
, all_features ? false
|
||||
, disable_features ? []
|
||||
, default_features ? true
|
||||
# default list of disabled features
|
||||
, disable_features ? [
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
# jemalloc profiling/stats features are expensive and shouldn't
|
||||
# be expected on non-debug builds.
|
||||
"jemalloc_prof"
|
||||
"jemalloc_stats"
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# conduwuit_mods is a development-only hot reload feature
|
||||
"conduwuit_mods"
|
||||
]
|
||||
, disable_release_max_log_level ? false
|
||||
, features ? []
|
||||
, profile ? "release"
|
||||
# rocksdb compiled with -march=haswell and target-cpu=haswell rustflag
|
||||
# haswell is pretty much any x86 cpu made in the last 12 years, and
|
||||
# supports modern CPU extensions that rocksdb can make use of.
|
||||
# disable if trying to make a portable x86_64 build for very old hardware
|
||||
, x86_64_haswell_target_optimised ? false
|
||||
}:
|
||||
|
||||
let
|
||||
@@ -66,7 +82,7 @@ rust-jemalloc-sys' = (rust-jemalloc-sys.override {
|
||||
buildDepsOnlyEnv =
|
||||
let
|
||||
rocksdb' = (rocksdb.override {
|
||||
jemalloc = rust-jemalloc-sys';
|
||||
jemalloc = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
|
||||
# rocksdb fails to build with prefixed jemalloc, which is required on
|
||||
# darwin due to [1]. In this case, fall back to building rocksdb with
|
||||
# libc malloc. This should not cause conflicts, because all of the
|
||||
@@ -80,6 +96,19 @@ buildDepsOnlyEnv =
|
||||
enableLiburing = enableLiburing;
|
||||
}).overrideAttrs (old: {
|
||||
enableLiburing = enableLiburing;
|
||||
cmakeFlags = (if x86_64_haswell_target_optimised then (lib.subtractLists [
|
||||
# dont make a portable build if x86_64_haswell_target_optimised is enabled
|
||||
"-DPORTABLE=1"
|
||||
] old.cmakeFlags
|
||||
++ [ "-DPORTABLE=haswell" ]) else ([ "-DPORTABLE=1" ])
|
||||
)
|
||||
++ old.cmakeFlags;
|
||||
|
||||
# outputs has "tools" which we dont need or use
|
||||
outputs = [ "out" ];
|
||||
|
||||
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
|
||||
preInstall = "";
|
||||
});
|
||||
in
|
||||
{
|
||||
@@ -96,7 +125,6 @@ buildDepsOnlyEnv =
|
||||
inherit
|
||||
lib
|
||||
pkgsBuildHost
|
||||
pkgsBuildTarget
|
||||
rust
|
||||
stdenv;
|
||||
});
|
||||
@@ -107,7 +135,9 @@ 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";
|
||||
" -L${lib.getLib liburing}/lib -luring"
|
||||
+ lib.optionalString x86_64_haswell_target_optimised
|
||||
" -Ctarget-cpu=haswell";
|
||||
};
|
||||
|
||||
|
||||
@@ -132,6 +162,19 @@ commonAttrs = {
|
||||
];
|
||||
};
|
||||
|
||||
# This is redundant with CI
|
||||
doCheck = false;
|
||||
|
||||
cargoTestCommand = "cargo test --locked ";
|
||||
cargoExtraArgs = "--no-default-features --locked "
|
||||
+ lib.optionalString
|
||||
(features'' != [])
|
||||
"--features " + (builtins.concatStringsSep "," features'');
|
||||
cargoTestExtraArgs = "--no-default-features --locked "
|
||||
+ lib.optionalString
|
||||
(features'' != [])
|
||||
"--features " + (builtins.concatStringsSep "," features'');
|
||||
|
||||
dontStrip = profile == "dev" || profile == "test";
|
||||
dontPatchELF = profile == "dev" || profile == "test";
|
||||
|
||||
@@ -157,27 +200,7 @@ commonAttrs = {
|
||||
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
|
||||
# rebuilds of bindgen and its depedents.
|
||||
jq
|
||||
|
||||
# needed so we can get rid of gcc and other unused deps that bloat OCI images
|
||||
removeReferencesTo
|
||||
]
|
||||
# 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
|
||||
];
|
||||
|
||||
# for some reason gcc and other weird deps are added to OCI images and bloats it up
|
||||
#
|
||||
# <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 ${llvm} -t ${libllvm} -t ${rustc.unwrapped} -t ${rustc} -t ${libidn2} -t ${libunistring} '{}' +
|
||||
'';
|
||||
];
|
||||
};
|
||||
in
|
||||
|
||||
@@ -186,15 +209,18 @@ craneLib.buildPackage ( commonAttrs // {
|
||||
env = buildDepsOnlyEnv;
|
||||
});
|
||||
|
||||
cargoExtraArgs = "--no-default-features "
|
||||
# This is redundant with CI
|
||||
doCheck = false;
|
||||
|
||||
cargoTestCommand = "cargo test --locked ";
|
||||
cargoExtraArgs = "--no-default-features --locked "
|
||||
+ lib.optionalString
|
||||
(features'' != [])
|
||||
"--features " + (builtins.concatStringsSep "," features'');
|
||||
cargoTestExtraArgs = "--no-default-features --locked "
|
||||
+ lib.optionalString
|
||||
(features'' != [])
|
||||
"--features " + (builtins.concatStringsSep "," features'');
|
||||
|
||||
# This is redundant with CI
|
||||
cargoTestCommand = "";
|
||||
cargoCheckCommand = "";
|
||||
doCheck = false;
|
||||
|
||||
env = buildPackageEnv;
|
||||
|
||||
|
||||
@@ -28,5 +28,19 @@ dockerTools.buildLayeredImage {
|
||||
Env = [
|
||||
"RUST_BACKTRACE=full"
|
||||
];
|
||||
Labels = {
|
||||
"org.opencontainers.image.authors" = "June Clementine Strawberry <june@girlboss.ceo> and Jason Volk
|
||||
<jason@zemos.net>";
|
||||
"org.opencontainers.image.created" ="@${toString inputs.self.lastModified}";
|
||||
"org.opencontainers.image.description" = "a very cool Matrix chat homeserver written in Rust";
|
||||
"org.opencontainers.image.documentation" = "https://conduwuit.puppyirl.gay/";
|
||||
"org.opencontainers.image.licenses" = "Apache-2.0";
|
||||
"org.opencontainers.image.revision" = inputs.self.rev or inputs.self.dirtyRev or "";
|
||||
"org.opencontainers.image.source" = "https://github.com/girlbossceo/conduwuit";
|
||||
"org.opencontainers.image.title" = main.pname;
|
||||
"org.opencontainers.image.url" = "https://conduwuit.puppyirl.gay/";
|
||||
"org.opencontainers.image.vendor" = "girlbossceo";
|
||||
"org.opencontainers.image.version" = main.version;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||
|
||||
[toolchain]
|
||||
channel = "1.83.0"
|
||||
channel = "1.84.0"
|
||||
profile = "minimal"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
|
||||
+9
-3
@@ -2,9 +2,8 @@ array_width = 80
|
||||
chain_width = 60
|
||||
comment_width = 80
|
||||
condense_wildcard_suffixes = true
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
fn_call_width = 80
|
||||
fn_params_layout = "Compressed"
|
||||
fn_single_line = true
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_bodies = true
|
||||
@@ -14,13 +13,20 @@ group_imports = "StdExternalCrate"
|
||||
hard_tabs = true
|
||||
hex_literal_case = "Upper"
|
||||
imports_granularity = "Crate"
|
||||
match_arm_blocks = false
|
||||
match_arm_leading_pipes = "Always"
|
||||
match_block_trailing_comma = true
|
||||
max_width = 120
|
||||
max_width = 98
|
||||
newline_style = "Unix"
|
||||
normalize_comments = false
|
||||
overflow_delimited_expr = true
|
||||
reorder_impl_items = true
|
||||
reorder_imports = true
|
||||
single_line_if_else_max_width = 60
|
||||
single_line_let_else_max_width = 80
|
||||
struct_lit_width = 40
|
||||
tab_spaces = 4
|
||||
unstable_features = true
|
||||
use_field_init_shorthand = true
|
||||
use_small_heuristics = "Off"
|
||||
use_try_shorthand = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "conduit_admin"
|
||||
name = "conduwuit_admin"
|
||||
categories.workspace = true
|
||||
description.workspace = true
|
||||
edition.workspace = true
|
||||
@@ -17,7 +17,6 @@ crate-type = [
|
||||
]
|
||||
|
||||
[features]
|
||||
#dev_release_log_level = []
|
||||
release_max_log_level = [
|
||||
"tracing/max_level_trace",
|
||||
"tracing/release_max_level_info",
|
||||
@@ -27,11 +26,11 @@ release_max_log_level = [
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
conduit-api.workspace = true
|
||||
conduit-core.workspace = true
|
||||
conduit-database.workspace = true
|
||||
conduit-macros.workspace = true
|
||||
conduit-service.workspace = true
|
||||
conduwuit-api.workspace = true
|
||||
conduwuit-core.workspace = true
|
||||
conduwuit-database.workspace = true
|
||||
conduwuit-macros.workspace = true
|
||||
conduwuit-service.workspace = true
|
||||
const-str.workspace = true
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
+20
-18
@@ -1,15 +1,15 @@
|
||||
use clap::Parser;
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use conduwuit::Result;
|
||||
|
||||
use crate::{
|
||||
appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command, debug,
|
||||
debug::DebugCommand, federation, federation::FederationCommand, media, media::MediaCommand, query,
|
||||
query::QueryCommand, room, room::RoomCommand, server, server::ServerCommand, user, user::UserCommand,
|
||||
appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command,
|
||||
debug, debug::DebugCommand, federation, federation::FederationCommand, media,
|
||||
media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand, server,
|
||||
server::ServerCommand, user, user::UserCommand,
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "conduwuit", version = conduit::version())]
|
||||
#[command(name = "conduwuit", version = conduwuit::version())]
|
||||
pub(super) enum AdminCommand {
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing appservices
|
||||
@@ -49,18 +49,20 @@ pub(super) enum AdminCommand {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, name = "command")]
|
||||
pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result {
|
||||
use AdminCommand::*;
|
||||
|
||||
Ok(match command {
|
||||
Appservices(command) => appservice::process(command, context).await?,
|
||||
Media(command) => media::process(command, context).await?,
|
||||
Users(command) => user::process(command, context).await?,
|
||||
Rooms(command) => room::process(command, context).await?,
|
||||
Federation(command) => federation::process(command, context).await?,
|
||||
Server(command) => server::process(command, context).await?,
|
||||
Debug(command) => debug::process(command, context).await?,
|
||||
Query(command) => query::process(command, context).await?,
|
||||
Check(command) => check::process(command, context).await?,
|
||||
})
|
||||
match command {
|
||||
| Appservices(command) => appservice::process(command, context).await?,
|
||||
| Media(command) => media::process(command, context).await?,
|
||||
| Users(command) => user::process(command, context).await?,
|
||||
| Rooms(command) => room::process(command, context).await?,
|
||||
| Federation(command) => federation::process(command, context).await?,
|
||||
| Server(command) => server::process(command, context).await?,
|
||||
| Debug(command) => debug::process(command, context).await?,
|
||||
| Query(command) => query::process(command, context).await?,
|
||||
| Check(command) => check::process(command, context).await?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ use crate::{admin_command, Result};
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn register(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
@@ -14,55 +16,63 @@ pub(super) async fn register(&self) -> Result<RoomMessageEventContent> {
|
||||
let appservice_config_body = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config_body);
|
||||
match parsed_config {
|
||||
Ok(registration) => match self
|
||||
| Ok(registration) => match self
|
||||
.services
|
||||
.appservice
|
||||
.register_appservice(®istration, &appservice_config_body)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Appservice registered with ID: {}",
|
||||
registration.id
|
||||
))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to register appservice: {e}"
|
||||
))),
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse appservice config as YAML: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn unregister(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn unregister(
|
||||
&self,
|
||||
appservice_identifier: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
match self
|
||||
.services
|
||||
.appservice
|
||||
.unregister_appservice(&appservice_identifier)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Appservice unregistered.")),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Ok(()) => Ok(RoomMessageEventContent::text_plain("Appservice unregistered.")),
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to unregister appservice: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn show_appservice_config(
|
||||
&self,
|
||||
appservice_identifier: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
match self
|
||||
.services
|
||||
.appservice
|
||||
.get_registration(&appservice_identifier)
|
||||
.await
|
||||
{
|
||||
Some(config) => {
|
||||
let config_str = serde_yaml::to_string(&config).expect("config should've been validated on register");
|
||||
let output = format!("Config for {appservice_identifier}:\n\n```yaml\n{config_str}\n```",);
|
||||
| Some(config) => {
|
||||
let config_str = serde_yaml::to_string(&config)
|
||||
.expect("config should've been validated on register");
|
||||
let output =
|
||||
format!("Config for {appservice_identifier}:\n\n```yaml\n{config_str}\n```",);
|
||||
Ok(RoomMessageEventContent::notice_markdown(output))
|
||||
},
|
||||
None => Ok(RoomMessageEventContent::text_plain("Appservice does not exist.")),
|
||||
| None => Ok(RoomMessageEventContent::text_plain("Appservice does not exist.")),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use conduit::Result;
|
||||
use conduit_macros::implement;
|
||||
use conduwuit::Result;
|
||||
use conduwuit_macros::implement;
|
||||
use futures::StreamExt;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
@@ -19,8 +19,9 @@ pub(super) async fn check_all_users(&self) -> Result<RoomMessageEventContent> {
|
||||
let ok_count = users.iter().filter(|_user| true).count();
|
||||
|
||||
let message = format!(
|
||||
"Database query completed in {query_time:?}:\n\n```\nTotal entries: {total:?}\nFailure/Invalid user count: \
|
||||
{err_count:?}\nSuccess/Valid user count: {ok_count:?}\n```"
|
||||
"Database query completed in {query_time:?}:\n\n```\nTotal entries: \
|
||||
{total:?}\nFailure/Invalid user count: {err_count:?}\nSuccess/Valid user count: \
|
||||
{ok_count:?}\n```"
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(message))
|
||||
|
||||
+4
-10
@@ -1,18 +1,12 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use conduwuit::Result;
|
||||
|
||||
use crate::Command;
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(super) enum CheckCommand {
|
||||
AllUsers,
|
||||
}
|
||||
|
||||
pub(super) async fn process(command: CheckCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
Ok(match command {
|
||||
CheckCommand::AllUsers => context.check_all_users().await?,
|
||||
})
|
||||
CheckAllUsers,
|
||||
}
|
||||
|
||||
+30
-2
@@ -1,6 +1,12 @@
|
||||
use std::time::SystemTime;
|
||||
use std::{fmt, time::SystemTime};
|
||||
|
||||
use conduit_service::Services;
|
||||
use conduwuit::Result;
|
||||
use conduwuit_service::Services;
|
||||
use futures::{
|
||||
io::{AsyncWriteExt, BufWriter},
|
||||
lock::Mutex,
|
||||
Future, FutureExt,
|
||||
};
|
||||
use ruma::EventId;
|
||||
|
||||
pub(crate) struct Command<'a> {
|
||||
@@ -8,4 +14,26 @@ pub(crate) struct Command<'a> {
|
||||
pub(crate) body: &'a [&'a str],
|
||||
pub(crate) timer: SystemTime,
|
||||
pub(crate) reply_id: Option<&'a EventId>,
|
||||
pub(crate) output: Mutex<BufWriter<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Command<'_> {
|
||||
pub(crate) fn write_fmt(
|
||||
&self,
|
||||
arguments: fmt::Arguments<'_>,
|
||||
) -> impl Future<Output = Result> + Send + '_ {
|
||||
let buf = format!("{arguments}");
|
||||
self.output.lock().then(|mut output| async move {
|
||||
output.write_all(buf.as_bytes()).await.map_err(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn write_str<'a>(
|
||||
&'a self,
|
||||
s: &'a str,
|
||||
) -> impl Future<Output = Result> + Send + 'a {
|
||||
self.output.lock().then(move |mut output| async move {
|
||||
output.write_all(s.as_bytes()).await.map_err(Into::into)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+290
-134
@@ -2,18 +2,28 @@ use std::{
|
||||
collections::HashMap,
|
||||
fmt::Write,
|
||||
iter::once,
|
||||
sync::Arc,
|
||||
time::{Instant, SystemTime},
|
||||
};
|
||||
|
||||
use conduit::{debug_error, err, info, trace, utils, utils::string::EMPTY, warn, Error, PduEvent, Result};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use conduwuit::{
|
||||
debug_error, err, info, trace, utils,
|
||||
utils::{
|
||||
stream::{IterStream, ReadyExt},
|
||||
string::EMPTY,
|
||||
},
|
||||
warn, Error, PduEvent, PduId, RawPduId, Result,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use ruma::{
|
||||
api::{client::error::ErrorKind, federation::event::get_room_state},
|
||||
events::room::message::RoomMessageEventContent,
|
||||
CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName,
|
||||
CanonicalJsonObject, EventId, OwnedEventId, OwnedRoomOrAliasId, RoomId, RoomVersionId,
|
||||
ServerName,
|
||||
};
|
||||
use service::rooms::{
|
||||
short::{ShortEventId, ShortRoomId},
|
||||
state_compressor::HashSetCompressStateEvent,
|
||||
};
|
||||
use service::rooms::state_compressor::HashSetCompressStateEvent;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::admin_command;
|
||||
@@ -26,7 +36,10 @@ pub(super) async fn echo(&self, message: Vec<String>) -> Result<RoomMessageEvent
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_auth_chain(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn get_auth_chain(
|
||||
&self,
|
||||
event_id: Box<EventId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let Ok(event) = self.services.rooms.timeline.get_pdu_json(&event_id).await else {
|
||||
return Ok(RoomMessageEventContent::notice_plain("Event not found."));
|
||||
};
|
||||
@@ -45,7 +58,7 @@ pub(super) async fn get_auth_chain(&self, event_id: Box<EventId>) -> Result<Room
|
||||
.rooms
|
||||
.auth_chain
|
||||
.event_ids_iter(room_id, once(event_id.as_ref()))
|
||||
.await?
|
||||
.ready_filter_map(Result::ok)
|
||||
.count()
|
||||
.await;
|
||||
|
||||
@@ -68,20 +81,26 @@ pub(super) async fn parse_pdu(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
let string = self.body[1..self.body.len().saturating_sub(1)].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||
Ok(hash) => {
|
||||
let event_id = EventId::parse(format!("${hash}"));
|
||||
| Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||
| Ok(hash) => {
|
||||
let event_id = OwnedEventId::parse(format!("${hash}"));
|
||||
|
||||
match serde_json::from_value::<PduEvent>(serde_json::to_value(value).expect("value is json")) {
|
||||
Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
match serde_json::from_value::<PduEvent>(
|
||||
serde_json::to_value(value).expect("value is json"),
|
||||
) {
|
||||
| Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\n{pdu:#?}"
|
||||
))),
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\nCould not parse event: {e}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))),
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse PDU JSON: {e:?}"
|
||||
))),
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid json in command body: {e}"
|
||||
))),
|
||||
}
|
||||
@@ -103,8 +122,9 @@ pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessage
|
||||
}
|
||||
|
||||
match pdu_json {
|
||||
Ok(json) => {
|
||||
let json_text = serde_json::to_string_pretty(&json).expect("canonical json is valid json");
|
||||
| Ok(json) => {
|
||||
let json_text =
|
||||
serde_json::to_string_pretty(&json).expect("canonical json is valid json");
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{}\n```json\n{}\n```",
|
||||
if outlier {
|
||||
@@ -115,15 +135,46 @@ pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessage
|
||||
json_text
|
||||
)))
|
||||
},
|
||||
Err(_) => Ok(RoomMessageEventContent::text_plain("PDU not found locally.")),
|
||||
| Err(_) => Ok(RoomMessageEventContent::text_plain("PDU not found locally.")),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_short_pdu(
|
||||
&self,
|
||||
shortroomid: ShortRoomId,
|
||||
shorteventid: ShortEventId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let pdu_id: RawPduId = PduId {
|
||||
shortroomid,
|
||||
shorteventid: shorteventid.into(),
|
||||
}
|
||||
.into();
|
||||
|
||||
let pdu_json = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_json_from_id(&pdu_id)
|
||||
.await;
|
||||
|
||||
match pdu_json {
|
||||
| Ok(json) => {
|
||||
let json_text =
|
||||
serde_json::to_string_pretty(&json).expect("canonical json is valid json");
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json_text}\n```",)))
|
||||
},
|
||||
| Err(_) => Ok(RoomMessageEventContent::text_plain("PDU not found locally.")),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_remote_pdu_list(
|
||||
&self, server: Box<ServerName>, force: bool,
|
||||
&self,
|
||||
server: Box<ServerName>,
|
||||
force: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self.services.globals.config.allow_federation {
|
||||
if !self.services.server.config.allow_federation {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Federation is disabled on this homeserver.",
|
||||
));
|
||||
@@ -131,8 +182,8 @@ pub(super) async fn get_remote_pdu_list(
|
||||
|
||||
if server == self.services.globals.server_name() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
|
||||
the database.",
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for \
|
||||
fetching local PDUs from the database.",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -184,9 +235,11 @@ pub(super) async fn get_remote_pdu_list(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_remote_pdu(
|
||||
&self, event_id: Box<EventId>, server: Box<ServerName>,
|
||||
&self,
|
||||
event_id: Box<EventId>,
|
||||
server: Box<ServerName>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self.services.globals.config.allow_federation {
|
||||
if !self.services.server.config.allow_federation {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Federation is disabled on this homeserver.",
|
||||
));
|
||||
@@ -194,30 +247,32 @@ pub(super) async fn get_remote_pdu(
|
||||
|
||||
if server == self.services.globals.server_name() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for \
|
||||
fetching local PDUs.",
|
||||
));
|
||||
}
|
||||
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&server,
|
||||
ruma::api::federation::event::get_event::v1::Request {
|
||||
event_id: event_id.clone().into(),
|
||||
include_unredacted_content: None,
|
||||
},
|
||||
)
|
||||
.send_federation_request(&server, ruma::api::federation::event::get_event::v1::Request {
|
||||
event_id: event_id.clone().into(),
|
||||
include_unredacted_content: None,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let json: CanonicalJsonObject = serde_json::from_str(response.pdu.get()).map_err(|e| {
|
||||
warn!(
|
||||
"Requested event ID {event_id} from server but failed to convert from RawValue to \
|
||||
CanonicalJsonObject (malformed event/response?): {e}"
|
||||
);
|
||||
Error::BadRequest(ErrorKind::Unknown, "Received response from server but failed to parse PDU")
|
||||
})?;
|
||||
| Ok(response) => {
|
||||
let json: CanonicalJsonObject =
|
||||
serde_json::from_str(response.pdu.get()).map_err(|e| {
|
||||
warn!(
|
||||
"Requested event ID {event_id} from server but failed to convert from \
|
||||
RawValue to CanonicalJsonObject (malformed event/response?): {e}"
|
||||
);
|
||||
Error::BadRequest(
|
||||
ErrorKind::Unknown,
|
||||
"Received response from server but failed to parse PDU",
|
||||
)
|
||||
})?;
|
||||
|
||||
trace!("Attempting to parse PDU: {:?}", &response.pdu);
|
||||
let _parsed_pdu = {
|
||||
@@ -229,8 +284,8 @@ pub(super) async fn get_remote_pdu(
|
||||
.await;
|
||||
|
||||
let (event_id, value, room_id) = match parsed_result {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
| Ok(t) => t,
|
||||
| Err(e) => {
|
||||
warn!("Failed to parse PDU: {e}");
|
||||
info!("Full PDU: {:?}", &response.pdu);
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -250,31 +305,36 @@ pub(super) async fn get_remote_pdu(
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
let json_text = serde_json::to_string_pretty(&json).expect("canonical json is valid json");
|
||||
let json_text =
|
||||
serde_json::to_string_pretty(&json).expect("canonical json is valid json");
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{}\n```json\n{}\n```",
|
||||
"Got PDU from specified server and handled as backfilled PDU successfully. Event body:", json_text
|
||||
"Got PDU from specified server and handled as backfilled PDU successfully. \
|
||||
Event body:",
|
||||
json_text
|
||||
)))
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Remote server did not have PDU or failed sending request to remote server: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn get_room_state(
|
||||
&self,
|
||||
room: OwnedRoomOrAliasId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let room_id = self.services.rooms.alias.resolve(&room).await?;
|
||||
let room_state: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full(&room_id)
|
||||
.await?
|
||||
.values()
|
||||
.map(PduEvent::to_state_event)
|
||||
.collect();
|
||||
.room_state_full_pdus(&room_id)
|
||||
.map_ok(PduEvent::into_state_event)
|
||||
.try_collect()
|
||||
.await?;
|
||||
|
||||
if room_state.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -285,7 +345,8 @@ pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<Ro
|
||||
let json = serde_json::to_string_pretty(&room_state).map_err(|e| {
|
||||
warn!("Failed converting room state vector in our database to pretty JSON: {e}");
|
||||
Error::bad_database(
|
||||
"Failed to convert room state events to pretty JSON, possible invalid room state events in our database",
|
||||
"Failed to convert room state events to pretty JSON, possible invalid room state \
|
||||
events in our database",
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -305,10 +366,13 @@ pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEv
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
|
||||
.send_federation_request(
|
||||
&server,
|
||||
ruma::api::federation::discovery::get_server_version::v1::Request {},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
| Ok(response) => {
|
||||
let ping_time = timer.elapsed();
|
||||
|
||||
let json_text_res = serde_json::to_string_pretty(&response.server);
|
||||
@@ -323,8 +387,11 @@ pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEv
|
||||
"Got non-JSON response which took {ping_time:?} time:\n{response:?}"
|
||||
)))
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("Failed sending federation request to specified server from ping debug command: {e}");
|
||||
| Err(e) => {
|
||||
warn!(
|
||||
"Failed sending federation request to specified server from ping debug command: \
|
||||
{e}"
|
||||
);
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed sending federation request to specified server:\n\n{e}",
|
||||
)))
|
||||
@@ -347,13 +414,17 @@ pub(super) async fn force_device_list_updates(&self) -> Result<RoomMessageEventC
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn change_log_level(
|
||||
&self,
|
||||
filter: Option<String>,
|
||||
reset: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let handles = &["console"];
|
||||
|
||||
if reset {
|
||||
let old_filter_layer = match EnvFilter::try_new(&self.services.globals.config.log) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
let old_filter_layer = match EnvFilter::try_new(&self.services.server.config.log) {
|
||||
| Ok(s) => s,
|
||||
| Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Log level from config appears to be invalid now: {e}"
|
||||
)));
|
||||
@@ -367,13 +438,13 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
|
||||
.reload
|
||||
.reload(&old_filter_layer, Some(handles))
|
||||
{
|
||||
Ok(()) => {
|
||||
| Ok(()) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully changed log level back to config value {}",
|
||||
self.services.globals.config.log
|
||||
self.services.server.config.log
|
||||
)));
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to modify and reload the global tracing log level: {e}"
|
||||
)));
|
||||
@@ -383,8 +454,8 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
|
||||
|
||||
if let Some(filter) = filter {
|
||||
let new_filter_layer = match EnvFilter::try_new(filter) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
| Ok(s) => s,
|
||||
| Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid log level filter specified: {e}"
|
||||
)));
|
||||
@@ -398,10 +469,10 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
|
||||
.reload
|
||||
.reload(&new_filter_layer, Some(handles))
|
||||
{
|
||||
Ok(()) => {
|
||||
| Ok(()) => {
|
||||
return Ok(RoomMessageEventContent::text_plain("Successfully changed log level"));
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to modify and reload the global tracing log level: {e}"
|
||||
)));
|
||||
@@ -414,7 +485,9 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn sign_json(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
@@ -423,21 +496,24 @@ pub(super) async fn sign_json(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(mut value) => {
|
||||
| Ok(mut value) => {
|
||||
self.services
|
||||
.server_keys
|
||||
.sign_json(&mut value)
|
||||
.expect("our request json is what ruma expects");
|
||||
let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
|
||||
let json_text =
|
||||
serde_json::to_string_pretty(&value).expect("canonical json is valid json");
|
||||
Ok(RoomMessageEventContent::text_plain(json_text))
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn verify_json(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
@@ -446,13 +522,13 @@ pub(super) async fn verify_json(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||
match serde_json::from_str::<CanonicalJsonObject>(&string) {
|
||||
Ok(value) => match self.services.server_keys.verify_json(&value, None).await {
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Ok(value) => match self.services.server_keys.verify_json(&value, None).await {
|
||||
| Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Signature verification failed: {e}"
|
||||
))),
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,9 +538,10 @@ pub(super) async fn verify_pdu(&self, event_id: Box<EventId>) -> Result<RoomMess
|
||||
|
||||
event.remove("event_id");
|
||||
let msg = match self.services.server_keys.verify_event(&event, None).await {
|
||||
Ok(ruma::signatures::Verified::Signatures) => "signatures OK, but content hash failed (redaction).",
|
||||
Ok(ruma::signatures::Verified::All) => "signatures and hashes OK.",
|
||||
Err(e) => return Err(e),
|
||||
| Ok(ruma::signatures::Verified::Signatures) =>
|
||||
"signatures OK, but content hash failed (redaction).",
|
||||
| Ok(ruma::signatures::Verified::All) => "signatures and hashes OK.",
|
||||
| Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain(msg))
|
||||
@@ -472,12 +549,15 @@ pub(super) async fn verify_pdu(&self, event_id: Box<EventId>) -> Result<RoomMess
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn first_pdu_in_room(
|
||||
&self,
|
||||
room_id: Box<RoomId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.globals.config.server_name, &room_id)
|
||||
.server_in_room(&self.services.server.name, &room_id)
|
||||
.await
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -498,12 +578,15 @@ pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<Roo
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn latest_pdu_in_room(
|
||||
&self,
|
||||
room_id: Box<RoomId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.globals.config.server_name, &room_id)
|
||||
.server_in_room(&self.services.server.name, &room_id)
|
||||
.await
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -525,13 +608,15 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<Ro
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(super) async fn force_set_room_state_from_server(
|
||||
&self, room_id: Box<RoomId>, server_name: Box<ServerName>,
|
||||
&self,
|
||||
room_id: Box<RoomId>,
|
||||
server_name: Box<ServerName>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.globals.config.server_name, &room_id)
|
||||
.server_in_room(&self.services.server.name, &room_id)
|
||||
.await
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -549,18 +634,16 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
|
||||
let room_version = self.services.rooms.state.get_room_version(&room_id).await?;
|
||||
|
||||
let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
|
||||
let mut state: HashMap<u64, OwnedEventId> = HashMap::new();
|
||||
|
||||
let remote_state_response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&server_name,
|
||||
get_room_state::v1::Request {
|
||||
room_id: room_id.clone().into(),
|
||||
event_id: first_pdu.event_id.clone().into(),
|
||||
},
|
||||
)
|
||||
.send_federation_request(&server_name, get_room_state::v1::Request {
|
||||
room_id: room_id.clone().into(),
|
||||
event_id: first_pdu.event_id.clone(),
|
||||
})
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
for pdu in remote_state_response.pdus.clone() {
|
||||
@@ -569,10 +652,11 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(&pdu)
|
||||
.boxed()
|
||||
.await
|
||||
{
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
| Ok(t) => t,
|
||||
| Err(e) => {
|
||||
warn!("Could not parse PDU, ignoring: {e}");
|
||||
continue;
|
||||
},
|
||||
@@ -631,7 +715,8 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.resolve_state(room_id.clone().as_ref(), &room_version, state)
|
||||
.resolve_state(&room_id, &room_version, state)
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
info!("Forcing new room state");
|
||||
@@ -646,7 +731,7 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.save_state(room_id.clone().as_ref(), new_room_state)
|
||||
.await?;
|
||||
|
||||
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
|
||||
let state_lock = self.services.rooms.state.mutex.lock(&*room_id).await;
|
||||
self.services
|
||||
.rooms
|
||||
.state
|
||||
@@ -654,8 +739,8 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
|
||||
m.room.member state"
|
||||
"Updating joined counts for room just in case (e.g. we may have found a difference in \
|
||||
the room's m.room.member state"
|
||||
);
|
||||
self.services
|
||||
.rooms
|
||||
@@ -672,9 +757,12 @@ pub(super) async fn force_set_room_state_from_server(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_signing_keys(
|
||||
&self, server_name: Option<Box<ServerName>>, notary: Option<Box<ServerName>>, query: bool,
|
||||
&self,
|
||||
server_name: Option<Box<ServerName>>,
|
||||
notary: Option<Box<ServerName>>,
|
||||
query: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
|
||||
let server_name = server_name.unwrap_or_else(|| self.services.server.name.clone().into());
|
||||
|
||||
if let Some(notary) = notary {
|
||||
let signing_keys = self
|
||||
@@ -706,8 +794,11 @@ pub(super) async fn get_signing_keys(
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_verify_keys(&self, server_name: Option<Box<ServerName>>) -> Result<RoomMessageEventContent> {
|
||||
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
|
||||
pub(super) async fn get_verify_keys(
|
||||
&self,
|
||||
server_name: Option<Box<ServerName>>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let server_name = server_name.unwrap_or_else(|| self.services.server.name.clone().into());
|
||||
|
||||
let keys = self
|
||||
.services
|
||||
@@ -727,17 +818,20 @@ pub(super) async fn get_verify_keys(&self, server_name: Option<Box<ServerName>>)
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn resolve_true_destination(
|
||||
&self, server_name: Box<ServerName>, no_cache: bool,
|
||||
&self,
|
||||
server_name: Box<ServerName>,
|
||||
no_cache: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self.services.globals.config.allow_federation {
|
||||
if !self.services.server.config.allow_federation {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Federation is disabled on this homeserver.",
|
||||
));
|
||||
}
|
||||
|
||||
if server_name == self.services.globals.config.server_name {
|
||||
if server_name == self.services.server.name {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for \
|
||||
fetching local PDUs.",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -753,19 +847,27 @@ pub(super) async fn resolve_true_destination(
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
|
||||
let html_body = conduit::alloc::memory_stats();
|
||||
pub(super) async fn memory_stats(&self, opts: Option<String>) -> Result<RoomMessageEventContent> {
|
||||
const OPTS: &str = "abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
if html_body.is_none() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"malloc stats are not supported on your compiled malloc.",
|
||||
));
|
||||
}
|
||||
let opts: String = OPTS
|
||||
.chars()
|
||||
.filter(|&c| {
|
||||
let allow_any = opts.as_ref().is_some_and(|opts| opts == "*");
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
"This command's output can only be viewed by clients that render HTML.".to_owned(),
|
||||
html_body.expect("string result"),
|
||||
))
|
||||
let allow = allow_any || opts.as_ref().is_some_and(|opts| opts.contains(c));
|
||||
|
||||
!allow
|
||||
})
|
||||
.collect();
|
||||
|
||||
let stats = conduwuit::alloc::memory_stats(&opts).unwrap_or_default();
|
||||
|
||||
self.write_str("```\n").await?;
|
||||
self.write_str(&stats).await?;
|
||||
self.write_str("\n```").await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
@@ -773,7 +875,14 @@ pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
|
||||
let out = self.services.server.metrics.runtime_metrics().map_or_else(
|
||||
|| "Runtime metrics are not available.".to_owned(),
|
||||
|metrics| format!("```rs\n{metrics:#?}\n```"),
|
||||
|metrics| {
|
||||
format!(
|
||||
"```rs\nnum_workers: {}\nnum_alive_tasks: {}\nglobal_queue_depth: {}\n```",
|
||||
metrics.num_workers(),
|
||||
metrics.num_alive_tasks(),
|
||||
metrics.global_queue_depth()
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::text_markdown(out))
|
||||
@@ -831,7 +940,7 @@ pub(super) async fn list_dependencies(&self, names: bool) -> Result<RoomMessageE
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
writeln!(out, "{name} | {version} | {feats}")?;
|
||||
writeln!(out, "| {name} | {version} | {feats} |")?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
@@ -839,21 +948,68 @@ pub(super) async fn list_dependencies(&self, names: bool) -> Result<RoomMessageE
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn database_stats(
|
||||
&self, property: Option<String>, map: Option<String>,
|
||||
&self,
|
||||
property: Option<String>,
|
||||
map: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned());
|
||||
let map_name = map.as_ref().map_or(EMPTY, String::as_str);
|
||||
let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned());
|
||||
self.services
|
||||
.db
|
||||
.iter()
|
||||
.filter(|(&name, _)| map_name.is_empty() || map_name == name)
|
||||
.try_stream()
|
||||
.try_for_each(|(&name, map)| {
|
||||
let res = map.property(&property).expect("invalid property");
|
||||
writeln!(self, "##### {name}:\n```\n{}\n```", res.trim())
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut out = String::new();
|
||||
for (name, map) in self.services.db.iter() {
|
||||
if !map_name.is_empty() && *map_name != *name {
|
||||
continue;
|
||||
}
|
||||
|
||||
let res = map.property(&property)?;
|
||||
let res = res.trim();
|
||||
writeln!(out, "##### {name}:\n```\n{res}\n```")?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
Ok(RoomMessageEventContent::notice_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn database_files(
|
||||
&self,
|
||||
map: Option<String>,
|
||||
level: Option<i32>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let mut files: Vec<_> = self.services.db.db.file_list().collect::<Result<_>>()?;
|
||||
|
||||
files.sort_by_key(|f| f.name.clone());
|
||||
|
||||
writeln!(self, "| lev | sst | keys | dels | size | column |").await?;
|
||||
writeln!(self, "| ---: | :--- | ---: | ---: | ---: | :--- |").await?;
|
||||
files
|
||||
.into_iter()
|
||||
.filter(|file| {
|
||||
map.as_deref()
|
||||
.is_none_or(|map| map == file.column_family_name)
|
||||
})
|
||||
.filter(|file| level.as_ref().is_none_or(|&level| level == file.level))
|
||||
.try_stream()
|
||||
.try_for_each(|file| {
|
||||
writeln!(
|
||||
self,
|
||||
"| {} | {:<13} | {:7}+ | {:4}- | {:9} | {} |",
|
||||
file.level,
|
||||
file.name,
|
||||
file.num_entries,
|
||||
file.num_deletions,
|
||||
file.size,
|
||||
file.column_family_name,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn trim_memory(&self) -> Result<RoomMessageEventContent> {
|
||||
conduwuit::alloc::trim(None)?;
|
||||
|
||||
writeln!(self, "done").await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain(""))
|
||||
}
|
||||
|
||||
+30
-3
@@ -2,8 +2,9 @@ mod commands;
|
||||
pub(crate) mod tester;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName};
|
||||
use service::rooms::short::{ShortEventId, ShortRoomId};
|
||||
|
||||
use self::tester::TesterCommand;
|
||||
use crate::admin_command_dispatch;
|
||||
@@ -31,12 +32,21 @@ pub(super) enum DebugCommand {
|
||||
/// the command.
|
||||
ParsePdu,
|
||||
|
||||
/// - Retrieve and print a PDU by ID from the conduwuit database
|
||||
/// - Retrieve and print a PDU by EventID from the conduwuit database
|
||||
GetPdu {
|
||||
/// An event ID (a $ followed by the base64 reference hash)
|
||||
event_id: Box<EventId>,
|
||||
},
|
||||
|
||||
/// - Retrieve and print a PDU by PduId from the conduwuit database
|
||||
GetShortPdu {
|
||||
/// Shortroomid integer
|
||||
shortroomid: ShortRoomId,
|
||||
|
||||
/// Shorteventid integer
|
||||
shorteventid: ShortEventId,
|
||||
},
|
||||
|
||||
/// - Attempts to retrieve a PDU from a remote server. Inserts it into our
|
||||
/// database/timeline if found and we do not have this PDU already
|
||||
/// (following normal event auth rules, handles it as an incoming PDU).
|
||||
@@ -181,7 +191,13 @@ pub(super) enum DebugCommand {
|
||||
},
|
||||
|
||||
/// - Print extended memory usage
|
||||
MemoryStats,
|
||||
///
|
||||
/// Optional argument is a character mask (a sequence of characters in any
|
||||
/// order) which enable additional extended statistics. Known characters are
|
||||
/// "abdeglmx". For convenience, a '*' will enable everything.
|
||||
MemoryStats {
|
||||
opts: Option<String>,
|
||||
},
|
||||
|
||||
/// - Print general tokio runtime metric totals.
|
||||
RuntimeMetrics,
|
||||
@@ -207,6 +223,17 @@ pub(super) enum DebugCommand {
|
||||
map: Option<String>,
|
||||
},
|
||||
|
||||
/// - Trim memory usage
|
||||
TrimMemory,
|
||||
|
||||
/// - List database files
|
||||
DatabaseFiles {
|
||||
map: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
level: Option<i32>,
|
||||
},
|
||||
|
||||
/// - Developer test stubs
|
||||
#[command(subcommand)]
|
||||
#[allow(non_snake_case)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use conduit::Err;
|
||||
use conduwuit::Err;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch, Result};
|
||||
@@ -31,7 +31,7 @@ async fn failure(&self) -> Result<RoomMessageEventContent> {
|
||||
#[admin_command]
|
||||
async fn tester(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain("completed"))
|
||||
Ok(RoomMessageEventContent::notice_plain("legacy"))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId,
|
||||
};
|
||||
|
||||
use crate::{admin_command, get_room_info};
|
||||
|
||||
@@ -38,7 +40,10 @@ pub(super) async fn incoming_federation(&self) -> Result<RoomMessageEventContent
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn fetch_support_well_known(
|
||||
&self,
|
||||
server_name: Box<ServerName>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let response = self
|
||||
.services
|
||||
.client
|
||||
@@ -60,16 +65,20 @@ pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>
|
||||
}
|
||||
|
||||
let json: serde_json::Value = match serde_json::from_str(&text) {
|
||||
Ok(json) => json,
|
||||
Err(_) => {
|
||||
return Ok(RoomMessageEventContent::text_plain("Response text/body is not valid JSON."));
|
||||
| Ok(json) => json,
|
||||
| Err(_) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Response text/body is not valid JSON.",
|
||||
));
|
||||
},
|
||||
};
|
||||
|
||||
let pretty_json: String = match serde_json::to_string_pretty(&json) {
|
||||
Ok(json) => json,
|
||||
Err(_) => {
|
||||
return Ok(RoomMessageEventContent::text_plain("Response text/body is not valid JSON."));
|
||||
| Ok(json) => json,
|
||||
| Err(_) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Response text/body is not valid JSON.",
|
||||
));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -79,10 +88,14 @@ pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn remote_user_in_rooms(&self, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
|
||||
if user_id.server_name() == self.services.globals.config.server_name {
|
||||
pub(super) async fn remote_user_in_rooms(
|
||||
&self,
|
||||
user_id: Box<UserId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if user_id.server_name() == self.services.server.name {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"User belongs to our server, please use `list-joined-rooms` user admin command instead.",
|
||||
"User belongs to our server, please use `list-joined-rooms` user admin command \
|
||||
instead.",
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use ruma::{RoomId, ServerName, UserId};
|
||||
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
+86
-37
@@ -1,16 +1,21 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use conduit::{debug, debug_info, debug_warn, error, info, trace, utils::time::parse_timepoint_ago, Result};
|
||||
use conduit_service::media::Dim;
|
||||
use conduwuit::{
|
||||
debug, debug_info, debug_warn, error, info, trace, utils::time::parse_timepoint_ago, Result,
|
||||
};
|
||||
use conduwuit_service::media::Dim;
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, OwnedMxcUri, OwnedServerName, ServerName,
|
||||
events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, OwnedMxcUri,
|
||||
OwnedServerName, ServerName,
|
||||
};
|
||||
|
||||
use crate::{admin_command, utils::parse_local_user_id};
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete(
|
||||
&self, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
|
||||
&self,
|
||||
mxc: Option<Box<MxcUri>>,
|
||||
event_id: Option<Box<EventId>>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if event_id.is_some() && mxc.is_some() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -52,7 +57,10 @@ pub(super) async fn delete(
|
||||
let final_url = url.to_string().replace('"', "");
|
||||
mxc_urls.push(final_url);
|
||||
} else {
|
||||
info!("Found a URL in the event ID {event_id} but did not start with mxc://, ignoring");
|
||||
info!(
|
||||
"Found a URL in the event ID {event_id} but did not start with \
|
||||
mxc://, ignoring"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,17 +75,24 @@ pub(super) async fn delete(
|
||||
debug!("Found a thumbnail_url in info key: {thumbnail_url}");
|
||||
|
||||
if thumbnail_url.to_string().starts_with("\"mxc://") {
|
||||
debug!("Pushing thumbnail URL {thumbnail_url} to list of MXCs to delete");
|
||||
let final_thumbnail_url = thumbnail_url.to_string().replace('"', "");
|
||||
debug!(
|
||||
"Pushing thumbnail URL {thumbnail_url} to list of MXCs \
|
||||
to delete"
|
||||
);
|
||||
let final_thumbnail_url =
|
||||
thumbnail_url.to_string().replace('"', "");
|
||||
mxc_urls.push(final_thumbnail_url);
|
||||
} else {
|
||||
info!(
|
||||
"Found a thumbnail URL in the event ID {event_id} but did not start with \
|
||||
mxc://, ignoring"
|
||||
"Found a thumbnail URL in the event ID {event_id} but \
|
||||
did not start with mxc://, ignoring"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
info!("No \"thumbnail_url\" key in \"info\" key, assuming no thumbnails.");
|
||||
info!(
|
||||
"No \"thumbnail_url\" key in \"info\" key, assuming no \
|
||||
thumbnails."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,8 +113,8 @@ pub(super) async fn delete(
|
||||
mxc_urls.push(final_url);
|
||||
} else {
|
||||
info!(
|
||||
"Found a URL in the event ID {event_id} but did not start with mxc://, \
|
||||
ignoring"
|
||||
"Found a URL in the event ID {event_id} but did not \
|
||||
start with mxc://, ignoring"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -109,13 +124,14 @@ pub(super) async fn delete(
|
||||
}
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Event ID does not have a \"content\" key or failed parsing the event ID JSON.",
|
||||
"Event ID does not have a \"content\" key or failed parsing the event \
|
||||
ID JSON.",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Event ID does not have a \"content\" key, this is not a message or an event type that contains \
|
||||
media.",
|
||||
"Event ID does not have a \"content\" key, this is not a message or an \
|
||||
event type that contains media.",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
@@ -126,7 +142,9 @@ pub(super) async fn delete(
|
||||
|
||||
if mxc_urls.is_empty() {
|
||||
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."));
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Parsed event ID but found no MXC URLs.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut mxc_deletion_count: usize = 0;
|
||||
@@ -138,11 +156,11 @@ pub(super) async fn delete(
|
||||
.delete(&mxc_url.as_str().try_into()?)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
| Ok(()) => {
|
||||
debug_info!("Successfully deleted {mxc_url} from filesystem and database");
|
||||
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
debug_warn!("Failed to delete {mxc_url}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
@@ -150,19 +168,22 @@ pub(super) async fn delete(
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deleted {mxc_deletion_count} total MXCs from our database and the filesystem from event ID {event_id}."
|
||||
"Deleted {mxc_deletion_count} total MXCs from our database and the filesystem from \
|
||||
event ID {event_id}."
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Please specify either an MXC using --mxc or an event ID using --event-id of the message containing an image. \
|
||||
See --help for details.",
|
||||
"Please specify either an MXC using --mxc or an event ID using --event-id of the \
|
||||
message containing an image. See --help for details.",
|
||||
))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
@@ -192,11 +213,11 @@ pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
|
||||
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(()) => {
|
||||
| Ok(()) => {
|
||||
debug_info!("Successfully deleted {mxc} from filesystem and database");
|
||||
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
debug_warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
@@ -204,14 +225,18 @@ pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"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.",
|
||||
"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, before: bool, after: bool, yes_i_want_to_delete_local_media: bool,
|
||||
&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(
|
||||
@@ -224,7 +249,12 @@ pub(super) async fn delete_past_remote_media(
|
||||
let deleted_count = self
|
||||
.services
|
||||
.media
|
||||
.delete_all_remote_media_at_after_time(duration, before, after, yes_i_want_to_delete_local_media)
|
||||
.delete_all_remote_media_at_after_time(
|
||||
duration,
|
||||
before,
|
||||
after,
|
||||
yes_i_want_to_delete_local_media,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -233,7 +263,10 @@ pub(super) async fn delete_past_remote_media(
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_all_from_user(&self, username: String) -> 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).await?;
|
||||
@@ -245,7 +278,9 @@ pub(super) async fn delete_all_from_user(&self, username: String) -> Result<Room
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_all_from_server(
|
||||
&self, server_name: Box<ServerName>, yes_i_want_to_delete_local_media: bool,
|
||||
&self,
|
||||
server_name: Box<ServerName>,
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if server_name == self.services.globals.server_name() && !yes_i_want_to_delete_local_media {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -260,20 +295,26 @@ pub(super) async fn delete_all_from_server(
|
||||
.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"));
|
||||
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 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}");
|
||||
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) && !yes_i_want_to_delete_local_media)
|
||||
|| (self.services.globals.server_is_ours(mxc_server_name)
|
||||
&& !yes_i_want_to_delete_local_media)
|
||||
{
|
||||
trace!("skipping MXC URI {mxc}");
|
||||
continue;
|
||||
@@ -282,10 +323,10 @@ pub(super) async fn delete_all_from_server(
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
|
||||
match self.services.media.delete(&mxc).await {
|
||||
Ok(()) => {
|
||||
| Ok(()) => {
|
||||
deleted_count = deleted_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
debug_warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
},
|
||||
@@ -307,7 +348,10 @@ pub(super) async fn get_file_info(&self, mxc: OwnedMxcUri) -> Result<RoomMessage
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_remote_file(
|
||||
&self, mxc: OwnedMxcUri, server: Option<OwnedServerName>, timeout: u32,
|
||||
&self,
|
||||
mxc: OwnedMxcUri,
|
||||
server: Option<OwnedServerName>,
|
||||
timeout: u32,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
let timeout = Duration::from_millis(timeout.into());
|
||||
@@ -327,7 +371,12 @@ pub(super) async fn get_remote_file(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_remote_thumbnail(
|
||||
&self, mxc: OwnedMxcUri, server: Option<OwnedServerName>, timeout: u32, width: u32, height: u32,
|
||||
&self,
|
||||
mxc: OwnedMxcUri,
|
||||
server: Option<OwnedServerName>,
|
||||
timeout: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
let timeout = Duration::from_millis(timeout.into());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use ruma::{EventId, MxcUri, OwnedMxcUri, OwnedServerName, ServerName};
|
||||
|
||||
use crate::admin_command_dispatch;
|
||||
@@ -26,23 +26,23 @@ pub(super) enum MediaCommand {
|
||||
/// filesystem. This will always ignore errors.
|
||||
DeleteList,
|
||||
|
||||
/// - 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
|
||||
/// - Deletes all remote (and optionally local) media created before or
|
||||
/// after \[duration] time using filesystem metadata first created at
|
||||
/// date, or fallback to last modified date. This will always ignore
|
||||
/// errors by default.
|
||||
DeletePastRemoteMedia {
|
||||
/// - The duration (at or after/before), e.g. "5m" to delete all media
|
||||
/// in the past or up to 5 minutes
|
||||
/// - The relative time (e.g. 30s, 5m, 7d) within which to search
|
||||
duration: String,
|
||||
|
||||
/// - Only delete media created more recently than \[duration] ago
|
||||
#[arg(long, short)]
|
||||
before: bool,
|
||||
|
||||
/// - Only delete media created after \[duration] ago
|
||||
#[arg(long, short)]
|
||||
after: bool,
|
||||
|
||||
/// Long argument to delete local media
|
||||
/// - Long argument to additionally delete local media
|
||||
#[arg(long)]
|
||||
yes_i_want_to_delete_local_media: bool,
|
||||
},
|
||||
|
||||
+9
-8
@@ -1,6 +1,7 @@
|
||||
#![recursion_limit = "192"]
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
#![allow(clippy::enum_glob_use)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
pub(crate) mod admin;
|
||||
pub(crate) mod command;
|
||||
@@ -18,12 +19,12 @@ pub(crate) mod room;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod user;
|
||||
|
||||
extern crate conduit_api as api;
|
||||
extern crate conduit_core as conduit;
|
||||
extern crate conduit_service as service;
|
||||
extern crate conduwuit_api as api;
|
||||
extern crate conduwuit_core as conduwuit;
|
||||
extern crate conduwuit_service as service;
|
||||
|
||||
pub(crate) use conduit::Result;
|
||||
pub(crate) use conduit_macros::{admin_command, admin_command_dispatch};
|
||||
pub(crate) use conduwuit::Result;
|
||||
pub(crate) use conduwuit_macros::{admin_command, admin_command_dispatch};
|
||||
|
||||
pub(crate) use crate::{
|
||||
command::Command,
|
||||
@@ -32,9 +33,9 @@ pub(crate) use crate::{
|
||||
|
||||
pub(crate) const PAGE_SIZE: usize = 100;
|
||||
|
||||
conduit::mod_ctor! {}
|
||||
conduit::mod_dtor! {}
|
||||
conduit::rustc_flags_capture! {}
|
||||
conduwuit::mod_ctor! {}
|
||||
conduwuit::mod_dtor! {}
|
||||
conduwuit::rustc_flags_capture! {}
|
||||
|
||||
/// Install the admin command processor
|
||||
pub async fn init(admin_service: &service::admin::Service) {
|
||||
|
||||
+49
-27
@@ -1,12 +1,13 @@
|
||||
use std::{
|
||||
fmt::Write,
|
||||
mem::take,
|
||||
panic::AssertUnwindSafe,
|
||||
sync::{Arc, Mutex},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
use conduit::{
|
||||
use conduwuit::{
|
||||
debug, error,
|
||||
log::{
|
||||
capture,
|
||||
@@ -17,7 +18,7 @@ use conduit::{
|
||||
utils::string::{collect_stream, common_prefix},
|
||||
warn, Error, Result,
|
||||
};
|
||||
use futures::future::FutureExt;
|
||||
use futures::{future::FutureExt, io::BufWriter, AsyncWriteExt};
|
||||
use ruma::{
|
||||
events::{
|
||||
relation::InReplyTo,
|
||||
@@ -53,8 +54,8 @@ async fn handle_command(services: Arc<Services>, command: CommandInput) -> Proce
|
||||
|
||||
async fn process_command(services: Arc<Services>, input: &CommandInput) -> ProcessorResult {
|
||||
let (command, args, body) = match parse(&services, input) {
|
||||
Err(error) => return Err(error),
|
||||
Ok(parsed) => parsed,
|
||||
| Err(error) => return Err(error),
|
||||
| Ok(parsed) => parsed,
|
||||
};
|
||||
|
||||
let context = Command {
|
||||
@@ -62,13 +63,37 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
|
||||
body: &body,
|
||||
timer: SystemTime::now(),
|
||||
reply_id: input.reply_id.as_deref(),
|
||||
output: BufWriter::new(Vec::new()).into(),
|
||||
};
|
||||
|
||||
process(&context, command, &args).await
|
||||
let (result, mut logs) = process(&context, command, &args).await;
|
||||
|
||||
let output = &mut context.output.lock().await;
|
||||
output.flush().await.expect("final flush of output stream");
|
||||
|
||||
let output =
|
||||
String::from_utf8(take(output.get_mut())).expect("invalid utf8 in command output stream");
|
||||
|
||||
match result {
|
||||
| Ok(()) if logs.is_empty() =>
|
||||
Ok(Some(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id))),
|
||||
|
||||
| Ok(()) => {
|
||||
logs.write_str(output.as_str()).expect("output buffer");
|
||||
Ok(Some(reply(RoomMessageEventContent::notice_markdown(logs), context.reply_id)))
|
||||
},
|
||||
| Err(error) => {
|
||||
write!(&mut logs, "Command failed with error:\n```\n{error:#?}\n```")
|
||||
.expect("output buffer");
|
||||
|
||||
Err(reply(RoomMessageEventContent::notice_markdown(logs), context.reply_id))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult {
|
||||
let link = "Please submit a [bug report](https://github.com/girlbossceo/conduwuit/issues/new). 🥺";
|
||||
let link =
|
||||
"Please submit a [bug report](https://github.com/girlbossceo/conduwuit/issues/new). 🥺";
|
||||
let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}");
|
||||
let content = RoomMessageEventContent::notice_markdown(msg);
|
||||
error!("Panic while processing command: {error:?}");
|
||||
@@ -76,7 +101,11 @@ fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult {
|
||||
}
|
||||
|
||||
// Parse and process a message from the admin room
|
||||
async fn process(context: &Command<'_>, command: AdminCommand, args: &[String]) -> ProcessorResult {
|
||||
async fn process(
|
||||
context: &Command<'_>,
|
||||
command: AdminCommand,
|
||||
args: &[String],
|
||||
) -> (Result, String) {
|
||||
let (capture, logs) = capture_create(context);
|
||||
|
||||
let capture_scope = capture.start();
|
||||
@@ -99,17 +128,7 @@ async fn process(context: &Command<'_>, command: AdminCommand, args: &[String])
|
||||
}
|
||||
drop(logs);
|
||||
|
||||
match result {
|
||||
Ok(content) => {
|
||||
write!(&mut output, "{0}", content.body()).expect("failed to format command result to output buffer");
|
||||
Ok(Some(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id)))
|
||||
},
|
||||
Err(error) => {
|
||||
write!(&mut output, "Command failed with error:\n```\n{error:#?}\n```")
|
||||
.expect("failed to format command result to output");
|
||||
Err(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id))
|
||||
},
|
||||
}
|
||||
(result, output)
|
||||
}
|
||||
|
||||
fn capture_create(context: &Command<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
|
||||
@@ -128,8 +147,9 @@ fn capture_create(context: &Command<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
|
||||
.and_then(LevelFilter::into_level)
|
||||
.unwrap_or(Level::DEBUG);
|
||||
|
||||
let filter =
|
||||
move |data: capture::Data<'_>| data.level() <= log_level && data.our_modules() && data.scope.contains(&"admin");
|
||||
let filter = move |data: capture::Data<'_>| {
|
||||
data.level() <= log_level && data.our_modules() && data.scope.contains(&"admin")
|
||||
};
|
||||
|
||||
let logs = Arc::new(Mutex::new(
|
||||
collect_stream(|s| markdown_table_head(s)).expect("markdown table header"),
|
||||
@@ -146,14 +166,15 @@ fn capture_create(context: &Command<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
|
||||
|
||||
// Parse chat messages from the admin room into an AdminCommand object
|
||||
fn parse<'a>(
|
||||
services: &Arc<Services>, input: &'a CommandInput,
|
||||
services: &Arc<Services>,
|
||||
input: &'a CommandInput,
|
||||
) -> Result<(AdminCommand, Vec<String>, Vec<&'a str>), CommandOutput> {
|
||||
let lines = input.command.lines().filter(|line| !line.trim().is_empty());
|
||||
let command_line = lines.clone().next().expect("command missing first line");
|
||||
let body = lines.skip(1).collect();
|
||||
match parse_command(command_line) {
|
||||
Ok((command, args)) => Ok((command, args, body)),
|
||||
Err(error) => {
|
||||
| Ok((command, args)) => Ok((command, args, body)),
|
||||
| Err(error) => {
|
||||
let message = error
|
||||
.to_string()
|
||||
.replace("server.name", services.globals.server_name().as_str());
|
||||
@@ -255,11 +276,12 @@ fn parse_line(command_line: &str) -> Vec<String> {
|
||||
argv
|
||||
}
|
||||
|
||||
fn reply(mut content: RoomMessageEventContent, reply_id: Option<&EventId>) -> RoomMessageEventContent {
|
||||
fn reply(
|
||||
mut content: RoomMessageEventContent,
|
||||
reply_id: Option<&EventId>,
|
||||
) -> RoomMessageEventContent {
|
||||
content.relates_to = reply_id.map(|event_id| Reply {
|
||||
in_reply_to: InReplyTo {
|
||||
event_id: event_id.to_owned(),
|
||||
},
|
||||
in_reply_to: InReplyTo { event_id: event_id.to_owned() },
|
||||
});
|
||||
|
||||
content
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId, UserId};
|
||||
|
||||
use crate::Command;
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/account_data.rs
|
||||
pub(crate) enum AccountDataCommand {
|
||||
@@ -19,7 +20,7 @@ pub(crate) enum AccountDataCommand {
|
||||
},
|
||||
|
||||
/// - Searches the account data for a specific kind.
|
||||
Get {
|
||||
AccountDataGet {
|
||||
/// Full user ID
|
||||
user_id: Box<UserId>,
|
||||
/// Account data event type
|
||||
@@ -29,43 +30,43 @@ pub(crate) enum AccountDataCommand {
|
||||
},
|
||||
}
|
||||
|
||||
/// All the getters and iterators from src/database/key_value/account_data.rs
|
||||
pub(super) async fn process(subcommand: AccountDataCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
#[admin_command]
|
||||
async fn changes_since(
|
||||
&self,
|
||||
user_id: Box<UserId>,
|
||||
since: u64,
|
||||
room_id: Option<Box<RoomId>>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = self
|
||||
.services
|
||||
.account_data
|
||||
.changes_since(room_id.as_deref(), &user_id, since, None)
|
||||
.collect()
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
match subcommand {
|
||||
AccountDataCommand::ChangesSince {
|
||||
user_id,
|
||||
since,
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.account_data
|
||||
.changes_since(room_id.as_deref(), &user_id, since)
|
||||
.collect()
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
AccountDataCommand::Get {
|
||||
user_id,
|
||||
kind,
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.account_data
|
||||
.get_raw(room_id.as_deref(), &user_id, &kind)
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn account_data_get(
|
||||
&self,
|
||||
user_id: Box<UserId>,
|
||||
kind: String,
|
||||
room_id: Option<Box<RoomId>>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = self
|
||||
.services
|
||||
.account_data
|
||||
.get_raw(room_id.as_deref(), &user_id, &kind)
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use conduwuit::Result;
|
||||
|
||||
use crate::Command;
|
||||
|
||||
@@ -18,30 +17,25 @@ pub(crate) enum AppserviceCommand {
|
||||
}
|
||||
|
||||
/// All the getters and iterators from src/database/key_value/appservice.rs
|
||||
pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
AppserviceCommand::GetRegistration {
|
||||
appservice_id,
|
||||
} => {
|
||||
| AppserviceCommand::GetRegistration { appservice_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.appservice.get_registration(&appservice_id).await;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
AppserviceCommand::All => {
|
||||
| AppserviceCommand::All => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.appservice.all().await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
+12
-21
@@ -1,6 +1,6 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, ServerName};
|
||||
use conduwuit::Result;
|
||||
use ruma::ServerName;
|
||||
|
||||
use crate::Command;
|
||||
|
||||
@@ -21,47 +21,38 @@ pub(crate) enum GlobalsCommand {
|
||||
}
|
||||
|
||||
/// All the getters and iterators from src/database/key_value/globals.rs
|
||||
pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
GlobalsCommand::DatabaseVersion => {
|
||||
| GlobalsCommand::DatabaseVersion => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.globals.db.database_version().await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
GlobalsCommand::CurrentCount => {
|
||||
| GlobalsCommand::CurrentCount => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.globals.db.current_count();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
GlobalsCommand::LastCheckForUpdatesId => {
|
||||
| GlobalsCommand::LastCheckForUpdatesId => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.updates.last_check_for_updates_id().await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
GlobalsCommand::SigningKeysFor {
|
||||
origin,
|
||||
} => {
|
||||
| GlobalsCommand::SigningKeysFor { origin } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.server_keys.verify_keys_for(&origin).await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
+20
-3
@@ -3,19 +3,24 @@ mod appservice;
|
||||
mod globals;
|
||||
mod presence;
|
||||
mod pusher;
|
||||
mod raw;
|
||||
mod resolver;
|
||||
mod room_alias;
|
||||
mod room_state_cache;
|
||||
mod room_timeline;
|
||||
mod sending;
|
||||
mod short;
|
||||
mod users;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
|
||||
use self::{
|
||||
account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
|
||||
presence::PresenceCommand, pusher::PusherCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
|
||||
room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand,
|
||||
presence::PresenceCommand, pusher::PusherCommand, raw::RawCommand, resolver::ResolverCommand,
|
||||
room_alias::RoomAliasCommand, room_state_cache::RoomStateCacheCommand,
|
||||
room_timeline::RoomTimelineCommand, sending::SendingCommand, short::ShortCommand,
|
||||
users::UsersCommand,
|
||||
};
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
@@ -43,6 +48,10 @@ pub(super) enum QueryCommand {
|
||||
#[command(subcommand)]
|
||||
RoomStateCache(RoomStateCacheCommand),
|
||||
|
||||
/// - rooms/timeline iterators and getters
|
||||
#[command(subcommand)]
|
||||
RoomTimeline(RoomTimelineCommand),
|
||||
|
||||
/// - globals.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
Globals(GlobalsCommand),
|
||||
@@ -62,4 +71,12 @@ pub(super) enum QueryCommand {
|
||||
/// - pusher service
|
||||
#[command(subcommand)]
|
||||
Pusher(PusherCommand),
|
||||
|
||||
/// - short service
|
||||
#[command(subcommand)]
|
||||
Short(ShortCommand),
|
||||
|
||||
/// - raw service
|
||||
#[command(subcommand)]
|
||||
Raw(RawCommand),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, UserId};
|
||||
use ruma::UserId;
|
||||
|
||||
use crate::Command;
|
||||
|
||||
@@ -23,24 +23,18 @@ pub(crate) enum PresenceCommand {
|
||||
}
|
||||
|
||||
/// All the getters and iterators in key_value/presence.rs
|
||||
pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>) -> Result {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
PresenceCommand::GetPresence {
|
||||
user_id,
|
||||
} => {
|
||||
| PresenceCommand::GetPresence { user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.presence.db.get_presence(&user_id).await;
|
||||
let results = services.presence.get_presence(&user_id).await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
PresenceCommand::PresenceSince {
|
||||
since,
|
||||
} => {
|
||||
| PresenceCommand::PresenceSince { since } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<(_, _, _)> = services
|
||||
.presence
|
||||
@@ -50,9 +44,8 @@ pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>)
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, UserId};
|
||||
use conduwuit::Result;
|
||||
use ruma::UserId;
|
||||
|
||||
use crate::Command;
|
||||
|
||||
@@ -13,20 +13,17 @@ pub(crate) enum PusherCommand {
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) async fn process(subcommand: PusherCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(subcommand: PusherCommand, context: &Command<'_>) -> Result {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
PusherCommand::GetPushers {
|
||||
user_id,
|
||||
} => {
|
||||
| PusherCommand::GetPushers { user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.pusher.get_pushers(&user_id).await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -0,0 +1,576 @@
|
||||
use std::{borrow::Cow, collections::BTreeMap, ops::Deref};
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduwuit::{
|
||||
apply, at, is_zero,
|
||||
utils::{
|
||||
stream::{ReadyExt, TryIgnore, TryParallelExt},
|
||||
string::EMPTY,
|
||||
IterStream,
|
||||
},
|
||||
Err, Result,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
/// Query tables from database
|
||||
pub(crate) enum RawCommand {
|
||||
/// - List database maps
|
||||
RawMaps,
|
||||
|
||||
/// - Raw database query
|
||||
RawGet {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
/// Key
|
||||
key: String,
|
||||
},
|
||||
|
||||
/// - Raw database delete (for string keys)
|
||||
RawDel {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
/// Key
|
||||
key: String,
|
||||
},
|
||||
|
||||
/// - Raw database keys iteration
|
||||
RawKeys {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
/// Key prefix
|
||||
prefix: Option<String>,
|
||||
},
|
||||
|
||||
/// - Raw database key size breakdown
|
||||
RawKeysSizes {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
/// Key prefix
|
||||
prefix: Option<String>,
|
||||
},
|
||||
|
||||
/// - Raw database keys total bytes
|
||||
RawKeysTotal {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
/// Key prefix
|
||||
prefix: Option<String>,
|
||||
},
|
||||
|
||||
/// - Raw database values size breakdown
|
||||
RawValsSizes {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
/// Key prefix
|
||||
prefix: Option<String>,
|
||||
},
|
||||
|
||||
/// - Raw database values total bytes
|
||||
RawValsTotal {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
/// Key prefix
|
||||
prefix: Option<String>,
|
||||
},
|
||||
|
||||
/// - Raw database items iteration
|
||||
RawIter {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
/// Key prefix
|
||||
prefix: Option<String>,
|
||||
},
|
||||
|
||||
/// - Raw database keys iteration
|
||||
RawKeysFrom {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
/// Lower-bound
|
||||
start: String,
|
||||
|
||||
/// Limit
|
||||
#[arg(short, long)]
|
||||
limit: Option<usize>,
|
||||
},
|
||||
|
||||
/// - Raw database items iteration
|
||||
RawIterFrom {
|
||||
/// Map name
|
||||
map: String,
|
||||
|
||||
/// Lower-bound
|
||||
start: String,
|
||||
|
||||
/// Limit
|
||||
#[arg(short, long)]
|
||||
limit: Option<usize>,
|
||||
},
|
||||
|
||||
/// - Raw database record count
|
||||
RawCount {
|
||||
/// Map name
|
||||
map: Option<String>,
|
||||
|
||||
/// Key prefix
|
||||
prefix: Option<String>,
|
||||
},
|
||||
|
||||
/// - Compact database
|
||||
Compact {
|
||||
#[arg(short, long, alias("column"))]
|
||||
map: Option<Vec<String>>,
|
||||
|
||||
#[arg(long)]
|
||||
start: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
stop: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
from: Option<usize>,
|
||||
|
||||
#[arg(long)]
|
||||
into: Option<usize>,
|
||||
|
||||
/// There is one compaction job per column; then this controls how many
|
||||
/// columns are compacted in parallel. If zero, one compaction job is
|
||||
/// still run at a time here, but in exclusive-mode blocking any other
|
||||
/// automatic compaction jobs until complete.
|
||||
#[arg(long)]
|
||||
parallelism: Option<usize>,
|
||||
|
||||
#[arg(long, default_value("false"))]
|
||||
exhaustive: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn compact(
|
||||
&self,
|
||||
map: Option<Vec<String>>,
|
||||
start: Option<String>,
|
||||
stop: Option<String>,
|
||||
from: Option<usize>,
|
||||
into: Option<usize>,
|
||||
parallelism: Option<usize>,
|
||||
exhaustive: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
use conduwuit_database::compact::Options;
|
||||
|
||||
let default_all_maps = map
|
||||
.is_none()
|
||||
.then(|| {
|
||||
self.services
|
||||
.db
|
||||
.keys()
|
||||
.map(Deref::deref)
|
||||
.map(ToOwned::to_owned)
|
||||
})
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let maps: Vec<_> = map
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.chain(default_all_maps)
|
||||
.map(|map| self.services.db.get(&map))
|
||||
.filter_map(Result::ok)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if maps.is_empty() {
|
||||
return Err!("--map argument invalid. not found in database");
|
||||
}
|
||||
|
||||
let range = (
|
||||
start.as_ref().map(String::as_bytes).map(Into::into),
|
||||
stop.as_ref().map(String::as_bytes).map(Into::into),
|
||||
);
|
||||
|
||||
let options = Options {
|
||||
range,
|
||||
level: (from, into),
|
||||
exclusive: parallelism.is_some_and(is_zero!()),
|
||||
exhaustive,
|
||||
};
|
||||
|
||||
let runtime = self.services.server.runtime().clone();
|
||||
let parallelism = parallelism.unwrap_or(1);
|
||||
let results = maps
|
||||
.into_iter()
|
||||
.try_stream()
|
||||
.paralleln_and_then(runtime, parallelism, move |map| {
|
||||
map.compact_blocking(options.clone())?;
|
||||
Ok(map.name().to_owned())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let timer = Instant::now();
|
||||
let results = results.await;
|
||||
let query_time = timer.elapsed();
|
||||
self.write_str(&format!("Jobs completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"))
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_count(
|
||||
&self,
|
||||
map: Option<String>,
|
||||
prefix: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let prefix = prefix.as_deref().unwrap_or(EMPTY);
|
||||
|
||||
let default_all_maps = map
|
||||
.is_none()
|
||||
.then(|| self.services.db.keys().map(Deref::deref))
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let maps: Vec<_> = map
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.chain(default_all_maps)
|
||||
.map(|map| self.services.db.get(map))
|
||||
.filter_map(Result::ok)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let timer = Instant::now();
|
||||
let count = maps
|
||||
.iter()
|
||||
.stream()
|
||||
.then(|map| map.raw_count_prefix(&prefix))
|
||||
.ready_fold(0_usize, usize::saturating_add)
|
||||
.await;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{count:#?}\n```"))
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_keys(
|
||||
&self,
|
||||
map: String,
|
||||
prefix: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
writeln!(self, "```").boxed().await?;
|
||||
|
||||
let map = self.services.db.get(map.as_str())?;
|
||||
let timer = Instant::now();
|
||||
prefix
|
||||
.as_deref()
|
||||
.map_or_else(|| map.raw_keys().boxed(), |prefix| map.raw_keys_prefix(prefix).boxed())
|
||||
.map_ok(String::from_utf8_lossy)
|
||||
.try_for_each(|str| writeln!(self, "{str:?}"))
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
let out = format!("\n```\n\nQuery completed in {query_time:?}");
|
||||
self.write_str(out.as_str()).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_keys_sizes(
|
||||
&self,
|
||||
map: Option<String>,
|
||||
prefix: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let prefix = prefix.as_deref().unwrap_or(EMPTY);
|
||||
|
||||
let default_all_maps = map
|
||||
.is_none()
|
||||
.then(|| self.services.db.keys().map(Deref::deref))
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let maps: Vec<_> = map
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.chain(default_all_maps)
|
||||
.map(|map| self.services.db.get(map))
|
||||
.filter_map(Result::ok)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let timer = Instant::now();
|
||||
let result = maps
|
||||
.iter()
|
||||
.stream()
|
||||
.map(|map| map.raw_keys_prefix(&prefix))
|
||||
.flatten()
|
||||
.ignore_err()
|
||||
.map(<[u8]>::len)
|
||||
.ready_fold_default(|mut map: BTreeMap<_, usize>, len| {
|
||||
let entry = map.entry(len).or_default();
|
||||
*entry = entry.saturating_add(1);
|
||||
map
|
||||
})
|
||||
.await;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
let result = format!("```\n{result:#?}\n```\n\nQuery completed in {query_time:?}");
|
||||
self.write_str(result.as_str()).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_keys_total(
|
||||
&self,
|
||||
map: Option<String>,
|
||||
prefix: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let prefix = prefix.as_deref().unwrap_or(EMPTY);
|
||||
|
||||
let default_all_maps = map
|
||||
.is_none()
|
||||
.then(|| self.services.db.keys().map(Deref::deref))
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let maps: Vec<_> = map
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.chain(default_all_maps)
|
||||
.map(|map| self.services.db.get(map))
|
||||
.filter_map(Result::ok)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let timer = Instant::now();
|
||||
let result = maps
|
||||
.iter()
|
||||
.stream()
|
||||
.map(|map| map.raw_keys_prefix(&prefix))
|
||||
.flatten()
|
||||
.ignore_err()
|
||||
.map(<[u8]>::len)
|
||||
.ready_fold_default(|acc: usize, len| acc.saturating_add(len))
|
||||
.await;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
self.write_str(&format!("```\n{result:#?}\n\n```\n\nQuery completed in {query_time:?}"))
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_vals_sizes(
|
||||
&self,
|
||||
map: Option<String>,
|
||||
prefix: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let prefix = prefix.as_deref().unwrap_or(EMPTY);
|
||||
|
||||
let default_all_maps = map
|
||||
.is_none()
|
||||
.then(|| self.services.db.keys().map(Deref::deref))
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let maps: Vec<_> = map
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.chain(default_all_maps)
|
||||
.map(|map| self.services.db.get(map))
|
||||
.filter_map(Result::ok)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let timer = Instant::now();
|
||||
let result = maps
|
||||
.iter()
|
||||
.stream()
|
||||
.map(|map| map.raw_stream_prefix(&prefix))
|
||||
.flatten()
|
||||
.ignore_err()
|
||||
.map(at!(1))
|
||||
.map(<[u8]>::len)
|
||||
.ready_fold_default(|mut map: BTreeMap<_, usize>, len| {
|
||||
let entry = map.entry(len).or_default();
|
||||
*entry = entry.saturating_add(1);
|
||||
map
|
||||
})
|
||||
.await;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
let result = format!("```\n{result:#?}\n```\n\nQuery completed in {query_time:?}");
|
||||
self.write_str(result.as_str()).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_vals_total(
|
||||
&self,
|
||||
map: Option<String>,
|
||||
prefix: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let prefix = prefix.as_deref().unwrap_or(EMPTY);
|
||||
|
||||
let default_all_maps = map
|
||||
.is_none()
|
||||
.then(|| self.services.db.keys().map(Deref::deref))
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let maps: Vec<_> = map
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.chain(default_all_maps)
|
||||
.map(|map| self.services.db.get(map))
|
||||
.filter_map(Result::ok)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let timer = Instant::now();
|
||||
let result = maps
|
||||
.iter()
|
||||
.stream()
|
||||
.map(|map| map.raw_stream_prefix(&prefix))
|
||||
.flatten()
|
||||
.ignore_err()
|
||||
.map(at!(1))
|
||||
.map(<[u8]>::len)
|
||||
.ready_fold_default(|acc: usize, len| acc.saturating_add(len))
|
||||
.await;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
self.write_str(&format!("```\n{result:#?}\n\n```\n\nQuery completed in {query_time:?}"))
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_iter(
|
||||
&self,
|
||||
map: String,
|
||||
prefix: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
writeln!(self, "```").await?;
|
||||
|
||||
let map = self.services.db.get(&map)?;
|
||||
let timer = Instant::now();
|
||||
prefix
|
||||
.as_deref()
|
||||
.map_or_else(|| map.raw_stream().boxed(), |prefix| map.raw_stream_prefix(prefix).boxed())
|
||||
.map_ok(apply!(2, String::from_utf8_lossy))
|
||||
.map_ok(apply!(2, Cow::into_owned))
|
||||
.try_for_each(|keyval| writeln!(self, "{keyval:?}"))
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}"))
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_keys_from(
|
||||
&self,
|
||||
map: String,
|
||||
start: String,
|
||||
limit: Option<usize>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
writeln!(self, "```").await?;
|
||||
|
||||
let map = self.services.db.get(&map)?;
|
||||
let timer = Instant::now();
|
||||
map.raw_keys_from(&start)
|
||||
.map_ok(String::from_utf8_lossy)
|
||||
.take(limit.unwrap_or(usize::MAX))
|
||||
.try_for_each(|str| writeln!(self, "{str:?}"))
|
||||
.boxed()
|
||||
.await?;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}"))
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_iter_from(
|
||||
&self,
|
||||
map: String,
|
||||
start: String,
|
||||
limit: Option<usize>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let map = self.services.db.get(&map)?;
|
||||
let timer = Instant::now();
|
||||
let result = map
|
||||
.raw_stream_from(&start)
|
||||
.map_ok(apply!(2, String::from_utf8_lossy))
|
||||
.map_ok(apply!(2, Cow::into_owned))
|
||||
.take(limit.unwrap_or(usize::MAX))
|
||||
.try_collect::<Vec<(String, String)>>()
|
||||
.await?;
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_del(&self, map: String, key: String) -> Result<RoomMessageEventContent> {
|
||||
let map = self.services.db.get(&map)?;
|
||||
let timer = Instant::now();
|
||||
map.remove(&key);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Operation completed in {query_time:?}"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_get(&self, map: String, key: String) -> Result<RoomMessageEventContent> {
|
||||
let map = self.services.db.get(&map)?;
|
||||
let timer = Instant::now();
|
||||
let handle = map.get(&key).await?;
|
||||
let query_time = timer.elapsed();
|
||||
let result = String::from_utf8_lossy(&handle);
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{result:?}\n```"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn raw_maps(&self) -> Result<RoomMessageEventContent> {
|
||||
let list: Vec<_> = self.services.db.iter().map(at!(0)).copied().collect();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!("{list:#?}")))
|
||||
}
|
||||
+38
-58
@@ -1,7 +1,6 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::{utils::time, Result};
|
||||
use conduwuit::{utils::time, Result};
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
@@ -22,73 +21,54 @@ pub(crate) enum ResolverCommand {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn destinations_cache(&self, server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> {
|
||||
async fn destinations_cache(
|
||||
&self,
|
||||
server_name: Option<OwnedServerName>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
use service::resolver::cache::CachedDest;
|
||||
|
||||
let mut out = String::new();
|
||||
writeln!(out, "| Server Name | Destination | Hostname | Expires |")?;
|
||||
writeln!(out, "| ----------- | ----------- | -------- | ------- |")?;
|
||||
let row = |(
|
||||
name,
|
||||
&CachedDest {
|
||||
ref dest,
|
||||
ref host,
|
||||
expire,
|
||||
},
|
||||
)| {
|
||||
writeln!(self, "| Server Name | Destination | Hostname | Expires |").await?;
|
||||
writeln!(self, "| ----------- | ----------- | -------- | ------- |").await?;
|
||||
|
||||
let mut destinations = self.services.resolver.cache.destinations().boxed();
|
||||
|
||||
while let Some((name, CachedDest { dest, host, expire })) = destinations.next().await {
|
||||
if let Some(server_name) = server_name.as_ref() {
|
||||
if name != server_name {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let expire = time::format(expire, "%+");
|
||||
writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line");
|
||||
};
|
||||
|
||||
let map = self
|
||||
.services
|
||||
.resolver
|
||||
.cache
|
||||
.destinations
|
||||
.read()
|
||||
.expect("locked");
|
||||
|
||||
if let Some(server_name) = server_name.as_ref() {
|
||||
map.get_key_value(server_name).map(row);
|
||||
} else {
|
||||
map.iter().for_each(row);
|
||||
self.write_str(&format!("| {name} | {dest} | {host} | {expire} |\n"))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
Ok(RoomMessageEventContent::notice_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn overrides_cache(&self, server_name: Option<String>) -> Result<RoomMessageEventContent> {
|
||||
use service::resolver::cache::CachedOverride;
|
||||
|
||||
let mut out = String::new();
|
||||
writeln!(out, "| Server Name | IP | Port | Expires |")?;
|
||||
writeln!(out, "| ----------- | --- | ----:| ------- |")?;
|
||||
let row = |(
|
||||
name,
|
||||
&CachedOverride {
|
||||
ref ips,
|
||||
port,
|
||||
expire,
|
||||
},
|
||||
)| {
|
||||
writeln!(self, "| Server Name | IP | Port | Expires | Overriding |").await?;
|
||||
writeln!(self, "| ----------- | --- | ----:| ------- | ---------- |").await?;
|
||||
|
||||
let mut overrides = self.services.resolver.cache.overrides().boxed();
|
||||
|
||||
while let Some((name, CachedOverride { ips, port, expire, overriding })) =
|
||||
overrides.next().await
|
||||
{
|
||||
if let Some(server_name) = server_name.as_ref() {
|
||||
if name != server_name {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let expire = time::format(expire, "%+");
|
||||
writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line");
|
||||
};
|
||||
|
||||
let map = self
|
||||
.services
|
||||
.resolver
|
||||
.cache
|
||||
.overrides
|
||||
.read()
|
||||
.expect("locked");
|
||||
|
||||
if let Some(server_name) = server_name.as_ref() {
|
||||
map.get_key_value(server_name).map(row);
|
||||
} else {
|
||||
map.iter().for_each(row);
|
||||
self.write_str(&format!("| {name} | {ips:?} | {port} | {expire} | {overriding:?} |\n"))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
Ok(RoomMessageEventContent::notice_plain(""))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
|
||||
use ruma::{RoomAliasId, RoomId};
|
||||
|
||||
use crate::Command;
|
||||
|
||||
@@ -24,24 +24,18 @@ pub(crate) enum RoomAliasCommand {
|
||||
}
|
||||
|
||||
/// All the getters and iterators in src/database/key_value/rooms/alias.rs
|
||||
pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>) -> Result {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
RoomAliasCommand::ResolveLocalAlias {
|
||||
alias,
|
||||
} => {
|
||||
| RoomAliasCommand::ResolveLocalAlias { alias } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.rooms.alias.resolve_local_alias(&alias).await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
|
||||
},
|
||||
RoomAliasCommand::LocalAliasesForRoom {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomAliasCommand::LocalAliasesForRoom { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let aliases: Vec<_> = services
|
||||
.rooms
|
||||
@@ -52,11 +46,9 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>)
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```")
|
||||
},
|
||||
RoomAliasCommand::AllLocalAliases => {
|
||||
| RoomAliasCommand::AllLocalAliases => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let aliases = services
|
||||
.rooms
|
||||
@@ -67,9 +59,8 @@ pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>)
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
|
||||
)))
|
||||
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```")
|
||||
},
|
||||
}
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::{Error, Result};
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
|
||||
|
||||
@@ -76,16 +76,11 @@ pub(crate) enum RoomStateCacheCommand {
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) async fn process(
|
||||
subcommand: RoomStateCacheCommand, context: &Command<'_>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(subcommand: RoomStateCacheCommand, context: &Command<'_>) -> Result {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
RoomStateCacheCommand::ServerInRoom {
|
||||
server,
|
||||
room_id,
|
||||
} => {
|
||||
let c = match subcommand {
|
||||
| RoomStateCacheCommand::ServerInRoom { server, room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = services
|
||||
.rooms
|
||||
@@ -94,13 +89,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomServers {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomServers { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -111,13 +104,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::ServerRooms {
|
||||
server,
|
||||
} => {
|
||||
| RoomStateCacheCommand::ServerRooms { server } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -128,13 +119,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomMembers {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomMembers { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -145,13 +134,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::LocalUsersInRoom {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::LocalUsersInRoom { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -162,13 +149,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::ActiveLocalUsersInRoom {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::ActiveLocalUsersInRoom { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -179,24 +164,20 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomJoinedCount {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomJoinedCount { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.rooms.state_cache.room_joined_count(&room_id).await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomInvitedCount {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomInvitedCount { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
@@ -205,13 +186,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomUserOnceJoined {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomUserOnceJoined { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -222,13 +201,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomMembersInvited {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomMembersInvited { room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -239,14 +216,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::GetInviteCount {
|
||||
room_id,
|
||||
user_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::GetInviteCount { room_id, user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
@@ -255,14 +229,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::GetLeftCount {
|
||||
room_id,
|
||||
user_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::GetLeftCount { room_id, user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
@@ -271,13 +242,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomsJoined {
|
||||
user_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomsJoined { user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -288,13 +257,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomsInvited {
|
||||
user_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomsInvited { user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -304,13 +271,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomsLeft {
|
||||
user_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::RoomsLeft { user_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
@@ -320,14 +285,11 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::InviteState {
|
||||
user_id,
|
||||
room_id,
|
||||
} => {
|
||||
| RoomStateCacheCommand::InviteState { user_id, room_id } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
@@ -336,9 +298,13 @@ pub(super) async fn process(
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
Result::<_, Error>::Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}?;
|
||||
|
||||
context.write_str(c.body()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
use clap::Subcommand;
|
||||
use conduwuit::{utils::stream::TryTools, PduCount, Result};
|
||||
use futures::TryStreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId};
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// Query tables from database
|
||||
pub(crate) enum RoomTimelineCommand {
|
||||
Pdus {
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
|
||||
from: Option<String>,
|
||||
|
||||
#[arg(short, long)]
|
||||
limit: Option<usize>,
|
||||
},
|
||||
|
||||
Last {
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
|
||||
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
|
||||
|
||||
let result = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.last_timeline_count(None, &room_id)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!("{result:#?}")))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn pdus(
|
||||
&self,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
from: Option<String>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
|
||||
|
||||
let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?;
|
||||
|
||||
let result: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(None, &room_id, from)
|
||||
.try_take(limit.unwrap_or(3))
|
||||
.try_collect()
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!("{result:#?}")))
|
||||
}
|
||||
+58
-42
@@ -1,5 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, ServerName, UserId};
|
||||
use service::sending::Destination;
|
||||
@@ -62,11 +62,21 @@ pub(crate) enum SendingCommand {
|
||||
}
|
||||
|
||||
/// All the getters and iterators in key_value/sending.rs
|
||||
pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -> Result {
|
||||
let c = reprocess(subcommand, context).await?;
|
||||
context.write_str(c.body()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// All the getters and iterators in key_value/sending.rs
|
||||
pub(super) async fn reprocess(
|
||||
subcommand: SendingCommand,
|
||||
context: &Command<'_>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
SendingCommand::ActiveRequests => {
|
||||
| SendingCommand::ActiveRequests => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.sending.db.active_requests();
|
||||
let active_requests = results.collect::<Vec<_>>().await;
|
||||
@@ -76,25 +86,29 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
|
||||
)))
|
||||
},
|
||||
SendingCommand::QueuedRequests {
|
||||
| SendingCommand::QueuedRequests {
|
||||
appservice_id,
|
||||
server_name,
|
||||
user_id,
|
||||
push_key,
|
||||
} => {
|
||||
if appservice_id.is_none() && server_name.is_none() && user_id.is_none() && push_key.is_none() {
|
||||
if appservice_id.is_none()
|
||||
&& server_name.is_none()
|
||||
&& user_id.is_none()
|
||||
&& push_key.is_none()
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via arguments. See \
|
||||
--help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. See --help for more details.",
|
||||
));
|
||||
}
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = match (appservice_id, server_name, user_id, push_key) {
|
||||
(Some(appservice_id), None, None, None) => {
|
||||
| (Some(appservice_id), None, None, None) => {
|
||||
if appservice_id.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via \
|
||||
arguments. See --help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. See --help for more details.",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -103,15 +117,15 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
|
||||
.db
|
||||
.queued_requests(&Destination::Appservice(appservice_id))
|
||||
},
|
||||
(None, Some(server_name), None, None) => services
|
||||
| (None, Some(server_name), None, None) => services
|
||||
.sending
|
||||
.db
|
||||
.queued_requests(&Destination::Normal(server_name.into())),
|
||||
(None, None, Some(user_id), Some(push_key)) => {
|
||||
.queued_requests(&Destination::Federation(server_name.into())),
|
||||
| (None, None, Some(user_id), Some(push_key)) => {
|
||||
if push_key.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via \
|
||||
arguments. See --help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. See --help for more details.",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -120,16 +134,16 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
|
||||
.db
|
||||
.queued_requests(&Destination::Push(user_id.into(), push_key))
|
||||
},
|
||||
(Some(_), Some(_), Some(_), Some(_)) => {
|
||||
| (Some(_), Some(_), Some(_), Some(_)) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via arguments. \
|
||||
Not all of them See --help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. Not all of them See --help for more details.",
|
||||
));
|
||||
},
|
||||
_ => {
|
||||
| _ => {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via arguments. \
|
||||
See --help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. See --help for more details.",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -141,26 +155,30 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```"
|
||||
)))
|
||||
},
|
||||
SendingCommand::ActiveRequestsFor {
|
||||
| SendingCommand::ActiveRequestsFor {
|
||||
appservice_id,
|
||||
server_name,
|
||||
user_id,
|
||||
push_key,
|
||||
} => {
|
||||
if appservice_id.is_none() && server_name.is_none() && user_id.is_none() && push_key.is_none() {
|
||||
if appservice_id.is_none()
|
||||
&& server_name.is_none()
|
||||
&& user_id.is_none()
|
||||
&& push_key.is_none()
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via arguments. See \
|
||||
--help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. See --help for more details.",
|
||||
));
|
||||
}
|
||||
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = match (appservice_id, server_name, user_id, push_key) {
|
||||
(Some(appservice_id), None, None, None) => {
|
||||
| (Some(appservice_id), None, None, None) => {
|
||||
if appservice_id.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via \
|
||||
arguments. See --help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. See --help for more details.",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -169,15 +187,15 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
|
||||
.db
|
||||
.active_requests_for(&Destination::Appservice(appservice_id))
|
||||
},
|
||||
(None, Some(server_name), None, None) => services
|
||||
| (None, Some(server_name), None, None) => services
|
||||
.sending
|
||||
.db
|
||||
.active_requests_for(&Destination::Normal(server_name.into())),
|
||||
(None, None, Some(user_id), Some(push_key)) => {
|
||||
.active_requests_for(&Destination::Federation(server_name.into())),
|
||||
| (None, None, Some(user_id), Some(push_key)) => {
|
||||
if push_key.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via \
|
||||
arguments. See --help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. See --help for more details.",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -186,16 +204,16 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
|
||||
.db
|
||||
.active_requests_for(&Destination::Push(user_id.into(), push_key))
|
||||
},
|
||||
(Some(_), Some(_), Some(_), Some(_)) => {
|
||||
| (Some(_), Some(_), Some(_), Some(_)) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via arguments. \
|
||||
Not all of them See --help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. Not all of them See --help for more details.",
|
||||
));
|
||||
},
|
||||
_ => {
|
||||
| _ => {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"An appservice ID, server name, or a user ID with push key must be specified via arguments. \
|
||||
See --help for more details.",
|
||||
"An appservice ID, server name, or a user ID with push key must be \
|
||||
specified via arguments. See --help for more details.",
|
||||
));
|
||||
},
|
||||
};
|
||||
@@ -207,9 +225,7 @@ pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
|
||||
)))
|
||||
},
|
||||
SendingCommand::GetLatestEduCount {
|
||||
server_name,
|
||||
} => {
|
||||
| SendingCommand::GetLatestEduCount { server_name } => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.sending.db.get_latest_educount(&server_name).await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use clap::Subcommand;
|
||||
use conduwuit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedEventId, OwnedRoomOrAliasId};
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// Query tables from database
|
||||
pub(crate) enum ShortCommand {
|
||||
ShortEventId {
|
||||
event_id: OwnedEventId,
|
||||
},
|
||||
|
||||
ShortRoomId {
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn short_event_id(
|
||||
&self,
|
||||
event_id: OwnedEventId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let shortid = self
|
||||
.services
|
||||
.rooms
|
||||
.short
|
||||
.get_shorteventid(&event_id)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!("{shortid:#?}")))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn short_room_id(
|
||||
&self,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
|
||||
|
||||
let shortid = self.services.rooms.short.get_shortroomid(&room_id).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!("{shortid:#?}")))
|
||||
}
|
||||
+92
-12
@@ -1,7 +1,9 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::stream::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedDeviceId, OwnedRoomId, OwnedUserId};
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContent, OwnedDeviceId, OwnedRoomId, OwnedUserId,
|
||||
};
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
|
||||
@@ -13,6 +15,8 @@ pub(crate) enum UsersCommand {
|
||||
|
||||
IterUsers,
|
||||
|
||||
IterUsers2,
|
||||
|
||||
PasswordHash {
|
||||
user_id: OwnedUserId,
|
||||
},
|
||||
@@ -87,11 +91,42 @@ pub(crate) enum UsersCommand {
|
||||
room_id: OwnedRoomId,
|
||||
session_id: String,
|
||||
},
|
||||
|
||||
GetSharedRooms {
|
||||
user_a: OwnedUserId,
|
||||
user_b: OwnedUserId,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn get_shared_rooms(
|
||||
&self,
|
||||
user_a: OwnedUserId,
|
||||
user_b: OwnedUserId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result: Vec<_> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_shared_rooms(&user_a, &user_b)
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn get_backup_session(
|
||||
&self, user_id: OwnedUserId, version: String, room_id: OwnedRoomId, session_id: String,
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
version: String,
|
||||
room_id: OwnedRoomId,
|
||||
session_id: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = self
|
||||
@@ -108,7 +143,10 @@ async fn get_backup_session(
|
||||
|
||||
#[admin_command]
|
||||
async fn get_room_backups(
|
||||
&self, user_id: OwnedUserId, version: String, room_id: OwnedRoomId,
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
version: String,
|
||||
room_id: OwnedRoomId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = self
|
||||
@@ -124,7 +162,11 @@ async fn get_room_backups(
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn get_all_backups(&self, user_id: OwnedUserId, version: String) -> Result<RoomMessageEventContent> {
|
||||
async fn get_all_backups(
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
version: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = self.services.key_backups.get_all(&user_id, &version).await;
|
||||
let query_time = timer.elapsed();
|
||||
@@ -135,7 +177,11 @@ async fn get_all_backups(&self, user_id: OwnedUserId, version: String) -> Result
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn get_backup_algorithm(&self, user_id: OwnedUserId, version: String) -> Result<RoomMessageEventContent> {
|
||||
async fn get_backup_algorithm(
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
version: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = self
|
||||
.services
|
||||
@@ -150,7 +196,10 @@ async fn get_backup_algorithm(&self, user_id: OwnedUserId, version: String) -> R
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn get_latest_backup_version(&self, user_id: OwnedUserId) -> Result<RoomMessageEventContent> {
|
||||
async fn get_latest_backup_version(
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = self
|
||||
.services
|
||||
@@ -187,6 +236,23 @@ async fn iter_users(&self) -> Result<RoomMessageEventContent> {
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn iter_users2(&self) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result: Vec<_> = self.services.users.stream().collect().await;
|
||||
let result: Vec<_> = result
|
||||
.into_iter()
|
||||
.map(ruma::UserId::as_bytes)
|
||||
.map(String::from_utf8_lossy)
|
||||
.collect();
|
||||
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{result:?}\n```"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn count_users(&self) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
@@ -244,7 +310,11 @@ async fn list_devices_metadata(&self, user_id: OwnedUserId) -> Result<RoomMessag
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn get_device_metadata(&self, user_id: OwnedUserId, device_id: OwnedDeviceId) -> Result<RoomMessageEventContent> {
|
||||
async fn get_device_metadata(
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
device_id: OwnedDeviceId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let device = self
|
||||
.services
|
||||
@@ -270,7 +340,11 @@ async fn get_devices_version(&self, user_id: OwnedUserId) -> Result<RoomMessageE
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn count_one_time_keys(&self, user_id: OwnedUserId, device_id: OwnedDeviceId) -> Result<RoomMessageEventContent> {
|
||||
async fn count_one_time_keys(
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
device_id: OwnedDeviceId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = self
|
||||
.services
|
||||
@@ -285,7 +359,11 @@ async fn count_one_time_keys(&self, user_id: OwnedUserId, device_id: OwnedDevice
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn get_device_keys(&self, user_id: OwnedUserId, device_id: OwnedDeviceId) -> Result<RoomMessageEventContent> {
|
||||
async fn get_device_keys(
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
device_id: OwnedDeviceId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = self
|
||||
.services
|
||||
@@ -327,13 +405,15 @@ async fn get_master_key(&self, user_id: OwnedUserId) -> Result<RoomMessageEventC
|
||||
|
||||
#[admin_command]
|
||||
async fn get_to_device_events(
|
||||
&self, user_id: OwnedUserId, device_id: OwnedDeviceId,
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
device_id: OwnedDeviceId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = self
|
||||
.services
|
||||
.users
|
||||
.get_to_device_events(&user_id, &device_id)
|
||||
.get_to_device_events(&user_id, &device_id, None, None)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
+91
-69
@@ -1,9 +1,11 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomAliasId, OwnedRoomId, RoomAliasId, RoomId};
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContent, OwnedRoomAliasId, OwnedRoomId, RoomId,
|
||||
};
|
||||
|
||||
use crate::{escape_html, Command};
|
||||
|
||||
@@ -42,82 +44,102 @@ pub(crate) enum RoomAliasCommand {
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) -> Result {
|
||||
let c = reprocess(command, context).await?;
|
||||
context.write_str(c.body()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn reprocess(
|
||||
command: RoomAliasCommand,
|
||||
context: &Command<'_>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
let server_user = &services.globals.server_user;
|
||||
|
||||
match command {
|
||||
RoomAliasCommand::Set {
|
||||
ref room_alias_localpart,
|
||||
..
|
||||
}
|
||||
| RoomAliasCommand::Remove {
|
||||
ref room_alias_localpart,
|
||||
}
|
||||
| RoomAliasCommand::Which {
|
||||
ref room_alias_localpart,
|
||||
} => {
|
||||
let room_alias_str = format!("#{}:{}", room_alias_localpart, services.globals.server_name());
|
||||
let room_alias = match RoomAliasId::parse_box(room_alias_str) {
|
||||
Ok(alias) => alias,
|
||||
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))),
|
||||
| RoomAliasCommand::Set { ref room_alias_localpart, .. }
|
||||
| RoomAliasCommand::Remove { ref room_alias_localpart }
|
||||
| RoomAliasCommand::Which { ref room_alias_localpart } => {
|
||||
let room_alias_str =
|
||||
format!("#{}:{}", room_alias_localpart, services.globals.server_name());
|
||||
let room_alias = match OwnedRoomAliasId::parse(room_alias_str) {
|
||||
| Ok(alias) => alias,
|
||||
| Err(err) =>
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse alias: {err}"
|
||||
))),
|
||||
};
|
||||
match command {
|
||||
RoomAliasCommand::Set {
|
||||
force,
|
||||
room_id,
|
||||
..
|
||||
} => match (force, services.rooms.alias.resolve_local_alias(&room_alias).await) {
|
||||
(true, Ok(id)) => match services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&room_alias, &room_id, server_user)
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully overwrote alias (formerly {id})"
|
||||
| RoomAliasCommand::Set { force, room_id, .. } => {
|
||||
match (force, services.rooms.alias.resolve_local_alias(&room_alias).await) {
|
||||
| (true, Ok(id)) => {
|
||||
match services.rooms.alias.set_alias(
|
||||
&room_alias,
|
||||
&room_id,
|
||||
server_user,
|
||||
) {
|
||||
| Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully overwrote alias (formerly {id})"
|
||||
))),
|
||||
| Err(err) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to remove alias: {err}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
| (false, Ok(id)) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Refusing to overwrite in use alias for {id}, use -f or --force to \
|
||||
overwrite"
|
||||
))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
(false, Ok(id)) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
|
||||
))),
|
||||
(_, Err(_)) => match services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&room_alias, &room_id, server_user)
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Successfully set alias")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
| (_, Err(_)) => {
|
||||
match services.rooms.alias.set_alias(
|
||||
&room_alias,
|
||||
&room_id,
|
||||
server_user,
|
||||
) {
|
||||
| Ok(()) => Ok(RoomMessageEventContent::text_plain(
|
||||
"Successfully set alias",
|
||||
)),
|
||||
| Err(err) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to remove alias: {err}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
RoomAliasCommand::Remove {
|
||||
..
|
||||
} => match services.rooms.alias.resolve_local_alias(&room_alias).await {
|
||||
Ok(id) => match services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&room_alias, server_user)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!("Removed alias from {id}"))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
Err(_) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||
| RoomAliasCommand::Remove { .. } => {
|
||||
match services.rooms.alias.resolve_local_alias(&room_alias).await {
|
||||
| Ok(id) => match services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&room_alias, server_user)
|
||||
.await
|
||||
{
|
||||
| Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Removed alias from {id}"
|
||||
))),
|
||||
| Err(err) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to remove alias: {err}"
|
||||
))),
|
||||
},
|
||||
| Err(_) =>
|
||||
Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||
}
|
||||
},
|
||||
RoomAliasCommand::Which {
|
||||
..
|
||||
} => match services.rooms.alias.resolve_local_alias(&room_alias).await {
|
||||
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))),
|
||||
Err(_) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||
| RoomAliasCommand::Which { .. } => {
|
||||
match services.rooms.alias.resolve_local_alias(&room_alias).await {
|
||||
| Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Alias resolves to {id}"
|
||||
))),
|
||||
| Err(_) =>
|
||||
Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||
}
|
||||
},
|
||||
RoomAliasCommand::List {
|
||||
..
|
||||
} => unreachable!(),
|
||||
| RoomAliasCommand::List { .. } => unreachable!(),
|
||||
}
|
||||
},
|
||||
RoomAliasCommand::List {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomAliasCommand::List { room_id } =>
|
||||
if let Some(room_id) = room_id {
|
||||
let aliases: Vec<OwnedRoomAliasId> = services
|
||||
.rooms
|
||||
@@ -128,7 +150,8 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) ->
|
||||
.await;
|
||||
|
||||
let plain_list = aliases.iter().fold(String::new(), |mut output, alias| {
|
||||
writeln!(output, "- {alias}").expect("should be able to write to string buffer");
|
||||
writeln!(output, "- {alias}")
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
@@ -176,7 +199,6 @@ pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) ->
|
||||
let plain = format!("Aliases:\n{plain_list}");
|
||||
let html = format!("Aliases:\n<ul>{html_list}</ul>");
|
||||
Ok(RoomMessageEventContent::text_html(plain, html))
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
|
||||
|
||||
@@ -6,7 +6,11 @@ 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, no_details: 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);
|
||||
@@ -16,10 +20,12 @@ pub(super) async fn list_rooms(
|
||||
.metadata
|
||||
.iter_ids()
|
||||
.filter_map(|room_id| async move {
|
||||
(!exclude_disabled || !self.services.rooms.metadata.is_disabled(room_id).await).then_some(room_id)
|
||||
(!exclude_disabled || !self.services.rooms.metadata.is_disabled(room_id).await)
|
||||
.then_some(room_id)
|
||||
})
|
||||
.filter_map(|room_id| async move {
|
||||
(!exclude_banned || !self.services.rooms.metadata.is_banned(room_id).await).then_some(room_id)
|
||||
(!exclude_banned || !self.services.rooms.metadata.is_banned(room_id).await)
|
||||
.then_some(room_id)
|
||||
})
|
||||
.then(|room_id| get_room_info(self.services, room_id))
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
+17
-12
@@ -1,5 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
|
||||
|
||||
@@ -25,24 +25,27 @@ pub(crate) enum RoomDirectoryCommand {
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>) -> Result {
|
||||
let c = reprocess(command, context).await?;
|
||||
context.write_str(c.body()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn reprocess(
|
||||
command: RoomDirectoryCommand,
|
||||
context: &Command<'_>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
match command {
|
||||
RoomDirectoryCommand::Publish {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomDirectoryCommand::Publish { room_id } => {
|
||||
services.rooms.directory.set_public(&room_id);
|
||||
Ok(RoomMessageEventContent::notice_plain("Room published"))
|
||||
},
|
||||
RoomDirectoryCommand::Unpublish {
|
||||
room_id,
|
||||
} => {
|
||||
| RoomDirectoryCommand::Unpublish { room_id } => {
|
||||
services.rooms.directory.set_not_public(&room_id);
|
||||
Ok(RoomMessageEventContent::notice_plain("Room unpublished"))
|
||||
},
|
||||
RoomDirectoryCommand::List {
|
||||
page,
|
||||
} => {
|
||||
| RoomDirectoryCommand::List { page } => {
|
||||
// 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);
|
||||
let mut rooms: Vec<_> = services
|
||||
@@ -70,7 +73,9 @@ pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>
|
||||
"Rooms (page {page}):\n```\n{}\n```",
|
||||
rooms
|
||||
.iter()
|
||||
.map(|(id, members, name)| format!("{id} | Members: {members} | Name: {name}"))
|
||||
.map(|(id, members, name)| format!(
|
||||
"{id} | Members: {members} | Name: {name}"
|
||||
))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::{utils::ReadyExt, Result};
|
||||
use conduwuit::{utils::ReadyExt, Result};
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
|
||||
|
||||
@@ -27,7 +27,11 @@ pub(crate) enum RoomInfoCommand {
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn list_joined_members(&self, room_id: Box<RoomId>, local_only: bool) -> Result<RoomMessageEventContent> {
|
||||
async fn list_joined_members(
|
||||
&self,
|
||||
room_id: Box<RoomId>,
|
||||
local_only: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let room_name = self
|
||||
.services
|
||||
.rooms
|
||||
|
||||
@@ -5,11 +5,12 @@ mod info;
|
||||
mod moderation;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use ruma::OwnedRoomId;
|
||||
|
||||
use self::{
|
||||
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, moderation::RoomModerationCommand,
|
||||
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand,
|
||||
moderation::RoomModerationCommand,
|
||||
};
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
|
||||
+143
-100
@@ -1,12 +1,15 @@
|
||||
use api::client::leave_room;
|
||||
use clap::Subcommand;
|
||||
use conduit::{
|
||||
use conduwuit::{
|
||||
debug, error, info,
|
||||
utils::{IterStream, ReadyExt},
|
||||
warn, Result,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId,
|
||||
RoomOrAliasId,
|
||||
};
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch, get_room_info};
|
||||
|
||||
@@ -75,7 +78,10 @@ pub(crate) enum RoomModerationCommand {
|
||||
|
||||
#[admin_command]
|
||||
async fn ban_room(
|
||||
&self, force: bool, disable_federation: bool, room: Box<RoomOrAliasId>,
|
||||
&self,
|
||||
force: bool,
|
||||
disable_federation: bool,
|
||||
room: Box<RoomOrAliasId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
debug!("Got room alias or ID: {}", room);
|
||||
|
||||
@@ -89,61 +95,67 @@ async fn ban_room(
|
||||
|
||||
let room_id = if room.is_room_id() {
|
||||
let room_id = match RoomId::parse(&room) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
| Ok(room_id) => room_id,
|
||||
| Err(e) =>
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room \
|
||||
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
|
||||
(`#roomalias:example.com`): {e}"
|
||||
))),
|
||||
};
|
||||
|
||||
debug!("Room specified is a room ID, banning room ID");
|
||||
self.services.rooms.metadata.ban_room(room_id, true);
|
||||
|
||||
self.services.rooms.metadata.ban_room(&room_id, true);
|
||||
|
||||
room_id
|
||||
room_id.to_owned()
|
||||
} else if room.is_room_alias_id() {
|
||||
let room_alias = match RoomAliasId::parse(&room) {
|
||||
Ok(room_alias) => room_alias,
|
||||
Err(e) => {
|
||||
| Ok(room_alias) => room_alias,
|
||||
| Err(e) =>
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room \
|
||||
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
|
||||
(`#roomalias:example.com`): {e}"
|
||||
))),
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Room specified is not a room ID, attempting to resolve room alias to a room ID locally, if not using \
|
||||
get_alias_helper to fetch room ID remotely"
|
||||
"Room specified is not a room ID, attempting to resolve room alias to a room ID \
|
||||
locally, if not using get_alias_helper to fetch room ID remotely"
|
||||
);
|
||||
|
||||
let room_id = if let Ok(room_id) = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&room_alias)
|
||||
.resolve_local_alias(room_alias)
|
||||
.await
|
||||
{
|
||||
room_id
|
||||
} else {
|
||||
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
|
||||
debug!(
|
||||
"We don't have this room alias to a room ID locally, attempting to fetch room \
|
||||
ID over federation"
|
||||
);
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, None)
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
{
|
||||
Ok((room_id, servers)) => {
|
||||
debug!(?room_id, ?servers, "Got federation response fetching room ID for {room}");
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
?room_id,
|
||||
?servers,
|
||||
"Got federation response fetching room ID for {room_id}"
|
||||
);
|
||||
room_id
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
return Ok(RoomMessageEventContent::notice_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
"Failed to resolve room alias {room_alias} to a room ID: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
@@ -154,8 +166,9 @@ async fn ban_room(
|
||||
room_id
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room specified is not a room ID or room alias. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`)",
|
||||
"Room specified is not a room ID or room alias. Please note that this requires a \
|
||||
full room ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
|
||||
(`#roomalias:example.com`)",
|
||||
));
|
||||
};
|
||||
|
||||
@@ -171,8 +184,8 @@ async fn ban_room(
|
||||
|
||||
while let Some(local_user) = users.next().await {
|
||||
debug!(
|
||||
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring all errors, evicting \
|
||||
admins too)",
|
||||
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring all \
|
||||
errors, evicting admins too)",
|
||||
);
|
||||
|
||||
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
|
||||
@@ -196,12 +209,14 @@ async fn ban_room(
|
||||
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
||||
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
|
||||
error!(
|
||||
"Error attempting to make local user {} leave room {} during room banning: {}",
|
||||
"Error attempting to make local user {} leave room {} during room banning: \
|
||||
{}",
|
||||
&local_user, &room_id, e
|
||||
);
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Error attempting to make local user {} leave room {} during room banning (room is still banned \
|
||||
but not removing any more users): {}\nIf you would like to ignore errors, use --force",
|
||||
"Error attempting to make local user {} leave room {} during room banning \
|
||||
(room is still banned but not removing any more users): {}\nIf you would \
|
||||
like to ignore errors, use --force",
|
||||
&local_user, &room_id, e
|
||||
)));
|
||||
}
|
||||
@@ -232,19 +247,26 @@ async fn ban_room(
|
||||
if disable_federation {
|
||||
self.services.rooms.metadata.disable_room(&room_id, true);
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room banned, removed all our local users, and disabled incoming federation with room.",
|
||||
"Room banned, removed all our local users, and disabled incoming federation with \
|
||||
room.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Room banned and removed all our local users, use `!admin federation disable-room` to stop receiving new \
|
||||
inbound federation events as well if needed.",
|
||||
"Room banned and removed all our local users, use `!admin federation disable-room` to \
|
||||
stop receiving new inbound federation events as well if needed.",
|
||||
))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
async fn ban_list_of_rooms(
|
||||
&self,
|
||||
force: bool,
|
||||
disable_federation: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
@@ -264,9 +286,10 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
|
||||
|
||||
for &room in &rooms_s {
|
||||
match <&RoomOrAliasId>::try_from(room) {
|
||||
Ok(room_alias_or_id) => {
|
||||
| Ok(room_alias_or_id) => {
|
||||
if let Ok(admin_room_id) = self.services.admin.get_admin_room().await {
|
||||
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) {
|
||||
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias)
|
||||
{
|
||||
info!("User specified admin room in bulk ban list, ignoring");
|
||||
continue;
|
||||
}
|
||||
@@ -274,51 +297,52 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
|
||||
|
||||
if room_alias_or_id.is_room_id() {
|
||||
let room_id = match RoomId::parse(room_alias_or_id) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
| Ok(room_id) => room_id,
|
||||
| Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force banning
|
||||
warn!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
|
||||
logging here: {e}"
|
||||
"Error parsing room \"{room}\" during bulk room banning, \
|
||||
ignoring error and logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
"{room} is not a valid room ID or room alias, please fix the \
|
||||
list and try again: {e}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
|
||||
room_ids.push(room_id);
|
||||
room_ids.push(room_id.to_owned());
|
||||
}
|
||||
|
||||
if room_alias_or_id.is_room_alias_id() {
|
||||
match RoomAliasId::parse(room_alias_or_id) {
|
||||
Ok(room_alias) => {
|
||||
| Ok(room_alias) => {
|
||||
let room_id = if let Ok(room_id) = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&room_alias)
|
||||
.resolve_local_alias(room_alias)
|
||||
.await
|
||||
{
|
||||
room_id
|
||||
} else {
|
||||
debug!(
|
||||
"We don't have this room alias to a room ID locally, attempting to fetch room ID \
|
||||
over federation"
|
||||
"We don't have this room alias to a room ID locally, \
|
||||
attempting to fetch room ID over federation"
|
||||
);
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, None)
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
{
|
||||
Ok((room_id, servers)) => {
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
?room_id,
|
||||
?servers,
|
||||
@@ -326,15 +350,19 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
|
||||
);
|
||||
room_id
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
// don't fail if force blocking
|
||||
if force {
|
||||
warn!("Failed to resolve room alias {room} to a room ID: {e}");
|
||||
warn!(
|
||||
"Failed to resolve room alias {room} to a room \
|
||||
ID: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
"Failed to resolve room alias {room} to a room ID: \
|
||||
{e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
@@ -342,34 +370,37 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
|
||||
|
||||
room_ids.push(room_id);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force deleting
|
||||
error!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
|
||||
logging here: {e}"
|
||||
"Error parsing room \"{room}\" during bulk room banning, \
|
||||
ignoring error and logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
"{room} is not a valid room ID or room alias, please fix the \
|
||||
list and try again: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force deleting
|
||||
error!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and logging here: {e}"
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error \
|
||||
and logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try \
|
||||
again: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
@@ -393,8 +424,8 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
|
||||
|
||||
while let Some(local_user) = users.next().await {
|
||||
debug!(
|
||||
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring all errors, evicting \
|
||||
admins too)",
|
||||
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring \
|
||||
all errors, evicting admins too)",
|
||||
);
|
||||
|
||||
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
|
||||
@@ -418,14 +449,15 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
|
||||
debug!("Attempting leave for user {local_user} in room {room_id}");
|
||||
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
|
||||
error!(
|
||||
"Error attempting to make local user {local_user} leave room {room_id} during bulk room \
|
||||
banning: {e}",
|
||||
"Error attempting to make local user {local_user} leave room {room_id} \
|
||||
during bulk room banning: {e}",
|
||||
);
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Error attempting to make local user {} leave room {} during room banning (room is still \
|
||||
banned but not removing any more users and not banning any more rooms): {}\nIf you would \
|
||||
like to ignore errors, use --force",
|
||||
"Error attempting to make local user {} leave room {} during room \
|
||||
banning (room is still banned but not removing any more users and not \
|
||||
banning any more rooms): {}\nIf you would like to ignore errors, use \
|
||||
--force",
|
||||
&local_user, &room_id, e
|
||||
)));
|
||||
}
|
||||
@@ -458,8 +490,8 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
|
||||
|
||||
if disable_federation {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and disabled incoming \
|
||||
federation with the room."
|
||||
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, \
|
||||
and disabled incoming federation with the room."
|
||||
)))
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -469,62 +501,72 @@ async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Resu
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
|
||||
async fn unban_room(
|
||||
&self,
|
||||
enable_federation: bool,
|
||||
room: Box<RoomOrAliasId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let room_id = if room.is_room_id() {
|
||||
let room_id = match RoomId::parse(&room) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
| Ok(room_id) => room_id,
|
||||
| Err(e) =>
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room \
|
||||
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
|
||||
(`#roomalias:example.com`): {e}"
|
||||
))),
|
||||
};
|
||||
|
||||
debug!("Room specified is a room ID, unbanning room ID");
|
||||
self.services.rooms.metadata.ban_room(room_id, false);
|
||||
|
||||
self.services.rooms.metadata.ban_room(&room_id, false);
|
||||
|
||||
room_id
|
||||
room_id.to_owned()
|
||||
} else if room.is_room_alias_id() {
|
||||
let room_alias = match RoomAliasId::parse(&room) {
|
||||
Ok(room_alias) => room_alias,
|
||||
Err(e) => {
|
||||
| Ok(room_alias) => room_alias,
|
||||
| Err(e) =>
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room \
|
||||
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
|
||||
(`#roomalias:example.com`): {e}"
|
||||
))),
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Room specified is not a room ID, attempting to resolve room alias to a room ID locally, if not using \
|
||||
get_alias_helper to fetch room ID remotely"
|
||||
"Room specified is not a room ID, attempting to resolve room alias to a room ID \
|
||||
locally, if not using get_alias_helper to fetch room ID remotely"
|
||||
);
|
||||
|
||||
let room_id = if let Ok(room_id) = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_local_alias(&room_alias)
|
||||
.resolve_local_alias(room_alias)
|
||||
.await
|
||||
{
|
||||
room_id
|
||||
} else {
|
||||
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
|
||||
debug!(
|
||||
"We don't have this room alias to a room ID locally, attempting to fetch room \
|
||||
ID over federation"
|
||||
);
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, None)
|
||||
.resolve_alias(room_alias, None)
|
||||
.await
|
||||
{
|
||||
Ok((room_id, servers)) => {
|
||||
debug!(?room_id, ?servers, "Got federation response fetching room ID for room {room}");
|
||||
| Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
?room_id,
|
||||
?servers,
|
||||
"Got federation response fetching room ID for room {room}"
|
||||
);
|
||||
room_id
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
)));
|
||||
@@ -537,8 +579,9 @@ async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) ->
|
||||
room_id
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room specified is not a room ID or room alias. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`)",
|
||||
"Room specified is not a room ID or room alias. Please note that this requires a \
|
||||
full room ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
|
||||
(`#roomalias:example.com`)",
|
||||
));
|
||||
};
|
||||
|
||||
@@ -548,8 +591,8 @@ async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) ->
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Room unbanned, you may need to re-enable federation with the room using enable-room if this is a remote room \
|
||||
to make it fully functional.",
|
||||
"Room unbanned, you may need to re-enable federation with the room using enable-room if \
|
||||
this is a remote room to make it fully functional.",
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{fmt::Write, sync::Arc};
|
||||
use std::{fmt::Write, path::PathBuf, sync::Arc};
|
||||
|
||||
use conduit::{info, utils::time, warn, Err, Result};
|
||||
use conduwuit::{info, utils::time, warn, Err, Result};
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::admin_command;
|
||||
@@ -22,20 +22,30 @@ pub(super) async fn uptime(&self) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn show_config(&self) -> Result<RoomMessageEventContent> {
|
||||
// Construct and send the response
|
||||
Ok(RoomMessageEventContent::text_markdown(format!(
|
||||
"```\n{}\n```",
|
||||
self.services.globals.config
|
||||
"{}",
|
||||
*self.services.server.config
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_features(
|
||||
&self, available: bool, enabled: bool, comma: bool,
|
||||
pub(super) async fn reload_config(
|
||||
&self,
|
||||
path: Option<PathBuf>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let delim = if comma {
|
||||
","
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let path = path.as_deref().into_iter();
|
||||
self.services.config.reload(path)?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain("Successfully reconfigured."))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_features(
|
||||
&self,
|
||||
available: bool,
|
||||
enabled: bool,
|
||||
comma: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let delim = if comma { "," } else { " " };
|
||||
if enabled && !available {
|
||||
let features = info::rustc::features().join(delim);
|
||||
let out = format!("`\n{features}\n`");
|
||||
@@ -53,16 +63,8 @@ pub(super) async fn list_features(
|
||||
let available = info::cargo::features();
|
||||
for feature in available {
|
||||
let active = enabled.contains(&feature.as_str());
|
||||
let emoji = if active {
|
||||
"✅"
|
||||
} else {
|
||||
"❌"
|
||||
};
|
||||
let remark = if active {
|
||||
"[enabled]"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let emoji = if active { "✅" } else { "❌" };
|
||||
let remark = if active { "[enabled]" } else { "" };
|
||||
writeln!(features, "{emoji} {feature} {remark}")?;
|
||||
}
|
||||
|
||||
@@ -73,7 +75,8 @@ pub(super) async fn list_features(
|
||||
pub(super) async fn memory_usage(&self) -> Result<RoomMessageEventContent> {
|
||||
let services_usage = self.services.memory_usage().await?;
|
||||
let database_usage = self.services.db.db.memory_usage()?;
|
||||
let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
|
||||
let allocator_usage =
|
||||
conduwuit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Services:\n{services_usage}\nDatabase:\n{database_usage}{allocator_usage}",
|
||||
@@ -89,7 +92,7 @@ pub(super) async fn clear_caches(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
|
||||
let result = self.services.globals.db.backup_list()?;
|
||||
let result = self.services.db.db.backup_list()?;
|
||||
|
||||
if result.is_empty() {
|
||||
Ok(RoomMessageEventContent::text_plain("No backups found."))
|
||||
@@ -100,31 +103,24 @@ pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> {
|
||||
let globals = Arc::clone(&self.services.globals);
|
||||
let db = Arc::clone(&self.services.db);
|
||||
let mut result = self
|
||||
.services
|
||||
.server
|
||||
.runtime()
|
||||
.spawn_blocking(move || match globals.db.backup() {
|
||||
Ok(()) => String::new(),
|
||||
Err(e) => e.to_string(),
|
||||
.spawn_blocking(move || match db.db.backup() {
|
||||
| Ok(()) => String::new(),
|
||||
| Err(e) => e.to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
if result.is_empty() {
|
||||
result = self.services.globals.db.backup_list()?;
|
||||
result = self.services.db.db.backup_list()?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(result))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_database_files(&self) -> Result<RoomMessageEventContent> {
|
||||
let result = self.services.globals.db.file_list()?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(result))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
|
||||
let message = message.join(" ");
|
||||
@@ -143,12 +139,12 @@ pub(super) async fn reload_mods(&self) -> Result<RoomMessageEventContent> {
|
||||
#[admin_command]
|
||||
#[cfg(unix)]
|
||||
pub(super) async fn restart(&self, force: bool) -> Result<RoomMessageEventContent> {
|
||||
use conduit::utils::sys::current_exe_deleted;
|
||||
use conduwuit::utils::sys::current_exe_deleted;
|
||||
|
||||
if !force && current_exe_deleted() {
|
||||
return Err!(
|
||||
"The server cannot be restarted because the executable changed. If this is expected use --force to \
|
||||
override."
|
||||
"The server cannot be restarted because the executable changed. If this is expected \
|
||||
use --force to override."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
mod commands;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
@@ -14,6 +16,11 @@ pub(super) enum ServerCommand {
|
||||
/// - Show configuration values
|
||||
ShowConfig,
|
||||
|
||||
/// - Reload configuration values
|
||||
ReloadConfig {
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// - List the features built into the server
|
||||
ListFeatures {
|
||||
#[arg(short, long)]
|
||||
@@ -39,9 +46,6 @@ pub(super) enum ServerCommand {
|
||||
/// - List database backups
|
||||
ListBackups,
|
||||
|
||||
/// - List database files
|
||||
ListDatabaseFiles,
|
||||
|
||||
/// - Send a message to the admin room.
|
||||
AdminNotice {
|
||||
message: Vec<String>,
|
||||
|
||||
+160
-98
@@ -1,12 +1,12 @@
|
||||
use std::{collections::BTreeMap, fmt::Write as _};
|
||||
|
||||
use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room};
|
||||
use conduit::{
|
||||
use conduwuit::{
|
||||
debug_warn, error, info, is_equal_to,
|
||||
utils::{self, ReadyExt},
|
||||
warn, PduBuilder, Result,
|
||||
};
|
||||
use conduit_api::client::{leave_all_rooms, update_avatar_url, update_displayname};
|
||||
use conduwuit_api::client::{leave_all_rooms, update_avatar_url, update_displayname};
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
events::{
|
||||
@@ -31,28 +31,36 @@ const BULK_JOIN_REASON: &str = "Bulk force joining this room as initiated by the
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_users(&self) -> Result<RoomMessageEventContent> {
|
||||
let users = self
|
||||
let users: Vec<_> = self
|
||||
.services
|
||||
.users
|
||||
.list_local_users()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
|
||||
plain_msg += users.join("\n").as_str();
|
||||
plain_msg += "\n```";
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(plain_msg))
|
||||
self.write_str(plain_msg.as_str()).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn create_user(&self, username: String, password: Option<String>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn create_user(
|
||||
&self,
|
||||
username: String,
|
||||
password: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
// Validate user id
|
||||
let user_id = parse_local_user_id(self.services, &username)?;
|
||||
|
||||
if self.services.users.exists(&user_id).await {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!("Userid {user_id} already exists")));
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Userid {user_id} already exists"
|
||||
)));
|
||||
}
|
||||
|
||||
if user_id.is_historical() {
|
||||
@@ -75,12 +83,12 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
// content is set to the user's display name with a space before it
|
||||
if !self
|
||||
.services
|
||||
.globals
|
||||
.server
|
||||
.config
|
||||
.new_user_displayname_suffix
|
||||
.is_empty()
|
||||
{
|
||||
write!(displayname, " {}", self.services.globals.config.new_user_displayname_suffix)
|
||||
write!(displayname, " {}", self.services.server.config.new_user_displayname_suffix)
|
||||
.expect("should be able to write to string buffer");
|
||||
}
|
||||
|
||||
@@ -106,8 +114,8 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !self.services.globals.config.auto_join_rooms.is_empty() {
|
||||
for room in &self.services.globals.config.auto_join_rooms {
|
||||
if !self.services.server.config.auto_join_rooms.is_empty() {
|
||||
for room in &self.services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = self.services.rooms.alias.resolve(room).await else {
|
||||
error!(%user_id, "Failed to resolve room alias to room ID when attempting to auto join {room}, skipping");
|
||||
continue;
|
||||
@@ -120,7 +128,9 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
.server_in_room(self.services.globals.server_name(), &room_id)
|
||||
.await
|
||||
{
|
||||
warn!("Skipping room {room} to automatically join as we have never joined before.");
|
||||
warn!(
|
||||
"Skipping room {room} to automatically join as we have never joined before."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -130,25 +140,31 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
&user_id,
|
||||
&room_id,
|
||||
Some("Automatically joining this room upon registration".to_owned()),
|
||||
&[self.services.globals.server_name().to_owned(), room_server_name.to_owned()],
|
||||
&[
|
||||
self.services.globals.server_name().to_owned(),
|
||||
room_server_name.to_owned(),
|
||||
],
|
||||
None,
|
||||
&None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_response) => {
|
||||
| Ok(_response) => {
|
||||
info!("Automatically joined room {room} for user {user_id}");
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
self.services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||
"Failed to automatically join room {room} for user {user_id}: \
|
||||
{e}"
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
// don't return this error so we don't fail registrations
|
||||
error!("Failed to automatically join room {room} for user {user_id}: {e}");
|
||||
error!(
|
||||
"Failed to automatically join room {room} for user {user_id}: {e}"
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -181,7 +197,11 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn deactivate(
|
||||
&self,
|
||||
no_leave_rooms: bool,
|
||||
user_id: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
// Validate user id
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
|
||||
@@ -213,8 +233,8 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
.await;
|
||||
|
||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
|
||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await?;
|
||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await?;
|
||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
|
||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms).await;
|
||||
leave_all_rooms(self.services, &user_id).await;
|
||||
}
|
||||
|
||||
@@ -224,34 +244,45 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn reset_password(&self, username: String) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn reset_password(
|
||||
&self,
|
||||
username: String,
|
||||
password: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_local_user_id(self.services, &username)?;
|
||||
|
||||
if user_id == self.services.globals.server_user {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to set the password for the server account. Please use the emergency password config option.",
|
||||
"Not allowed to set the password for the server account. Please use the emergency \
|
||||
password config option.",
|
||||
));
|
||||
}
|
||||
|
||||
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
|
||||
let new_password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
||||
|
||||
match self
|
||||
.services
|
||||
.users
|
||||
.set_password(&user_id, Some(new_password.as_str()))
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully reset the password for user {user_id}: `{new_password}`"
|
||||
))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
| Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Couldn't reset the password for user {user_id}: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
pub(super) async fn deactivate_all(
|
||||
&self,
|
||||
no_leave_rooms: bool,
|
||||
force: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
@@ -269,7 +300,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
|
||||
for username in usernames {
|
||||
match parse_active_local_user_id(self.services, username).await {
|
||||
Ok(user_id) => {
|
||||
| Ok(user_id) => {
|
||||
if self.services.users.is_admin(&user_id).await && !force {
|
||||
self.services
|
||||
.admin
|
||||
@@ -296,7 +327,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
|
||||
user_ids.push(user_id);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
self.services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -313,7 +344,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
|
||||
for user_id in user_ids {
|
||||
match self.services.users.deactivate_account(&user_id).await {
|
||||
Ok(()) => {
|
||||
| Ok(()) => {
|
||||
deactivation_count = deactivation_count.saturating_add(1);
|
||||
if !no_leave_rooms {
|
||||
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
|
||||
@@ -327,19 +358,18 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
.await;
|
||||
|
||||
full_user_deactivate(self.services, &user_id, &all_joined_rooms).await?;
|
||||
update_displayname(self.services, &user_id, None, &all_joined_rooms)
|
||||
.await
|
||||
.ok();
|
||||
update_displayname(self.services, &user_id, None, &all_joined_rooms).await;
|
||||
update_avatar_url(self.services, &user_id, None, None, &all_joined_rooms)
|
||||
.await
|
||||
.ok();
|
||||
.await;
|
||||
leave_all_rooms(self.services, &user_id).await;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
self.services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed deactivating user: {e}"
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
},
|
||||
@@ -352,8 +382,8 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
|
||||
)))
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deactivated {deactivation_count} accounts.\nSkipped admin accounts: {}. Use --force to deactivate admin \
|
||||
accounts",
|
||||
"Deactivated {deactivation_count} accounts.\nSkipped admin accounts: {}. Use \
|
||||
--force to deactivate admin accounts",
|
||||
admins.join(", ")
|
||||
)))
|
||||
}
|
||||
@@ -395,9 +425,13 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result<RoomMess
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_join_list_of_local_users(
|
||||
&self, room_id: OwnedRoomOrAliasId, yes_i_want_to_do_this: bool,
|
||||
&self,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
yes_i_want_to_do_this: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
if self.body.len() < 2
|
||||
|| !self.body[0].trim().starts_with("```")
|
||||
|| self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
@@ -406,8 +440,8 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
|
||||
if !yes_i_want_to_do_this {
|
||||
return Ok(RoomMessageEventContent::notice_markdown(
|
||||
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force bulk join all \
|
||||
specified local users.",
|
||||
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force \
|
||||
bulk join all specified local users.",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -466,7 +500,7 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
|
||||
for username in usernames {
|
||||
match parse_active_local_user_id(self.services, username).await {
|
||||
Ok(user_id) => {
|
||||
| Ok(user_id) => {
|
||||
// don't make the server service account join
|
||||
if user_id == self.services.globals.server_user {
|
||||
self.services
|
||||
@@ -481,7 +515,7 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
|
||||
user_ids.push(user_id);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
self.services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
@@ -509,10 +543,10 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_res) => {
|
||||
| Ok(_res) => {
|
||||
successful_joins = successful_joins.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
debug_warn!("Failed force joining {user_id} to {room_id} during bulk join: {e}");
|
||||
failed_joins = failed_joins.saturating_add(1);
|
||||
},
|
||||
@@ -520,18 +554,21 @@ pub(super) async fn force_join_list_of_local_users(
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins failed.",
|
||||
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
|
||||
failed.",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_join_all_local_users(
|
||||
&self, room_id: OwnedRoomOrAliasId, yes_i_want_to_do_this: bool,
|
||||
&self,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
yes_i_want_to_do_this: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !yes_i_want_to_do_this {
|
||||
return Ok(RoomMessageEventContent::notice_markdown(
|
||||
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force bulk join all local \
|
||||
users.",
|
||||
"You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force \
|
||||
bulk join all local users.",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -602,10 +639,10 @@ pub(super) async fn force_join_all_local_users(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_res) => {
|
||||
| Ok(_res) => {
|
||||
successful_joins = successful_joins.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
| Err(e) => {
|
||||
debug_warn!("Failed force joining {user_id} to {room_id} during bulk join: {e}");
|
||||
failed_joins = failed_joins.saturating_add(1);
|
||||
},
|
||||
@@ -613,13 +650,16 @@ pub(super) async fn force_join_all_local_users(
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins failed.",
|
||||
"{successful_joins} local users have been joined to {room_id}. {failed_joins} joins \
|
||||
failed.",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_join_room(
|
||||
&self, user_id: String, room_id: OwnedRoomOrAliasId,
|
||||
&self,
|
||||
user_id: String,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||
let (room_id, servers) = self
|
||||
@@ -633,7 +673,8 @@ 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, &servers, None, &None).await?;
|
||||
join_room_by_id_helper(self.services, &user_id, &room_id, None, &servers, None, &None)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{user_id} has been joined to {room_id}.",
|
||||
@@ -642,7 +683,9 @@ pub(super) async fn force_join_room(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_leave_room(
|
||||
&self, user_id: String, room_id: OwnedRoomOrAliasId,
|
||||
&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?;
|
||||
@@ -660,7 +703,9 @@ pub(super) async fn force_leave_room(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_demote(
|
||||
&self, user_id: String, room_id: OwnedRoomOrAliasId,
|
||||
&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?;
|
||||
@@ -676,14 +721,19 @@ pub(super) async fn force_demote(
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<RoomPowerLevelsEventContent>(&room_id, &StateEventType::RoomPowerLevels, "")
|
||||
.room_state_get_content::<RoomPowerLevelsEventContent>(
|
||||
&room_id,
|
||||
&StateEventType::RoomPowerLevels,
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let user_can_demote_self = room_power_levels
|
||||
.as_ref()
|
||||
.is_some_and(|power_levels_content| {
|
||||
RoomPowerLevels::from(power_levels_content.clone()).user_can_change_user_power_level(&user_id, &user_id)
|
||||
RoomPowerLevels::from(power_levels_content.clone())
|
||||
.user_can_change_user_power_level(&user_id, &user_id)
|
||||
}) || self
|
||||
.services
|
||||
.rooms
|
||||
@@ -714,7 +764,8 @@ pub(super) async fn force_demote(
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"User {user_id} demoted themselves to the room default power level in {room_id} - {event_id}"
|
||||
"User {user_id} demoted themselves to the room default power level in {room_id} - \
|
||||
{event_id}"
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -735,7 +786,10 @@ pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessag
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn put_room_tag(
|
||||
&self, user_id: String, room_id: Box<RoomId>, tag: String,
|
||||
&self,
|
||||
user_id: String,
|
||||
room_id: Box<RoomId>,
|
||||
tag: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
@@ -745,9 +799,7 @@ pub(super) async fn put_room_tag(
|
||||
.get_room(&room_id, &user_id, RoomAccountDataEventType::Tag)
|
||||
.await
|
||||
.unwrap_or(TagEvent {
|
||||
content: TagEventContent {
|
||||
tags: BTreeMap::new(),
|
||||
},
|
||||
content: TagEventContent { tags: BTreeMap::new() },
|
||||
});
|
||||
|
||||
tags_event
|
||||
@@ -772,7 +824,10 @@ pub(super) async fn put_room_tag(
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_room_tag(
|
||||
&self, user_id: String, room_id: Box<RoomId>, tag: String,
|
||||
&self,
|
||||
user_id: String,
|
||||
room_id: Box<RoomId>,
|
||||
tag: String,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
@@ -782,9 +837,7 @@ pub(super) async fn delete_room_tag(
|
||||
.get_room(&room_id, &user_id, RoomAccountDataEventType::Tag)
|
||||
.await
|
||||
.unwrap_or(TagEvent {
|
||||
content: TagEventContent {
|
||||
tags: BTreeMap::new(),
|
||||
},
|
||||
content: TagEventContent { tags: BTreeMap::new() },
|
||||
});
|
||||
|
||||
tags_event.content.tags.remove(&tag.clone().into());
|
||||
@@ -800,12 +853,17 @@ pub(super) async fn delete_room_tag(
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully updated room account data for {user_id} and room {room_id}, deleting room tag {tag}"
|
||||
"Successfully updated room account data for {user_id} and room {room_id}, deleting room \
|
||||
tag {tag}"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn get_room_tags(
|
||||
&self,
|
||||
user_id: String,
|
||||
room_id: Box<RoomId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
|
||||
|
||||
let tags_event = self
|
||||
@@ -814,9 +872,7 @@ pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>)
|
||||
.get_room(&room_id, &user_id, RoomAccountDataEventType::Tag)
|
||||
.await
|
||||
.unwrap_or(TagEvent {
|
||||
content: TagEventContent {
|
||||
tags: BTreeMap::new(),
|
||||
},
|
||||
content: TagEventContent { tags: BTreeMap::new() },
|
||||
});
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
@@ -826,7 +882,10 @@ pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>)
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn redact_event(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn redact_event(
|
||||
&self,
|
||||
event_id: Box<EventId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let Ok(event) = self
|
||||
.services
|
||||
.rooms
|
||||
@@ -845,7 +904,9 @@ pub(super) async fn redact_event(&self, event_id: Box<EventId>) -> Result<RoomMe
|
||||
let sender_user = event.sender;
|
||||
|
||||
if !self.services.globals.user_is_local(&sender_user) {
|
||||
return Ok(RoomMessageEventContent::text_plain("This command only works on local users."));
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"This command only works on local users.",
|
||||
));
|
||||
}
|
||||
|
||||
let reason = format!(
|
||||
@@ -853,29 +914,30 @@ pub(super) async fn redact_event(&self, event_id: Box<EventId>) -> Result<RoomMe
|
||||
self.services.globals.server_name()
|
||||
);
|
||||
|
||||
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
|
||||
let redaction_event_id = {
|
||||
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
|
||||
|
||||
let redaction_event_id = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
redacts: Some(event.event_id.clone()),
|
||||
..PduBuilder::timeline(&RoomRedactionEventContent {
|
||||
redacts: Some(event.event_id.clone().into()),
|
||||
reason: Some(reason),
|
||||
})
|
||||
},
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
self.services
|
||||
.rooms
|
||||
.timeline
|
||||
.build_and_append_pdu(
|
||||
PduBuilder {
|
||||
redacts: Some(event.event_id.clone()),
|
||||
..PduBuilder::timeline(&RoomRedactionEventContent {
|
||||
redacts: Some(event.event_id.clone()),
|
||||
reason: Some(reason),
|
||||
})
|
||||
},
|
||||
&sender_user,
|
||||
&room_id,
|
||||
&state_lock,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
drop(state_lock);
|
||||
let out = format!("Successfully redacted event. Redaction event ID: {redaction_event_id}");
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully redacted event. Redaction event ID: {redaction_event_id}"
|
||||
)))
|
||||
self.write_str(out.as_str()).await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(""))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use conduwuit::Result;
|
||||
use ruma::{EventId, OwnedRoomOrAliasId, RoomId};
|
||||
|
||||
use crate::admin_command_dispatch;
|
||||
@@ -22,6 +22,8 @@ pub(super) enum UserCommand {
|
||||
ResetPassword {
|
||||
/// Username of the user for whom the password should be reset
|
||||
username: String,
|
||||
/// New password for the user, if unspecified one is generated
|
||||
password: Option<String>,
|
||||
},
|
||||
|
||||
/// - Deactivate a user
|
||||
|
||||
+9
-3
@@ -1,4 +1,4 @@
|
||||
use conduit_core::{err, Err, Result};
|
||||
use conduwuit_core::{err, Err, Result};
|
||||
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
|
||||
use service::Services;
|
||||
|
||||
@@ -8,7 +8,10 @@ pub(crate) fn escape_html(s: &str) -> String {
|
||||
.replace('>', ">")
|
||||
}
|
||||
|
||||
pub(crate) async fn get_room_info(services: &Services, room_id: &RoomId) -> (OwnedRoomId, u64, String) {
|
||||
pub(crate) async fn get_room_info(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
) -> (OwnedRoomId, u64, String) {
|
||||
(
|
||||
room_id.into(),
|
||||
services
|
||||
@@ -44,7 +47,10 @@ pub(crate) fn parse_local_user_id(services: &Services, user_id: &str) -> Result<
|
||||
}
|
||||
|
||||
/// Parses user ID that is an active (not guest or deactivated) local user
|
||||
pub(crate) async fn parse_active_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
|
||||
pub(crate) async fn parse_active_local_user_id(
|
||||
services: &Services,
|
||||
user_id: &str,
|
||||
) -> Result<OwnedUserId> {
|
||||
let user_id = parse_local_user_id(services, user_id)?;
|
||||
|
||||
if !services.users.exists(&user_id).await {
|
||||
|
||||
+5
-6
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "conduit_api"
|
||||
name = "conduwuit_api"
|
||||
categories.workspace = true
|
||||
description.workspace = true
|
||||
edition.workspace = true
|
||||
@@ -18,7 +18,6 @@ crate-type = [
|
||||
|
||||
[features]
|
||||
element_hacks = []
|
||||
#dev_release_log_level = []
|
||||
release_max_log_level = [
|
||||
"tracing/max_level_trace",
|
||||
"tracing/release_max_level_info",
|
||||
@@ -41,9 +40,9 @@ axum-extra.workspace = true
|
||||
axum.workspace = true
|
||||
base64.workspace = true
|
||||
bytes.workspace = true
|
||||
conduit-core.workspace = true
|
||||
conduit-database.workspace = true
|
||||
conduit-service.workspace = true
|
||||
conduwuit-core.workspace = true
|
||||
conduwuit-database.workspace = true
|
||||
conduwuit-service.workspace = true
|
||||
const-str.workspace = true
|
||||
futures.workspace = true
|
||||
hmac.workspace = true
|
||||
@@ -51,7 +50,7 @@ http.workspace = true
|
||||
http-body-util.workspace = true
|
||||
hyper.workspace = true
|
||||
ipaddress.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
itertools.workspace = true
|
||||
log.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest.workspace = true
|
||||
|
||||
+129
-100
@@ -2,16 +2,19 @@ use std::fmt::Write;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::{debug_info, error, info, is_equal_to, utils, utils::ReadyExt, warn, Error, PduBuilder, Result};
|
||||
use conduwuit::{
|
||||
debug_info, error, info, is_equal_to, utils, utils::ReadyExt, warn, Error, PduBuilder, Result,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use register::RegistrationKind;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
account::{
|
||||
change_password, check_registration_token_validity, deactivate, get_3pids, get_username_availability,
|
||||
change_password, check_registration_token_validity, deactivate, get_3pids,
|
||||
get_username_availability,
|
||||
register::{self, LoginType},
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, whoami,
|
||||
ThirdPartyIdRemovalStatus,
|
||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
||||
whoami, ThirdPartyIdRemovalStatus,
|
||||
},
|
||||
error::ErrorKind,
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
@@ -45,7 +48,8 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||
/// invalid when trying to register
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
|
||||
pub(crate) async fn get_register_available_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_username_availability::v3::Request>,
|
||||
) -> Result<get_username_availability::v3::Response> {
|
||||
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||
@@ -66,7 +70,8 @@ pub(crate) async fn get_register_available_route(
|
||||
let user_id = UserId::parse_with_server_name(body_username, services.globals.server_name())
|
||||
.ok()
|
||||
.filter(|user_id| {
|
||||
(!user_id.is_historical() || is_matrix_appservice_irc) && services.globals.user_is_local(user_id)
|
||||
(!user_id.is_historical() || is_matrix_appservice_irc)
|
||||
&& services.globals.user_is_local(user_id)
|
||||
})
|
||||
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
|
||||
|
||||
@@ -86,9 +91,7 @@ pub(crate) async fn get_register_available_route(
|
||||
// TODO add check for appservice namespaces
|
||||
|
||||
// If no if check is true we have an username that's available to be used.
|
||||
Ok(get_username_availability::v3::Response {
|
||||
available: true,
|
||||
})
|
||||
Ok(get_username_availability::v3::Response { available: true })
|
||||
}
|
||||
|
||||
/// # `POST /_matrix/client/v3/register`
|
||||
@@ -111,12 +114,14 @@ pub(crate) async fn get_register_available_route(
|
||||
#[allow(clippy::doc_markdown)]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "register")]
|
||||
pub(crate) async fn register_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp, body: Ruma<register::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<register::v3::Request>,
|
||||
) -> Result<register::v3::Response> {
|
||||
if !services.globals.allow_registration() && body.appservice_info.is_none() {
|
||||
info!(
|
||||
"Registration disabled and request not from known appservice, rejecting registration attempt for username \
|
||||
\"{}\"",
|
||||
"Registration disabled and request not from known appservice, rejecting \
|
||||
registration attempt for username \"{}\"",
|
||||
body.username.as_deref().unwrap_or("")
|
||||
);
|
||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Registration has been disabled."));
|
||||
@@ -126,11 +131,12 @@ pub(crate) async fn register_route(
|
||||
|
||||
if is_guest
|
||||
&& (!services.globals.allow_guest_registration()
|
||||
|| (services.globals.allow_registration() && services.globals.registration_token.is_some()))
|
||||
|| (services.globals.allow_registration()
|
||||
&& services.globals.registration_token.is_some()))
|
||||
{
|
||||
info!(
|
||||
"Guest registration disabled / registration enabled with token configured, rejecting guest registration \
|
||||
attempt, initial device name: \"{}\"",
|
||||
"Guest registration disabled / registration enabled with token configured, \
|
||||
rejecting guest registration attempt, initial device name: \"{}\"",
|
||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
||||
);
|
||||
return Err(Error::BadRequest(
|
||||
@@ -143,21 +149,25 @@ pub(crate) async fn register_route(
|
||||
// generic user error.
|
||||
if is_guest && services.users.count().await < 2 {
|
||||
warn!(
|
||||
"Guest account attempted to register before a real admin user has been registered, rejecting \
|
||||
registration. Guest's initial device name: \"{}\"",
|
||||
"Guest account attempted to register before a real admin user has been registered, \
|
||||
rejecting registration. Guest's initial device name: \"{}\"",
|
||||
body.initial_device_display_name.as_deref().unwrap_or("")
|
||||
);
|
||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Registration temporarily disabled."));
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::forbidden(),
|
||||
"Registration temporarily disabled.",
|
||||
));
|
||||
}
|
||||
|
||||
let user_id = match (&body.username, is_guest) {
|
||||
(Some(username), false) => {
|
||||
| (Some(username), false) => {
|
||||
// workaround for https://github.com/matrix-org/matrix-appservice-irc/issues/1780 due to inactivity of fixing the issue
|
||||
let is_matrix_appservice_irc = body.appservice_info.as_ref().is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice.registration.id.contains("matrix-appservice-irc")
|
||||
|| appservice.registration.id.contains("matrix_appservice_irc")
|
||||
});
|
||||
let is_matrix_appservice_irc =
|
||||
body.appservice_info.as_ref().is_some_and(|appservice| {
|
||||
appservice.registration.id == "irc"
|
||||
|| appservice.registration.id.contains("matrix-appservice-irc")
|
||||
|| appservice.registration.id.contains("matrix_appservice_irc")
|
||||
});
|
||||
|
||||
// don't force the username lowercase if it's from matrix-appservice-irc
|
||||
let body_username = if is_matrix_appservice_irc {
|
||||
@@ -166,15 +176,23 @@ pub(crate) async fn register_route(
|
||||
username.to_lowercase()
|
||||
};
|
||||
|
||||
let proposed_user_id = UserId::parse_with_server_name(body_username, services.globals.server_name())
|
||||
.ok()
|
||||
.filter(|user_id| {
|
||||
(!user_id.is_historical() || is_matrix_appservice_irc) && services.globals.user_is_local(user_id)
|
||||
})
|
||||
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
|
||||
let proposed_user_id =
|
||||
UserId::parse_with_server_name(body_username, services.globals.server_name())
|
||||
.ok()
|
||||
.filter(|user_id| {
|
||||
(!user_id.is_historical() || is_matrix_appservice_irc)
|
||||
&& services.globals.user_is_local(user_id)
|
||||
})
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidUsername,
|
||||
"Username is invalid.",
|
||||
))?;
|
||||
|
||||
if services.users.exists(&proposed_user_id).await {
|
||||
return Err(Error::BadRequest(ErrorKind::UserInUse, "Desired user ID is already taken."));
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::UserInUse,
|
||||
"Desired user ID is already taken.",
|
||||
));
|
||||
}
|
||||
|
||||
if services
|
||||
@@ -187,7 +205,7 @@ pub(crate) async fn register_route(
|
||||
|
||||
proposed_user_id
|
||||
},
|
||||
_ => loop {
|
||||
| _ => loop {
|
||||
let proposed_user_id = UserId::parse_with_server_name(
|
||||
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
||||
services.globals.server_name(),
|
||||
@@ -228,9 +246,7 @@ pub(crate) async fn register_route(
|
||||
} else {
|
||||
// No registration token necessary, but clients must still go through the flow
|
||||
uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::Dummy],
|
||||
}],
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Dummy] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
@@ -244,7 +260,8 @@ pub(crate) async fn register_route(
|
||||
let (worked, uiaainfo) = services
|
||||
.uiaa
|
||||
.try_auth(
|
||||
&UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
|
||||
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||
.expect("we know this is valid"),
|
||||
"".into(),
|
||||
auth,
|
||||
&uiaainfo,
|
||||
@@ -257,7 +274,8 @@ pub(crate) async fn register_route(
|
||||
} else if let Some(json) = body.json_body {
|
||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||
services.uiaa.create(
|
||||
&UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
|
||||
&UserId::parse_with_server_name("", services.globals.server_name())
|
||||
.expect("we know this is valid"),
|
||||
"".into(),
|
||||
&uiaainfo,
|
||||
&json,
|
||||
@@ -268,11 +286,7 @@ pub(crate) async fn register_route(
|
||||
}
|
||||
}
|
||||
|
||||
let password = if is_guest {
|
||||
None
|
||||
} else {
|
||||
body.password.as_deref()
|
||||
};
|
||||
let password = if is_guest { None } else { body.password.as_deref() };
|
||||
|
||||
// Create user
|
||||
services.users.create(&user_id, password)?;
|
||||
@@ -282,8 +296,10 @@ pub(crate) async fn register_route(
|
||||
|
||||
// If `new_user_displayname_suffix` is set, registration will push whatever
|
||||
// content is set to the user's display name with a space before it
|
||||
if !services.globals.new_user_displayname_suffix().is_empty() && body.appservice_info.is_none() {
|
||||
write!(displayname, " {}", services.globals.config.new_user_displayname_suffix)
|
||||
if !services.globals.new_user_displayname_suffix().is_empty()
|
||||
&& body.appservice_info.is_none()
|
||||
{
|
||||
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)
|
||||
.expect("should be able to write to string buffer");
|
||||
}
|
||||
|
||||
@@ -319,12 +335,8 @@ pub(crate) async fn register_route(
|
||||
}
|
||||
|
||||
// Generate new device id if the user didn't specify one
|
||||
let device_id = if is_guest {
|
||||
None
|
||||
} else {
|
||||
body.device_id.clone()
|
||||
}
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
let device_id = if is_guest { None } else { body.device_id.clone() }
|
||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
|
||||
|
||||
// Generate new token for the device
|
||||
let token = utils::random_string(TOKEN_LENGTH);
|
||||
@@ -349,15 +361,16 @@ pub(crate) async fn register_route(
|
||||
if body.appservice_info.is_none() && !is_guest {
|
||||
if !device_display_name.is_empty() {
|
||||
info!(
|
||||
"New user \"{user_id}\" registered on this server with device display name: \"{device_display_name}\""
|
||||
"New user \"{user_id}\" registered on this server with device display name: \
|
||||
\"{device_display_name}\""
|
||||
);
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
if services.server.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}\""
|
||||
"New user \"{user_id}\" registered on this server from IP {client} and \
|
||||
device display name \"{device_display_name}\""
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
@@ -365,7 +378,7 @@ pub(crate) async fn register_route(
|
||||
} else {
|
||||
info!("New user \"{user_id}\" registered on this server.");
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
@@ -382,24 +395,24 @@ pub(crate) async fn register_route(
|
||||
info!("New guest user \"{user_id}\" registered on this server.");
|
||||
|
||||
if !device_display_name.is_empty() {
|
||||
if services.globals.config.admin_room_notices {
|
||||
if services.server.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}"
|
||||
"Guest user \"{user_id}\" with device display name \
|
||||
\"{device_display_name}\" registered on this server from IP {client}"
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
} else {
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if services.globals.config.admin_room_notices {
|
||||
if services.server.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}",
|
||||
"Guest user \"{user_id}\" with no device display name registered on \
|
||||
this server from IP {client}",
|
||||
)))
|
||||
.await
|
||||
.ok();
|
||||
@@ -425,12 +438,15 @@ pub(crate) async fn register_route(
|
||||
}
|
||||
|
||||
if body.appservice_info.is_none()
|
||||
&& !services.globals.config.auto_join_rooms.is_empty()
|
||||
&& !services.server.config.auto_join_rooms.is_empty()
|
||||
&& (services.globals.allow_guests_auto_join_rooms() || !is_guest)
|
||||
{
|
||||
for room in &services.globals.config.auto_join_rooms {
|
||||
for room in &services.server.config.auto_join_rooms {
|
||||
let Ok(room_id) = services.rooms.alias.resolve(room).await else {
|
||||
error!("Failed to resolve room alias to room ID when attempting to auto join {room}, skipping");
|
||||
error!(
|
||||
"Failed to resolve room alias to room ID when attempting to auto join \
|
||||
{room}, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -440,7 +456,9 @@ pub(crate) async fn register_route(
|
||||
.server_in_room(services.globals.server_name(), &room_id)
|
||||
.await
|
||||
{
|
||||
warn!("Skipping room {room} to automatically join as we have never joined before.");
|
||||
warn!(
|
||||
"Skipping room {room} to automatically join as we have never joined before."
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -494,7 +512,8 @@ pub(crate) async fn register_route(
|
||||
/// - Triggers device list updates
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
|
||||
pub(crate) async fn change_password_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<change_password::v3::Request>,
|
||||
) -> Result<change_password::v3::Response> {
|
||||
// Authentication for this endpoint was made optional, but we need
|
||||
@@ -506,9 +525,7 @@ pub(crate) async fn change_password_route(
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::Password],
|
||||
}],
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
@@ -553,7 +570,7 @@ pub(crate) async fn change_password_route(
|
||||
|
||||
info!("User {sender_user} changed their password.");
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
@@ -572,7 +589,8 @@ pub(crate) async fn change_password_route(
|
||||
///
|
||||
/// Note: Also works for Application Services
|
||||
pub(crate) async fn whoami_route(
|
||||
State(services): State<crate::State>, body: Ruma<whoami::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<whoami::v3::Request>,
|
||||
) -> Result<whoami::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let device_id = body.sender_device.clone();
|
||||
@@ -580,7 +598,8 @@ pub(crate) async fn whoami_route(
|
||||
Ok(whoami::v3::Response {
|
||||
user_id: sender_user.clone(),
|
||||
device_id,
|
||||
is_guest: services.users.is_deactivated(sender_user).await? && body.appservice_info.is_none(),
|
||||
is_guest: services.users.is_deactivated(sender_user).await?
|
||||
&& body.appservice_info.is_none(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -597,7 +616,8 @@ pub(crate) async fn whoami_route(
|
||||
/// - Removes ability to log in again
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
|
||||
pub(crate) async fn deactivate_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<deactivate::v3::Request>,
|
||||
) -> Result<deactivate::v3::Response> {
|
||||
// Authentication for this endpoint was made optional, but we need
|
||||
@@ -609,9 +629,7 @@ pub(crate) async fn deactivate_route(
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::Password],
|
||||
}],
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
@@ -648,14 +666,14 @@ pub(crate) async fn deactivate_route(
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await?;
|
||||
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await?;
|
||||
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await;
|
||||
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await;
|
||||
|
||||
full_user_deactivate(&services, sender_user, &all_joined_rooms).await?;
|
||||
|
||||
info!("User {sender_user} deactivated their account.");
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||
@@ -675,7 +693,9 @@ pub(crate) async fn deactivate_route(
|
||||
/// Get a list of third party identifiers associated with this account.
|
||||
///
|
||||
/// - Currently always returns empty list
|
||||
pub(crate) async fn third_party_route(body: Ruma<get_3pids::v3::Request>) -> Result<get_3pids::v3::Response> {
|
||||
pub(crate) async fn third_party_route(
|
||||
body: Ruma<get_3pids::v3::Request>,
|
||||
) -> Result<get_3pids::v3::Response> {
|
||||
let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
Ok(get_3pids::v3::Response::new(Vec::new()))
|
||||
@@ -720,7 +740,8 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
||||
/// Currently does not have any ratelimiting, and this isn't very practical as
|
||||
/// there is only one registration token allowed.
|
||||
pub(crate) async fn check_registration_token_validity(
|
||||
State(services): State<crate::State>, body: Ruma<check_registration_token_validity::v1::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<check_registration_token_validity::v1::Request>,
|
||||
) -> Result<check_registration_token_validity::v1::Response> {
|
||||
let Some(reg_token) = services.globals.registration_token.clone() else {
|
||||
return Err(Error::BadRequest(
|
||||
@@ -729,9 +750,7 @@ pub(crate) async fn check_registration_token_validity(
|
||||
));
|
||||
};
|
||||
|
||||
Ok(check_registration_token_validity::v1::Response {
|
||||
valid: reg_token == body.token,
|
||||
})
|
||||
Ok(check_registration_token_validity::v1::Response { valid: reg_token == body.token })
|
||||
}
|
||||
|
||||
/// Runs through all the deactivation steps:
|
||||
@@ -742,16 +761,20 @@ pub(crate) async fn check_registration_token_validity(
|
||||
/// - Removing all profile data
|
||||
/// - Leaving all rooms (and forgets all of them)
|
||||
pub async fn full_user_deactivate(
|
||||
services: &Services, user_id: &UserId, all_joined_rooms: &[OwnedRoomId],
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
all_joined_rooms: &[OwnedRoomId],
|
||||
) -> Result<()> {
|
||||
services.users.deactivate_account(user_id).await?;
|
||||
super::update_displayname(services, user_id, None, all_joined_rooms).await?;
|
||||
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await?;
|
||||
services.users.deactivate_account(user_id).await.ok();
|
||||
super::update_displayname(services, user_id, None, all_joined_rooms).await;
|
||||
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await;
|
||||
|
||||
services
|
||||
.users
|
||||
.all_profile_keys(user_id)
|
||||
.ready_for_each(|(profile_key, _)| services.users.set_profile_key(user_id, &profile_key, None))
|
||||
.ready_for_each(|(profile_key, _)| {
|
||||
services.users.set_profile_key(user_id, &profile_key, None);
|
||||
})
|
||||
.await;
|
||||
|
||||
for room_id in all_joined_rooms {
|
||||
@@ -760,20 +783,26 @@ pub async fn full_user_deactivate(
|
||||
let room_power_levels = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get_content::<RoomPowerLevelsEventContent>(room_id, &StateEventType::RoomPowerLevels, "")
|
||||
.room_state_get_content::<RoomPowerLevelsEventContent>(
|
||||
room_id,
|
||||
&StateEventType::RoomPowerLevels,
|
||||
"",
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
let user_can_demote_self = room_power_levels
|
||||
.as_ref()
|
||||
.is_some_and(|power_levels_content| {
|
||||
RoomPowerLevels::from(power_levels_content.clone()).user_can_change_user_power_level(user_id, user_id)
|
||||
}) || services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.is_ok_and(|event| event.sender == user_id);
|
||||
let user_can_demote_self =
|
||||
room_power_levels
|
||||
.as_ref()
|
||||
.is_some_and(|power_levels_content| {
|
||||
RoomPowerLevels::from(power_levels_content.clone())
|
||||
.user_can_change_user_power_level(user_id, user_id)
|
||||
}) || services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomCreate, "")
|
||||
.await
|
||||
.is_ok_and(|event| event.sender == user_id);
|
||||
|
||||
if user_can_demote_self {
|
||||
let mut power_levels_content = room_power_levels.unwrap_or_default();
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
use axum::extract::State;
|
||||
use conduwuit::{err, Err};
|
||||
use ruma::{
|
||||
api::client::config::{
|
||||
get_global_account_data, get_room_account_data, set_global_account_data,
|
||||
set_room_account_data,
|
||||
},
|
||||
events::{
|
||||
AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent,
|
||||
GlobalAccountDataEventType, RoomAccountDataEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
RoomId, UserId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, value::RawValue as RawJsonValue};
|
||||
|
||||
use crate::{service::Services, Result, Ruma};
|
||||
|
||||
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
|
||||
///
|
||||
/// Sets some account data for the sender user.
|
||||
pub(crate) async fn set_global_account_data_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<set_global_account_data::v3::Request>,
|
||||
) -> Result<set_global_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot set account data for other users.")));
|
||||
}
|
||||
|
||||
set_account_data(
|
||||
&services,
|
||||
None,
|
||||
&body.user_id,
|
||||
&body.event_type.to_string(),
|
||||
body.data.json(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(set_global_account_data::v3::Response {})
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
|
||||
///
|
||||
/// Sets some room account data for the sender user.
|
||||
pub(crate) async fn set_room_account_data_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<set_room_account_data::v3::Request>,
|
||||
) -> Result<set_room_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot set account data for other users.")));
|
||||
}
|
||||
|
||||
set_account_data(
|
||||
&services,
|
||||
Some(&body.room_id),
|
||||
&body.user_id,
|
||||
&body.event_type.to_string(),
|
||||
body.data.json(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(set_room_account_data::v3::Response {})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
|
||||
///
|
||||
/// Gets some account data for the sender user.
|
||||
pub(crate) async fn get_global_account_data_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_global_account_data::v3::Request>,
|
||||
) -> Result<get_global_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot get account data of other users.")));
|
||||
}
|
||||
|
||||
let account_data: ExtractGlobalEventContent = services
|
||||
.account_data
|
||||
.get_global(&body.user_id, body.event_type.clone())
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
|
||||
|
||||
Ok(get_global_account_data::v3::Response { account_data: account_data.content })
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
|
||||
///
|
||||
/// Gets some room account data for the sender user.
|
||||
pub(crate) async fn get_room_account_data_route(
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_room_account_data::v3::Request>,
|
||||
) -> Result<get_room_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user();
|
||||
|
||||
if sender_user != body.user_id && body.appservice_info.is_none() {
|
||||
return Err!(Request(Forbidden("You cannot get account data of other users.")));
|
||||
}
|
||||
|
||||
let account_data: ExtractRoomEventContent = services
|
||||
.account_data
|
||||
.get_room(&body.room_id, &body.user_id, body.event_type.clone())
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
|
||||
|
||||
Ok(get_room_account_data::v3::Response { account_data: account_data.content })
|
||||
}
|
||||
|
||||
async fn set_account_data(
|
||||
services: &Services,
|
||||
room_id: Option<&RoomId>,
|
||||
sender_user: &UserId,
|
||||
event_type_s: &str,
|
||||
data: &RawJsonValue,
|
||||
) -> Result {
|
||||
if event_type_s == RoomAccountDataEventType::FullyRead.to_cow_str() {
|
||||
return Err!(Request(BadJson(
|
||||
"This endpoint cannot be used for marking a room as fully read (setting \
|
||||
m.fully_read)"
|
||||
)));
|
||||
}
|
||||
|
||||
if event_type_s == GlobalAccountDataEventType::PushRules.to_cow_str() {
|
||||
return Err!(Request(BadJson(
|
||||
"This endpoint cannot be used for setting/configuring push rules."
|
||||
)));
|
||||
}
|
||||
|
||||
let data: serde_json::Value = serde_json::from_str(data.get())
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Invalid JSON provided: {e}")))))?;
|
||||
|
||||
services
|
||||
.account_data
|
||||
.update(
|
||||
room_id,
|
||||
sender_user,
|
||||
event_type_s.into(),
|
||||
&json!({
|
||||
"type": event_type_s,
|
||||
"content": data,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtractRoomEventContent {
|
||||
content: Raw<AnyRoomAccountDataEventContent>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtractGlobalEventContent {
|
||||
content: Raw<AnyGlobalAccountDataEventContent>,
|
||||
}
|
||||
+13
-6
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use conduit::{debug, Err, Result};
|
||||
use conduwuit::{debug, Err, Result};
|
||||
use futures::StreamExt;
|
||||
use rand::seq::SliceRandom;
|
||||
use ruma::{
|
||||
@@ -14,7 +14,8 @@ use crate::Ruma;
|
||||
///
|
||||
/// Creates a new room alias on this server.
|
||||
pub(crate) async fn create_alias_route(
|
||||
State(services): State<crate::State>, body: Ruma<create_alias::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<create_alias::v3::Request>,
|
||||
) -> Result<create_alias::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -58,7 +59,8 @@ pub(crate) async fn create_alias_route(
|
||||
///
|
||||
/// - TODO: Update canonical alias event
|
||||
pub(crate) async fn delete_alias_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_alias::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_alias::v3::Request>,
|
||||
) -> Result<delete_alias::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -83,11 +85,13 @@ pub(crate) async fn delete_alias_route(
|
||||
///
|
||||
/// Resolve an alias locally or over federation.
|
||||
pub(crate) async fn get_alias_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_alias::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_alias::v3::Request>,
|
||||
) -> Result<get_alias::v3::Response> {
|
||||
let room_alias = body.body.room_alias;
|
||||
|
||||
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias, None).await else {
|
||||
let Ok((room_id, servers)) = services.rooms.alias.resolve_alias(&room_alias, None).await
|
||||
else {
|
||||
return Err!(Request(NotFound("Room with alias not found.")));
|
||||
};
|
||||
|
||||
@@ -98,7 +102,10 @@ pub(crate) async fn get_alias_route(
|
||||
}
|
||||
|
||||
async fn room_available_servers(
|
||||
services: &Services, room_id: &RoomId, room_alias: &RoomAliasId, pre_servers: Vec<OwnedServerName>,
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
room_alias: &RoomAliasId,
|
||||
pre_servers: Vec<OwnedServerName>,
|
||||
) -> Vec<OwnedServerName> {
|
||||
// find active servers in room state cache to suggest
|
||||
let mut servers: Vec<OwnedServerName> = services
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use conduit::{err, Err, Result};
|
||||
use conduwuit::{err, Err, Result};
|
||||
use ruma::api::{appservice::ping, client::appservice::request_ping};
|
||||
|
||||
use crate::Ruma;
|
||||
@@ -9,12 +9,12 @@ use crate::Ruma;
|
||||
/// 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>,
|
||||
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."))))?;
|
||||
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(
|
||||
@@ -41,7 +41,5 @@ pub(crate) async fn appservice_ping(
|
||||
.await?
|
||||
.expect("We already validated if an appservice URL exists above");
|
||||
|
||||
Ok(request_ping::v1::Response {
|
||||
duration: timer.elapsed(),
|
||||
})
|
||||
Ok(request_ping::v1::Response { duration: timer.elapsed() })
|
||||
}
|
||||
|
||||
+44
-33
@@ -1,11 +1,12 @@
|
||||
use axum::extract::State;
|
||||
use conduit::{err, Err};
|
||||
use conduwuit::{err, Err};
|
||||
use ruma::{
|
||||
api::client::backup::{
|
||||
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
|
||||
delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
|
||||
get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
|
||||
get_latest_backup_info, update_backup_version,
|
||||
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session,
|
||||
create_backup_version, delete_backup_keys, delete_backup_keys_for_room,
|
||||
delete_backup_keys_for_session, delete_backup_version, get_backup_info, get_backup_keys,
|
||||
get_backup_keys_for_room, get_backup_keys_for_session, get_latest_backup_info,
|
||||
update_backup_version,
|
||||
},
|
||||
UInt,
|
||||
};
|
||||
@@ -16,15 +17,14 @@ use crate::{Result, Ruma};
|
||||
///
|
||||
/// Creates a new backup.
|
||||
pub(crate) async fn create_backup_version_route(
|
||||
State(services): State<crate::State>, body: Ruma<create_backup_version::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<create_backup_version::v3::Request>,
|
||||
) -> Result<create_backup_version::v3::Response> {
|
||||
let version = services
|
||||
.key_backups
|
||||
.create_backup(body.sender_user(), &body.algorithm)?;
|
||||
|
||||
Ok(create_backup_version::v3::Response {
|
||||
version,
|
||||
})
|
||||
Ok(create_backup_version::v3::Response { version })
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/room_keys/version/{version}`
|
||||
@@ -32,7 +32,8 @@ pub(crate) async fn create_backup_version_route(
|
||||
/// Update information about an existing backup. Only `auth_data` can be
|
||||
/// modified.
|
||||
pub(crate) async fn update_backup_version_route(
|
||||
State(services): State<crate::State>, body: Ruma<update_backup_version::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<update_backup_version::v3::Request>,
|
||||
) -> Result<update_backup_version::v3::Response> {
|
||||
services
|
||||
.key_backups
|
||||
@@ -46,7 +47,8 @@ pub(crate) async fn update_backup_version_route(
|
||||
///
|
||||
/// Get information about the latest backup version.
|
||||
pub(crate) async fn get_latest_backup_info_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_latest_backup_info::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_latest_backup_info::v3::Request>,
|
||||
) -> Result<get_latest_backup_info::v3::Response> {
|
||||
let (version, algorithm) = services
|
||||
.key_backups
|
||||
@@ -75,13 +77,16 @@ pub(crate) async fn get_latest_backup_info_route(
|
||||
///
|
||||
/// Get information about an existing backup.
|
||||
pub(crate) async fn get_backup_info_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_backup_info::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_backup_info::v3::Request>,
|
||||
) -> Result<get_backup_info::v3::Response> {
|
||||
let algorithm = services
|
||||
.key_backups
|
||||
.get_backup(body.sender_user(), &body.version)
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Key backup does not exist at version {:?}", body.version))))?;
|
||||
.map_err(|_| {
|
||||
err!(Request(NotFound("Key backup does not exist at version {:?}", body.version)))
|
||||
})?;
|
||||
|
||||
Ok(get_backup_info::v3::Response {
|
||||
algorithm,
|
||||
@@ -105,7 +110,8 @@ pub(crate) async fn get_backup_info_route(
|
||||
/// - Deletes both information about the backup, as well as all key data related
|
||||
/// to the backup
|
||||
pub(crate) async fn delete_backup_version_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_backup_version::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_backup_version::v3::Request>,
|
||||
) -> Result<delete_backup_version::v3::Response> {
|
||||
services
|
||||
.key_backups
|
||||
@@ -124,7 +130,8 @@ pub(crate) async fn delete_backup_version_route(
|
||||
/// - Adds the keys to the backup
|
||||
/// - Returns the new number of keys in this backup and the etag
|
||||
pub(crate) async fn add_backup_keys_route(
|
||||
State(services): State<crate::State>, body: Ruma<add_backup_keys::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<add_backup_keys::v3::Request>,
|
||||
) -> Result<add_backup_keys::v3::Response> {
|
||||
if services
|
||||
.key_backups
|
||||
@@ -168,7 +175,8 @@ pub(crate) async fn add_backup_keys_route(
|
||||
/// - Adds the keys to the backup
|
||||
/// - Returns the new number of keys in this backup and the etag
|
||||
pub(crate) async fn add_backup_keys_for_room_route(
|
||||
State(services): State<crate::State>, body: Ruma<add_backup_keys_for_room::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<add_backup_keys_for_room::v3::Request>,
|
||||
) -> Result<add_backup_keys_for_room::v3::Response> {
|
||||
if services
|
||||
.key_backups
|
||||
@@ -210,7 +218,8 @@ pub(crate) async fn add_backup_keys_for_room_route(
|
||||
/// - Adds the keys to the backup
|
||||
/// - Returns the new number of keys in this backup and the etag
|
||||
pub(crate) async fn add_backup_keys_for_session_route(
|
||||
State(services): State<crate::State>, body: Ruma<add_backup_keys_for_session::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<add_backup_keys_for_session::v3::Request>,
|
||||
) -> Result<add_backup_keys_for_session::v3::Response> {
|
||||
if services
|
||||
.key_backups
|
||||
@@ -251,56 +260,56 @@ pub(crate) async fn add_backup_keys_for_session_route(
|
||||
///
|
||||
/// Retrieves all keys from the backup.
|
||||
pub(crate) async fn get_backup_keys_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_backup_keys::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_backup_keys::v3::Request>,
|
||||
) -> Result<get_backup_keys::v3::Response> {
|
||||
let rooms = services
|
||||
.key_backups
|
||||
.get_all(body.sender_user(), &body.version)
|
||||
.await;
|
||||
|
||||
Ok(get_backup_keys::v3::Response {
|
||||
rooms,
|
||||
})
|
||||
Ok(get_backup_keys::v3::Response { rooms })
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}`
|
||||
///
|
||||
/// Retrieves all keys from the backup for a given room.
|
||||
pub(crate) async fn get_backup_keys_for_room_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_backup_keys_for_room::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_backup_keys_for_room::v3::Request>,
|
||||
) -> Result<get_backup_keys_for_room::v3::Response> {
|
||||
let sessions = services
|
||||
.key_backups
|
||||
.get_room(body.sender_user(), &body.version, &body.room_id)
|
||||
.await;
|
||||
|
||||
Ok(get_backup_keys_for_room::v3::Response {
|
||||
sessions,
|
||||
})
|
||||
Ok(get_backup_keys_for_room::v3::Response { sessions })
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
|
||||
///
|
||||
/// Retrieves a key from the backup.
|
||||
pub(crate) async fn get_backup_keys_for_session_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_backup_keys_for_session::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_backup_keys_for_session::v3::Request>,
|
||||
) -> Result<get_backup_keys_for_session::v3::Response> {
|
||||
let key_data = services
|
||||
.key_backups
|
||||
.get_session(body.sender_user(), &body.version, &body.room_id, &body.session_id)
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound(debug_error!("Backup key not found for this user's session.")))))?;
|
||||
.map_err(|_| {
|
||||
err!(Request(NotFound(debug_error!("Backup key not found for this user's session."))))
|
||||
})?;
|
||||
|
||||
Ok(get_backup_keys_for_session::v3::Response {
|
||||
key_data,
|
||||
})
|
||||
Ok(get_backup_keys_for_session::v3::Response { key_data })
|
||||
}
|
||||
|
||||
/// # `DELETE /_matrix/client/r0/room_keys/keys`
|
||||
///
|
||||
/// Delete the keys from the backup.
|
||||
pub(crate) async fn delete_backup_keys_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_backup_keys::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_backup_keys::v3::Request>,
|
||||
) -> Result<delete_backup_keys::v3::Response> {
|
||||
services
|
||||
.key_backups
|
||||
@@ -324,7 +333,8 @@ pub(crate) async fn delete_backup_keys_route(
|
||||
///
|
||||
/// Delete the keys from the backup for a given room.
|
||||
pub(crate) async fn delete_backup_keys_for_room_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_backup_keys_for_room::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_backup_keys_for_room::v3::Request>,
|
||||
) -> Result<delete_backup_keys_for_room::v3::Response> {
|
||||
services
|
||||
.key_backups
|
||||
@@ -348,7 +358,8 @@ pub(crate) async fn delete_backup_keys_for_room_route(
|
||||
///
|
||||
/// Delete a key from the backup.
|
||||
pub(crate) async fn delete_backup_keys_for_session_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_backup_keys_for_session::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_backup_keys_for_session::v3::Request>,
|
||||
) -> Result<delete_backup_keys_for_session::v3::Response> {
|
||||
services
|
||||
.key_backups
|
||||
|
||||
@@ -1,52 +1,40 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{Result, Server};
|
||||
use ruma::{
|
||||
api::client::discovery::get_capabilities::{
|
||||
self, Capabilities, GetLoginTokenCapability, RoomVersionStability, RoomVersionsCapability,
|
||||
ThirdPartyIdChangesCapability,
|
||||
self, Capabilities, GetLoginTokenCapability, RoomVersionStability,
|
||||
RoomVersionsCapability, ThirdPartyIdChangesCapability,
|
||||
},
|
||||
RoomVersionId,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{Result, Ruma};
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /_matrix/client/v3/capabilities`
|
||||
///
|
||||
/// Get information on the supported feature set and other relevent capabilities
|
||||
/// of this server.
|
||||
pub(crate) async fn get_capabilities_route(
|
||||
State(services): State<crate::State>, _body: Ruma<get_capabilities::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
_body: Ruma<get_capabilities::v3::Request>,
|
||||
) -> Result<get_capabilities::v3::Response> {
|
||||
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 available: BTreeMap<RoomVersionId, RoomVersionStability> =
|
||||
Server::available_room_versions().collect();
|
||||
|
||||
let mut capabilities = Capabilities::default();
|
||||
capabilities.room_versions = RoomVersionsCapability {
|
||||
default: services.globals.default_room_version(),
|
||||
default: services.server.config.default_room_version.clone(),
|
||||
available,
|
||||
};
|
||||
|
||||
// we do not implement 3PID stuff
|
||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability {
|
||||
enabled: false,
|
||||
};
|
||||
capabilities.thirdparty_id_changes = ThirdPartyIdChangesCapability { enabled: false };
|
||||
|
||||
// we dont support generating tokens yet
|
||||
capabilities.get_login_token = GetLoginTokenCapability {
|
||||
enabled: false,
|
||||
enabled: services.server.config.login_via_existing_session,
|
||||
};
|
||||
|
||||
// MSC4133 capability
|
||||
@@ -54,7 +42,5 @@ pub(crate) async fn get_capabilities_route(
|
||||
.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))
|
||||
.expect("this is valid JSON we created");
|
||||
|
||||
Ok(get_capabilities::v3::Response {
|
||||
capabilities,
|
||||
})
|
||||
Ok(get_capabilities::v3::Response { capabilities })
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
use axum::extract::State;
|
||||
use conduit::err;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
config::{get_global_account_data, get_room_account_data, set_global_account_data, set_room_account_data},
|
||||
error::ErrorKind,
|
||||
},
|
||||
events::{AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent},
|
||||
serde::Raw,
|
||||
OwnedUserId, RoomId,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, value::RawValue as RawJsonValue};
|
||||
|
||||
use crate::{service::Services, Error, Result, Ruma};
|
||||
|
||||
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
|
||||
///
|
||||
/// Sets some account data for the sender user.
|
||||
pub(crate) async fn set_global_account_data_route(
|
||||
State(services): State<crate::State>, body: Ruma<set_global_account_data::v3::Request>,
|
||||
) -> Result<set_global_account_data::v3::Response> {
|
||||
set_account_data(
|
||||
&services,
|
||||
None,
|
||||
body.sender_user.as_ref(),
|
||||
&body.event_type.to_string(),
|
||||
body.data.json(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(set_global_account_data::v3::Response {})
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
|
||||
///
|
||||
/// Sets some room account data for the sender user.
|
||||
pub(crate) async fn set_room_account_data_route(
|
||||
State(services): State<crate::State>, body: Ruma<set_room_account_data::v3::Request>,
|
||||
) -> Result<set_room_account_data::v3::Response> {
|
||||
set_account_data(
|
||||
&services,
|
||||
Some(&body.room_id),
|
||||
body.sender_user.as_ref(),
|
||||
&body.event_type.to_string(),
|
||||
body.data.json(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(set_room_account_data::v3::Response {})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
|
||||
///
|
||||
/// Gets some account data for the sender user.
|
||||
pub(crate) async fn get_global_account_data_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_global_account_data::v3::Request>,
|
||||
) -> Result<get_global_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let account_data: ExtractGlobalEventContent = services
|
||||
.account_data
|
||||
.get_global(sender_user, body.event_type.clone())
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
|
||||
|
||||
Ok(get_global_account_data::v3::Response {
|
||||
account_data: account_data.content,
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
|
||||
///
|
||||
/// Gets some room account data for the sender user.
|
||||
pub(crate) async fn get_room_account_data_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_room_account_data::v3::Request>,
|
||||
) -> Result<get_room_account_data::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let account_data: ExtractRoomEventContent = services
|
||||
.account_data
|
||||
.get_room(&body.room_id, sender_user, body.event_type.clone())
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
|
||||
|
||||
Ok(get_room_account_data::v3::Response {
|
||||
account_data: account_data.content,
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_account_data(
|
||||
services: &Services, room_id: Option<&RoomId>, sender_user: Option<&OwnedUserId>, event_type: &str,
|
||||
data: &RawJsonValue,
|
||||
) -> Result<()> {
|
||||
let sender_user = sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
let data: serde_json::Value =
|
||||
serde_json::from_str(data.get()).map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
|
||||
|
||||
services
|
||||
.account_data
|
||||
.update(
|
||||
room_id,
|
||||
sender_user,
|
||||
event_type.into(),
|
||||
&json!({
|
||||
"type": event_type,
|
||||
"content": data,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtractRoomEventContent {
|
||||
content: Raw<AnyRoomAccountDataEventContent>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ExtractGlobalEventContent {
|
||||
content: Raw<AnyGlobalAccountDataEventContent>,
|
||||
}
|
||||
+92
-87
@@ -1,24 +1,22 @@
|
||||
use std::iter::once;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduit::{
|
||||
use conduwuit::{
|
||||
at, err, ref_at,
|
||||
utils::{
|
||||
future::TryExtExt,
|
||||
stream::{BroadbandExt, ReadyExt, WidebandExt},
|
||||
stream::{BroadbandExt, ReadyExt, TryIgnore, WidebandExt},
|
||||
IterStream,
|
||||
},
|
||||
Err, Result,
|
||||
Err, PduEvent, Result,
|
||||
};
|
||||
use futures::{join, try_join, FutureExt, StreamExt, TryFutureExt};
|
||||
use ruma::{
|
||||
api::client::{context::get_context, filter::LazyLoadOptions},
|
||||
events::StateEventType,
|
||||
OwnedEventId, UserId,
|
||||
use futures::{
|
||||
future::{join, join3, try_join3, OptionFuture},
|
||||
FutureExt, StreamExt, TryFutureExt, TryStreamExt,
|
||||
};
|
||||
use ruma::{api::client::context::get_context, events::StateEventType, OwnedEventId, UserId};
|
||||
use service::rooms::{lazy_loading, lazy_loading::Options, short::ShortStateKey};
|
||||
|
||||
use crate::{
|
||||
client::message::{event_filter, ignored_filter, update_lazy, visibility_filter, LazySet},
|
||||
client::message::{event_filter, ignored_filter, lazy_loading_witness, visibility_filter},
|
||||
Ruma,
|
||||
};
|
||||
|
||||
@@ -32,12 +30,13 @@ const LIMIT_DEFAULT: usize = 10;
|
||||
/// - Only works if the user is joined (TODO: always allow, but only show events
|
||||
/// if the user was joined, depending on history_visibility)
|
||||
pub(crate) async fn get_context_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_context::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_context::v3::Request>,
|
||||
) -> Result<get_context::v3::Response> {
|
||||
let filter = &body.filter;
|
||||
let sender = body.sender();
|
||||
let (sender_user, _) = sender;
|
||||
let (sender_user, sender_device) = sender;
|
||||
let room_id = &body.room_id;
|
||||
let filter = &body.filter;
|
||||
|
||||
// Use limit or else 10, with maximum 100
|
||||
let limit: usize = body
|
||||
@@ -46,26 +45,13 @@ pub(crate) async fn get_context_route(
|
||||
.unwrap_or(LIMIT_DEFAULT)
|
||||
.min(LIMIT_MAX);
|
||||
|
||||
// some clients, at least element, seem to require knowledge of redundant
|
||||
// members for "inline" profiles on the timeline to work properly
|
||||
let lazy_load_enabled = matches!(filter.lazy_load_options, LazyLoadOptions::Enabled { .. });
|
||||
|
||||
let lazy_load_redundant = if let LazyLoadOptions::Enabled {
|
||||
include_redundant_members,
|
||||
} = filter.lazy_load_options
|
||||
{
|
||||
include_redundant_members
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let base_token = services
|
||||
let base_id = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu_count(&body.event_id)
|
||||
.get_pdu_id(&body.event_id)
|
||||
.map_err(|_| err!(Request(NotFound("Event not found."))));
|
||||
|
||||
let base_event = services
|
||||
let base_pdu = services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_pdu(&body.event_id)
|
||||
@@ -77,114 +63,133 @@ pub(crate) async fn get_context_route(
|
||||
.user_can_see_event(sender_user, &body.room_id, &body.event_id)
|
||||
.map(Ok);
|
||||
|
||||
let (base_token, base_event, visible) = try_join!(base_token, base_event, visible)?;
|
||||
let (base_id, base_pdu, visible) = try_join3(base_id, base_pdu, visible).await?;
|
||||
|
||||
if base_event.room_id != body.room_id || base_event.event_id != body.event_id {
|
||||
if base_pdu.room_id != body.room_id || base_pdu.event_id != body.event_id {
|
||||
return Err!(Request(NotFound("Base event not found.")));
|
||||
}
|
||||
|
||||
if !visible
|
||||
|| ignored_filter(&services, (base_token, base_event.clone()), sender_user)
|
||||
.await
|
||||
.is_none()
|
||||
{
|
||||
if !visible {
|
||||
return Err!(Request(Forbidden("You don't have permission to view this event.")));
|
||||
}
|
||||
|
||||
let base_count = base_id.pdu_count();
|
||||
|
||||
let base_event = ignored_filter(&services, (base_count, base_pdu), sender_user);
|
||||
|
||||
let events_before = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(base_token));
|
||||
.pdus_rev(Some(sender_user), room_id, Some(base_count))
|
||||
.ignore_err()
|
||||
.ready_filter_map(|item| event_filter(item, filter))
|
||||
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
|
||||
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
|
||||
.take(limit / 2)
|
||||
.collect();
|
||||
|
||||
let events_after = services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus(Some(sender_user), room_id, Some(base_token));
|
||||
|
||||
let (events_before, events_after) = try_join!(events_before, events_after)?;
|
||||
|
||||
let events_before = events_before
|
||||
.pdus(Some(sender_user), room_id, Some(base_count))
|
||||
.ignore_err()
|
||||
.ready_filter_map(|item| event_filter(item, filter))
|
||||
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
|
||||
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
|
||||
.take(limit / 2)
|
||||
.collect();
|
||||
|
||||
let events_after = events_after
|
||||
.ready_filter_map(|item| event_filter(item, filter))
|
||||
.wide_filter_map(|item| ignored_filter(&services, item, sender_user))
|
||||
.wide_filter_map(|item| visibility_filter(&services, item, sender_user))
|
||||
.take(limit / 2)
|
||||
.collect();
|
||||
let (base_event, events_before, events_after): (_, Vec<_>, Vec<_>) =
|
||||
join3(base_event, events_before, events_after).await;
|
||||
|
||||
let (events_before, events_after): (Vec<_>, Vec<_>) = join!(events_before, events_after);
|
||||
let lazy_loading_context = lazy_loading::Context {
|
||||
user_id: sender_user,
|
||||
device_id: sender_device,
|
||||
room_id,
|
||||
token: Some(base_count.into_unsigned()),
|
||||
options: Some(&filter.lazy_load_options),
|
||||
};
|
||||
|
||||
let lazy_loading_witnessed: OptionFuture<_> = filter
|
||||
.lazy_load_options
|
||||
.is_enabled()
|
||||
.then_some(
|
||||
base_event
|
||||
.iter()
|
||||
.chain(events_before.iter())
|
||||
.chain(events_after.iter()),
|
||||
)
|
||||
.map(|witnessed| lazy_loading_witness(&services, &lazy_loading_context, witnessed))
|
||||
.into();
|
||||
|
||||
let state_at = events_after
|
||||
.last()
|
||||
.map(ref_at!(1))
|
||||
.map_or(body.event_id.as_ref(), |e| e.event_id.as_ref());
|
||||
.map_or(body.event_id.as_ref(), |pdu| pdu.event_id.as_ref());
|
||||
|
||||
let state_ids = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.pdu_shortstatehash(state_at)
|
||||
.or_else(|_| services.rooms.state.get_room_shortstatehash(room_id))
|
||||
.and_then(|shortstatehash| services.rooms.state_accessor.state_full_ids(shortstatehash))
|
||||
.map_err(|e| err!(Database("State not found: {e}")))
|
||||
.await?;
|
||||
|
||||
let lazy = once(&(base_token, base_event.clone()))
|
||||
.chain(events_before.iter())
|
||||
.chain(events_after.iter())
|
||||
.stream()
|
||||
.fold(LazySet::new(), |lazy, item| {
|
||||
update_lazy(&services, room_id, sender, lazy, item, lazy_load_redundant)
|
||||
})
|
||||
.await;
|
||||
|
||||
let lazy = &lazy;
|
||||
let state: Vec<_> = state_ids
|
||||
.iter()
|
||||
.stream()
|
||||
.broad_filter_map(|(shortstatekey, event_id)| {
|
||||
.map_ok(|shortstatehash| {
|
||||
services
|
||||
.rooms
|
||||
.short
|
||||
.get_statekey_from_short(*shortstatekey)
|
||||
.map_ok(move |(event_type, state_key)| (event_type, state_key, event_id))
|
||||
.ok()
|
||||
.state_accessor
|
||||
.state_full_ids(shortstatehash)
|
||||
.map(Ok)
|
||||
})
|
||||
.ready_filter_map(|(event_type, state_key, event_id)| {
|
||||
if !lazy_load_enabled || event_type != StateEventType::RoomMember {
|
||||
return Some(event_id);
|
||||
.map_err(|e| err!(Database("State not found: {e}")))
|
||||
.try_flatten_stream()
|
||||
.try_collect()
|
||||
.boxed();
|
||||
|
||||
let (lazy_loading_witnessed, state_ids) = join(lazy_loading_witnessed, state_ids).await;
|
||||
|
||||
let state_ids: Vec<(ShortStateKey, OwnedEventId)> = state_ids?;
|
||||
let shortstatekeys = state_ids.iter().map(at!(0)).stream();
|
||||
let shorteventids = state_ids.iter().map(ref_at!(1)).stream();
|
||||
let lazy_loading_witnessed = lazy_loading_witnessed.unwrap_or_default();
|
||||
let state: Vec<_> = services
|
||||
.rooms
|
||||
.short
|
||||
.multi_get_statekey_from_short(shortstatekeys)
|
||||
.zip(shorteventids)
|
||||
.ready_filter_map(|item| Some((item.0.ok()?, item.1)))
|
||||
.ready_filter_map(|((event_type, state_key), event_id)| {
|
||||
if filter.lazy_load_options.is_enabled()
|
||||
&& event_type == StateEventType::RoomMember
|
||||
&& state_key
|
||||
.as_str()
|
||||
.try_into()
|
||||
.is_ok_and(|user_id: &UserId| !lazy_loading_witnessed.contains(user_id))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
state_key
|
||||
.as_str()
|
||||
.try_into()
|
||||
.ok()
|
||||
.filter(|&user_id: &&UserId| lazy.contains(user_id))
|
||||
.map(|_| event_id)
|
||||
Some(event_id)
|
||||
})
|
||||
.broad_filter_map(|event_id: &OwnedEventId| services.rooms.timeline.get_pdu(event_id).ok())
|
||||
.map(|pdu| pdu.to_state_event())
|
||||
.broad_filter_map(|event_id: &OwnedEventId| {
|
||||
services.rooms.timeline.get_pdu(event_id.as_ref()).ok()
|
||||
})
|
||||
.map(PduEvent::into_state_event)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
Ok(get_context::v3::Response {
|
||||
event: Some(base_event.to_room_event()),
|
||||
event: base_event.map(at!(1)).as_ref().map(PduEvent::to_room_event),
|
||||
|
||||
start: events_before
|
||||
.last()
|
||||
.map(at!(0))
|
||||
.or(Some(base_token))
|
||||
.or(Some(base_count))
|
||||
.as_ref()
|
||||
.map(ToString::to_string),
|
||||
|
||||
end: events_after
|
||||
.last()
|
||||
.map(at!(0))
|
||||
.or(Some(base_token))
|
||||
.or(Some(base_count))
|
||||
.as_ref()
|
||||
.map(ToString::to_string),
|
||||
|
||||
|
||||
+15
-18
@@ -1,6 +1,6 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::{err, Err};
|
||||
use conduwuit::{err, Err};
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
@@ -18,7 +18,8 @@ use crate::{utils, Error, Result, Ruma};
|
||||
///
|
||||
/// Get metadata on all devices of the sender user.
|
||||
pub(crate) async fn get_devices_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_devices::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_devices::v3::Request>,
|
||||
) -> Result<get_devices::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -28,16 +29,15 @@ pub(crate) async fn get_devices_route(
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
Ok(get_devices::v3::Response {
|
||||
devices,
|
||||
})
|
||||
Ok(get_devices::v3::Response { devices })
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/r0/devices/{deviceId}`
|
||||
///
|
||||
/// Get metadata on a single device of the sender user.
|
||||
pub(crate) async fn get_device_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_device::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_device::v3::Request>,
|
||||
) -> Result<get_device::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -47,9 +47,7 @@ pub(crate) async fn get_device_route(
|
||||
.await
|
||||
.map_err(|_| err!(Request(NotFound("Device not found."))))?;
|
||||
|
||||
Ok(get_device::v3::Response {
|
||||
device,
|
||||
})
|
||||
Ok(get_device::v3::Response { device })
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
|
||||
@@ -57,7 +55,8 @@ pub(crate) async fn get_device_route(
|
||||
/// Updates the metadata on a given device of the sender user.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "update_device")]
|
||||
pub(crate) async fn update_device_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<update_device::v3::Request>,
|
||||
) -> Result<update_device::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -93,16 +92,15 @@ pub(crate) async fn update_device_route(
|
||||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
pub(crate) async fn delete_device_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_device::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_device::v3::Request>,
|
||||
) -> Result<delete_device::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::Password],
|
||||
}],
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
@@ -151,16 +149,15 @@ pub(crate) async fn delete_device_route(
|
||||
/// - Forgets to-device events
|
||||
/// - Triggers device list updates
|
||||
pub(crate) async fn delete_devices_route(
|
||||
State(services): State<crate::State>, body: Ruma<delete_devices::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<delete_devices::v3::Request>,
|
||||
) -> Result<delete_devices::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::Password],
|
||||
}],
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
|
||||
+58
-30
@@ -1,11 +1,14 @@
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::{info, warn, Err, Error, Result};
|
||||
use conduwuit::{info, warn, Err, Error, Result};
|
||||
use futures::{StreamExt, TryFutureExt};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::{
|
||||
directory::{get_public_rooms, get_public_rooms_filtered, get_room_visibility, set_room_visibility},
|
||||
directory::{
|
||||
get_public_rooms, get_public_rooms_filtered, get_room_visibility,
|
||||
set_room_visibility,
|
||||
},
|
||||
error::ErrorKind,
|
||||
room,
|
||||
},
|
||||
@@ -32,7 +35,8 @@ use crate::Ruma;
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
||||
pub(crate) async fn get_public_rooms_filtered_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_public_rooms_filtered::v3::Request>,
|
||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||
if let Some(server) = &body.server {
|
||||
@@ -57,7 +61,10 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!(?body.server, "Failed to return /publicRooms: {e}");
|
||||
Error::BadRequest(ErrorKind::Unknown, "Failed to return the requested server's public room list.")
|
||||
Error::BadRequest(
|
||||
ErrorKind::Unknown,
|
||||
"Failed to return the requested server's public room list.",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(response)
|
||||
@@ -70,7 +77,8 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
||||
/// - Rooms are ordered by the number of joined members
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
||||
pub(crate) async fn get_public_rooms_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_public_rooms::v3::Request>,
|
||||
) -> Result<get_public_rooms::v3::Response> {
|
||||
if let Some(server) = &body.server {
|
||||
@@ -95,7 +103,10 @@ pub(crate) async fn get_public_rooms_route(
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!(?body.server, "Failed to return /publicRooms: {e}");
|
||||
Error::BadRequest(ErrorKind::Unknown, "Failed to return the requested server's public room list.")
|
||||
Error::BadRequest(
|
||||
ErrorKind::Unknown,
|
||||
"Failed to return the requested server's public room list.",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(get_public_rooms::v3::Response {
|
||||
@@ -111,7 +122,8 @@ pub(crate) async fn get_public_rooms_route(
|
||||
/// Sets the visibility of a given room in the room directory.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
|
||||
pub(crate) async fn set_room_visibility_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<set_room_visibility::v3::Request>,
|
||||
) -> Result<set_room_visibility::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -139,23 +151,23 @@ pub(crate) async fn set_room_visibility_route(
|
||||
}
|
||||
|
||||
match &body.visibility {
|
||||
room::Visibility::Public => {
|
||||
if services.globals.config.lockdown_public_room_directory
|
||||
| room::Visibility::Public => {
|
||||
if services.server.config.lockdown_public_room_directory
|
||||
&& !services.users.is_admin(sender_user).await
|
||||
&& 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",
|
||||
"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 {
|
||||
if services.server.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",
|
||||
"Non-admin user {sender_user} tried to publish {0} to the room \
|
||||
directory while \"lockdown_public_room_directory\" is enabled",
|
||||
body.room_id
|
||||
))
|
||||
.await;
|
||||
@@ -169,16 +181,19 @@ pub(crate) async fn set_room_visibility_route(
|
||||
|
||||
services.rooms.directory.set_public(&body.room_id);
|
||||
|
||||
if services.globals.config.admin_room_notices {
|
||||
if services.server.config.admin_room_notices {
|
||||
services
|
||||
.admin
|
||||
.send_text(&format!("{sender_user} made {} public to the room directory", body.room_id))
|
||||
.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),
|
||||
_ => {
|
||||
| room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id),
|
||||
| _ => {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Room visibility type is not supported.",
|
||||
@@ -193,7 +208,8 @@ pub(crate) async fn set_room_visibility_route(
|
||||
///
|
||||
/// Gets the visibility of a given room in the room directory.
|
||||
pub(crate) async fn get_room_visibility_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_room_visibility::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_room_visibility::v3::Request>,
|
||||
) -> Result<get_room_visibility::v3::Response> {
|
||||
if !services.rooms.metadata.exists(&body.room_id).await {
|
||||
// Return 404 if the room doesn't exist
|
||||
@@ -210,10 +226,16 @@ pub(crate) async fn get_room_visibility_route(
|
||||
}
|
||||
|
||||
pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
services: &Services, server: Option<&ServerName>, limit: Option<UInt>, since: Option<&str>, filter: &Filter,
|
||||
services: &Services,
|
||||
server: Option<&ServerName>,
|
||||
limit: Option<UInt>,
|
||||
since: Option<&str>,
|
||||
filter: &Filter,
|
||||
_network: &RoomNetwork,
|
||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||
if let Some(other_server) = server.filter(|server_name| !services.globals.server_is_ours(server_name)) {
|
||||
if let Some(other_server) =
|
||||
server.filter(|server_name| !services.globals.server_is_ours(server_name))
|
||||
{
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
@@ -245,9 +267,10 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
if let Some(s) = &since {
|
||||
let mut characters = s.chars();
|
||||
let backwards = match characters.next() {
|
||||
Some('n') => false,
|
||||
Some('p') => true,
|
||||
_ => return Err(Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token")),
|
||||
| Some('n') => false,
|
||||
| Some('p') => true,
|
||||
| _ =>
|
||||
return Err(Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token")),
|
||||
};
|
||||
|
||||
num_since = characters
|
||||
@@ -337,7 +360,11 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
||||
|
||||
/// Check whether the user can publish to the room directory via power levels of
|
||||
/// room history visibility event or room creator
|
||||
async fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
||||
async fn user_can_publish_room(
|
||||
services: &Services,
|
||||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
if let Ok(event) = services
|
||||
.rooms
|
||||
.state_accessor
|
||||
@@ -347,7 +374,8 @@ async fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &
|
||||
serde_json::from_str(event.content.get())
|
||||
.map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels"))
|
||||
.map(|content: RoomPowerLevelsEventContent| {
|
||||
RoomPowerLevels::from(content).user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
|
||||
RoomPowerLevels::from(content)
|
||||
.user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
|
||||
})
|
||||
} else if let Ok(event) = services
|
||||
.rooms
|
||||
@@ -406,10 +434,10 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public
|
||||
.state_accessor
|
||||
.room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "")
|
||||
.map_ok(|c: RoomJoinRulesEventContent| match c.join_rule {
|
||||
JoinRule::Public => PublicRoomJoinRule::Public,
|
||||
JoinRule::Knock => "knock".into(),
|
||||
JoinRule::KnockRestricted(_) => "knock_restricted".into(),
|
||||
_ => "invite".into(),
|
||||
| JoinRule::Public => PublicRoomJoinRule::Public,
|
||||
| JoinRule::Knock => "knock".into(),
|
||||
| JoinRule::KnockRestricted(_) => "knock_restricted".into(),
|
||||
| _ => "invite".into(),
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use axum::extract::State;
|
||||
use conduit::err;
|
||||
use conduwuit::err;
|
||||
use ruma::api::client::filter::{create_filter, get_filter};
|
||||
|
||||
use crate::{Result, Ruma};
|
||||
@@ -10,7 +10,8 @@ use crate::{Result, Ruma};
|
||||
///
|
||||
/// - A user can only access their own filters
|
||||
pub(crate) async fn get_filter_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_filter::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_filter::v3::Request>,
|
||||
) -> Result<get_filter::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -26,7 +27,8 @@ pub(crate) async fn get_filter_route(
|
||||
///
|
||||
/// Creates a new filter to be used by other endpoints.
|
||||
pub(crate) async fn create_filter_route(
|
||||
State(services): State<crate::State>, body: Ruma<create_filter::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<create_filter::v3::Request>,
|
||||
) -> Result<create_filter::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
|
||||
+57
-75
@@ -1,16 +1,16 @@
|
||||
use std::{
|
||||
collections::{hash_map, BTreeMap, HashMap, HashSet},
|
||||
time::Instant,
|
||||
};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
use axum::extract::State;
|
||||
use conduit::{err, utils, utils::math::continue_exponential_backoff_secs, Err, Error, Result};
|
||||
use conduwuit::{err, utils, Error, Result};
|
||||
use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::{
|
||||
error::ErrorKind,
|
||||
keys::{claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures, upload_signing_keys},
|
||||
keys::{
|
||||
claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures,
|
||||
upload_signing_keys,
|
||||
},
|
||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
||||
},
|
||||
federation,
|
||||
@@ -34,7 +34,8 @@ use crate::{
|
||||
/// - If there are no device keys yet: Adds device keys (TODO: merge with
|
||||
/// existing keys?)
|
||||
pub(crate) async fn upload_keys_route(
|
||||
State(services): State<crate::State>, body: Ruma<upload_keys::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<upload_keys::v3::Request>,
|
||||
) -> Result<upload_keys::v3::Response> {
|
||||
let (sender_user, sender_device) = body.sender();
|
||||
|
||||
@@ -78,7 +79,8 @@ pub(crate) async fn upload_keys_route(
|
||||
/// - The master and self-signing keys contain signatures that the user is
|
||||
/// allowed to see
|
||||
pub(crate) async fn get_keys_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_keys::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_keys::v3::Request>,
|
||||
) -> Result<get_keys::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -96,7 +98,8 @@ pub(crate) async fn get_keys_route(
|
||||
///
|
||||
/// Claims one-time keys
|
||||
pub(crate) async fn claim_keys_route(
|
||||
State(services): State<crate::State>, body: Ruma<claim_keys::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<claim_keys::v3::Request>,
|
||||
) -> Result<claim_keys::v3::Response> {
|
||||
claim_keys_helper(&services, &body.one_time_keys).await
|
||||
}
|
||||
@@ -107,16 +110,15 @@ pub(crate) async fn claim_keys_route(
|
||||
///
|
||||
/// - Requires UIAA to verify password
|
||||
pub(crate) async fn upload_signing_keys_route(
|
||||
State(services): State<crate::State>, body: Ruma<upload_signing_keys::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<upload_signing_keys::v3::Request>,
|
||||
) -> Result<upload_signing_keys::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||
|
||||
// UIAA
|
||||
let mut uiaainfo = UiaaInfo {
|
||||
flows: vec![AuthFlow {
|
||||
stages: vec![AuthType::Password],
|
||||
}],
|
||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
||||
completed: Vec::new(),
|
||||
params: Box::default(),
|
||||
session: None,
|
||||
@@ -164,7 +166,8 @@ pub(crate) async fn upload_signing_keys_route(
|
||||
///
|
||||
/// Uploads end-to-end key signatures from the sender user.
|
||||
pub(crate) async fn upload_signatures_route(
|
||||
State(services): State<crate::State>, body: Ruma<upload_signatures::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<upload_signatures::v3::Request>,
|
||||
) -> Result<upload_signatures::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -177,7 +180,10 @@ pub(crate) async fn upload_signatures_route(
|
||||
.get("signatures")
|
||||
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Missing signatures field."))?
|
||||
.get(sender_user.to_string())
|
||||
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid user in signatures field."))?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid user in signatures field.",
|
||||
))?
|
||||
.as_object()
|
||||
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature."))?
|
||||
.clone()
|
||||
@@ -188,7 +194,10 @@ pub(crate) async fn upload_signatures_route(
|
||||
signature
|
||||
.1
|
||||
.as_str()
|
||||
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature value."))?
|
||||
.ok_or(Error::BadRequest(
|
||||
ErrorKind::InvalidParam,
|
||||
"Invalid signature value.",
|
||||
))?
|
||||
.to_owned(),
|
||||
);
|
||||
|
||||
@@ -212,7 +221,8 @@ pub(crate) async fn upload_signatures_route(
|
||||
///
|
||||
/// - TODO: left users
|
||||
pub(crate) async fn get_key_changes_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_key_changes::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_key_changes::v3::Request>,
|
||||
) -> Result<get_key_changes::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
|
||||
@@ -258,8 +268,11 @@ pub(crate) async fn get_key_changes_route(
|
||||
}
|
||||
|
||||
pub(crate) async fn get_keys_helper<F>(
|
||||
services: &Services, sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
|
||||
allowed_signatures: F, include_display_names: bool,
|
||||
services: &Services,
|
||||
sender_user: Option<&UserId>,
|
||||
device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
|
||||
allowed_signatures: F,
|
||||
include_display_names: bool,
|
||||
) -> Result<get_keys::v3::Response>
|
||||
where
|
||||
F: Fn(&UserId) -> bool + Send + Sync,
|
||||
@@ -292,7 +305,9 @@ where
|
||||
.users
|
||||
.get_device_metadata(user_id, device_id)
|
||||
.await
|
||||
.map_err(|_| err!(Database("all_device_keys contained nonexistent device.")))?;
|
||||
.map_err(|_| {
|
||||
err!(Database("all_device_keys contained nonexistent device."))
|
||||
})?;
|
||||
|
||||
add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
|
||||
.map_err(|_| err!(Database("invalid device keys in database")))?;
|
||||
@@ -310,7 +325,11 @@ where
|
||||
.users
|
||||
.get_device_metadata(user_id, device_id)
|
||||
.await
|
||||
.map_err(|_| err!(Request(InvalidParam("Tried to get keys for nonexistent device."))))?;
|
||||
.map_err(|_| {
|
||||
err!(Request(InvalidParam(
|
||||
"Tried to get keys for nonexistent device."
|
||||
)))
|
||||
})?;
|
||||
|
||||
add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
|
||||
.map_err(|_| err!(Database("invalid device keys in database")))?;
|
||||
@@ -345,60 +364,28 @@ where
|
||||
|
||||
let mut failures = BTreeMap::new();
|
||||
|
||||
let back_off = |id| async {
|
||||
match services
|
||||
.globals
|
||||
.bad_query_ratelimiter
|
||||
.write()
|
||||
.expect("locked")
|
||||
.entry(id)
|
||||
{
|
||||
hash_map::Entry::Vacant(e) => {
|
||||
e.insert((Instant::now(), 1));
|
||||
},
|
||||
hash_map::Entry::Occupied(mut e) => {
|
||||
*e.get_mut() = (Instant::now(), e.get().1.saturating_add(1));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||
.into_iter()
|
||||
.map(|(server, vec)| async move {
|
||||
if let Some((time, tries)) = services
|
||||
.globals
|
||||
.bad_query_ratelimiter
|
||||
.read()
|
||||
.expect("locked")
|
||||
.get(server)
|
||||
{
|
||||
// Exponential backoff
|
||||
const MIN: u64 = 5 * 60;
|
||||
const MAX: u64 = 60 * 60 * 24;
|
||||
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
|
||||
return (server, Err!(BadServerResponse("bad query from {server:?}, still backing off")));
|
||||
}
|
||||
}
|
||||
|
||||
let mut device_keys_input_fed = BTreeMap::new();
|
||||
for (user_id, keys) in vec {
|
||||
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
|
||||
}
|
||||
|
||||
let request = federation::keys::get_keys::v1::Request {
|
||||
device_keys: device_keys_input_fed,
|
||||
};
|
||||
let request =
|
||||
federation::keys::get_keys::v1::Request { device_keys: device_keys_input_fed };
|
||||
|
||||
let response = services
|
||||
.sending
|
||||
.send_federation_request(server, request)
|
||||
.await;
|
||||
|
||||
(server, Ok(response))
|
||||
(server, response)
|
||||
})
|
||||
.collect();
|
||||
|
||||
while let Some((server, response)) = futures.next().await {
|
||||
if let Ok(Ok(response)) = response {
|
||||
if let Ok(response) = response {
|
||||
for (user, master_key) in response.master_keys {
|
||||
let (master_key_id, mut master_key) = parse_master_key(&user, &master_key)?;
|
||||
|
||||
@@ -416,8 +403,8 @@ where
|
||||
.users
|
||||
.add_cross_signing_keys(
|
||||
&user, &raw, &None, &None,
|
||||
false, /* Dont notify. A notification would trigger another key request resulting in an
|
||||
* endless loop */
|
||||
false, /* Dont notify. A notification would trigger another key request
|
||||
* resulting in an endless loop */
|
||||
)
|
||||
.await?;
|
||||
master_keys.insert(user.clone(), raw);
|
||||
@@ -426,7 +413,6 @@ where
|
||||
self_signing_keys.extend(response.self_signing_keys);
|
||||
device_keys.extend(response.device_keys);
|
||||
} else {
|
||||
back_off(server.to_owned()).await;
|
||||
failures.insert(server.to_string(), json!({}));
|
||||
}
|
||||
}
|
||||
@@ -441,7 +427,8 @@ where
|
||||
}
|
||||
|
||||
fn add_unsigned_device_display_name(
|
||||
keys: &mut Raw<ruma::encryption::DeviceKeys>, metadata: ruma::api::client::device::Device,
|
||||
keys: &mut Raw<ruma::encryption::DeviceKeys>,
|
||||
metadata: ruma::api::client::device::Device,
|
||||
include_display_names: bool,
|
||||
) -> serde_json::Result<()> {
|
||||
if let Some(display_name) = metadata.display_name {
|
||||
@@ -466,7 +453,8 @@ fn add_unsigned_device_display_name(
|
||||
}
|
||||
|
||||
pub(crate) async fn claim_keys_helper(
|
||||
services: &Services, one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>>,
|
||||
services: &Services,
|
||||
one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>>,
|
||||
) -> Result<claim_keys::v3::Response> {
|
||||
let mut one_time_keys = BTreeMap::new();
|
||||
|
||||
@@ -508,12 +496,9 @@ pub(crate) async fn claim_keys_helper(
|
||||
server,
|
||||
services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
server,
|
||||
federation::keys::claim_keys::v1::Request {
|
||||
one_time_keys: one_time_keys_input_fed,
|
||||
},
|
||||
)
|
||||
.send_federation_request(server, federation::keys::claim_keys::v1::Request {
|
||||
one_time_keys: one_time_keys_input_fed,
|
||||
})
|
||||
.await,
|
||||
)
|
||||
})
|
||||
@@ -521,17 +506,14 @@ pub(crate) async fn claim_keys_helper(
|
||||
|
||||
while let Some((server, response)) = futures.next().await {
|
||||
match response {
|
||||
Ok(keys) => {
|
||||
| Ok(keys) => {
|
||||
one_time_keys.extend(keys.one_time_keys);
|
||||
},
|
||||
Err(_e) => {
|
||||
| Err(_e) => {
|
||||
failures.insert(server.to_string(), json!({}));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(claim_keys::v3::Response {
|
||||
failures,
|
||||
one_time_keys,
|
||||
})
|
||||
Ok(claim_keys::v3::Response { failures, one_time_keys })
|
||||
}
|
||||
|
||||
+84
-26
@@ -2,12 +2,12 @@ use std::time::Duration;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::{
|
||||
use conduwuit::{
|
||||
err,
|
||||
utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize},
|
||||
Err, Result,
|
||||
};
|
||||
use conduit_service::{
|
||||
use conduwuit_service::{
|
||||
media::{Dim, FileMeta, CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, MXC_LENGTH},
|
||||
Services,
|
||||
};
|
||||
@@ -15,7 +15,8 @@ use reqwest::Url;
|
||||
use ruma::{
|
||||
api::client::{
|
||||
authenticated_media::{
|
||||
get_content, get_content_as_filename, get_content_thumbnail, get_media_config, get_media_preview,
|
||||
get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
|
||||
get_media_preview,
|
||||
},
|
||||
media::create_content,
|
||||
},
|
||||
@@ -26,10 +27,11 @@ use crate::Ruma;
|
||||
|
||||
/// # `GET /_matrix/client/v1/media/config`
|
||||
pub(crate) async fn get_media_config_route(
|
||||
State(services): State<crate::State>, _body: Ruma<get_media_config::v1::Request>,
|
||||
State(services): State<crate::State>,
|
||||
_body: Ruma<get_media_config::v1::Request>,
|
||||
) -> Result<get_media_config::v1::Response> {
|
||||
Ok(get_media_config::v1::Response {
|
||||
upload_size: ruma_from_usize(services.globals.config.max_request_size),
|
||||
upload_size: ruma_from_usize(services.server.config.max_request_size),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -39,9 +41,15 @@ pub(crate) async fn get_media_config_route(
|
||||
///
|
||||
/// - Some metadata will be saved in the database
|
||||
/// - Media will be saved in the media/ directory
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
|
||||
#[tracing::instrument(
|
||||
name = "media_upload",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(%client),
|
||||
)]
|
||||
pub(crate) async fn create_content_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<create_content::v3::Request>,
|
||||
) -> Result<create_content::v3::Response> {
|
||||
let user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -49,27 +57,42 @@ pub(crate) async fn create_content_route(
|
||||
let filename = body.filename.as_deref();
|
||||
let content_type = body.content_type.as_deref();
|
||||
let content_disposition = make_content_disposition(None, content_type, filename);
|
||||
let mxc = Mxc {
|
||||
let ref mxc = Mxc {
|
||||
server_name: services.globals.server_name(),
|
||||
media_id: &utils::random_string(MXC_LENGTH),
|
||||
};
|
||||
|
||||
services
|
||||
.media
|
||||
.create(&mxc, Some(user), Some(&content_disposition), content_type, &body.file)
|
||||
.await
|
||||
.map(|()| create_content::v3::Response {
|
||||
content_uri: mxc.to_string().into(),
|
||||
blurhash: None,
|
||||
})
|
||||
.create(mxc, Some(user), Some(&content_disposition), content_type, &body.file)
|
||||
.await?;
|
||||
|
||||
let blurhash = body.generate_blurhash.then(|| {
|
||||
services
|
||||
.media
|
||||
.create_blurhash(&body.file, content_type, filename)
|
||||
.ok()
|
||||
.flatten()
|
||||
});
|
||||
|
||||
Ok(create_content::v3::Response {
|
||||
content_uri: mxc.to_string().into(),
|
||||
blurhash: blurhash.flatten(),
|
||||
})
|
||||
}
|
||||
|
||||
/// # `GET /_matrix/client/v1/media/thumbnail/{serverName}/{mediaId}`
|
||||
///
|
||||
/// Load media thumbnail from our server or over federation.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
|
||||
#[tracing::instrument(
|
||||
name = "media_thumbnail_get",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(%client),
|
||||
)]
|
||||
pub(crate) async fn get_content_thumbnail_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content_thumbnail::v1::Request>,
|
||||
) -> Result<get_content_thumbnail::v1::Response> {
|
||||
let user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -98,9 +121,15 @@ pub(crate) async fn get_content_thumbnail_route(
|
||||
/// # `GET /_matrix/client/v1/media/download/{serverName}/{mediaId}`
|
||||
///
|
||||
/// Load media from our server or over federation.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
|
||||
#[tracing::instrument(
|
||||
name = "media_get",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(%client),
|
||||
)]
|
||||
pub(crate) async fn get_content_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content::v1::Request>,
|
||||
) -> Result<get_content::v1::Response> {
|
||||
let user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -128,9 +157,15 @@ pub(crate) async fn get_content_route(
|
||||
/// # `GET /_matrix/client/v1/media/download/{serverName}/{mediaId}/{fileName}`
|
||||
///
|
||||
/// Load media from our server or over federation as fileName.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_get_af")]
|
||||
#[tracing::instrument(
|
||||
name = "media_get_af",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(%client),
|
||||
)]
|
||||
pub(crate) async fn get_content_as_filename_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content_as_filename::v1::Request>,
|
||||
) -> Result<get_content_as_filename::v1::Response> {
|
||||
let user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -158,9 +193,15 @@ pub(crate) async fn get_content_as_filename_route(
|
||||
/// # `GET /_matrix/client/v1/media/preview_url`
|
||||
///
|
||||
/// Returns URL preview.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
|
||||
#[tracing::instrument(
|
||||
name = "url_preview",
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(%client),
|
||||
)]
|
||||
pub(crate) async fn get_media_preview_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_media_preview::v1::Request>,
|
||||
) -> Result<get_media_preview::v1::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -198,7 +239,11 @@ pub(crate) async fn get_media_preview_route(
|
||||
}
|
||||
|
||||
async fn fetch_thumbnail(
|
||||
services: &Services, mxc: &Mxc<'_>, user: &UserId, timeout_ms: Duration, dim: &Dim,
|
||||
services: &Services,
|
||||
mxc: &Mxc<'_>,
|
||||
user: &UserId,
|
||||
timeout_ms: Duration,
|
||||
dim: &Dim,
|
||||
) -> Result<FileMeta> {
|
||||
let FileMeta {
|
||||
content,
|
||||
@@ -220,7 +265,11 @@ async fn fetch_thumbnail(
|
||||
}
|
||||
|
||||
async fn fetch_file(
|
||||
services: &Services, mxc: &Mxc<'_>, user: &UserId, timeout_ms: Duration, filename: Option<&str>,
|
||||
services: &Services,
|
||||
mxc: &Mxc<'_>,
|
||||
user: &UserId,
|
||||
timeout_ms: Duration,
|
||||
filename: Option<&str>,
|
||||
) -> Result<FileMeta> {
|
||||
let FileMeta {
|
||||
content,
|
||||
@@ -242,7 +291,11 @@ async fn fetch_file(
|
||||
}
|
||||
|
||||
async fn fetch_thumbnail_meta(
|
||||
services: &Services, mxc: &Mxc<'_>, user: &UserId, timeout_ms: Duration, dim: &Dim,
|
||||
services: &Services,
|
||||
mxc: &Mxc<'_>,
|
||||
user: &UserId,
|
||||
timeout_ms: Duration,
|
||||
dim: &Dim,
|
||||
) -> Result<FileMeta> {
|
||||
if let Some(filemeta) = services.media.get_thumbnail(mxc, dim).await? {
|
||||
return Ok(filemeta);
|
||||
@@ -258,7 +311,12 @@ async fn fetch_thumbnail_meta(
|
||||
.await
|
||||
}
|
||||
|
||||
async fn fetch_file_meta(services: &Services, mxc: &Mxc<'_>, user: &UserId, timeout_ms: Duration) -> Result<FileMeta> {
|
||||
async fn fetch_file_meta(
|
||||
services: &Services,
|
||||
mxc: &Mxc<'_>,
|
||||
user: &UserId,
|
||||
timeout_ms: Duration,
|
||||
) -> Result<FileMeta> {
|
||||
if let Some(filemeta) = services.media.get(mxc).await? {
|
||||
return Ok(filemeta);
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduit::{
|
||||
use conduwuit::{
|
||||
err,
|
||||
utils::{content_disposition::make_content_disposition, math::ruma_from_usize},
|
||||
Err, Result,
|
||||
};
|
||||
use conduit_service::media::{Dim, FileMeta, CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN};
|
||||
use conduwuit_service::media::{Dim, FileMeta, CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN};
|
||||
use reqwest::Url;
|
||||
use ruma::{
|
||||
api::client::media::{
|
||||
create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
|
||||
get_media_preview,
|
||||
create_content, get_content, get_content_as_filename, get_content_thumbnail,
|
||||
get_media_config, get_media_preview,
|
||||
},
|
||||
Mxc,
|
||||
};
|
||||
@@ -23,10 +23,11 @@ use crate::{client::create_content_route, Ruma, RumaResponse};
|
||||
///
|
||||
/// Returns max upload size.
|
||||
pub(crate) async fn get_media_config_legacy_route(
|
||||
State(services): State<crate::State>, _body: Ruma<get_media_config::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
_body: Ruma<get_media_config::v3::Request>,
|
||||
) -> Result<get_media_config::v3::Response> {
|
||||
Ok(get_media_config::v3::Response {
|
||||
upload_size: ruma_from_usize(services.globals.config.max_request_size),
|
||||
upload_size: ruma_from_usize(services.server.config.max_request_size),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +39,8 @@ pub(crate) async fn get_media_config_legacy_route(
|
||||
///
|
||||
/// Returns max upload size.
|
||||
pub(crate) async fn get_media_config_legacy_legacy_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_media_config::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_media_config::v3::Request>,
|
||||
) -> Result<RumaResponse<get_media_config::v3::Response>> {
|
||||
get_media_config_legacy_route(State(services), body)
|
||||
.await
|
||||
@@ -48,9 +50,10 @@ pub(crate) async fn get_media_config_legacy_legacy_route(
|
||||
/// # `GET /_matrix/media/v3/preview_url`
|
||||
///
|
||||
/// Returns URL preview.
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "url_preview_legacy")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "url_preview_legacy", level = "debug")]
|
||||
pub(crate) async fn get_media_preview_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_media_preview::v3::Request>,
|
||||
) -> Result<get_media_preview::v3::Response> {
|
||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||
@@ -91,7 +94,8 @@ pub(crate) async fn get_media_preview_legacy_route(
|
||||
///
|
||||
/// Returns URL preview.
|
||||
pub(crate) async fn get_media_preview_legacy_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_media_preview::v3::Request>,
|
||||
) -> Result<RumaResponse<get_media_preview::v3::Response>> {
|
||||
get_media_preview_legacy_route(State(services), InsecureClientIp(client), body)
|
||||
@@ -110,7 +114,8 @@ pub(crate) async fn get_media_preview_legacy_legacy_route(
|
||||
/// - Some metadata will be saved in the database
|
||||
/// - Media will be saved in the media/ directory
|
||||
pub(crate) async fn create_content_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<create_content::v3::Request>,
|
||||
) -> Result<RumaResponse<create_content::v3::Response>> {
|
||||
create_content_route(State(services), InsecureClientIp(client), body)
|
||||
@@ -126,9 +131,10 @@ pub(crate) async fn create_content_legacy_route(
|
||||
/// - Only redirects if `allow_redirect` is true
|
||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||
/// seconds
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy", level = "debug")]
|
||||
pub(crate) async fn get_content_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content::v3::Request>,
|
||||
) -> Result<get_content::v3::Response> {
|
||||
let mxc = Mxc {
|
||||
@@ -142,7 +148,8 @@ pub(crate) async fn get_content_legacy_route(
|
||||
content_disposition,
|
||||
}) = services.media.get(&mxc).await?
|
||||
{
|
||||
let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
|
||||
let content_disposition =
|
||||
make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
|
||||
|
||||
Ok(get_content::v3::Response {
|
||||
file: content.expect("entire file contents"),
|
||||
@@ -156,10 +163,15 @@ pub(crate) async fn get_content_legacy_route(
|
||||
.media
|
||||
.fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms)
|
||||
.await
|
||||
.map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?;
|
||||
.map_err(|e| {
|
||||
err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
|
||||
})?;
|
||||
|
||||
let content_disposition =
|
||||
make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None);
|
||||
let content_disposition = make_content_disposition(
|
||||
response.content_disposition.as_ref(),
|
||||
response.content_type.as_deref(),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(get_content::v3::Response {
|
||||
file: response.file,
|
||||
@@ -185,9 +197,10 @@ pub(crate) async fn get_content_legacy_route(
|
||||
/// - Only redirects if `allow_redirect` is true
|
||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||
/// seconds
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy", level = "debug")]
|
||||
pub(crate) async fn get_content_legacy_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content::v3::Request>,
|
||||
) -> Result<RumaResponse<get_content::v3::Response>> {
|
||||
get_content_legacy_route(State(services), InsecureClientIp(client), body)
|
||||
@@ -203,9 +216,10 @@ pub(crate) async fn get_content_legacy_legacy_route(
|
||||
/// - Only redirects if `allow_redirect` is true
|
||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||
/// seconds
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy", level = "debug")]
|
||||
pub(crate) async fn get_content_as_filename_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content_as_filename::v3::Request>,
|
||||
) -> Result<get_content_as_filename::v3::Response> {
|
||||
let mxc = Mxc {
|
||||
@@ -219,8 +233,11 @@ pub(crate) async fn get_content_as_filename_legacy_route(
|
||||
content_disposition,
|
||||
}) = services.media.get(&mxc).await?
|
||||
{
|
||||
let content_disposition =
|
||||
make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), Some(&body.filename));
|
||||
let content_disposition = make_content_disposition(
|
||||
content_disposition.as_ref(),
|
||||
content_type.as_deref(),
|
||||
Some(&body.filename),
|
||||
);
|
||||
|
||||
Ok(get_content_as_filename::v3::Response {
|
||||
file: content.expect("entire file contents"),
|
||||
@@ -234,10 +251,15 @@ pub(crate) async fn get_content_as_filename_legacy_route(
|
||||
.media
|
||||
.fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms)
|
||||
.await
|
||||
.map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?;
|
||||
.map_err(|e| {
|
||||
err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
|
||||
})?;
|
||||
|
||||
let content_disposition =
|
||||
make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None);
|
||||
let content_disposition = make_content_disposition(
|
||||
response.content_disposition.as_ref(),
|
||||
response.content_type.as_deref(),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(get_content_as_filename::v3::Response {
|
||||
content_disposition: Some(content_disposition),
|
||||
@@ -264,7 +286,8 @@ pub(crate) async fn get_content_as_filename_legacy_route(
|
||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||
/// seconds
|
||||
pub(crate) async fn get_content_as_filename_legacy_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content_as_filename::v3::Request>,
|
||||
) -> Result<RumaResponse<get_content_as_filename::v3::Response>> {
|
||||
get_content_as_filename_legacy_route(State(services), InsecureClientIp(client), body)
|
||||
@@ -280,9 +303,10 @@ pub(crate) async fn get_content_as_filename_legacy_legacy_route(
|
||||
/// - Only redirects if `allow_redirect` is true
|
||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||
/// seconds
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get_legacy")]
|
||||
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get_legacy", level = "debug")]
|
||||
pub(crate) async fn get_content_thumbnail_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content_thumbnail::v3::Request>,
|
||||
) -> Result<get_content_thumbnail::v3::Response> {
|
||||
let mxc = Mxc {
|
||||
@@ -297,7 +321,8 @@ pub(crate) async fn get_content_thumbnail_legacy_route(
|
||||
content_disposition,
|
||||
}) = services.media.get_thumbnail(&mxc, &dim).await?
|
||||
{
|
||||
let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
|
||||
let content_disposition =
|
||||
make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None);
|
||||
|
||||
Ok(get_content_thumbnail::v3::Response {
|
||||
file: content.expect("entire file contents"),
|
||||
@@ -311,10 +336,15 @@ pub(crate) async fn get_content_thumbnail_legacy_route(
|
||||
.media
|
||||
.fetch_remote_thumbnail_legacy(&body)
|
||||
.await
|
||||
.map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?;
|
||||
.map_err(|e| {
|
||||
err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
|
||||
})?;
|
||||
|
||||
let content_disposition =
|
||||
make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None);
|
||||
let content_disposition = make_content_disposition(
|
||||
response.content_disposition.as_ref(),
|
||||
response.content_type.as_deref(),
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(get_content_thumbnail::v3::Response {
|
||||
file: response.file,
|
||||
@@ -341,7 +371,8 @@ pub(crate) async fn get_content_thumbnail_legacy_route(
|
||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||
/// seconds
|
||||
pub(crate) async fn get_content_thumbnail_legacy_legacy_route(
|
||||
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||
State(services): State<crate::State>,
|
||||
InsecureClientIp(client): InsecureClientIp,
|
||||
body: Ruma<get_content_thumbnail::v3::Request>,
|
||||
) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> {
|
||||
get_content_thumbnail_legacy_route(State(services), InsecureClientIp(client), body)
|
||||
|
||||
+1338
-554
File diff suppressed because it is too large
Load Diff
+127
-94
@@ -1,16 +1,14 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduit::{
|
||||
use conduwuit::{
|
||||
at, is_equal_to,
|
||||
utils::{
|
||||
result::{FlatOk, LogErr},
|
||||
stream::{BroadbandExt, WidebandExt},
|
||||
stream::{BroadbandExt, TryIgnore, WidebandExt},
|
||||
IterStream, ReadyExt,
|
||||
},
|
||||
Event, PduCount, Result,
|
||||
Event, PduCount, PduEvent, Result,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use futures::{future::OptionFuture, pin_mut, FutureExt, StreamExt, TryFutureExt};
|
||||
use ruma::{
|
||||
api::{
|
||||
client::{filter::RoomEventFilter, message::get_message_events},
|
||||
@@ -18,32 +16,38 @@ use ruma::{
|
||||
},
|
||||
events::{AnyStateEvent, StateEventType, TimelineEventType, TimelineEventType::*},
|
||||
serde::Raw,
|
||||
DeviceId, OwnedUserId, RoomId, UserId,
|
||||
RoomId, UserId,
|
||||
};
|
||||
use service::{
|
||||
rooms::{
|
||||
lazy_loading,
|
||||
lazy_loading::{Options, Witness},
|
||||
timeline::PdusIterItem,
|
||||
},
|
||||
Services,
|
||||
};
|
||||
use service::{rooms::timeline::PdusIterItem, Services};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
pub(crate) type LazySet = HashSet<OwnedUserId>;
|
||||
|
||||
/// list of safe and common non-state events to ignore if the user is ignored
|
||||
const IGNORED_MESSAGE_TYPES: &[TimelineEventType; 16] = &[
|
||||
const IGNORED_MESSAGE_TYPES: &[TimelineEventType; 17] = &[
|
||||
Audio,
|
||||
CallInvite,
|
||||
Emote,
|
||||
File,
|
||||
Image,
|
||||
KeyVerificationStart,
|
||||
Location,
|
||||
PollStart,
|
||||
UnstablePollStart,
|
||||
Beacon,
|
||||
Reaction,
|
||||
RoomEncrypted,
|
||||
RoomMessage,
|
||||
Sticker,
|
||||
CallInvite,
|
||||
CallNotify,
|
||||
RoomEncrypted,
|
||||
Image,
|
||||
File,
|
||||
Audio,
|
||||
Voice,
|
||||
Video,
|
||||
UnstablePollStart,
|
||||
PollStart,
|
||||
KeyVerificationStart,
|
||||
Reaction,
|
||||
Emote,
|
||||
Location,
|
||||
Voice,
|
||||
CallNotify,
|
||||
];
|
||||
|
||||
const LIMIT_MAX: usize = 100;
|
||||
@@ -56,8 +60,10 @@ const LIMIT_DEFAULT: usize = 10;
|
||||
/// - Only works if the user is joined (TODO: always allow, but only show events
|
||||
/// where the user was joined, depending on `history_visibility`)
|
||||
pub(crate) async fn get_message_events_route(
|
||||
State(services): State<crate::State>, body: Ruma<get_message_events::v3::Request>,
|
||||
State(services): State<crate::State>,
|
||||
body: Ruma<get_message_events::v3::Request>,
|
||||
) -> Result<get_message_events::v3::Response> {
|
||||
debug_assert!(IGNORED_MESSAGE_TYPES.is_sorted(), "IGNORED_MESSAGE_TYPES is not sorted");
|
||||
let sender = body.sender();
|
||||
let (sender_user, sender_device) = sender;
|
||||
let room_id = &body.room_id;
|
||||
@@ -69,8 +75,8 @@ pub(crate) async fn get_message_events_route(
|
||||
.map(str::parse)
|
||||
.transpose()?
|
||||
.unwrap_or_else(|| match body.dir {
|
||||
Direction::Forward => PduCount::min(),
|
||||
Direction::Backward => PduCount::max(),
|
||||
| Direction::Forward => PduCount::min(),
|
||||
| Direction::Backward => PduCount::max(),
|
||||
});
|
||||
|
||||
let to: Option<PduCount> = body.to.as_deref().map(str::parse).flat_ok();
|
||||
@@ -81,11 +87,6 @@ pub(crate) async fn get_message_events_route(
|
||||
.unwrap_or(LIMIT_DEFAULT)
|
||||
.min(LIMIT_MAX);
|
||||
|
||||
services
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.lazy_load_confirm_delivery(sender_user, sender_device, room_id, from);
|
||||
|
||||
if matches!(body.dir, Direction::Backward) {
|
||||
services
|
||||
.rooms
|
||||
@@ -98,18 +99,18 @@ pub(crate) async fn get_message_events_route(
|
||||
}
|
||||
|
||||
let it = match body.dir {
|
||||
Direction::Forward => services
|
||||
| Direction::Forward => services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus(Some(sender_user), room_id, Some(from))
|
||||
.await?
|
||||
.ignore_err()
|
||||
.boxed(),
|
||||
|
||||
Direction::Backward => services
|
||||
| Direction::Backward => services
|
||||
.rooms
|
||||
.timeline
|
||||
.pdus_rev(Some(sender_user), room_id, Some(from))
|
||||
.await?
|
||||
.ignore_err()
|
||||
.boxed(),
|
||||
};
|
||||
|
||||
@@ -122,32 +123,34 @@ pub(crate) async fn get_message_events_route(
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let lazy = events
|
||||
.iter()
|
||||
.stream()
|
||||
.fold(LazySet::new(), |lazy, item| {
|
||||
update_lazy(&services, room_id, sender, lazy, item, false)
|
||||
})
|
||||
.await;
|
||||
let lazy_loading_context = lazy_loading::Context {
|
||||
user_id: sender_user,
|
||||
device_id: sender_device,
|
||||
room_id,
|
||||
token: Some(from.into_unsigned()),
|
||||
options: Some(&filter.lazy_load_options),
|
||||
};
|
||||
|
||||
let state = lazy
|
||||
.iter()
|
||||
.stream()
|
||||
.broad_filter_map(|user_id| get_member_event(&services, room_id, user_id))
|
||||
let witness: OptionFuture<_> = filter
|
||||
.lazy_load_options
|
||||
.is_enabled()
|
||||
.then(|| lazy_loading_witness(&services, &lazy_loading_context, events.iter()))
|
||||
.into();
|
||||
|
||||
let state = witness
|
||||
.map(Option::into_iter)
|
||||
.map(|option| option.flat_map(Witness::into_iter))
|
||||
.map(IterStream::stream)
|
||||
.into_stream()
|
||||
.flatten()
|
||||
.broad_filter_map(|user_id| async move {
|
||||
get_member_event(&services, room_id, &user_id).await
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
let next_token = events.last().map(at!(0));
|
||||
|
||||
if !cfg!(feature = "element_hacks") {
|
||||
if let Some(next_token) = next_token {
|
||||
services
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.lazy_load_mark_sent(sender_user, sender_device, room_id, lazy, next_token);
|
||||
}
|
||||
}
|
||||
|
||||
let chunk = events
|
||||
.into_iter()
|
||||
.map(at!(1))
|
||||
@@ -162,49 +165,71 @@ pub(crate) async fn get_message_events_route(
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_member_event(services: &Services, room_id: &RoomId, user_id: &UserId) -> Option<Raw<AnyStateEvent>> {
|
||||
pub(crate) async fn lazy_loading_witness<'a, I>(
|
||||
services: &Services,
|
||||
lazy_loading_context: &lazy_loading::Context<'_>,
|
||||
events: I,
|
||||
) -> Witness
|
||||
where
|
||||
I: Iterator<Item = &'a PdusIterItem> + Clone + Send,
|
||||
{
|
||||
let oldest = events
|
||||
.clone()
|
||||
.map(|(count, _)| count)
|
||||
.copied()
|
||||
.min()
|
||||
.unwrap_or_else(PduCount::max);
|
||||
|
||||
let newest = events
|
||||
.clone()
|
||||
.map(|(count, _)| count)
|
||||
.copied()
|
||||
.max()
|
||||
.unwrap_or_else(PduCount::max);
|
||||
|
||||
let receipts = services
|
||||
.rooms
|
||||
.read_receipt
|
||||
.readreceipts_since(lazy_loading_context.room_id, oldest.into_unsigned());
|
||||
|
||||
pin_mut!(receipts);
|
||||
let witness: Witness = events
|
||||
.stream()
|
||||
.map(|(_, pdu)| pdu.sender.clone())
|
||||
.chain(
|
||||
receipts
|
||||
.ready_take_while(|(_, c, _)| *c <= newest.into_unsigned())
|
||||
.map(|(user_id, ..)| user_id.to_owned()),
|
||||
)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
services
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.witness_retain(witness, lazy_loading_context)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_member_event(
|
||||
services: &Services,
|
||||
room_id: &RoomId,
|
||||
user_id: &UserId,
|
||||
) -> Option<Raw<AnyStateEvent>> {
|
||||
services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
|
||||
.map_ok(PduEvent::into_state_event)
|
||||
.await
|
||||
.map(|member_event| member_event.to_state_event())
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub(crate) async fn update_lazy(
|
||||
services: &Services, room_id: &RoomId, sender: (&UserId, &DeviceId), mut lazy: LazySet, item: &PdusIterItem,
|
||||
force: bool,
|
||||
) -> LazySet {
|
||||
let (_, event) = &item;
|
||||
let (sender_user, sender_device) = sender;
|
||||
|
||||
/* TODO: Remove the not "element_hacks" check when these are resolved:
|
||||
* https://github.com/vector-im/element-android/issues/3417
|
||||
* https://github.com/vector-im/element-web/issues/21034
|
||||
*/
|
||||
if force || cfg!(features = "element_hacks") {
|
||||
lazy.insert(event.sender().into());
|
||||
return lazy;
|
||||
}
|
||||
|
||||
if lazy.contains(event.sender()) {
|
||||
return lazy;
|
||||
}
|
||||
|
||||
if !services
|
||||
.rooms
|
||||
.lazy_loading
|
||||
.lazy_load_was_sent_before(sender_user, sender_device, room_id, event.sender())
|
||||
.await
|
||||
{
|
||||
lazy.insert(event.sender().into());
|
||||
}
|
||||
|
||||
lazy
|
||||
}
|
||||
|
||||
pub(crate) async fn ignored_filter(services: &Services, item: PdusIterItem, user_id: &UserId) -> Option<PdusIterItem> {
|
||||
pub(crate) async fn ignored_filter(
|
||||
services: &Services,
|
||||
item: PdusIterItem,
|
||||
user_id: &UserId,
|
||||
) -> Option<PdusIterItem> {
|
||||
let (_, pdu) = &item;
|
||||
|
||||
// exclude Synapse's dummy events from bloating up response bodies. clients
|
||||
@@ -213,8 +238,14 @@ pub(crate) async fn ignored_filter(services: &Services, item: PdusIterItem, user
|
||||
return None;
|
||||
}
|
||||
|
||||
if IGNORED_MESSAGE_TYPES.iter().any(is_equal_to!(&pdu.kind))
|
||||
&& services.users.user_is_ignored(&pdu.sender, user_id).await
|
||||
if IGNORED_MESSAGE_TYPES.binary_search(&pdu.kind).is_ok()
|
||||
&& (services.users.user_is_ignored(&pdu.sender, user_id).await
|
||||
|| services
|
||||
.server
|
||||
.config
|
||||
.forbidden_remote_server_names
|
||||
.iter()
|
||||
.any(is_equal_to!(pdu.sender().server_name())))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -223,7 +254,9 @@ pub(crate) async fn ignored_filter(services: &Services, item: PdusIterItem, user
|
||||
}
|
||||
|
||||
pub(crate) async fn visibility_filter(
|
||||
services: &Services, item: PdusIterItem, user_id: &UserId,
|
||||
services: &Services,
|
||||
item: PdusIterItem,
|
||||
user_id: &UserId,
|
||||
) -> Option<PdusIterItem> {
|
||||
let (_, pdu) = &item;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user