Compare commits
367 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af80565e6a | |||
| fe0969e7ca | |||
| 2e3e14b045 | |||
| f51d4237c7 | |||
| d8a87258d6 | |||
| 3da2242730 | |||
| 5add9a8c34 | |||
| 5b1642f641 | |||
| eb94aa7079 | |||
| 24884c3b9b | |||
| a0a002b17f | |||
| f632b06e6d | |||
| 1f88866612 | |||
| ccef1a4c8b | |||
| 5c258f41c8 | |||
| 15126ee1b2 | |||
| e37ac56dba | |||
| a74613be96 | |||
| a95ded167f | |||
| 95121b6e05 | |||
| e6884ef13a | |||
| 49033db2b8 | |||
| ddc8c3b46c | |||
| ea95627dce | |||
| c3e751542d | |||
| 4b765aa17c | |||
| 954cfc6bb7 | |||
| 2f85a5c1ac | |||
| 7e50db4193 | |||
| 7a3cc3941e | |||
| ca82b59c6f | |||
| 3b5607ecdc | |||
| 96f6a75bc8 | |||
| 68f42baf73 | |||
| 271959ee27 | |||
| c423a83656 | |||
| 4e975887cf | |||
| 8bb69eb81d | |||
| 2100618d47 | |||
| 936d2915e2 | |||
| f014231644 | |||
| 7d487d53d8 | |||
| d7d874f88d | |||
| ee864bcd9e | |||
| 111cbea6fc | |||
| 85f734ec74 | |||
| 2468e0c3de | |||
| f841c2356d | |||
| c64adbec0e | |||
| 4458efa2b2 | |||
| 5c0bf29122 | |||
| 91b49a7786 | |||
| 5a17fbccf5 | |||
| 5ed95ea357 | |||
| ccfa939bd3 | |||
| 263e338088 | |||
| 2fb43dd38d | |||
| 59efabbbc2 | |||
| 010e4ee35a | |||
| 992c0a1e58 | |||
| 9b20c6918f | |||
| 3dc91525ce | |||
| a88f913a17 | |||
| 4343218957 | |||
| b116984e46 | |||
| b0ac5255c8 | |||
| 29fc5b9b52 | |||
| 3ccd9ea326 | |||
| 8a2ae401df | |||
| f465d77ad3 | |||
| 2fd6f6b0ff | |||
| 8b6018d77d | |||
| 463f1a1287 | |||
| 04fc87ad07 | |||
| dc573f4562 | |||
| 74826dcb94 | |||
| b20c4e0539 | |||
| fb8a2846df | |||
| 04971d0430 | |||
| f098532b09 | |||
| 60927c1c72 | |||
| 03296412ab | |||
| 934ab6a4fe | |||
| 1378399f9d | |||
| 09680f653f | |||
| 39f19c483a | |||
| 39c0f4ea3d | |||
| 405167fc3f | |||
| b13ea4ff45 | |||
| 100fc5e0f7 | |||
| 96f9d64111 | |||
| 30ad5da5f1 | |||
| ce0ca96df4 | |||
| 4851ad26e2 | |||
| 328759a60f | |||
| 463fa4fd53 | |||
| db494e0d68 | |||
| 4788040458 | |||
| 06531993f6 | |||
| c29197b3f4 | |||
| 739eab46d5 | |||
| 923a98eb66 | |||
| 4430e4dee0 | |||
| d67f19a55d | |||
| b903b46d16 | |||
| 167559bb27 | |||
| 838e4b9d8d | |||
| 038b71fc9d | |||
| 720fbd09c2 | |||
| c42cb90dd3 | |||
| 5950355348 | |||
| f79bd2ac72 | |||
| 80ec0e31b1 | |||
| bda44b16b1 | |||
| e2280aa1a5 | |||
| bdf2de076a | |||
| 1797fec3c9 | |||
| 188fa5a073 | |||
| f0c63c539b | |||
| 649e9da1f8 | |||
| df28359a19 | |||
| 9370e93a8d | |||
| bdd5845490 | |||
| bacffd6174 | |||
| a1bfd7a018 | |||
| 7009f56a7a | |||
| 2c0bfac43e | |||
| fcb6c8a113 | |||
| 1ab77aeb91 | |||
| 3d73b53136 | |||
| 101fdbc9b1 | |||
| e53c2fbc5a | |||
| 95006f7e46 | |||
| cce270d938 | |||
| 5ec49b3f62 | |||
| e4dc4a1ba5 | |||
| d2fb6d04c9 | |||
| 05efd9b044 | |||
| b3f2288d07 | |||
| 084751ae38 | |||
| aa7a310200 | |||
| 38c989a07e | |||
| 5be679e17b | |||
| b8baa1223d | |||
| b87f1649d4 | |||
| 4a6f089b23 | |||
| 4600c7f32d | |||
| 4cc92dd175 | |||
| 93ec4e579b | |||
| c111d2e395 | |||
| 03d890cd49 | |||
| a35b6cbfdd | |||
| 5570220c89 | |||
| 899b79873e | |||
| 57969f9480 | |||
| d88ab37120 | |||
| bc58e5002d | |||
| 160f48043e | |||
| 0023b09f5b | |||
| db3c718ddc | |||
| e73aa2aa21 | |||
| 61f2a3c68b | |||
| 0627b46f40 | |||
| f10f5319db | |||
| 16e76d45cb | |||
| 2a304c2b6c | |||
| 271f720286 | |||
| 2d251eb19c | |||
| 01b2928d55 | |||
| 50c2d2b801 | |||
| 5dcdafe207 | |||
| c62d653989 | |||
| 454dd43d4c | |||
| 8077e910f6 | |||
| b5d4a1c1b0 | |||
| aa9540af21 | |||
| 080975ab0e | |||
| f129d90900 | |||
| 53223a4d5f | |||
| 05befa4ba2 | |||
| a8e690f22b | |||
| 192c1e08da | |||
| 0fa6976d86 | |||
| 93e7cf461d | |||
| d036d8adcb | |||
| 7a4bbe2ff6 | |||
| 438911c18d | |||
| bd71435a22 | |||
| 53fa7c3729 | |||
| d2facaee0b | |||
| 0dae9280d9 | |||
| 35336eb686 | |||
| efea13a675 | |||
| b5ee15a216 | |||
| 0873e18e14 | |||
| 6abc4ad798 | |||
| 373991a8d6 | |||
| 68ad351f84 | |||
| 83e853e7a3 | |||
| 1c453b1b55 | |||
| 60141950f7 | |||
| 391bfd986e | |||
| da03de1d32 | |||
| e54f4d4397 | |||
| daa5c34ea3 | |||
| 3259ea08b5 | |||
| 158de10fe6 | |||
| 4718387dbe | |||
| a43c78e55f | |||
| 7ba0777bd3 | |||
| 59d86d3641 | |||
| 51df946911 | |||
| 23a9055199 | |||
| 100c6f572b | |||
| eb5556e74e | |||
| 8296e0ed67 | |||
| 8e3be6feb0 | |||
| 59c4062305 | |||
| 113a27c1d5 | |||
| 5af880e4f4 | |||
| 56a1b0e761 | |||
| 5722c4ae39 | |||
| dfd13780df | |||
| dcd7422c45 | |||
| 7397064edd | |||
| 52a561ff9e | |||
| 5e72d36800 | |||
| 611f09829e | |||
| a388c2e06e | |||
| 24b37e03a0 | |||
| a309ef55c9 | |||
| c1c084dda1 | |||
| 72d9e8ed2b | |||
| a3638dbb15 | |||
| 5254eb4f72 | |||
| b31e81a469 | |||
| 0e580292a6 | |||
| 38a24e0170 | |||
| 8350aced39 | |||
| 04e3de08eb | |||
| 2bc53139fa | |||
| 8691141237 | |||
| 17a3ed4c56 | |||
| 2dd68d3fa5 | |||
| dc18f89c0b | |||
| eaf1cf38a5 | |||
| eeda96d94a | |||
| 1e8b8cce0f | |||
| db46d6dd6b | |||
| 08274150e5 | |||
| e125af620e | |||
| 177c9e8bfa | |||
| 4f5c6de853 | |||
| 25c004f08c | |||
| f104ced55d | |||
| 0cea64309a | |||
| 229f2fde7a | |||
| 9a0ffd55da | |||
| d1b3faf4bd | |||
| 4014cb4413 | |||
| b2dbc3bc10 | |||
| e2033fb6c1 | |||
| 6b433392a4 | |||
| 0832c28a40 | |||
| b0eec60724 | |||
| 82acc7c0b0 | |||
| c9384e9c78 | |||
| 3502e6ef87 | |||
| deca2c52c6 | |||
| daa46a0dc4 | |||
| 74791642c1 | |||
| 1a64e42cfe | |||
| 5edd391e83 | |||
| 7658387a74 | |||
| be2d1c722b | |||
| 8cf55c702f | |||
| 3b9fba233c | |||
| af81baae44 | |||
| 52d470058a | |||
| df0328f43f | |||
| 473b29d524 | |||
| e1d1dac95e | |||
| a2d25215a3 | |||
| 46423cab4f | |||
| 2e2cf08bb2 | |||
| ee64fb149c | |||
| 0522fe7d92 | |||
| 6dd6e4bfaf | |||
| a1ced0a56f | |||
| 0171262581 | |||
| 972037dcd9 | |||
| 0613140130 | |||
| f43c09b05d | |||
| b035cd7cbe | |||
| b7aa79884d | |||
| 9c6cc44abb | |||
| 9c0d7b1da0 | |||
| ed3a464722 | |||
| 28ac3790c2 | |||
| 9115901c66 | |||
| 652faafe6e | |||
| 8deba94a12 | |||
| bb27f21ac1 | |||
| 339a1fc4e8 | |||
| 75731671d1 | |||
| 20aab1792b | |||
| e9f4cd58a9 | |||
| 6bae136720 | |||
| 39a1e994eb | |||
| c2c36197e9 | |||
| c952db72e7 | |||
| 1ca484f90d | |||
| b1150cded9 | |||
| 2b35f83089 | |||
| 792d4aa6ce | |||
| 6c461025e5 | |||
| 9d1db7d171 | |||
| 2387f7f955 | |||
| 5805394ca5 | |||
| 3301cde8e7 | |||
| b28d216e00 | |||
| f127987c7a | |||
| fa02d7b7e3 | |||
| 96a16190c5 | |||
| d9054f5364 | |||
| db288b169e | |||
| bc50034173 | |||
| 20fe84379c | |||
| 06f01d384f | |||
| bd19fac4c5 | |||
| 703c275266 | |||
| c05f00661b | |||
| 0e46b750a0 | |||
| 3a51e18ce6 | |||
| 7638bbc49c | |||
| 35049c94de | |||
| 7db8478dc0 | |||
| df7a1dee15 | |||
| dc633100da | |||
| 0f1432f448 | |||
| 30b5ad3870 | |||
| cb48e25783 | |||
| 57acc4f655 | |||
| 875d9e8b07 | |||
| de21f7442a | |||
| 7ad7badd60 | |||
| a6edaad6fc | |||
| fbcdb3860a | |||
| 01f4455ceb | |||
| c1712d4d8b | |||
| 3480074f61 | |||
| efbdced535 | |||
| 19d8f0b27e | |||
| 8d5305c769 | |||
| 73b85372d6 | |||
| 5e6b9ec1cf | |||
| e24b7d2f3e | |||
| c61258fa0d | |||
| 65177d237a | |||
| 325f1c0bd2 | |||
| 68f1b4789a | |||
| 624cd2acfa | |||
| fb758f5f4b | |||
| 5d138a7e5e | |||
| 563618cc9e | |||
| 0a28d80cf2 | |||
| 6f9d7b5667 |
@@ -11,5 +11,10 @@ indent_style = space
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
max_line_length = 120
|
max_line_length = 120
|
||||||
|
|
||||||
|
[*.{md,txt}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
[*.nix]
|
[*.nix]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
+314
-53
@@ -16,6 +16,7 @@ on:
|
|||||||
- 'docker/**'
|
- 'docker/**'
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- change-ci-cache
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
@@ -26,6 +27,16 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
env:
|
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_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 }}
|
||||||
|
SCCACHE_ENDPOINT: ${{ vars.SCCACHE_ENDPOINT }}
|
||||||
|
SCCACHE_CACHE_MULTIARCH: ${{ vars.SCCACHE_CACHE_MULTIARCH }}
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
# Required to make some things output color
|
# Required to make some things output color
|
||||||
TERM: ansi
|
TERM: ansi
|
||||||
# Publishing to my nix binary cache
|
# Publishing to my nix binary cache
|
||||||
@@ -37,8 +48,11 @@ env:
|
|||||||
# Custom nix binary cache if fork is being used
|
# Custom nix binary cache if fork is being used
|
||||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||||
# Get error output from nix that we can actually use
|
# 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
|
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=
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
@@ -48,12 +62,17 @@ jobs:
|
|||||||
tests:
|
tests:
|
||||||
name: Test
|
name: Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CARGO_PROFILE: "test"
|
||||||
steps:
|
steps:
|
||||||
|
- name: Free Disk Space (Ubuntu)
|
||||||
|
uses: jlumbroso/free-disk-space@main
|
||||||
|
|
||||||
- name: Sync repository
|
- name: Sync repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Tag comparison check
|
- name: Tag comparison check
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
|
||||||
run: |
|
run: |
|
||||||
# Tag mismatch with latest repo tag check to prevent potential downgrades
|
# Tag mismatch with latest repo tag check to prevent potential downgrades
|
||||||
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||||
@@ -64,13 +83,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Install Nix
|
- uses: nixbuild/nix-quick-install-action@v28
|
||||||
uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
with:
|
|
||||||
diagnostic-endpoint: ""
|
|
||||||
extra-conf: |
|
|
||||||
experimental-features = nix-command flakes
|
|
||||||
accept-flake-config = true
|
|
||||||
|
|
||||||
- name: Enable Cachix binary cache
|
- name: Enable Cachix binary cache
|
||||||
run: |
|
run: |
|
||||||
@@ -78,15 +91,30 @@ jobs:
|
|||||||
cachix use crane
|
cachix use crane
|
||||||
cachix use nix-community
|
cachix use nix-community
|
||||||
|
|
||||||
- name: Configure Magic Nix Cache
|
- name: Restore and cache Nix store
|
||||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
uses: nix-community/cache-nix-action@v5.1.0
|
||||||
with:
|
with:
|
||||||
diagnostic-endpoint: ""
|
# restore and save a cache using this key
|
||||||
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
|
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/.lock') }}
|
||||||
|
# if there's no cache hit, restore a cache by this prefix
|
||||||
|
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||||
|
# collect garbage until Nix store size (in bytes) is at most this number
|
||||||
|
# before trying to save a new cache
|
||||||
|
gc-max-store-size-linux: 2073741824
|
||||||
|
# do purge caches
|
||||||
|
purge: true
|
||||||
|
# purge all versions of the cache
|
||||||
|
purge-prefixes: nix-${{ runner.os }}-
|
||||||
|
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
|
||||||
|
purge-last-accessed: 86400
|
||||||
|
# except the version with the `primary-key`, if it exists
|
||||||
|
purge-primary-key: never
|
||||||
|
# always save the cache
|
||||||
|
save-always: true
|
||||||
|
|
||||||
- name: Apply Nix binary cache configuration
|
- name: Apply Nix binary cache configuration
|
||||||
run: |
|
run: |
|
||||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
extra-substituters = https://attic.kennel.juneis.dog/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-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||||
EOF
|
EOF
|
||||||
@@ -94,7 +122,7 @@ jobs:
|
|||||||
- name: Use alternative Nix binary caches if specified
|
- name: Use alternative Nix binary caches if specified
|
||||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||||
run: |
|
run: |
|
||||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||||
EOF
|
EOF
|
||||||
@@ -102,27 +130,48 @@ jobs:
|
|||||||
- name: Prepare build environment
|
- name: Prepare build environment
|
||||||
run: |
|
run: |
|
||||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||||
direnv allow
|
direnv allow
|
||||||
nix develop .#all-features --command true
|
nix develop .#all-features --command true
|
||||||
|
|
||||||
- name: Cache CI dependencies
|
- name: Cache CI dependencies
|
||||||
run: |
|
run: |
|
||||||
|
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||||
|
ATTEMPTS=3
|
||||||
|
SUCCESS=false
|
||||||
|
while (( ATTEMPTS-- > 0 ))
|
||||||
|
do
|
||||||
bin/nix-build-and-cache ci
|
bin/nix-build-and-cache ci
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $SUCCESS == "false" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# use sccache for Rust
|
||||||
|
- name: Run sccache-cache
|
||||||
|
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
|
||||||
|
uses: mozilla-actions/sccache-action@main
|
||||||
|
|
||||||
|
# use rust-cache
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
cache-all-crates: "true"
|
||||||
|
|
||||||
- name: Run CI tests
|
- name: Run CI tests
|
||||||
run: |
|
run: |
|
||||||
direnv exec . engage > >(tee -a test_output.log)
|
direnv exec . engage > >(tee -a test_output.log)
|
||||||
|
|
||||||
- name: Sync Complement repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
repository: 'matrix-org/complement'
|
|
||||||
path: complement_src
|
|
||||||
|
|
||||||
- name: Run Complement tests
|
- name: Run Complement tests
|
||||||
run: |
|
run: |
|
||||||
direnv exec . bin/complement 'complement_src' 'complement_test_logs.jsonl' 'complement_test_results.jsonl'
|
# the nix devshell sets $COMPLEMENT_SRC, so "/dev/null" is no-op
|
||||||
|
direnv exec . bin/complement "/dev/null" complement_test_logs.jsonl complement_test_results.jsonl > >(tee -a test_output.log)
|
||||||
cp -v -f result complement_oci_image.tar.gz
|
cp -v -f result complement_oci_image.tar.gz
|
||||||
|
|
||||||
- name: Upload Complement OCI image
|
- name: Upload Complement OCI image
|
||||||
@@ -148,11 +197,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Diff Complement results with checked-in repo results
|
- name: Diff Complement results with checked-in repo results
|
||||||
run: |
|
run: |
|
||||||
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_test_output.log)
|
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_diff_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
|
- name: Update Job Summary
|
||||||
if: success() || failure()
|
if: success() || failure()
|
||||||
@@ -160,11 +205,21 @@ jobs:
|
|||||||
if [ ${{ job.status }} == 'success' ]; then
|
if [ ${{ job.status }} == 'success' ]; then
|
||||||
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
|
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
|
echo '# CI failure' >> $GITHUB_STEP_SUMMARY
|
||||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
tail -n 40 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
tail -n 40 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||||
|
tail -n 100 complement_diff_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Run cargo clean test artifacts
|
||||||
|
run: |
|
||||||
|
cargo clean --profile test
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -175,32 +230,44 @@ jobs:
|
|||||||
- target: aarch64-unknown-linux-musl
|
- target: aarch64-unknown-linux-musl
|
||||||
- target: x86_64-unknown-linux-musl
|
- target: x86_64-unknown-linux-musl
|
||||||
steps:
|
steps:
|
||||||
|
- name: Free Disk Space (Ubuntu)
|
||||||
|
uses: jlumbroso/free-disk-space@main
|
||||||
|
|
||||||
- name: Sync repository
|
- name: Sync repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Nix
|
- uses: nixbuild/nix-quick-install-action@v28
|
||||||
uses: 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
|
- name: Enable Cachix binary cache
|
||||||
run: |
|
run: |
|
||||||
nix profile install nixpkgs#cachix
|
nix profile install nixpkgs#cachix
|
||||||
cachix use crane
|
cachix use crane
|
||||||
cachix use nix-community
|
cachix use nix-community
|
||||||
|
|
||||||
- name: Configure Magic Nix Cache
|
- name: Restore and cache Nix store
|
||||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
uses: nix-community/cache-nix-action@v5.1.0
|
||||||
with:
|
with:
|
||||||
diagnostic-endpoint: ""
|
# restore and save a cache using this key
|
||||||
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
|
primary-key: nix-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/*.nix', '**/.lock') }}
|
||||||
|
# if there's no cache hit, restore a cache by this prefix
|
||||||
|
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||||
|
# collect garbage until Nix store size (in bytes) is at most this number
|
||||||
|
# before trying to save a new cache
|
||||||
|
gc-max-store-size-linux: 2073741824
|
||||||
|
# do purge caches
|
||||||
|
purge: true
|
||||||
|
# purge all versions of the cache
|
||||||
|
purge-prefixes: nix-${{ runner.os }}-
|
||||||
|
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
|
||||||
|
purge-last-accessed: 86400
|
||||||
|
# except the version with the `primary-key`, if it exists
|
||||||
|
purge-primary-key: never
|
||||||
|
# always save the cache
|
||||||
|
save-always: true
|
||||||
|
|
||||||
- name: Apply Nix binary cache configuration
|
- name: Apply Nix binary cache configuration
|
||||||
run: |
|
run: |
|
||||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
extra-substituters = https://attic.kennel.juneis.dog/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-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||||
EOF
|
EOF
|
||||||
@@ -208,7 +275,7 @@ jobs:
|
|||||||
- name: Use alternative Nix binary caches if specified
|
- name: Use alternative Nix binary caches if specified
|
||||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||||
run: |
|
run: |
|
||||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||||
EOF
|
EOF
|
||||||
@@ -216,16 +283,43 @@ jobs:
|
|||||||
- name: Prepare build environment
|
- name: Prepare build environment
|
||||||
run: |
|
run: |
|
||||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||||
direnv allow
|
direnv allow
|
||||||
nix develop .#all-features --command true
|
nix develop .#all-features --command true
|
||||||
|
|
||||||
|
# use sccache for Rust
|
||||||
|
- name: Run sccache-cache
|
||||||
|
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
|
||||||
|
uses: mozilla-actions/sccache-action@main
|
||||||
|
|
||||||
|
# use rust-cache
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
cache-all-crates: "true"
|
||||||
|
|
||||||
- name: Build static ${{ matrix.target }}
|
- name: Build static ${{ matrix.target }}
|
||||||
run: |
|
run: |
|
||||||
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||||
|
|
||||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}
|
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||||
|
ATTEMPTS=3
|
||||||
|
SUCCESS=false
|
||||||
|
while (( ATTEMPTS-- > 0 ))
|
||||||
|
do
|
||||||
|
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $SUCCESS == "false" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
mkdir -v -p target/release/
|
mkdir -v -p target/release/
|
||||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/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/release/conduwuit
|
||||||
@@ -235,6 +329,66 @@ jobs:
|
|||||||
mv -v target/release/conduwuit static-${{ matrix.target }}
|
mv -v target/release/conduwuit static-${{ matrix.target }}
|
||||||
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
|
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
|
||||||
|
|
||||||
|
# quick smoke test of the x86_64 static release binary
|
||||||
|
- name: Run x86_64 static release binary
|
||||||
|
run: |
|
||||||
|
# GH actions default runners are x86_64 only
|
||||||
|
if file result/bin/conduit | grep x86-64; then
|
||||||
|
result/bin/conduit --version
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build static debug ${{ matrix.target }}
|
||||||
|
run: |
|
||||||
|
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||||
|
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||||
|
|
||||||
|
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||||
|
ATTEMPTS=3
|
||||||
|
SUCCESS=false
|
||||||
|
while (( ATTEMPTS-- > 0 ))
|
||||||
|
do
|
||||||
|
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features-debug
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $SUCCESS == "false" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# > warning: dev profile is not supported and will be a hard error in the future. cargo-deb is for making releases, and it doesn't make sense to use it with dev profiles.
|
||||||
|
# so we need to coerce cargo-deb into thinking this is a release binary
|
||||||
|
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
|
||||||
|
mv -v target/release/conduwuit static-${{ matrix.target }}-debug
|
||||||
|
mv -v target/release/${{ matrix.target }}-debug.deb ${{ matrix.target }}-debug.deb
|
||||||
|
|
||||||
|
# quick smoke test of the x86_64 static debug binary
|
||||||
|
- 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
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check validity of produced deb package, invalid debs will error on these commands
|
||||||
|
- name: Validate produced deb package
|
||||||
|
run: |
|
||||||
|
# List contents
|
||||||
|
dpkg-deb --contents ${{ matrix.target }}.deb
|
||||||
|
dpkg-deb --contents ${{ matrix.target }}-debug.deb
|
||||||
|
# List info
|
||||||
|
dpkg-deb --info ${{ matrix.target }}.deb
|
||||||
|
dpkg-deb --info ${{ matrix.target }}-debug.deb
|
||||||
|
|
||||||
- name: Upload static-${{ matrix.target }}
|
- name: Upload static-${{ matrix.target }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -250,11 +404,65 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
|
|
||||||
|
- name: Upload static-${{ matrix.target }}-debug
|
||||||
|
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
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: deb-${{ matrix.target }}-debug
|
||||||
|
path: ${{ matrix.target }}-debug.deb
|
||||||
|
if-no-files-found: error
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
- name: Build OCI image ${{ matrix.target }}
|
- name: Build OCI image ${{ matrix.target }}
|
||||||
run: |
|
run: |
|
||||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}
|
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||||
|
ATTEMPTS=3
|
||||||
|
SUCCESS=false
|
||||||
|
while (( ATTEMPTS-- > 0 ))
|
||||||
|
do
|
||||||
|
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $SUCCESS == "false" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
|
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
|
||||||
|
|
||||||
|
- name: Build debug OCI image ${{ matrix.target }}
|
||||||
|
run: |
|
||||||
|
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||||
|
ATTEMPTS=3
|
||||||
|
SUCCESS=false
|
||||||
|
while (( ATTEMPTS-- > 0 ))
|
||||||
|
do
|
||||||
|
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features-debug
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $SUCCESS == "false" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -v -f result oci-image-${{ matrix.target }}-debug.tar.gz
|
||||||
|
|
||||||
- name: Upload OCI image ${{ matrix.target }}
|
- name: Upload OCI image ${{ matrix.target }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -263,24 +471,32 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
|
|
||||||
|
- name: Upload OCI image ${{ matrix.target }}-debug
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: oci-image-${{ matrix.target }}-debug
|
||||||
|
path: oci-image-${{ matrix.target }}-debug.tar.gz
|
||||||
|
if-no-files-found: error
|
||||||
|
compression-level: 0
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
name: Docker publish
|
name: Docker publish
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
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'
|
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]'
|
||||||
env:
|
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_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_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_TAG: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||||
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
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_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_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_TAG: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||||
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
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_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_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_TAG: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||||
GLCR_BRANCH: registry.gitlab.com/conduwuit/conduwuit:${{ (startsWith(github.ref, 'refs/tags/v') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
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 }}
|
||||||
|
|
||||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
|
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
|
||||||
@@ -313,8 +529,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Move OCI images into position
|
- name: Move OCI images into position
|
||||||
run: |
|
run: |
|
||||||
mv -v oci-image-x86_64-*/*.tar.gz oci-image-amd64.tar.gz
|
mv -v oci-image-x86_64-unknown-linux-musl/*.tar.gz oci-image-amd64.tar.gz
|
||||||
mv -v oci-image-aarch64-*/*.tar.gz oci-image-arm64v8.tar.gz
|
mv -v oci-image-aarch64-unknown-linux-musl/*.tar.gz oci-image-arm64v8.tar.gz
|
||||||
|
mv -v oci-image-x86_64-unknown-linux-musl-debug/*.tar.gz oci-image-amd64-debug.tar.gz
|
||||||
|
mv -v oci-image-aarch64-unknown-linux-musl-debug/*.tar.gz oci-image-arm64v8-debug.tar.gz
|
||||||
|
|
||||||
- name: Load and push amd64 image
|
- name: Load and push amd64 image
|
||||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||||
@@ -338,6 +556,28 @@ jobs:
|
|||||||
docker push ${{ env.GHCR_ARM64 }}
|
docker push ${{ env.GHCR_ARM64 }}
|
||||||
docker push ${{ env.GLCR_ARM64 }}
|
docker push ${{ env.GLCR_ARM64 }}
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
- name: Create Docker combined manifests
|
- name: Create Docker combined manifests
|
||||||
run: |
|
run: |
|
||||||
# Dockerhub Container Registry
|
# Dockerhub Container Registry
|
||||||
@@ -347,9 +587,21 @@ jobs:
|
|||||||
docker manifest create ${{ env.GHCR_TAG }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
|
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 }}
|
docker manifest create ${{ env.GHCR_BRANCH }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
|
||||||
# GitLab Container Registry
|
# GitLab Container Registry
|
||||||
docker manifest create ${{ env.GLCR_TAG }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GCCR_AMD64 }}
|
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 }}
|
docker manifest create ${{ env.GLCR_BRANCH }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
|
||||||
|
|
||||||
|
- 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
|
||||||
|
# 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
|
||||||
|
# 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
|
||||||
|
|
||||||
- name: Push manifests to Docker registries
|
- name: Push manifests to Docker registries
|
||||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||||
run: |
|
run: |
|
||||||
@@ -359,6 +611,12 @@ jobs:
|
|||||||
docker manifest push ${{ env.GHCR_BRANCH }}
|
docker manifest push ${{ env.GHCR_BRANCH }}
|
||||||
docker manifest push ${{ env.GLCR_TAG }}
|
docker manifest push ${{ env.GLCR_TAG }}
|
||||||
docker manifest push ${{ env.GLCR_BRANCH }}
|
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
|
||||||
|
|
||||||
- name: Add Image Links to Job Summary
|
- name: Add Image Links to Job Summary
|
||||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||||
@@ -366,3 +624,6 @@ jobs:
|
|||||||
echo "- \`docker pull ${{ env.DOCKER_TAG }}\`" >> $GITHUB_STEP_SUMMARY
|
echo "- \`docker pull ${{ env.DOCKER_TAG }}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- \`docker pull ${{ env.GHCR_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.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
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ env:
|
|||||||
# Custom nix binary cache if fork is being used
|
# Custom nix binary cache if fork is being used
|
||||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||||
# Get error output from nix that we can actually use
|
# 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
|
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=
|
||||||
|
|
||||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||||
@@ -44,6 +47,9 @@ jobs:
|
|||||||
url: ${{ steps.deployment.outputs.page_url }}
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Free Disk Space (Ubuntu)
|
||||||
|
uses: jlumbroso/free-disk-space@main
|
||||||
|
|
||||||
- name: Sync repository
|
- name: Sync repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -51,13 +57,7 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: actions/configure-pages@v5
|
uses: actions/configure-pages@v5
|
||||||
|
|
||||||
- name: Install Nix
|
- uses: nixbuild/nix-quick-install-action@v28
|
||||||
uses: DeterminateSystems/nix-installer-action@main
|
|
||||||
with:
|
|
||||||
diagnostic-endpoint: ""
|
|
||||||
extra-conf: |
|
|
||||||
experimental-features = nix-command flakes
|
|
||||||
accept-flake-config = true
|
|
||||||
|
|
||||||
- name: Enable Cachix binary cache
|
- name: Enable Cachix binary cache
|
||||||
run: |
|
run: |
|
||||||
@@ -65,23 +65,38 @@ jobs:
|
|||||||
cachix use crane
|
cachix use crane
|
||||||
cachix use nix-community
|
cachix use nix-community
|
||||||
|
|
||||||
- name: Configure Magic Nix Cache
|
- name: Restore and cache Nix store
|
||||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
uses: nix-community/cache-nix-action@v5.1.0
|
||||||
with:
|
with:
|
||||||
diagnostic-endpoint: ""
|
# restore and save a cache using this key
|
||||||
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
|
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/.lock') }}
|
||||||
|
# if there's no cache hit, restore a cache by this prefix
|
||||||
|
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||||
|
# collect garbage until Nix store size (in bytes) is at most this number
|
||||||
|
# before trying to save a new cache
|
||||||
|
gc-max-store-size-linux: 2073741824
|
||||||
|
# do purge caches
|
||||||
|
purge: true
|
||||||
|
# purge all versions of the cache
|
||||||
|
purge-prefixes: nix-${{ runner.os }}-
|
||||||
|
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
|
||||||
|
purge-last-accessed: 86400
|
||||||
|
# except the version with the `primary-key`, if it exists
|
||||||
|
purge-primary-key: never
|
||||||
|
# always save the cache
|
||||||
|
save-always: true
|
||||||
|
|
||||||
- name: Apply Nix binary cache configuration
|
- name: Apply Nix binary cache configuration
|
||||||
run: |
|
run: |
|
||||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wkconduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTEcache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
- name: Use alternative Nix binary caches if specified
|
- name: Use alternative Nix binary caches if specified
|
||||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||||
run: |
|
run: |
|
||||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||||
EOF
|
EOF
|
||||||
@@ -89,18 +104,57 @@ jobs:
|
|||||||
- name: Prepare build environment
|
- name: Prepare build environment
|
||||||
run: |
|
run: |
|
||||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||||
direnv allow
|
direnv allow
|
||||||
nix develop --command true
|
nix develop --command true
|
||||||
|
|
||||||
- name: Cache CI dependencies
|
- name: Cache CI dependencies
|
||||||
run: |
|
run: |
|
||||||
|
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||||
|
ATTEMPTS=3
|
||||||
|
SUCCESS=false
|
||||||
|
while (( ATTEMPTS-- > 0 ))
|
||||||
|
do
|
||||||
bin/nix-build-and-cache ci
|
bin/nix-build-and-cache ci
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $SUCCESS == "false" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run lychee and markdownlint
|
||||||
|
run: |
|
||||||
|
direnv exec . engage just lints lychee
|
||||||
|
direnv exec . engage just lints markdownlint
|
||||||
|
|
||||||
- name: Build documentation (book)
|
- name: Build documentation (book)
|
||||||
run: |
|
run: |
|
||||||
./bin/nix-build-and-cache just .#book
|
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||||
|
ATTEMPTS=3
|
||||||
|
SUCCESS=false
|
||||||
|
while (( ATTEMPTS-- > 0 ))
|
||||||
|
do
|
||||||
|
bin/nix-build-and-cache just .#book
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
SUCCESS=true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $SUCCESS == "false" ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
cp -r --dereference result public
|
cp -r --dereference result public
|
||||||
|
|
||||||
- name: Upload generated documentation (book) as normal artifact
|
- name: Upload generated documentation (book) as normal artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run Trivy code and vulnerability scanner on repo
|
- name: Run Trivy code and vulnerability scanner on repo
|
||||||
uses: aquasecurity/trivy-action@0.23.0
|
uses: aquasecurity/trivy-action@0.24.0
|
||||||
with:
|
with:
|
||||||
scan-type: repo
|
scan-type: repo
|
||||||
format: sarif
|
format: sarif
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
severity: CRITICAL,HIGH,MEDIUM,LOW
|
severity: CRITICAL,HIGH,MEDIUM,LOW
|
||||||
|
|
||||||
- name: Run Trivy code and vulnerability scanner on filesystem
|
- name: Run Trivy code and vulnerability scanner on filesystem
|
||||||
uses: aquasecurity/trivy-action@0.23.0
|
uses: aquasecurity/trivy-action@0.24.0
|
||||||
with:
|
with:
|
||||||
scan-type: fs
|
scan-type: fs
|
||||||
format: sarif
|
format: sarif
|
||||||
|
|||||||
+2
-2
@@ -58,7 +58,7 @@ before_script:
|
|||||||
|
|
||||||
ci:
|
ci:
|
||||||
stage: ci
|
stage: ci
|
||||||
image: nixos/nix:2.23.0
|
image: nixos/nix:2.23.3
|
||||||
script:
|
script:
|
||||||
# Cache CI dependencies
|
# Cache CI dependencies
|
||||||
- ./bin/nix-build-and-cache ci
|
- ./bin/nix-build-and-cache ci
|
||||||
@@ -83,7 +83,7 @@ ci:
|
|||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
stage: artifacts
|
stage: artifacts
|
||||||
image: nixos/nix:2.23.0
|
image: nixos/nix:2.23.3
|
||||||
script:
|
script:
|
||||||
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl
|
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl
|
||||||
- cp result/bin/conduit x86_64-unknown-linux-musl
|
- cp result/bin/conduit x86_64-unknown-linux-musl
|
||||||
|
|||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
.gitignore
|
||||||
+1
-2
@@ -61,7 +61,7 @@ representative at an online or offline event.
|
|||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement over email at
|
reported to the community leaders responsible for enforcement over email at
|
||||||
strawberry@puppygock.gay or over Matrix at @strawberry:puppygock.gay.
|
<strawberry@puppygock.gay> or over Matrix at @strawberry:puppygock.gay.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
@@ -131,4 +131,3 @@ For answers to common questions about this code of conduct, see the FAQ at
|
|||||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
[FAQ]: https://www.contributor-covenant.org/faq
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
[translations]: https://www.contributor-covenant.org/translations
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,6 @@
|
|||||||
# Contributing guide
|
# Contributing guide
|
||||||
|
|
||||||
This page is for about contributing to conduwuit. The [development](docs/development.md) page may be of interest for you as well.
|
This page is for about contributing to conduwuit. The [development](development.md) page may be of interest for you as well.
|
||||||
|
|
||||||
If you would like to work on an [issue][issues] that is not assigned, preferably ask in the Matrix room first at [#conduwuit:puppygock.gay][conduwuit-matrix], and comment on it.
|
If you would like to work on an [issue][issues] that is not assigned, preferably ask in the Matrix room first at [#conduwuit:puppygock.gay][conduwuit-matrix], and comment on it.
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ To test, format, lint, etc that CI would do, install engage, allow the `.envrc`
|
|||||||
All of the tasks are defined at the [engage.toml][engage.toml] file. You can view all of them neatly by running `engage list`
|
All of the tasks are defined at the [engage.toml][engage.toml] file. You can view all of them neatly by running `engage list`
|
||||||
|
|
||||||
If you would like to run only a specific engage task group, use `just`:
|
If you would like to run only a specific engage task group, use `just`:
|
||||||
|
|
||||||
- `engage just <group>`
|
- `engage just <group>`
|
||||||
- Example: `engage just lints`
|
- Example: `engage just lints`
|
||||||
|
|
||||||
@@ -37,6 +38,8 @@ The following binaries are used in [`engage.toml`][engage.toml]:
|
|||||||
- [`cargo-audit`][cargo-audit]
|
- [`cargo-audit`][cargo-audit]
|
||||||
- [`cargo-deb`][cargo-deb]
|
- [`cargo-deb`][cargo-deb]
|
||||||
- [`lychee`][lychee]
|
- [`lychee`][lychee]
|
||||||
|
- [`markdownlint-cli`][markdownlint-cli]
|
||||||
|
- `dpkg`
|
||||||
|
|
||||||
### Matrix tests
|
### Matrix tests
|
||||||
|
|
||||||
@@ -86,6 +89,7 @@ By sending a pull request or patch, you are agreeing that your changes are allow
|
|||||||
[sytest]: https://github.com/matrix-org/sytest/
|
[sytest]: https://github.com/matrix-org/sytest/
|
||||||
[cargo-deb]: https://github.com/kornelski/cargo-deb
|
[cargo-deb]: https://github.com/kornelski/cargo-deb
|
||||||
[lychee]: https://github.com/lycheeverse/lychee
|
[lychee]: https://github.com/lycheeverse/lychee
|
||||||
|
[markdownlint-cli]: https://github.com/igorshubovych/markdownlint-cli
|
||||||
[cargo-audit]: https://github.com/RustSec/rustsec/tree/main/cargo-audit
|
[cargo-audit]: https://github.com/RustSec/rustsec/tree/main/cargo-audit
|
||||||
[direnv]: https://direnv.net/
|
[direnv]: https://direnv.net/
|
||||||
[mdbook]: https://rust-lang.github.io/mdBook/
|
[mdbook]: https://rust-lang.github.io/mdBook/
|
||||||
|
|||||||
Generated
+424
-514
File diff suppressed because it is too large
Load Diff
+119
-66
@@ -14,17 +14,33 @@ categories = ["network-programming"]
|
|||||||
description = "a very cool fork of Conduit, a Matrix homeserver written in Rust"
|
description = "a very cool fork of Conduit, a Matrix homeserver written in Rust"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://conduwuit.puppyirl.gay/"
|
homepage = "https://conduwuit.puppyirl.gay/"
|
||||||
keywords = ["chat", "matrix", "server"]
|
keywords = ["chat", "matrix", "server", "uwu"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
# See also `rust-toolchain.toml`
|
# See also `rust-toolchain.toml`
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/girlbossceo/conduwuit"
|
repository = "https://github.com/girlbossceo/conduwuit"
|
||||||
rust-version = "1.77.0"
|
rust-version = "1.80.0"
|
||||||
version = "0.4.3"
|
version = "0.4.6"
|
||||||
|
|
||||||
[workspace.metadata.crane]
|
[workspace.metadata.crane]
|
||||||
name = "conduit"
|
name = "conduit"
|
||||||
|
|
||||||
|
[workspace.dependencies.const-str]
|
||||||
|
version = "0.5.7"
|
||||||
|
|
||||||
|
[workspace.dependencies.ctor]
|
||||||
|
version = "0.2.8"
|
||||||
|
|
||||||
|
[workspace.dependencies.cargo_toml]
|
||||||
|
version = "0.20"
|
||||||
|
default-features = false
|
||||||
|
features = ["features"]
|
||||||
|
|
||||||
|
[workspace.dependencies.toml]
|
||||||
|
version = "0.8.14"
|
||||||
|
default-features = false
|
||||||
|
features = ["parse"]
|
||||||
|
|
||||||
[workspace.dependencies.sanitize-filename]
|
[workspace.dependencies.sanitize-filename]
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
||||||
@@ -50,7 +66,7 @@ version = "0.8.5"
|
|||||||
|
|
||||||
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
# Used for the http request / response body type for Ruma endpoints used with reqwest
|
||||||
[workspace.dependencies.bytes]
|
[workspace.dependencies.bytes]
|
||||||
version = "1.6.0"
|
version = "1.6.1"
|
||||||
|
|
||||||
[workspace.dependencies.http-body-util]
|
[workspace.dependencies.http-body-util]
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -81,6 +97,7 @@ features = ["typed-header", "tracing"]
|
|||||||
|
|
||||||
[workspace.dependencies.axum-server]
|
[workspace.dependencies.axum-server]
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
default-features = false
|
||||||
features = ["tls-rustls"]
|
features = ["tls-rustls"]
|
||||||
|
|
||||||
[workspace.dependencies.axum-client-ip]
|
[workspace.dependencies.axum-client-ip]
|
||||||
@@ -88,10 +105,12 @@ version = "0.6.0"
|
|||||||
|
|
||||||
[workspace.dependencies.tower]
|
[workspace.dependencies.tower]
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
|
default-features = false
|
||||||
features = ["util"]
|
features = ["util"]
|
||||||
|
|
||||||
[workspace.dependencies.tower-http]
|
[workspace.dependencies.tower-http]
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"add-extension",
|
"add-extension",
|
||||||
"cors",
|
"cors",
|
||||||
@@ -113,11 +132,13 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.serde]
|
[workspace.dependencies.serde]
|
||||||
version = "1.0.203"
|
version = "1.0.204"
|
||||||
|
default-features = false
|
||||||
features = ["rc"]
|
features = ["rc"]
|
||||||
|
|
||||||
[workspace.dependencies.serde_json]
|
[workspace.dependencies.serde_json]
|
||||||
version = "1.0.117"
|
version = "1.0.121"
|
||||||
|
default-features = false
|
||||||
features = ["raw_value"]
|
features = ["raw_value"]
|
||||||
|
|
||||||
# Used for appservice registration files
|
# Used for appservice registration files
|
||||||
@@ -169,7 +190,7 @@ default-features = false
|
|||||||
|
|
||||||
# used for conduit's CLI and admin room command parsing
|
# used for conduit's CLI and admin room command parsing
|
||||||
[workspace.dependencies.clap]
|
[workspace.dependencies.clap]
|
||||||
version = "4.5.4"
|
version = "4.5.9"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"std",
|
"std",
|
||||||
@@ -185,7 +206,8 @@ version = "0.3.30"
|
|||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[workspace.dependencies.tokio]
|
[workspace.dependencies.tokio]
|
||||||
version = "1.38.0"
|
version = "1.39.2"
|
||||||
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"fs",
|
"fs",
|
||||||
"net",
|
"net",
|
||||||
@@ -197,22 +219,27 @@ features = [
|
|||||||
"io-util",
|
"io-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[workspace.dependencies.tokio-metrics]
|
||||||
|
version = "0.3.1"
|
||||||
|
|
||||||
[workspace.dependencies.libloading]
|
[workspace.dependencies.libloading]
|
||||||
version = "0.8.3"
|
version = "0.8.5"
|
||||||
|
|
||||||
# Validating urls in config, was already a transitive dependency
|
# Validating urls in config, was already a transitive dependency
|
||||||
[workspace.dependencies.url]
|
[workspace.dependencies.url]
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
default-features = false
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
# standard date and time tools
|
# standard date and time tools
|
||||||
[workspace.dependencies.chrono]
|
[workspace.dependencies.chrono]
|
||||||
version = "0.4.38"
|
version = "0.4.38"
|
||||||
features = ["alloc"]
|
features = ["alloc", "std"]
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[workspace.dependencies.hyper]
|
[workspace.dependencies.hyper]
|
||||||
version = "1.3.1"
|
version = "1.4.1"
|
||||||
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"server",
|
"server",
|
||||||
"http1",
|
"http1",
|
||||||
@@ -220,7 +247,8 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.hyper-util]
|
[workspace.dependencies.hyper-util]
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
|
default-features = false
|
||||||
features = [
|
features = [
|
||||||
"client",
|
"client",
|
||||||
"server-auto",
|
"server-auto",
|
||||||
@@ -232,11 +260,13 @@ features = [
|
|||||||
# to support multiple variations of setting a config option
|
# to support multiple variations of setting a config option
|
||||||
[workspace.dependencies.either]
|
[workspace.dependencies.either]
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
default-features = false
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
# Used for reading the configuration from conduwuit.toml & environment variables
|
# Used for reading the configuration from conduwuit.toml & environment variables
|
||||||
[workspace.dependencies.figment]
|
[workspace.dependencies.figment]
|
||||||
version = "0.10.18"
|
version = "0.10.18"
|
||||||
|
default-features = false
|
||||||
features = ["env", "toml"]
|
features = ["env", "toml"]
|
||||||
|
|
||||||
[workspace.dependencies.hickory-resolver]
|
[workspace.dependencies.hickory-resolver]
|
||||||
@@ -245,7 +275,7 @@ default-features = false
|
|||||||
|
|
||||||
# Used for conduit::Error type
|
# Used for conduit::Error type
|
||||||
[workspace.dependencies.thiserror]
|
[workspace.dependencies.thiserror]
|
||||||
version = "1.0.61"
|
version = "1.0.63"
|
||||||
|
|
||||||
# Used when hashing the state
|
# Used when hashing the state
|
||||||
[workspace.dependencies.ring]
|
[workspace.dependencies.ring]
|
||||||
@@ -265,7 +295,7 @@ version = "2.1.1"
|
|||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
||||||
[workspace.dependencies.async-trait]
|
[workspace.dependencies.async-trait]
|
||||||
version = "0.1.80"
|
version = "0.1.81"
|
||||||
|
|
||||||
[workspace.dependencies.lru-cache]
|
[workspace.dependencies.lru-cache]
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -274,7 +304,7 @@ version = "0.1.2"
|
|||||||
[workspace.dependencies.ruma]
|
[workspace.dependencies.ruma]
|
||||||
git = "https://github.com/girlbossceo/ruwuma"
|
git = "https://github.com/girlbossceo/ruwuma"
|
||||||
#branch = "conduwuit-changes"
|
#branch = "conduwuit-changes"
|
||||||
rev = "fd686e77950680462377c9105dfb4136dd49c7a0"
|
rev = "c76e2873c1593a3308d4ba3e0e4a1db65acf8536"
|
||||||
features = [
|
features = [
|
||||||
"compat",
|
"compat",
|
||||||
"rand",
|
"rand",
|
||||||
@@ -284,8 +314,10 @@ features = [
|
|||||||
"markdown",
|
"markdown",
|
||||||
"push-gateway-api-c",
|
"push-gateway-api-c",
|
||||||
"state-res",
|
"state-res",
|
||||||
|
"server-util",
|
||||||
"unstable-exhaustive-types",
|
"unstable-exhaustive-types",
|
||||||
"ring-compat",
|
"ring-compat",
|
||||||
|
"identifiers-validation",
|
||||||
"unstable-unspecified",
|
"unstable-unspecified",
|
||||||
"unstable-msc2448",
|
"unstable-msc2448",
|
||||||
"unstable-msc2666",
|
"unstable-msc2666",
|
||||||
@@ -293,23 +325,19 @@ features = [
|
|||||||
"unstable-msc2870",
|
"unstable-msc2870",
|
||||||
"unstable-msc3026",
|
"unstable-msc3026",
|
||||||
"unstable-msc3061",
|
"unstable-msc3061",
|
||||||
|
"unstable-msc3266",
|
||||||
"unstable-msc3575",
|
"unstable-msc3575",
|
||||||
"unstable-msc4121",
|
"unstable-msc4121",
|
||||||
"unstable-msc4125",
|
"unstable-msc4125",
|
||||||
"unstable-extensible-events",
|
"unstable-extensible-events",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies.ruma-identifiers-validation]
|
|
||||||
git = "https://github.com/girlbossceo/ruwuma"
|
|
||||||
rev = "fd686e77950680462377c9105dfb4136dd49c7a0"
|
|
||||||
|
|
||||||
[workspace.dependencies.rust-rocksdb]
|
[workspace.dependencies.rust-rocksdb]
|
||||||
path = "deps/rust-rocksdb"
|
path = "deps/rust-rocksdb"
|
||||||
package = "rust-rocksdb-uwu"
|
package = "rust-rocksdb-uwu"
|
||||||
features = [
|
features = [
|
||||||
"multi-threaded-cf",
|
"multi-threaded-cf",
|
||||||
"mt_static",
|
"mt_static",
|
||||||
"snappy",
|
|
||||||
"lz4",
|
"lz4",
|
||||||
"zstd",
|
"zstd",
|
||||||
"zlib",
|
"zlib",
|
||||||
@@ -365,42 +393,26 @@ version = "0.34.0"
|
|||||||
version = "0.34.0"
|
version = "0.34.0"
|
||||||
|
|
||||||
# jemalloc usage
|
# jemalloc usage
|
||||||
|
# locked to 0.5.4 due to static binary linking breakage
|
||||||
[workspace.dependencies.tikv-jemalloc-sys]
|
[workspace.dependencies.tikv-jemalloc-sys]
|
||||||
version = "0.5.4"
|
version = "=0.5.4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
|
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
|
||||||
[workspace.dependencies.tikv-jemallocator]
|
[workspace.dependencies.tikv-jemallocator]
|
||||||
version = "0.5.4"
|
version = "=0.5.4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
|
features = ["stats", "unprefixed_malloc_on_supported_platforms"]
|
||||||
[workspace.dependencies.tikv-jemalloc-ctl]
|
[workspace.dependencies.tikv-jemalloc-ctl]
|
||||||
version = "0.5.4"
|
version = "=0.5.4"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["use_std"]
|
features = ["use_std"]
|
||||||
|
|
||||||
[workspace.dependencies.rusqlite]
|
|
||||||
git = "https://github.com/rusqlite/rusqlite"
|
|
||||||
#branch = "master"
|
|
||||||
rev = "e00b626e2b1c67347d789fb7f600281705c89381"
|
|
||||||
features = ["bundled"]
|
|
||||||
|
|
||||||
# used only by rusqlite
|
|
||||||
[workspace.dependencies.parking_lot]
|
|
||||||
version = "0.12.3"
|
|
||||||
|
|
||||||
# used only by rusqlite
|
|
||||||
[workspace.dependencies.thread_local]
|
|
||||||
version = "1.1.8"
|
|
||||||
|
|
||||||
[workspace.dependencies.tokio-metrics]
|
|
||||||
version = "0.3.1"
|
|
||||||
default-features = false
|
|
||||||
|
|
||||||
[workspace.dependencies.console-subscriber]
|
[workspace.dependencies.console-subscriber]
|
||||||
version = "0.3"
|
version = "0.4"
|
||||||
|
|
||||||
[workspace.dependencies.nix]
|
[workspace.dependencies.nix]
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
|
default-features = false
|
||||||
features = ["resource"]
|
features = ["resource"]
|
||||||
|
|
||||||
[workspace.dependencies.sd-notify]
|
[workspace.dependencies.sd-notify]
|
||||||
@@ -417,11 +429,26 @@ features = [
|
|||||||
|
|
||||||
[workspace.dependencies.rustyline-async]
|
[workspace.dependencies.rustyline-async]
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[workspace.dependencies.termimad]
|
[workspace.dependencies.termimad]
|
||||||
version = "0.29.4"
|
version = "0.29.4"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
|
[workspace.dependencies.checked_ops]
|
||||||
|
version = "0.1"
|
||||||
|
|
||||||
|
[workspace.dependencies.syn]
|
||||||
|
version = "2.0.72"
|
||||||
|
default-features = false
|
||||||
|
features = ["full", "extra-traits"]
|
||||||
|
|
||||||
|
[workspace.dependencies.quote]
|
||||||
|
version = "1.0.36"
|
||||||
|
|
||||||
|
[workspace.dependencies.proc-macro2]
|
||||||
|
version = "1.0.86"
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Patches
|
# Patches
|
||||||
@@ -432,16 +459,16 @@ default-features = false
|
|||||||
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
|
# https://github.com/girlbossceo/tracing/commit/b348dca742af641c47bc390261f60711c2af573c
|
||||||
[patch.crates-io.tracing-subscriber]
|
[patch.crates-io.tracing-subscriber]
|
||||||
git = "https://github.com/girlbossceo/tracing"
|
git = "https://github.com/girlbossceo/tracing"
|
||||||
rev = "b348dca742af641c47bc390261f60711c2af573c"
|
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
|
||||||
[patch.crates-io.tracing]
|
[patch.crates-io.tracing]
|
||||||
git = "https://github.com/girlbossceo/tracing"
|
git = "https://github.com/girlbossceo/tracing"
|
||||||
rev = "b348dca742af641c47bc390261f60711c2af573c"
|
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
|
||||||
[patch.crates-io.tracing-core]
|
[patch.crates-io.tracing-core]
|
||||||
git = "https://github.com/girlbossceo/tracing"
|
git = "https://github.com/girlbossceo/tracing"
|
||||||
rev = "b348dca742af641c47bc390261f60711c2af573c"
|
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
|
||||||
[patch.crates-io.tracing-log]
|
[patch.crates-io.tracing-log]
|
||||||
git = "https://github.com/girlbossceo/tracing"
|
git = "https://github.com/girlbossceo/tracing"
|
||||||
rev = "b348dca742af641c47bc390261f60711c2af573c"
|
rev = "4d78a14a5e03f539b8c6b475aefa08bb14e4de91"
|
||||||
|
|
||||||
# fixes hyper graceful shutdowns [https://github.com/programatik29/axum-server/issues/114]
|
# fixes hyper graceful shutdowns [https://github.com/programatik29/axum-server/issues/114]
|
||||||
# https://github.com/girlbossceo/axum-server/commit/8e3368d899079818934e61cc9c839abcbbcada8a
|
# https://github.com/girlbossceo/axum-server/commit/8e3368d899079818934e61cc9c839abcbbcada8a
|
||||||
@@ -449,6 +476,12 @@ rev = "b348dca742af641c47bc390261f60711c2af573c"
|
|||||||
git = "https://github.com/girlbossceo/axum-server"
|
git = "https://github.com/girlbossceo/axum-server"
|
||||||
rev = "8e3368d899079818934e61cc9c839abcbbcada8a"
|
rev = "8e3368d899079818934e61cc9c839abcbbcada8a"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
[patch.crates-io.rustyline-async]
|
||||||
|
git = "https://github.com/girlbossceo/rustyline-async"
|
||||||
|
rev = "de26100b0db03e419a3d8e1dd26895d170d1fe50"
|
||||||
|
|
||||||
#
|
#
|
||||||
# Our crates
|
# Our crates
|
||||||
#
|
#
|
||||||
@@ -483,6 +516,11 @@ package = "conduit_core"
|
|||||||
path = "src/core"
|
path = "src/core"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
|
[workspace.dependencies.conduit-macros]
|
||||||
|
package = "conduit_macros"
|
||||||
|
path = "src/macros"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
# Release profiles
|
# Release profiles
|
||||||
@@ -531,7 +569,17 @@ lto = "fat"
|
|||||||
[profile.release-max-perf.build-override]
|
[profile.release-max-perf.build-override]
|
||||||
inherits = "release-max-perf"
|
inherits = "release-max-perf"
|
||||||
opt-level = 0
|
opt-level = 0
|
||||||
|
codegen-units = 32
|
||||||
#rustflags = [
|
#rustflags = [
|
||||||
|
# '-Crelocation-model=pic',
|
||||||
|
# '-Ctarget-feature=-crt-static',
|
||||||
|
# '-Clink-arg=-Wl,--no-gc-sections',
|
||||||
|
#]
|
||||||
|
|
||||||
|
[profile.release-max-perf.package.conduit_macros]
|
||||||
|
inherits = "release-max-perf.build-override"
|
||||||
|
#rustflags = [
|
||||||
|
# '-Crelocation-model=pic',
|
||||||
# '-Ctarget-feature=-crt-static',
|
# '-Ctarget-feature=-crt-static',
|
||||||
#]
|
#]
|
||||||
|
|
||||||
@@ -660,7 +708,16 @@ opt-level = 'z'
|
|||||||
# '-Clink-arg=-Wl,-z,nodelete',
|
# '-Clink-arg=-Wl,-z,nodelete',
|
||||||
#]
|
#]
|
||||||
|
|
||||||
|
# primarily used for CI
|
||||||
[profile.test]
|
[profile.test]
|
||||||
|
inherits = "dev"
|
||||||
|
codegen-units = 16
|
||||||
|
incremental = false
|
||||||
|
|
||||||
|
[profile.test.package.'*']
|
||||||
|
inherits = "dev"
|
||||||
|
debug = 0
|
||||||
|
codegen-units = 16
|
||||||
incremental = false
|
incremental = false
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -722,47 +779,39 @@ variant_size_differences = "allow"
|
|||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
|
|
||||||
###################
|
###################
|
||||||
cargo = "warn"
|
cargo = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
## some sadness
|
## some sadness
|
||||||
multiple_crate_versions = { level = "allow", priority = 1 }
|
multiple_crate_versions = { level = "allow", priority = 1 }
|
||||||
|
|
||||||
###################
|
###################
|
||||||
complexity = "warn"
|
complexity = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
###################
|
###################
|
||||||
correctness = "warn"
|
correctness = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
###################
|
###################
|
||||||
nursery = "warn"
|
nursery = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
## some sadness
|
## some sadness
|
||||||
missing_const_for_fn = { level = "allow", priority = 1 } # TODO
|
missing_const_for_fn = { level = "allow", priority = 1 } # TODO
|
||||||
needless_collect = { level = "allow", priority = 1 } # TODO
|
|
||||||
option_if_let_else = { level = "allow", priority = 1 } # TODO
|
option_if_let_else = { level = "allow", priority = 1 } # TODO
|
||||||
redundant_pub_crate = { level = "allow", priority = 1 } # TODO
|
redundant_pub_crate = { level = "allow", priority = 1 } # TODO
|
||||||
significant_drop_in_scrutinee = { level = "allow", priority = 1 } # TODO
|
significant_drop_in_scrutinee = { level = "allow", priority = 1 } # TODO
|
||||||
significant_drop_tightening = { level = "allow", priority = 1 } # TODO
|
significant_drop_tightening = { level = "allow", priority = 1 } # TODO
|
||||||
|
|
||||||
###################
|
###################
|
||||||
pedantic = "warn"
|
pedantic = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
## some sadness
|
## some sadness
|
||||||
cast_possible_truncation = { level = "allow", priority = 1 }
|
|
||||||
cast_precision_loss = { level = "allow", priority = 1 }
|
|
||||||
cast_sign_loss = { level = "allow", priority = 1 }
|
|
||||||
doc_markdown = { level = "allow", priority = 1 }
|
doc_markdown = { level = "allow", priority = 1 }
|
||||||
error_impl_error = { level = "allow", priority = 1 }
|
enum_glob_use = { level = "allow", priority = 1 }
|
||||||
expect_used = { level = "allow", priority = 1 }
|
|
||||||
if_not_else = { level = "allow", priority = 1 }
|
if_not_else = { level = "allow", priority = 1 }
|
||||||
if_then_some_else_none = { level = "allow", priority = 1 }
|
if_then_some_else_none = { level = "allow", priority = 1 }
|
||||||
implicit_return = { level = "allow", priority = 1 }
|
|
||||||
inline_always = { level = "allow", priority = 1 }
|
inline_always = { level = "allow", priority = 1 }
|
||||||
map_err_ignore = { level = "allow", priority = 1 }
|
|
||||||
missing_docs_in_private_items = { level = "allow", priority = 1 }
|
missing_docs_in_private_items = { level = "allow", priority = 1 }
|
||||||
missing_errors_doc = { level = "allow", priority = 1 }
|
missing_errors_doc = { level = "allow", priority = 1 }
|
||||||
missing_panics_doc = { level = "allow", priority = 1 }
|
missing_panics_doc = { level = "allow", priority = 1 }
|
||||||
mod_module_files = { level = "allow", priority = 1 }
|
|
||||||
module_name_repetitions = { level = "allow", priority = 1 }
|
module_name_repetitions = { level = "allow", priority = 1 }
|
||||||
no_effect_underscore_binding = { level = "allow", priority = 1 }
|
no_effect_underscore_binding = { level = "allow", priority = 1 }
|
||||||
similar_names = { level = "allow", priority = 1 }
|
similar_names = { level = "allow", priority = 1 }
|
||||||
@@ -771,13 +820,15 @@ unnecessary_wraps = { level = "allow", priority = 1 }
|
|||||||
unused_async = { level = "allow", priority = 1 }
|
unused_async = { level = "allow", priority = 1 }
|
||||||
|
|
||||||
###################
|
###################
|
||||||
perf = "warn"
|
perf = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
###################
|
###################
|
||||||
#restriction = "warn"
|
#restriction = "warn"
|
||||||
|
|
||||||
#arithmetic_side_effects = "warn" # TODO
|
#allow_attributes = "warn" # UNSTABLE
|
||||||
#as_conversions = "warn" # TODO
|
arithmetic_side_effects = "warn"
|
||||||
|
as_conversions = "warn"
|
||||||
|
as_underscore = "warn"
|
||||||
assertions_on_result_states = "warn"
|
assertions_on_result_states = "warn"
|
||||||
dbg_macro = "warn"
|
dbg_macro = "warn"
|
||||||
default_union_representation = "warn"
|
default_union_representation = "warn"
|
||||||
@@ -791,7 +842,6 @@ fn_to_numeric_cast_any = "warn"
|
|||||||
format_push_string = "warn"
|
format_push_string = "warn"
|
||||||
get_unwrap = "warn"
|
get_unwrap = "warn"
|
||||||
impl_trait_in_params = "warn"
|
impl_trait_in_params = "warn"
|
||||||
let_underscore_must_use = "warn"
|
|
||||||
let_underscore_untyped = "warn"
|
let_underscore_untyped = "warn"
|
||||||
lossy_float_literal = "warn"
|
lossy_float_literal = "warn"
|
||||||
mem_forget = "warn"
|
mem_forget = "warn"
|
||||||
@@ -805,6 +855,7 @@ rest_pat_in_fully_bound_structs = "warn"
|
|||||||
semicolon_outside_block = "warn"
|
semicolon_outside_block = "warn"
|
||||||
str_to_string = "warn"
|
str_to_string = "warn"
|
||||||
string_lit_chars_any = "warn"
|
string_lit_chars_any = "warn"
|
||||||
|
string_slice = "warn"
|
||||||
string_to_string = "warn"
|
string_to_string = "warn"
|
||||||
suspicious_xor_used_as_pow = "warn"
|
suspicious_xor_used_as_pow = "warn"
|
||||||
tests_outside_test_module = "warn"
|
tests_outside_test_module = "warn"
|
||||||
@@ -815,17 +866,19 @@ unnecessary_safety_doc = "warn"
|
|||||||
unnecessary_self_imports = "warn"
|
unnecessary_self_imports = "warn"
|
||||||
unneeded_field_pattern = "warn"
|
unneeded_field_pattern = "warn"
|
||||||
unseparated_literal_suffix = "warn"
|
unseparated_literal_suffix = "warn"
|
||||||
|
#unwrap_used = "warn" # TODO
|
||||||
verbose_file_reads = "warn"
|
verbose_file_reads = "warn"
|
||||||
|
|
||||||
###################
|
###################
|
||||||
style = "warn"
|
style = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
## some sadness
|
## some sadness
|
||||||
# trivial assertions are quite alright
|
# trivial assertions are quite alright
|
||||||
assertions_on_constants = { level = "allow", priority = 1 }
|
assertions_on_constants = { level = "allow", priority = 1 }
|
||||||
|
module_inception = { level = "allow", priority = 1 }
|
||||||
|
|
||||||
###################
|
###################
|
||||||
suspicious = "warn"
|
suspicious = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
## some sadness
|
## some sadness
|
||||||
let_underscore_future = { level = "allow", priority = 1 }
|
let_underscore_future = { level = "allow", priority = 1 }
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from time to time.
|
|||||||
#### Contact
|
#### Contact
|
||||||
|
|
||||||
If you run into any question, feel free to
|
If you run into any question, feel free to
|
||||||
|
|
||||||
- Ask us in `#conduwuit:puppygock.gay` on Matrix
|
- Ask us in `#conduwuit:puppygock.gay` on Matrix
|
||||||
- [Open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new)
|
- [Open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new)
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ RuntimeDirectory=conduwuit
|
|||||||
RuntimeDirectoryMode=0750
|
RuntimeDirectoryMode=0750
|
||||||
|
|
||||||
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||||
|
BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit
|
||||||
|
BindPaths=/var/lib/private/conduwuit:/var/lib/private/matrix-conduit
|
||||||
|
|
||||||
ExecStart=/usr/bin/conduwuit
|
ExecStart=/usr/bin/conduwuit
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
+13
-6
@@ -7,7 +7,7 @@ set -euo pipefail
|
|||||||
# The `COMPLEMENT_SRC` environment variable is set in the Nix dev shell, which
|
# The `COMPLEMENT_SRC` environment variable is set in the Nix dev shell, which
|
||||||
# points to a store path containing the Complement source code. It's likely you
|
# points to a store path containing the Complement source code. It's likely you
|
||||||
# want to just pass that as the first argument to use it here.
|
# want to just pass that as the first argument to use it here.
|
||||||
COMPLEMENT_SRC="$1"
|
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
|
||||||
|
|
||||||
# A `.jsonl` file to write test logs to
|
# A `.jsonl` file to write test logs to
|
||||||
LOG_FILE="$2"
|
LOG_FILE="$2"
|
||||||
@@ -17,11 +17,18 @@ RESULTS_FILE="$3"
|
|||||||
|
|
||||||
OCI_IMAGE="complement-conduit:main"
|
OCI_IMAGE="complement-conduit:main"
|
||||||
|
|
||||||
# Complement tests that are skipped due to flakiness/reliability issues (likely
|
# Complement tests that are skipped due to flakiness/reliability issues
|
||||||
# Complement itself induced based on various open issues)
|
SKIPPED_COMPLEMENT_TESTS='-skip=TestClientSpacesSummary.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestJumpToDateEndpoint.*'
|
||||||
#
|
|
||||||
# According to Go docs, these are separated by forward slashes and not pipes (why)
|
# $COMPLEMENT_SRC needs to be a directory to Complement source code
|
||||||
SKIPPED_COMPLEMENT_TESTS='-skip=TestJumpToDateEndpoint.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestFederationRoomsInvite.*|TestClientSpacesSummary.*'
|
if [ -f "$COMPLEMENT_SRC" ]; then
|
||||||
|
echo "\$COMPLEMENT_SRC must be a directory/path to Complement source code"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# quick test to make sure we can actually write to $LOG_FILE and $RESULTS_FILE
|
||||||
|
touch $LOG_FILE && rm -v $LOG_FILE
|
||||||
|
touch $RESULTS_FILE && rm -v $RESULTS_FILE
|
||||||
|
|
||||||
toplevel="$(git rev-parse --show-toplevel)"
|
toplevel="$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
|||||||
+54
-6
@@ -52,11 +52,22 @@
|
|||||||
|
|
||||||
# Performance monitoring/tracing sample rate for Sentry.io
|
# Performance monitoring/tracing sample rate for Sentry.io
|
||||||
#
|
#
|
||||||
# Note that too high values may impact performance, and can be disabled by setting it to 0.0
|
# Note that too high values may impact performance, and can be disabled by setting it to 0.0 (0%)
|
||||||
|
# This value is read as a percentage to Sentry, represented as a decimal
|
||||||
#
|
#
|
||||||
# Defaults to 0.15
|
# Defaults to 15% of traces (0.15)
|
||||||
#sentry_traces_sample_rate = 0.15
|
#sentry_traces_sample_rate = 0.15
|
||||||
|
|
||||||
|
# Whether to attach a stacktrace to Sentry reports.
|
||||||
|
#sentry_attach_stacktrace = false
|
||||||
|
|
||||||
|
# Send panics to sentry. This is true by default, but sentry has to be enabled.
|
||||||
|
#sentry_send_panic = true
|
||||||
|
|
||||||
|
# Send errors to sentry. This is true by default, but sentry has to be enabled. This option is
|
||||||
|
# only effective in release-mode; forced to false in debug-mode.
|
||||||
|
#sentry_send_error = true
|
||||||
|
|
||||||
|
|
||||||
### Database configuration
|
### Database configuration
|
||||||
|
|
||||||
@@ -64,9 +75,7 @@
|
|||||||
# Note: this was previously "/var/lib/matrix-conduit"
|
# Note: this was previously "/var/lib/matrix-conduit"
|
||||||
database_path = "/var/lib/conduwuit"
|
database_path = "/var/lib/conduwuit"
|
||||||
|
|
||||||
# Database backend: Only rocksdb and sqlite are supported. Please note that sqlite
|
# Database backend: Only rocksdb is supported.
|
||||||
# will perform significantly worse than rocksdb as it is not intended to be used the
|
|
||||||
# way it is by conduwuit. sqlite only exists for historical reasons.
|
|
||||||
database_backend = "rocksdb"
|
database_backend = "rocksdb"
|
||||||
|
|
||||||
|
|
||||||
@@ -373,12 +382,51 @@ allow_profile_lookup_federation_requests = true
|
|||||||
# If 'tracing_flame' is enabled, set the path to write the generated profile.
|
# If 'tracing_flame' is enabled, set the path to write the generated profile.
|
||||||
# tracing_flame_output_path = "./tracing.folded"
|
# tracing_flame_output_path = "./tracing.folded"
|
||||||
|
|
||||||
|
# Enable the tokio-console. This option is only relevant to developers.
|
||||||
|
# See: docs/development.md#debugging-with-tokio-console for more information.
|
||||||
|
#tokio_console = false
|
||||||
|
|
||||||
|
# Enable backward-compatibility with Conduit's media directory by creating symlinks of media. This
|
||||||
|
# option is only necessary if you plan on using Conduit again. Otherwise setting this to false
|
||||||
|
# reduces filesystem clutter and overhead for managing these symlinks in the directory.
|
||||||
|
#
|
||||||
|
# Enabled by default.
|
||||||
|
#media_compat_file_link = true
|
||||||
|
|
||||||
|
# Checks consistency of the media directory at startup:
|
||||||
|
# 1. When `media_compat_file_link` is enbled, this check will upgrade media when switching back
|
||||||
|
# and forth between Conduit and Conduwuit. Both options must be enabled to handle this.
|
||||||
|
# 2. When media is deleted from the directory, this check will also delete its database entry.
|
||||||
|
#
|
||||||
|
# If none of these checks apply to your use cases, and your media directory is significantly large
|
||||||
|
# setting this to false may reduce startup time.
|
||||||
|
#
|
||||||
|
# Enabled by default.
|
||||||
|
#media_startup_check = true
|
||||||
|
|
||||||
|
# OpenID token expiration/TTL in seconds
|
||||||
|
#
|
||||||
|
# These are the OpenID tokens that are primarily used for Matrix account integrations, *not* OIDC/OpenID Connect/etc
|
||||||
|
#
|
||||||
|
# Defaults to 3600 (1 hour)
|
||||||
|
#openid_token_ttl = 3600
|
||||||
|
|
||||||
|
# Emergency password feature. This password set here will let you login to the server service account (e.g. `@conduit`)
|
||||||
|
# and let you run admin commands, invite yourself to the admin room, etc.
|
||||||
|
#
|
||||||
|
# no default.
|
||||||
|
#emergency_password = ""
|
||||||
|
|
||||||
|
|
||||||
### Generic database options
|
### Generic database options
|
||||||
|
|
||||||
# Set this to any float value to multiply conduwuit's in-memory LRU caches with.
|
# Set this to any float value to multiply conduwuit's in-memory LRU caches with.
|
||||||
# May be useful if you have significant memory to spare to increase performance.
|
# May be useful if you have significant memory to spare to increase performance.
|
||||||
|
#
|
||||||
|
# This was previously called `conduit_cache_capacity_modifier`
|
||||||
|
#
|
||||||
# Defaults to 1.0.
|
# Defaults to 1.0.
|
||||||
#conduit_cache_capacity_modifier = 1.0
|
#cache_capacity_modifier = 1.0
|
||||||
|
|
||||||
# Set this to any float value in megabytes for conduwuit to tell the database engine that this much memory is available for database-related caches.
|
# Set this to any float value in megabytes for conduwuit to tell the database engine that this much memory is available for database-related caches.
|
||||||
# May be useful if you have significant memory to spare to increase performance.
|
# May be useful if you have significant memory to spare to increase performance.
|
||||||
|
|||||||
Vendored
+1
-1
@@ -15,7 +15,7 @@ in `/etc/conduwuit/conduwuit.toml`.
|
|||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
The package uses the [`conduwuit.service`](../configuration.md#example-systemd-unit-file) systemd unit file to start and stop conduwuit. The binary is installed at `/usr/sbin/conduwuit`.
|
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop conduwuit. The binary is installed at `/usr/sbin/conduwuit`.
|
||||||
|
|
||||||
This package assumes by default that conduwuit will be placed behind a reverse proxy. The default config options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS, so you will need to set up TLS certificates and renewal for it to work properly if you intend to federate.
|
This package assumes by default that conduwuit will be placed behind a reverse proxy. The default config options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS, so you will need to set up TLS certificates and renewal for it to work properly if you intend to federate.
|
||||||
|
|
||||||
|
|||||||
Vendored
-1
@@ -6,7 +6,6 @@ set -e
|
|||||||
|
|
||||||
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
|
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
|
||||||
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
|
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
|
||||||
CONDUWUIT_CONFIG_FILE="${CONDUWUIT_CONFIG_PATH}/conduwuit.toml"
|
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
configure)
|
configure)
|
||||||
|
|||||||
Vendored
+2
-2
@@ -10,7 +10,7 @@ repository.workspace = true
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["snappy", "lz4", "zstd", "zlib", "bzip2"]
|
default = ["lz4", "zstd", "zlib", "bzip2"]
|
||||||
jemalloc = ["rust-rocksdb/jemalloc"]
|
jemalloc = ["rust-rocksdb/jemalloc"]
|
||||||
io-uring = ["rust-rocksdb/io-uring"]
|
io-uring = ["rust-rocksdb/io-uring"]
|
||||||
valgrind = ["rust-rocksdb/valgrind"]
|
valgrind = ["rust-rocksdb/valgrind"]
|
||||||
@@ -27,7 +27,7 @@ malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
|
|||||||
|
|
||||||
[dependencies.rust-rocksdb]
|
[dependencies.rust-rocksdb]
|
||||||
git = "https://github.com/zaidoon1/rust-rocksdb"
|
git = "https://github.com/zaidoon1/rust-rocksdb"
|
||||||
rev = "e9e1cb5ba92a44ea225fe8d13b31aa23621b9035"
|
rev = "4056a3b0f823013fec49f6d0b3e5698856e6476a"
|
||||||
#branch = "master"
|
#branch = "master"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
- [Introduction](introduction.md)
|
- [Introduction](introduction.md)
|
||||||
- [Differences from upstream Conduit](differences.md)
|
- [Differences from upstream Conduit](differences.md)
|
||||||
- [Example configuration](configuration.md)
|
- [Configuration](configuration.md)
|
||||||
|
- [Examples](configuration/examples.md)
|
||||||
- [Deploying](deploying.md)
|
- [Deploying](deploying.md)
|
||||||
- [Generic](deploying/generic.md)
|
- [Generic](deploying/generic.md)
|
||||||
- [NixOS](deploying/nixos.md)
|
- [NixOS](deploying/nixos.md)
|
||||||
|
|||||||
+13
-27
@@ -1,32 +1,18 @@
|
|||||||
## Example configuration
|
# Configuration
|
||||||
|
|
||||||
<details>
|
This chapter describes various ways to configure conduwuit.
|
||||||
<summary>Example configuration</summary>
|
|
||||||
|
|
||||||
```toml
|
## Basics
|
||||||
{{#include ../conduwuit-example.toml}}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
Conduwuit uses a config file for the majority of the settings. Please refer to the
|
||||||
|
[example config file](./configuration/examples.md#example-configuration) for all of those settings.
|
||||||
|
The config file to use can either be specified on the command line when running conduwuit by specifying the
|
||||||
|
`-c`, `--config` flag. Alternatively, you can use the environment variable `CONDUWUIT_CONFIG` to specify the config
|
||||||
|
file to used.
|
||||||
|
|
||||||
## Debian systemd unit file
|
## Environment variables
|
||||||
|
|
||||||
<details>
|
All of the settings that are found in the config file can be specified by using environment variables.
|
||||||
<summary>Debian systemd unit file</summary>
|
The environment variable names should be all caps and prefixed with `CONDUWUIT_`.
|
||||||
|
For example, if the setting you are changing is `max_request_size`, then the environment variable to set is
|
||||||
```
|
`CONDUWUIT_MAX_REQUEST_SIZE`.
|
||||||
{{#include ../debian/conduwuit.service}}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Arch Linux systemd unit file
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Arch Linux systemd unit file</summary>
|
|
||||||
|
|
||||||
```
|
|
||||||
{{#include ../arch/conduwuit.service}}
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
## Example configuration
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Example configuration</summary>
|
||||||
|
|
||||||
|
```toml
|
||||||
|
{{#include ../../conduwuit-example.toml}}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Debian systemd unit file
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Debian systemd unit file</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
{{#include ../../debian/conduwuit.service}}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Arch Linux systemd unit file
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Arch Linux systemd unit file</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
{{#include ../../arch/conduwuit.service}}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
# conduwuit - Behind Traefik Reverse Proxy
|
# conduwuit - Behind Traefik Reverse Proxy
|
||||||
version: '2.4' # uses '2.4' for cpuset
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
homeserver:
|
homeserver:
|
||||||
@@ -24,7 +23,7 @@ services:
|
|||||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||||
#CONDUWUIT_LOG: warn,state_res=warn
|
#CONDUWUIT_LOG: warn,state_res=warn
|
||||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||||
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||||
|
|
||||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
# conduwuit - Traefik Reverse Proxy Labels
|
# conduwuit - Traefik Reverse Proxy Labels
|
||||||
version: '2.4' # uses '2.4' for cpuset
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
homeserver:
|
homeserver:
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
# This compose file uses caddy-docker-proxy as the reverse proxy for conduwuit!
|
||||||
|
# For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
|
||||||
|
image: lucaslorentz/caddy-docker-proxy:ci-alpine
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
environment:
|
||||||
|
- CADDY_INGRESS_NETWORKS=caddy
|
||||||
|
networks:
|
||||||
|
- caddy
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
caddy: example.com
|
||||||
|
caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
|
||||||
|
caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}
|
||||||
|
|
||||||
|
homeserver:
|
||||||
|
### If you already built the conduwuit image with 'docker build' or want to use a registry image,
|
||||||
|
### then you are ready to go.
|
||||||
|
image: girlbossceo/conduwuit:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- db:/var/lib/conduwuit
|
||||||
|
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||||
|
environment:
|
||||||
|
CONDUWUIT_SERVER_NAME: example.com # EDIT THIS
|
||||||
|
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||||
|
CONDUWUIT_DATABASE_BACKEND: rocksdb
|
||||||
|
CONDUWUIT_PORT: 6167
|
||||||
|
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||||
|
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||||
|
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||||
|
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||||
|
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||||
|
#CONDUWUIT_LOG: warn,state_res=warn
|
||||||
|
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||||
|
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||||
|
networks:
|
||||||
|
- caddy
|
||||||
|
labels:
|
||||||
|
caddy: matrix.example.com
|
||||||
|
caddy.reverse_proxy: "{{upstreams 6167}}"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
caddy:
|
||||||
|
external: true
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
# conduwuit - Behind Traefik Reverse Proxy
|
# conduwuit - Behind Traefik Reverse Proxy
|
||||||
version: '2.4' # uses '2.4' for cpuset
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
homeserver:
|
homeserver:
|
||||||
@@ -16,7 +15,7 @@ services:
|
|||||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||||
CONDUWUIT_ALLOW_REGISTRATION : 'true'
|
CONDUWUIT_ALLOW_REGISTRATION : 'true'
|
||||||
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||||
### Uncomment and change values as desired
|
### Uncomment and change values as desired
|
||||||
# CONDUWUIT_ADDRESS: 0.0.0.0
|
# CONDUWUIT_ADDRESS: 0.0.0.0
|
||||||
# CONDUWUIT_PORT: 6167
|
# CONDUWUIT_PORT: 6167
|
||||||
@@ -28,7 +27,6 @@ services:
|
|||||||
# CONDUWUIT_DATABASE_PATH: /srv/conduwuit/.local/share/conduwuit
|
# CONDUWUIT_DATABASE_PATH: /srv/conduwuit/.local/share/conduwuit
|
||||||
# CONDUWUIT_WORKERS: 10
|
# CONDUWUIT_WORKERS: 10
|
||||||
# CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
# CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
||||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
|
||||||
|
|
||||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||||
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
|
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
# conduwuit
|
# conduwuit
|
||||||
version: '2.4' # uses '2.4' for cpuset
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
homeserver:
|
homeserver:
|
||||||
@@ -24,8 +23,7 @@ services:
|
|||||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||||
#CONDUWUIT_LOG: warn,state_res=warn
|
#CONDUWUIT_LOG: warn,state_res=warn
|
||||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||||
#CONDUWUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
|
||||||
#
|
#
|
||||||
### Uncomment if you want to use your own Element-Web App.
|
### Uncomment if you want to use your own Element-Web App.
|
||||||
### Note: You need to provide a config.json for Element and you also need a second
|
### Note: You need to provide a config.json for Element and you also need a second
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ OCI images for conduwuit are available in the registries listed below.
|
|||||||
[shield-main]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/main
|
[shield-main]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/main
|
||||||
|
|
||||||
Use
|
Use
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker image pull <link>
|
docker image pull <link>
|
||||||
```
|
```
|
||||||
|
|
||||||
to pull it to your machine.
|
to pull it to your machine.
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
@@ -44,7 +46,7 @@ docker run -d -p 8448:6167 \
|
|||||||
|
|
||||||
or you can use [docker compose](#docker-compose).
|
or you can use [docker compose](#docker-compose).
|
||||||
|
|
||||||
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, the example config can be found [here](../configuration.md).
|
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, the example config can be found [here](../configuration/examples.md).
|
||||||
You can pass in different env vars to change config values on the fly. You can even configure conduwuit completely by using env vars. For an overview of possible
|
You can pass in different env vars to change config values on the fly. You can even configure conduwuit completely by using env vars. For an overview of possible
|
||||||
values, please take a look at the [`docker-compose.yml`](docker-compose.yml) file.
|
values, please take a look at the [`docker-compose.yml`](docker-compose.yml) file.
|
||||||
|
|
||||||
@@ -55,14 +57,24 @@ If you just want to test conduwuit for a short time, you can use the `--rm` flag
|
|||||||
If the `docker run` command is not for you or your setup, you can also use one of the provided `docker-compose` files.
|
If the `docker run` command is not for you or your setup, you can also use one of the provided `docker-compose` files.
|
||||||
|
|
||||||
Depending on your proxy setup, you can use one of the following files;
|
Depending on your proxy setup, you can use one of the following files;
|
||||||
|
|
||||||
- If you already have a `traefik` instance set up, use [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml)
|
- If you already have a `traefik` instance set up, use [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml)
|
||||||
- If you don't have a `traefik` instance set up (or any other reverse proxy), use [`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
|
- If you don't have a `traefik` instance set up and would like to use it, use [`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
|
||||||
|
- If you want a setup that works out of the box with `caddy-docker-proxy`, use [`docker-compose.with-caddy.yml`](docker-compose.with-caddy.yml) and replace all `example.com` placeholders with your own domain
|
||||||
- For any other reverse proxy, use [`docker-compose.yml`](docker-compose.yml)
|
- For any other reverse proxy, use [`docker-compose.yml`](docker-compose.yml)
|
||||||
|
|
||||||
When picking the traefik-related compose file, rename it so it matches `docker-compose.yml`, and
|
When picking the traefik-related compose file, rename it so it matches `docker-compose.yml`, and
|
||||||
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
|
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
|
||||||
for your server.
|
for your server.
|
||||||
|
|
||||||
|
When picking the `caddy-docker-proxy` compose file, it's important to first create the `caddy` network before spinning up the containers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker network create caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, you can rename it so it matches `docker-compose.yml` and spin up the containers!
|
||||||
|
|
||||||
Additional info about deploying conduwuit can be found [here](generic.md).
|
Additional info about deploying conduwuit can be found [here](generic.md).
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
@@ -100,7 +112,6 @@ either expose ports `443` and `8448` or serve two endpoints `.well-known/matrix/
|
|||||||
|
|
||||||
With the service `well-known` we use a single `nginx` container that will serve those two files.
|
With the service `well-known` we use a single `nginx` container that will serve those two files.
|
||||||
|
|
||||||
|
|
||||||
## Voice communication
|
## Voice communication
|
||||||
|
|
||||||
See the [TURN](../turn.md) page.
|
See the [TURN](../turn.md) page.
|
||||||
|
|||||||
+10
-12
@@ -23,7 +23,7 @@ Otherwise, follow standard Rust project build guides (installing git and cloning
|
|||||||
While conduwuit can run as any user it is better to use dedicated users for different services. This also allows
|
While conduwuit can run as any user it is better to use dedicated users for different services. This also allows
|
||||||
you to make sure that the file permissions are correctly set up.
|
you to make sure that the file permissions are correctly set up.
|
||||||
|
|
||||||
In Debian or RHEL, you can use this command to create a conduwuit user:
|
In Debian or Fedora/RHEL, you can use this command to create a conduwuit user:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo adduser --system conduwuit --group --disabled-login --no-create-home
|
sudo adduser --system conduwuit --group --disabled-login --no-create-home
|
||||||
@@ -43,23 +43,21 @@ If conduwuit runs behind a router or in a container and has a different public I
|
|||||||
|
|
||||||
## Setting up a systemd service
|
## Setting up a systemd service
|
||||||
|
|
||||||
The systemd unit for conduwuit can be found [here](../configuration.md#example-systemd-unit-file). You may need to change the `ExecStart=` path to where you placed the conduwuit binary.
|
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.
|
||||||
|
|
||||||
## Creating the conduwuit configuration file
|
## Creating the conduwuit configuration file
|
||||||
|
|
||||||
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. The example config can be found at [conduwuit-example.toml](../configuration.md).**Please take a moment to read it. You need to change at least the server name.**
|
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. The example config can be found at [conduwuit-example.toml](../configuration/examples.md).**Please take a moment to read it. You need to change at least the server name.**
|
||||||
|
|
||||||
RocksDB is the only supported database backend. SQLite only exists for historical reasons, is not recommended, and will be removed soon (likely in v0.5.0). Any performance issues, storage issues, database issues, etc will not be assisted if using SQLite and you will be asked to migrate to RocksDB first.
|
RocksDB is the only supported database backend.
|
||||||
|
|
||||||
## Setting the correct file permissions
|
## Setting the correct file permissions
|
||||||
|
|
||||||
If you are using a dedicated user for conduwuit, you will need to allow it to read the config. To do that you can run this command on
|
If you are using a dedicated user for conduwuit, you will need to allow it to read the config. To do that you can run this:
|
||||||
|
|
||||||
Debian or RHEL:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo chown -R root:root /etc/conduwuit
|
sudo chown -R root:root /etc/conduwuit
|
||||||
sudo chmod 755 /etc/conduwuit
|
sudo chmod -R 755 /etc/conduwuit
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use the default database path you also need to run this:
|
If you use the default database path you also need to run this:
|
||||||
@@ -93,21 +91,21 @@ your.server.name, your.server.name:8448 {
|
|||||||
That's it! Just start and enable the service and you're set.
|
That's it! Just start and enable the service and you're set.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo systemctl enable --now caddy
|
sudo systemctl enable --now caddy
|
||||||
```
|
```
|
||||||
|
|
||||||
## You're done!
|
## You're done
|
||||||
|
|
||||||
Now you can start conduwuit with:
|
Now you can start conduwuit with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo systemctl start conduwuit
|
sudo systemctl start conduwuit
|
||||||
```
|
```
|
||||||
|
|
||||||
Set it to start automatically when your system boots with:
|
Set it to start automatically when your system boots with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo systemctl enable conduwuit
|
sudo systemctl enable conduwuit
|
||||||
```
|
```
|
||||||
|
|
||||||
## How do I know it works?
|
## How do I know it works?
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ conduwuit can be acquired by [Lix][lix] from various places:
|
|||||||
|
|
||||||
A binary cache for conduwuit that the CI/CD publishes to is available at the
|
A binary cache for conduwuit that the CI/CD publishes to is available at the
|
||||||
following places (both are the same just different names):
|
following places (both are the same just different names):
|
||||||
|
|
||||||
```
|
```
|
||||||
https://attic.kennel.juneis.dog/conduit
|
https://attic.kennel.juneis.dog/conduit
|
||||||
conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
|
conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ look like this:
|
|||||||
RUSTFLAGS="--cfg tokio_unstable" cargo build \
|
RUSTFLAGS="--cfg tokio_unstable" cargo build \
|
||||||
--release \
|
--release \
|
||||||
--no-default-features \
|
--no-default-features \
|
||||||
--features=rocksdb,systemd,element_hacks,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
--features=systemd,element_hacks,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
||||||
```
|
```
|
||||||
|
|
||||||
[1]: https://docs.rs/tokio-console/latest/tokio_console/
|
[1]: https://docs.rs/tokio-console/latest/tokio_console/
|
||||||
|
|||||||
@@ -65,14 +65,17 @@ When a symbol is referenced between crates they become bound: **crates cannot be
|
|||||||
Proper resource management is essential for reliable reloading to occur. This is a very basic ask in RAII-idiomatic Rust and the exposure to reloading hazards is remarkably low, generally stemming from poor patterns and practices. Unfortunately static analysis doesn't enforce reload-safety programmatically (though it could one day), for now hazards can be avoided by knowing a few basic do's and dont's:
|
Proper resource management is essential for reliable reloading to occur. This is a very basic ask in RAII-idiomatic Rust and the exposure to reloading hazards is remarkably low, generally stemming from poor patterns and practices. Unfortunately static analysis doesn't enforce reload-safety programmatically (though it could one day), for now hazards can be avoided by knowing a few basic do's and dont's:
|
||||||
|
|
||||||
1. Understand that code is memory. Just like one is forbidden from referencing free'd memory, one must not transfer control to free'd code. Exposure to this is primarily from two things:
|
1. Understand that code is memory. Just like one is forbidden from referencing free'd memory, one must not transfer control to free'd code. Exposure to this is primarily from two things:
|
||||||
|
|
||||||
- Callbacks, which this project makes very little use of.
|
- Callbacks, which this project makes very little use of.
|
||||||
- Async tasks, which are addressed below.
|
- Async tasks, which are addressed below.
|
||||||
|
|
||||||
2. Tie all resources to a scope or object lifetime with greatest possible symmetry (locality). For our purposes this applies to code resources, which means async blocks and tokio tasks.
|
2. Tie all resources to a scope or object lifetime with greatest possible symmetry (locality). For our purposes this applies to code resources, which means async blocks and tokio tasks.
|
||||||
|
|
||||||
- **Never spawn a task without receiving and storing its JoinHandle**.
|
- **Never spawn a task without receiving and storing its JoinHandle**.
|
||||||
- **Always wait on join handles** before leaving a scope or in another cleanup function called by an owning scope.
|
- **Always wait on join handles** before leaving a scope or in another cleanup function called by an owning scope.
|
||||||
|
|
||||||
3. Know any minor specific quirks documented in code or here:
|
3. Know any minor specific quirks documented in code or here:
|
||||||
|
|
||||||
- Don't use `tokio::spawn`, instead use our `Handle` in `core/server.rs`, which is reachable in most of the codebase via `services()` or other state. This is due to some bugs or assumptions made in tokio, as it happens in `unsafe {}` blocks, which are mitigated by circumventing some thread-local variables. Using runtime handles is good practice in any case.
|
- Don't use `tokio::spawn`, instead use our `Handle` in `core/server.rs`, which is reachable in most of the codebase via `services()` or other state. This is due to some bugs or assumptions made in tokio, as it happens in `unsafe {}` blocks, which are mitigated by circumventing some thread-local variables. Using runtime handles is good practice in any case.
|
||||||
|
|
||||||
The initial implementation PR is available [here][1].
|
The initial implementation PR is available [here][1].
|
||||||
|
|||||||
+17
-13
@@ -1,10 +1,11 @@
|
|||||||
#### **Note: This list may not up to date. There are rapidly more and more improvements, fixes, changes, etc being made that it is becoming more difficult to maintain this list. I recommend that you give conduwuit a try and see the differences for yourself. If you have any concerns, feel free to join the conduwuit Matrix room and ask any pre-usage questions.**
|
#### **Note: This list may not up to date. There are rapidly more and more improvements, fixes, changes, etc being made that it is becoming more difficult to maintain this list. I recommend that you give conduwuit a try and see the differences for yourself. If you have any concerns, feel free to join the conduwuit Matrix room and ask any pre-usage questions.**
|
||||||
|
|
||||||
### list of features, bug fixes, etc that conduwuit does that Conduit does not:
|
### list of features, bug fixes, etc that conduwuit does that Conduit does not
|
||||||
|
|
||||||
Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
||||||
|
|
||||||
## Performance:
|
## Performance
|
||||||
|
|
||||||
- Concurrency support for individual homeserver key fetching for faster remote room joins and room joins that will error less frequently
|
- Concurrency support for individual homeserver key fetching for faster remote room joins and room joins that will error less frequently
|
||||||
- Send `Cache-Control` response header with `immutable` and 1 year cache length for all media requests (download and thumbnail) to instruct clients to cache media, and reduce server load from media requests that could be otherwise cached
|
- Send `Cache-Control` response header with `immutable` and 1 year cache length for all media requests (download and thumbnail) to instruct clients to cache media, and reduce server load from media requests that could be otherwise cached
|
||||||
- Add feature flags and config options to enable/build with zstd, brotli, and/or gzip HTTP body compression (response and request)
|
- Add feature flags and config options to enable/build with zstd, brotli, and/or gzip HTTP body compression (response and request)
|
||||||
@@ -25,9 +26,10 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
|||||||
- Use `tracing`/`log`'s `release_max_level_info` feature to improve performance, build speeds, binary size, and CPU usage in release builds by avoid compiling debug/trace log level macros that users will generally never use (can be disabled with a build-time feature flag)
|
- Use `tracing`/`log`'s `release_max_level_info` feature to improve performance, build speeds, binary size, and CPU usage in release builds by avoid compiling debug/trace log level macros that users will generally never use (can be disabled with a build-time feature flag)
|
||||||
- Remove some unnecessary checks on EDU handling for incoming transactions, effectively speeding them up
|
- Remove some unnecessary checks on EDU handling for incoming transactions, effectively speeding them up
|
||||||
- Simplify, dedupe, etc huge chunks of the codebase, including some that were unnecessary overhead, binary bloats, or preventing compiler/linker optimisations
|
- Simplify, dedupe, etc huge chunks of the codebase, including some that were unnecessary overhead, binary bloats, or preventing compiler/linker optimisations
|
||||||
|
- Implement zero-copy RocksDB database accessors, substantially improving performance caused by unnecessary memory allocations
|
||||||
|
|
||||||
|
## General Fixes/Features
|
||||||
|
|
||||||
## General Fixes/Features:
|
|
||||||
- Add legacy Element client hack fixing password changes and deactivations on legacy Element Android/iOS due to usage of an unspecced `user` field for UIAA
|
- Add legacy Element client hack fixing password changes and deactivations on legacy Element Android/iOS due to usage of an unspecced `user` field for UIAA
|
||||||
- Raise and improve all the various request timeouts making some things like room joins and client bugs error less or none at all than they should, and make them all user configurable
|
- Raise and improve all the various request timeouts making some things like room joins and client bugs error less or none at all than they should, and make them all user configurable
|
||||||
- Add missing `reason` field to user ban events (`/ban`)
|
- Add missing `reason` field to user ban events (`/ban`)
|
||||||
@@ -56,11 +58,11 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
|||||||
- Explicitly define support for sliding sync at `/_matrix/client/versions` (`org.matrix.msc3575`)
|
- Explicitly define support for sliding sync at `/_matrix/client/versions` (`org.matrix.msc3575`)
|
||||||
- Fix seeing empty status messages on user presences
|
- Fix seeing empty status messages on user presences
|
||||||
|
|
||||||
|
## Moderation
|
||||||
|
|
||||||
## Moderation:
|
|
||||||
- (Also see [Admin Room](#admin-room) for all the admin commands pertaining to moderation, there's a lot!)
|
- (Also see [Admin Room](#admin-room) for all the admin commands pertaining to moderation, there's a lot!)
|
||||||
- Add support for room banning/blocking by ID using admin command
|
- Add support for room banning/blocking by ID using admin command
|
||||||
- Add support for serving `support` well-known from `[well_known.support]` (MSC1929)
|
- Add support for serving `support` well-known from `[global.well_known]` (MSC1929) (`/.well-known/matrix/support`)
|
||||||
- Config option to forbid publishing rooms to the room directory (`lockdown_public_room_directory`) except for admins
|
- Config option to forbid publishing rooms to the room directory (`lockdown_public_room_directory`) except for admins
|
||||||
- Admin commands to delete room aliases and unpublish rooms from our room directory
|
- Admin commands to delete room aliases and unpublish rooms from our room directory
|
||||||
- For all [`/report`](https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3roomsroomidreporteventid) requests: check if the reported event ID belongs to the reported room ID, raise report reasoning character limit to 750, fix broken formatting, make a small delayed random response per spec suggestion on privacy, and check if the sender user is in the reported room.
|
- For all [`/report`](https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3roomsroomidreporteventid) requests: check if the reported event ID belongs to the reported room ID, raise report reasoning character limit to 750, fix broken formatting, make a small delayed random response per spec suggestion on privacy, and check if the sender user is in the reported room.
|
||||||
@@ -73,8 +75,8 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
|||||||
- Deactivating users will remove their profile picture, blurhash, display name, and leave all rooms by default just like Synapse and for additional privacy
|
- Deactivating users will remove their profile picture, blurhash, display name, and leave all rooms by default just like Synapse and for additional privacy
|
||||||
- Reject some EDUs from ACL'd users such as read receipts and typing indicators
|
- Reject some EDUs from ACL'd users such as read receipts and typing indicators
|
||||||
|
|
||||||
|
## Privacy/Security
|
||||||
|
|
||||||
## Privacy/Security:
|
|
||||||
- Add config option for device name federation with a privacy-friendly default (disabled)
|
- Add config option for device name federation with a privacy-friendly default (disabled)
|
||||||
- Add config option for requiring authentication to the `/publicRooms` endpoint (room directory) with a default enabled for privacy
|
- Add config option for requiring authentication to the `/publicRooms` endpoint (room directory) with a default enabled for privacy
|
||||||
- Add config option for federating `/publicRooms` endpoint (room directory) to other servers with a default disabled for privacy
|
- Add config option for federating `/publicRooms` endpoint (room directory) to other servers with a default disabled for privacy
|
||||||
@@ -89,8 +91,8 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
|||||||
- Media repository on handling `Content-Disposition` and `Content-Type` is fully spec compliant and secured
|
- Media repository on handling `Content-Disposition` and `Content-Type` is fully spec compliant and secured
|
||||||
- Send secure default HTTP headers such as a strong restrictive CSP (see MSC4149), deny iframes, disable `X-XSS-Protection`, disable interest cohort in `Permission-Policy`, etc to mitigate any potential attack surface such as from untrusted media
|
- Send secure default HTTP headers such as a strong restrictive CSP (see MSC4149), deny iframes, disable `X-XSS-Protection`, disable interest cohort in `Permission-Policy`, etc to mitigate any potential attack surface such as from untrusted media
|
||||||
|
|
||||||
|
## Administration/Logging
|
||||||
|
|
||||||
## Administration/Logging:
|
|
||||||
- Commandline argument to specify the path to a config file instead of relying on `CONDUIT_CONFIG`
|
- Commandline argument to specify the path to a config file instead of relying on `CONDUIT_CONFIG`
|
||||||
- Revamped admin room infrastructure and commands
|
- Revamped admin room infrastructure and commands
|
||||||
- Substantially clean up, improve, and fix logging (less noisy dead server logging, registration attempts, more useful troubleshooting logging, proper error propagation, etc)
|
- Substantially clean up, improve, and fix logging (less noisy dead server logging, registration attempts, more useful troubleshooting logging, proper error propagation, etc)
|
||||||
@@ -108,8 +110,8 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
|||||||
- Log the client IP on various requests such as registrations, banned room join attempts, logins, deactivations, federation transactions, etc
|
- Log the client IP on various requests such as registrations, banned room join attempts, logins, deactivations, federation transactions, etc
|
||||||
- Fix Conduit dropping some remote server federation response errors
|
- Fix Conduit dropping some remote server federation response errors
|
||||||
|
|
||||||
|
## Maintenance/Stability
|
||||||
|
|
||||||
## Maintenance/Stability:
|
|
||||||
- GitLab CI ported to GitHub Actions
|
- GitLab CI ported to GitHub Actions
|
||||||
- Add support for the Matrix spec compliance test suite [Complement](https://github.com/matrix-org/complement/) via the Nix flake and various other fixes for it
|
- Add support for the Matrix spec compliance test suite [Complement](https://github.com/matrix-org/complement/) via the Nix flake and various other fixes for it
|
||||||
- Implement running and diff'ing Complement results in CI and error if any mismatch occurs to prevent large cases of conduwuit regressions
|
- Implement running and diff'ing Complement results in CI and error if any mismatch occurs to prevent large cases of conduwuit regressions
|
||||||
@@ -119,19 +121,21 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
|||||||
- Fixed every single clippy (default lints) and rustc warnings, including some that were performance related or potential safety issues / unsoundness
|
- Fixed every single clippy (default lints) and rustc warnings, including some that were performance related or potential safety issues / unsoundness
|
||||||
- Add a **lot** of other clippy and rustc lints and a rustfmt.toml file
|
- Add a **lot** of other clippy and rustc lints and a rustfmt.toml file
|
||||||
- Repo uses [Renovate](https://docs.renovatebot.com/), [Trivy](https://github.com/aquasecurity/trivy-action), and keeps ALL dependencies as up to date as possible
|
- Repo uses [Renovate](https://docs.renovatebot.com/), [Trivy](https://github.com/aquasecurity/trivy-action), and keeps ALL dependencies as up to date as possible
|
||||||
- Attempts and interest in removing extreme and unnecessary panics/unwraps/expects that can lead to denial of service or such (upstream and upstream contributors want this unusual behaviour for some reason)
|
|
||||||
- Purge unmaintained/irrelevant/broken database backends (heed, sled, persy) and other unnecessary code or overhead
|
- Purge unmaintained/irrelevant/broken database backends (heed, sled, persy) and other unnecessary code or overhead
|
||||||
- webp support for images
|
- webp support for images
|
||||||
- Add cargo audit support to CI
|
- Add cargo audit support to CI
|
||||||
|
- Add documentation lints via lychee and markdownlint-cli to CI
|
||||||
- CI tests for all sorts of feature matrixes (jemalloc, non-defaullt, all features, etc)
|
- CI tests for all sorts of feature matrixes (jemalloc, non-defaullt, all features, etc)
|
||||||
- Add static and dynamic linking smoke tests in CI to prevent any potential linking regressions for Complement, static binaries, Nix devshells, etc
|
- Add static and dynamic linking smoke tests in CI to prevent any potential linking regressions for Complement, static binaries, Nix devshells, etc
|
||||||
- Add timestamp by commit date when building OCI images for keeping image build reproducibility and still have a meaningful "last modified date" for OCI image
|
- Add timestamp by commit date when building OCI images for keeping image build reproducibility and still have a meaningful "last modified date" for OCI image
|
||||||
- Add timestamp by commit date via `SOURCE_DATE_EPOCH` for Debian packages
|
- Add timestamp by commit date via `SOURCE_DATE_EPOCH` for Debian packages
|
||||||
- Startup check if conduwuit running in a container and is listening on 127.0.0.1 (generally containers are using NAT networking and 0.0.0.0 is the intended listening address)
|
- Startup check if conduwuit running in a container and is listening on 127.0.0.1 (generally containers are using NAT networking and 0.0.0.0 is the intended listening address)
|
||||||
- Add a panic catcher layer to return panic messages in HTTP responses if a panic occurs
|
- Add a panic catcher layer to return panic messages in HTTP responses if a panic occurs
|
||||||
|
- Add full compatibility support for SHA256 media file names instead of base64 file names to overcome filesystem file name length limitations (OS error file name too long) while still retaining upstream database compatibility
|
||||||
|
- Remove SQLite support due to being very poor performance, difficult to maintain against RocksDB, and is a blocker to significantly improved database code
|
||||||
|
|
||||||
|
## Admin Room
|
||||||
|
|
||||||
## Admin Room:
|
|
||||||
- Add support for a console CLI interface that can issue admin commands and output them in your terminal
|
- Add support for a console CLI interface that can issue admin commands and output them in your terminal
|
||||||
- Add support for an admin-user-only commandline admin room interface that can be issued in any room with the `\\!admin` or `\!admin` prefix and returns the response as yourself in the same room
|
- Add support for an admin-user-only commandline admin room interface that can be issued in any room with the `\\!admin` or `\!admin` prefix and returns the response as yourself in the same room
|
||||||
- Add admin commands for uptime, server startup, server shutdown, and server restart
|
- Add admin commands for uptime, server startup, server shutdown, and server restart
|
||||||
@@ -165,12 +169,11 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
|||||||
- Extend memory usage admin server command to support showing memory allocator stats such as jemalloc's
|
- Extend memory usage admin server command to support showing memory allocator stats such as jemalloc's
|
||||||
- Add admin debug command to see memory allocator's full extended debug statistics such as jemalloc's
|
- Add admin debug command to see memory allocator's full extended debug statistics such as jemalloc's
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
## Misc:
|
|
||||||
- Add guest support for accessing TURN servers via `turn_allow_guests` like Synapse
|
- Add guest support for accessing TURN servers via `turn_allow_guests` like Synapse
|
||||||
- Support for creating rooms with custom room IDs like Maunium Synapse (`room_id` request body field to `/createRoom`)
|
- Support for creating rooms with custom room IDs like Maunium Synapse (`room_id` request body field to `/createRoom`)
|
||||||
- Query parameter `?format=event|content` for returning either the room state event's content (default) for the full room state event on ` /_matrix/client/v3/rooms/{roomId}/state/{eventType}[/{stateKey}]` requests (see https://github.com/matrix-org/matrix-spec/issues/1047)
|
- Query parameter `?format=event|content` for returning either the room state event's content (default) for the full room state event on `/_matrix/client/v3/rooms/{roomId}/state/{eventType}[/{stateKey}]` requests (see <https://github.com/matrix-org/matrix-spec/issues/1047>)
|
||||||
- Add **optional** feature flag to use SHA256 key names for media instead of base64 to overcome filesystem file name length limitations (OS error file name too long)
|
|
||||||
- Send a User-Agent on all of our requests
|
- Send a User-Agent on all of our requests
|
||||||
- Send `avatar_url` on invite room membership events/changes
|
- Send `avatar_url` on invite room membership events/changes
|
||||||
- Support sending [`well_known` response to client login responses](https://spec.matrix.org/v1.10/client-server-api/#post_matrixclientv3login) if using config option `[well_known.client]`
|
- Support sending [`well_known` response to client login responses](https://spec.matrix.org/v1.10/client-server-api/#post_matrixclientv3login) if using config option `[well_known.client]`
|
||||||
@@ -179,6 +182,7 @@ Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
|||||||
- Implement legacy Matrix `/v1/` media endpoints that some clients and servers may still call
|
- Implement legacy Matrix `/v1/` media endpoints that some clients and servers may still call
|
||||||
- Config option to change Conduit's behaviour of homeserver key fetching (`query_trusted_key_servers_first`). This option sets whether conduwuit will query trusted notary key servers first before the individual homeserver(s), or vice versa which may help in joining certain rooms.
|
- Config option to change Conduit's behaviour of homeserver key fetching (`query_trusted_key_servers_first`). This option sets whether conduwuit will query trusted notary key servers first before the individual homeserver(s), or vice versa which may help in joining certain rooms.
|
||||||
- Implement unstable MSC2666 support for querying mutual rooms with a user
|
- Implement unstable MSC2666 support for querying mutual rooms with a user
|
||||||
|
- Implement unstable MSC3266 room summary API support
|
||||||
- Implement unstable MSC4125 support for specifying servers to join via on federated invites
|
- Implement unstable MSC4125 support for specifying servers to join via on federated invites
|
||||||
- Make conduwuit build and be functional under Nix + macOS
|
- Make conduwuit build and be functional under Nix + macOS
|
||||||
- Log out all sessions after unsetting the emergency password
|
- Log out all sessions after unsetting the emergency password
|
||||||
|
|||||||
+4
-1
@@ -5,6 +5,7 @@
|
|||||||
conduwuit has moderation through admin room commands. "binary commands" (medium priority) and an admin API (low priority) is planned. Some moderation-related config options are available in the example config such as "global ACLs" and blocking media requests to certain servers. See the example config for the moderation config options under the "Moderation / Privacy / Security" section.
|
conduwuit has moderation through admin room commands. "binary commands" (medium priority) and an admin API (low priority) is planned. Some moderation-related config options are available in the example config such as "global ACLs" and blocking media requests to certain servers. See the example config for the moderation config options under the "Moderation / Privacy / Security" section.
|
||||||
|
|
||||||
conduwuit has moderation admin commands for:
|
conduwuit has moderation admin commands for:
|
||||||
|
|
||||||
- managing room aliases (`!admin rooms alias`)
|
- managing room aliases (`!admin rooms alias`)
|
||||||
- managing room directory (`!admin rooms directory`)
|
- managing room directory (`!admin rooms directory`)
|
||||||
- managing room banning/blocking and user removal (`!admin rooms moderation`)
|
- managing room banning/blocking and user removal (`!admin rooms moderation`)
|
||||||
@@ -28,7 +29,7 @@ Any commands with `-list` in them will require a codeblock in the message with e
|
|||||||
|
|
||||||
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.
|
If using RocksDB, there's very little you need to do. Compaction is ran automatically based on various defined thresholds tuned for conduwuit to be high performance with the least I/O amplifcation or overhead. Manually running compaction is not recommended, or compaction via a timer. RocksDB is built with io_uring support via liburing for async read I/O.
|
||||||
|
|
||||||
Some RocksDB settings can be adjusted such as the compression method chosen. See the RocksDB section in the [example config](configuration.md). btrfs users may benefit from disabling compression on RocksDB if CoW is in use.
|
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.
|
||||||
|
|
||||||
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](troubleshooting.md).
|
RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting](troubleshooting.md).
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ RocksDB troubleshooting can be found [in the RocksDB section of troubleshooting]
|
|||||||
Currently only RocksDB supports online backups. If you'd like to backup your database online without any downtime, see the `!admin server` command for the backup commands and the `database_backup_path` config options in the example config. Please note that the format of the database backup is not the exact same. This is unfortunately a bad design choice by Facebook as we are using the database backup engine API from RocksDB, however the data is still there and can still be joined together.
|
Currently only RocksDB supports online backups. If you'd like to backup your database online without any downtime, see the `!admin server` command for the backup commands and the `database_backup_path` config options in the example config. Please note that the format of the database backup is not the exact same. This is unfortunately a bad design choice by Facebook as we are using the database backup engine API from RocksDB, however the data is still there and can still be joined together.
|
||||||
|
|
||||||
To restore a backup from an online RocksDB backup:
|
To restore a backup from an online RocksDB backup:
|
||||||
|
|
||||||
- shutdown conduwuit
|
- shutdown conduwuit
|
||||||
- create a new directory for merging together the data
|
- create a new directory for merging together the data
|
||||||
- in the online backup created, copy all `.sst` files in `$DATABASE_BACKUP_PATH/shared_checksum` to your new directory
|
- in the online backup created, copy all `.sst` files in `$DATABASE_BACKUP_PATH/shared_checksum` to your new directory
|
||||||
@@ -52,6 +54,7 @@ Backing up media is also just copying the `media/` directory from your database
|
|||||||
## Media
|
## Media
|
||||||
|
|
||||||
Media still needs various work, however conduwuit implements media deletion via:
|
Media still needs various work, however conduwuit implements media deletion via:
|
||||||
|
|
||||||
- MXC URI or Event ID (unencrypted and attempts to find the MXC URI in the event)
|
- MXC URI or Event ID (unencrypted and attempts to find the MXC URI in the event)
|
||||||
- Delete list of MXC URIs
|
- Delete list of MXC URIs
|
||||||
- Delete remote media in the past `N` seconds/minutes via filesystem metadata on the file created time (`btime`) or file modified time (`mtime`)
|
- Delete remote media in the past `N` seconds/minutes via filesystem metadata on the file created time (`btime`) or file modified time (`mtime`)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
#### Direct IO
|
#### Direct IO
|
||||||
|
|
||||||
Some filesystems may not like RocksDB using [Direct IO](https://github.com/facebook/rocksdb/wiki/Direct-IO). Direct IO is for non-buffered I/O which improves conduwuit performance, but at least FUSE is a filesystem potentially known to not like this. See the [example config](configuration.md) for disabling it if needed. Issues from Direct IO on unsupported filesystems are usually shown as startup errors.
|
Some filesystems may not like RocksDB using [Direct IO](https://github.com/facebook/rocksdb/wiki/Direct-IO). Direct IO is for non-buffered I/O which improves conduwuit performance, but at least FUSE is a filesystem potentially known to not like this. See the [example config](configuration/examples.md) for disabling it if needed. Issues from Direct IO on unsupported filesystems are usually shown as startup errors.
|
||||||
|
|
||||||
#### Database corruption
|
#### Database corruption
|
||||||
|
|
||||||
@@ -32,17 +32,14 @@ By default, conduwuit uses `TolerateCorruptedTailRecords` as generally these may
|
|||||||
`AbsoluteConsistency` will fail to start the database if any sign of corruption is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless it forbids the database from opening (e.g. too severe). Usage of `SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or leave your database in a permanently inconsistent state, but it may do something if `PointInTime` does not work as a last ditch effort.
|
`AbsoluteConsistency` will fail to start the database if any sign of corruption is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless it forbids the database from opening (e.g. too severe). Usage of `SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or leave your database in a permanently inconsistent state, but it may do something if `PointInTime` does not work as a last ditch effort.
|
||||||
|
|
||||||
With this in mind:
|
With this in mind:
|
||||||
- First start conduwuit with the `PointInTime` recovery method. See the [example config](configuration.md) for how to do this using `rocksdb_recovery_mode`
|
|
||||||
|
- First start conduwuit with the `PointInTime` recovery method. See the [example config](configuration/examples.md) for how to do this using `rocksdb_recovery_mode`
|
||||||
- If your database successfully opens, clients are recommended to clear their client cache to account for the rollback
|
- If your database successfully opens, clients are recommended to clear their client cache to account for the rollback
|
||||||
- Leave your conduwuit running in `PointInTime` for at least 30-60 minutes so as much possible corruption is restored
|
- Leave your conduwuit running in `PointInTime` for at least 30-60 minutes so as much possible corruption is restored
|
||||||
- If all goes will, you should be able to restore back to using `TolerateCorruptedTailRecords` and you have successfully recovered your database
|
- If all goes will, you should be able to restore back to using `TolerateCorruptedTailRecords` and you have successfully recovered your database
|
||||||
|
|
||||||
## Media
|
## Media
|
||||||
|
|
||||||
#### "File name too long"
|
|
||||||
|
|
||||||
If you are running into the "file name is too long" OS error for media requests, your filesystem cannot handle file name lengths >=255 characters. This is unfortuntely due to Conduit (upstream) using base64 for file name keys which is very problematic for some filesystems as the base64 input is untrusted and long file names or specific inputs can cause this. If you would like to avoid this, you may build conduwuit yourself with the `sha256_media` feature. **This will lose database compatibility with upstream**.
|
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
Note that users should not really be debugging things. If you find yourself debugging and find the issue, please let us know and/or how we can fix it. Various debug commands can be found in `!admin debug`.
|
Note that users should not really be debugging things. If you find yourself debugging and find the issue, please let us know and/or how we can fix it. Various debug commands can be found in `!admin debug`.
|
||||||
|
|||||||
+4
-1
@@ -11,12 +11,15 @@ use-auth-secret
|
|||||||
static-auth-secret=<a secret key>
|
static-auth-secret=<a secret key>
|
||||||
realm=<your server domain>
|
realm=<your server domain>
|
||||||
```
|
```
|
||||||
|
|
||||||
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
|
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
|
||||||
|
|
||||||
These same values need to be set in conduwuit. See the [example config](configuration.md) in the TURN section for configuring these and restart conduwuit after.
|
These same values need to be set in conduwuit. See the [example config](configuration/examples.md) in the TURN section for configuring these and restart conduwuit after.
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
|
||||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
||||||
```
|
```
|
||||||
|
|||||||
+33
-3
@@ -55,6 +55,16 @@ name = "lychee"
|
|||||||
group = "versions"
|
group = "versions"
|
||||||
script = "lychee --version"
|
script = "lychee --version"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "markdownlint"
|
||||||
|
group = "versions"
|
||||||
|
script = "markdownlint --version"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "dpkg"
|
||||||
|
group = "versions"
|
||||||
|
script = "dpkg --version"
|
||||||
|
|
||||||
[[task]]
|
[[task]]
|
||||||
name = "cargo-audit"
|
name = "cargo-audit"
|
||||||
group = "security"
|
group = "security"
|
||||||
@@ -63,7 +73,9 @@ script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked"
|
|||||||
[[task]]
|
[[task]]
|
||||||
name = "cargo-fmt"
|
name = "cargo-fmt"
|
||||||
group = "lints"
|
group = "lints"
|
||||||
script = "cargo fmt --check -- --color=always"
|
script = """
|
||||||
|
cargo fmt --check -- --color=always
|
||||||
|
"""
|
||||||
|
|
||||||
[[task]]
|
[[task]]
|
||||||
name = "cargo-doc"
|
name = "cargo-doc"
|
||||||
@@ -74,6 +86,7 @@ env DIRENV_DEVSHELL=all-features \
|
|||||||
direnv exec . \
|
direnv exec . \
|
||||||
cargo doc \
|
cargo doc \
|
||||||
--workspace \
|
--workspace \
|
||||||
|
--profile test \
|
||||||
--all-features \
|
--all-features \
|
||||||
--no-deps \
|
--no-deps \
|
||||||
--document-private-items \
|
--document-private-items \
|
||||||
@@ -86,6 +99,7 @@ group = "lints"
|
|||||||
script = """
|
script = """
|
||||||
cargo clippy \
|
cargo clippy \
|
||||||
--workspace \
|
--workspace \
|
||||||
|
--profile test \
|
||||||
--all-targets \
|
--all-targets \
|
||||||
--color=always \
|
--color=always \
|
||||||
-- \
|
-- \
|
||||||
@@ -100,6 +114,7 @@ env DIRENV_DEVSHELL=all-features \
|
|||||||
direnv exec . \
|
direnv exec . \
|
||||||
cargo clippy \
|
cargo clippy \
|
||||||
--workspace \
|
--workspace \
|
||||||
|
--profile test \
|
||||||
--all-targets \
|
--all-targets \
|
||||||
--all-features \
|
--all-features \
|
||||||
--color=always \
|
--color=always \
|
||||||
@@ -113,6 +128,7 @@ group = "lints"
|
|||||||
script = """
|
script = """
|
||||||
cargo clippy \
|
cargo clippy \
|
||||||
--workspace \
|
--workspace \
|
||||||
|
--profile test \
|
||||||
--features jemalloc \
|
--features jemalloc \
|
||||||
--all-targets \
|
--all-targets \
|
||||||
--color=always \
|
--color=always \
|
||||||
@@ -136,7 +152,12 @@ cargo clippy \
|
|||||||
[[task]]
|
[[task]]
|
||||||
name = "lychee"
|
name = "lychee"
|
||||||
group = "lints"
|
group = "lints"
|
||||||
script = "lychee --verbose --offline docs *.md"
|
script = "lychee --verbose --offline docs *.md --exclude development.md"
|
||||||
|
|
||||||
|
[[task]]
|
||||||
|
name = "markdownlint"
|
||||||
|
group = "lints"
|
||||||
|
script = "markdownlint docs *.md || true" # TODO: fix the ton of markdown lints so we can drop `|| true`
|
||||||
|
|
||||||
[[task]]
|
[[task]]
|
||||||
name = "cargo/all"
|
name = "cargo/all"
|
||||||
@@ -146,6 +167,7 @@ env DIRENV_DEVSHELL=all-features \
|
|||||||
direnv exec . \
|
direnv exec . \
|
||||||
cargo test \
|
cargo test \
|
||||||
--workspace \
|
--workspace \
|
||||||
|
--profile test \
|
||||||
--all-targets \
|
--all-targets \
|
||||||
--all-features \
|
--all-features \
|
||||||
--color=always \
|
--color=always \
|
||||||
@@ -159,6 +181,7 @@ group = "tests"
|
|||||||
script = """
|
script = """
|
||||||
cargo test \
|
cargo test \
|
||||||
--workspace \
|
--workspace \
|
||||||
|
--profile test \
|
||||||
--all-targets \
|
--all-targets \
|
||||||
--color=always \
|
--color=always \
|
||||||
-- \
|
-- \
|
||||||
@@ -174,5 +197,12 @@ cargo test \
|
|||||||
name = "nix-default"
|
name = "nix-default"
|
||||||
group = "tests"
|
group = "tests"
|
||||||
script = """
|
script = """
|
||||||
nix run .#default -- --help
|
env DIRENV_DEVSHELL=dynamic \
|
||||||
|
CARGO_PROFILE="test" \
|
||||||
|
direnv exec . \
|
||||||
|
bin/nix-build-and-cache just .#default-test
|
||||||
|
env DIRENV_DEVSHELL=dynamic \
|
||||||
|
CARGO_PROFILE="test" \
|
||||||
|
direnv exec . \
|
||||||
|
nix run -L .#default-test -- --help && nix run -L .#default-test -- --version
|
||||||
"""
|
"""
|
||||||
|
|||||||
Generated
+163
-78
@@ -9,11 +9,11 @@
|
|||||||
"nixpkgs-stable": "nixpkgs-stable"
|
"nixpkgs-stable": "nixpkgs-stable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1711742460,
|
"lastModified": 1720542474,
|
||||||
"narHash": "sha256-0O4v6e4a1toxXZ2gf5INhg4WPE5C5T+SVvsBt+45Mcc=",
|
"narHash": "sha256-aKjJ/4l2I9+wNGTaOGRsuS3M1+IoTibqgEMPDikXm04=",
|
||||||
"owner": "zhaofengli",
|
"owner": "zhaofengli",
|
||||||
"repo": "attic",
|
"repo": "attic",
|
||||||
"rev": "4dbdbee45728d8ce5788db6461aaaa89d98081f0",
|
"rev": "6139576a3ce6bb992e0f6c3022528ec233e45f00",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -31,11 +31,11 @@
|
|||||||
"pre-commit-hooks": "pre-commit-hooks"
|
"pre-commit-hooks": "pre-commit-hooks"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1717420532,
|
"lastModified": 1719923519,
|
||||||
"narHash": "sha256-OCCmI69EMaA4BcxRKrXJsx5Ozua2f/PKEy4aJbE7ziM=",
|
"narHash": "sha256-7Rhljj2fsklFRsu+eq7N683Z9qukmreMEj5C1GqCrSA=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "cachix",
|
"repo": "cachix",
|
||||||
"rev": "5727f0676f08a4b41ed13d403ec64dcce989f6e5",
|
"rev": "4e9e71f78b9500fa6210cf1eaa4d75bdbab777c3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -45,14 +45,47 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cachix_2": {
|
||||||
|
"inputs": {
|
||||||
|
"devenv": "devenv_2",
|
||||||
|
"flake-compat": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
|
"flake-compat"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"pre-commit-hooks": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
|
"pre-commit-hooks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1712055811,
|
||||||
|
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "cachix",
|
||||||
|
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "cachix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"complement": {
|
"complement": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1715700731,
|
"lastModified": 1720637557,
|
||||||
"narHash": "sha256-cie+b5N/TQAFD8vF/XbqfyFJkFU0qUPDbtJQDm/TfQc=",
|
"narHash": "sha256-oZz6nCmFmdJZpC+K1iOG2KkzTI6rlAmndxANPDVU7X0=",
|
||||||
"owner": "matrix-org",
|
"owner": "matrix-org",
|
||||||
"repo": "complement",
|
"repo": "complement",
|
||||||
"rev": "8587fb3cbe746754b2c883ff6c818ca4d987d0a5",
|
"rev": "0d14432e010482ea9e13a6f7c47c1533c0c9d62f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -70,11 +103,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1702918879,
|
"lastModified": 1717025063,
|
||||||
"narHash": "sha256-tWJqzajIvYcaRWxn+cLUB9L9Pv4dQ3Bfit/YjU5ze3g=",
|
"narHash": "sha256-dIubLa56W9sNNz0e8jGxrX3CAkPXsq7snuFA/Ie6dn8=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
|
"rev": "480dff0be03dac0e51a8dfc26e882b0d123a450e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -90,11 +123,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716569590,
|
"lastModified": 1720546058,
|
||||||
"narHash": "sha256-5eDbq8TuXFGGO3mqJFzhUbt5zHVTf5zilQoyW5jnJwo=",
|
"narHash": "sha256-iU2yVaPIZm5vMGdlT0+57vdB/aPq/V5oZFBRwYw+HBM=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "109987da061a1bf452f435f1653c47511587d919",
|
"rev": "2d83156f23c43598cf44e152c33a59d3892f8b29",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -106,14 +139,49 @@
|
|||||||
},
|
},
|
||||||
"devenv": {
|
"devenv": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"cachix": "cachix_2",
|
||||||
"flake-compat": [
|
"flake-compat": [
|
||||||
"cachix",
|
"cachix",
|
||||||
"flake-compat"
|
"flake-compat"
|
||||||
],
|
],
|
||||||
|
"nix": "nix_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"cachix",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"pre-commit-hooks": [
|
||||||
|
"cachix",
|
||||||
|
"pre-commit-hooks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1719759336,
|
||||||
|
"narHash": "sha256-3a34VL/QnHprl5gMy9xlx6d8J+iNp+W88Ex8smkgH9M=",
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"rev": "bb32aa986f2f695385e54428d0eaf7d05b31466e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "cachix",
|
||||||
|
"repo": "devenv",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devenv_2": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
|
"cachix",
|
||||||
|
"flake-compat"
|
||||||
|
],
|
||||||
"nix": "nix",
|
"nix": "nix",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"poetry2nix": "poetry2nix",
|
"poetry2nix": "poetry2nix",
|
||||||
"pre-commit-hooks": [
|
"pre-commit-hooks": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
"cachix",
|
"cachix",
|
||||||
"pre-commit-hooks"
|
"pre-commit-hooks"
|
||||||
]
|
]
|
||||||
@@ -141,11 +209,11 @@
|
|||||||
"rust-analyzer-src": "rust-analyzer-src"
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716359173,
|
"lastModified": 1720852044,
|
||||||
"narHash": "sha256-pYcjP6Gy7i6jPWrjiWAVV0BCQp+DdmGaI/k65lBb/kM=",
|
"narHash": "sha256-3NBYz8VuXuKU+8ONd9NFafCNjPEGHIZQ2Mdoam1a4mY=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "fenix",
|
"repo": "fenix",
|
||||||
"rev": "b6fc5035b28e36a98370d0eac44f4ef3fd323df6",
|
"rev": "5087b12a595ee73131a944d922f24d81dae05725",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -281,24 +349,6 @@
|
|||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils_4": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems_3"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1710146030,
|
|
||||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"ref": "main",
|
"ref": "main",
|
||||||
@@ -331,11 +381,11 @@
|
|||||||
"liburing": {
|
"liburing": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716565485,
|
"lastModified": 1720798442,
|
||||||
"narHash": "sha256-4R19aJNQYs6vb0/Hz4bWT56YN1P1DkFL/sxdE4Yj0CE=",
|
"narHash": "sha256-gtPppAoksMLW4GuruQ36nf4EAqIA1Bs6V9Xcx8dBxrQ=",
|
||||||
"owner": "axboe",
|
"owner": "axboe",
|
||||||
"repo": "liburing",
|
"repo": "liburing",
|
||||||
"rev": "b90c0e670a93caabbebe2d9e24ff85cece4cfe0e",
|
"rev": "1d674f83b7d0f07553ac44d99a401b05853d9dbe",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -349,6 +399,8 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_2",
|
"flake-compat": "flake-compat_2",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
"cachix",
|
"cachix",
|
||||||
"devenv",
|
"devenv",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
@@ -356,11 +408,11 @@
|
|||||||
"nixpkgs-regression": "nixpkgs-regression"
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1708577783,
|
"lastModified": 1712911606,
|
||||||
"narHash": "sha256-92xq7eXlxIT5zFNccLpjiP7sdQqQI30Gyui2p/PfKZM=",
|
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||||
"owner": "domenkozar",
|
"owner": "domenkozar",
|
||||||
"repo": "nix",
|
"repo": "nix",
|
||||||
"rev": "ecd0af0c1f56de32cbad14daa1d82a132bf298f8",
|
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -389,6 +441,8 @@
|
|||||||
"nix-github-actions": {
|
"nix-github-actions": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
"cachix",
|
"cachix",
|
||||||
"devenv",
|
"devenv",
|
||||||
"poetry2nix",
|
"poetry2nix",
|
||||||
@@ -409,6 +463,35 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nix_2": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
|
"flake-compat"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1711401922,
|
"lastModified": 1711401922,
|
||||||
@@ -441,6 +524,22 @@
|
|||||||
"type": "github"
|
"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-stable": {
|
"nixpkgs-stable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1711460390,
|
"lastModified": 1711460390,
|
||||||
@@ -459,11 +558,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-stable_2": {
|
"nixpkgs-stable_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1710695816,
|
"lastModified": 1718811006,
|
||||||
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
|
"narHash": "sha256-0Y8IrGhRmBmT7HHXlxxepg2t8j1X90++qRN3lukGaIk=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "614b4613980a522ba49f0d194531beddbb7220d3",
|
"rev": "03d771e513ce90147b65fe922d87d3a0356fc125",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -491,11 +590,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1715534503,
|
"lastModified": 1719848872,
|
||||||
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
|
"narHash": "sha256-H3+EC5cYuq+gQW8y0lSrrDZfH71LB4DAf+TDFyvwCNA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
|
"rev": "00d80d13810dbfea8ab4ed1009b09100cca86ba8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -507,11 +606,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_4": {
|
"nixpkgs_4": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716330097,
|
"lastModified": 1720768451,
|
||||||
"narHash": "sha256-8BO3B7e3BiyIDsaKA0tY8O88rClYRTjvAp66y+VBUeU=",
|
"narHash": "sha256-EYekUHJE2gxeo2pM/zM9Wlqw1Uw2XTJXOSAO79ksc4Y=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5710852ba686cc1fd0d3b8e22b3117d43ba374c2",
|
"rev": "7e7c39ea35c5cdd002cd4588b03a3fb9ece6fad9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -526,6 +625,8 @@
|
|||||||
"flake-utils": "flake-utils_2",
|
"flake-utils": "flake-utils_2",
|
||||||
"nix-github-actions": "nix-github-actions",
|
"nix-github-actions": "nix-github-actions",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
"cachix",
|
||||||
|
"devenv",
|
||||||
"cachix",
|
"cachix",
|
||||||
"devenv",
|
"devenv",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
@@ -548,7 +649,6 @@
|
|||||||
"pre-commit-hooks": {
|
"pre-commit-hooks": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_4",
|
"flake-compat": "flake-compat_4",
|
||||||
"flake-utils": "flake-utils_3",
|
|
||||||
"gitignore": "gitignore",
|
"gitignore": "gitignore",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"cachix",
|
"cachix",
|
||||||
@@ -557,11 +657,11 @@
|
|||||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1715609711,
|
"lastModified": 1719259945,
|
||||||
"narHash": "sha256-/5u29K0c+4jyQ8x7dUIEUWlz2BoTSZWUP2quPwFCE7M=",
|
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "c182c876690380f8d3b9557c4609472ebfa1b141",
|
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -573,16 +673,16 @@
|
|||||||
"rocksdb": {
|
"rocksdb": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716773462,
|
"lastModified": 1720900786,
|
||||||
"narHash": "sha256-5kUH+XK+2lbFfUgbxuNy3YMLHbp6scfWPdtc8za1wDM=",
|
"narHash": "sha256-Vta9Um/RRuWwZ46BjXftV06iWLm/j/9MX39emXUvSAY=",
|
||||||
"owner": "girlbossceo",
|
"owner": "girlbossceo",
|
||||||
"repo": "rocksdb",
|
"repo": "rocksdb",
|
||||||
"rev": "c8a1450231e9c608edf535538dbe8ca1a8d2f3bc",
|
"rev": "911f4243e69c2e320a7a209bf1f5f3ff5f825495",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "girlbossceo",
|
"owner": "girlbossceo",
|
||||||
"ref": "v9.2.1",
|
"ref": "v9.4.0",
|
||||||
"repo": "rocksdb",
|
"repo": "rocksdb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -595,7 +695,7 @@
|
|||||||
"crane": "crane_2",
|
"crane": "crane_2",
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"flake-compat": "flake-compat_5",
|
"flake-compat": "flake-compat_5",
|
||||||
"flake-utils": "flake-utils_4",
|
"flake-utils": "flake-utils_3",
|
||||||
"liburing": "liburing",
|
"liburing": "liburing",
|
||||||
"nix-filter": "nix-filter",
|
"nix-filter": "nix-filter",
|
||||||
"nixpkgs": "nixpkgs_4",
|
"nixpkgs": "nixpkgs_4",
|
||||||
@@ -605,11 +705,11 @@
|
|||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1716107283,
|
"lastModified": 1720717809,
|
||||||
"narHash": "sha256-NJgrwLiLGHDrCia5AeIvZUHUY7xYGVryee0/9D3Ir1I=",
|
"narHash": "sha256-6I+fm+nTLF/iaj7ffiFGlSY7POmubwUaPA/Wq0Bm53M=",
|
||||||
"owner": "rust-lang",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-analyzer",
|
||||||
"rev": "21ec8f523812b88418b2bfc64240c62b3dd967bd",
|
"rev": "ffbc5ad993d5cd2f3b8bcf9a511165470944ab91",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -648,21 +748,6 @@
|
|||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
flake-utils.url = "github:numtide/flake-utils?ref=main";
|
flake-utils.url = "github:numtide/flake-utils?ref=main";
|
||||||
nix-filter.url = "github:numtide/nix-filter?ref=main";
|
nix-filter.url = "github:numtide/nix-filter?ref=main";
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
||||||
# https://github.com/girlbossceo/rocksdb/commit/db6df0b185774778457dabfcbd822cb81760cade
|
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.4.0"; flake = false; };
|
||||||
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.2.1"; flake = false; };
|
|
||||||
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
|
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@
|
|||||||
file = ./rust-toolchain.toml;
|
file = ./rust-toolchain.toml;
|
||||||
|
|
||||||
# See also `rust-toolchain.toml`
|
# See also `rust-toolchain.toml`
|
||||||
sha256 = "sha256-+syqAd2kX8KVa8/U2gz3blIQTTsYYt3U63xBWaGOSc8";
|
sha256 = "sha256-6eN/GKzjVSjEhGO9FhWObkRFaE1Jf+uqMSdQnb8lcB4=";
|
||||||
};
|
};
|
||||||
|
|
||||||
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
||||||
@@ -42,6 +41,37 @@
|
|||||||
"v"
|
"v"
|
||||||
(builtins.fromJSON (builtins.readFile ./flake.lock))
|
(builtins.fromJSON (builtins.readFile ./flake.lock))
|
||||||
.nodes.rocksdb.original.ref;
|
.nodes.rocksdb.original.ref;
|
||||||
|
# we have this already at https://github.com/girlbossceo/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
|
||||||
|
# unsetting this so i don't have to revert it and make this nix exclusive
|
||||||
|
patches = [];
|
||||||
|
cmakeFlags = pkgs.lib.subtractLists
|
||||||
|
[
|
||||||
|
# no real reason to have snappy, no one uses this
|
||||||
|
"-DWITH_SNAPPY=1"
|
||||||
|
# we dont need to use ldb or sst_dump (core_tools)
|
||||||
|
"-DWITH_CORE_TOOLS=1"
|
||||||
|
# we dont need to build rocksdb tests
|
||||||
|
"-DWITH_TESTS=1"
|
||||||
|
# we use rust-rocksdb via C interface and dont need C++ RTTI
|
||||||
|
"-DUSE_RTTI=1"
|
||||||
|
]
|
||||||
|
old.cmakeFlags
|
||||||
|
++ [
|
||||||
|
# we dont need to use ldb or sst_dump (core_tools)
|
||||||
|
"-DWITH_CORE_TOOLS=0"
|
||||||
|
# we dont need trace tools
|
||||||
|
"-DWITH_TRACE_TOOLS=0"
|
||||||
|
# we dont need to build rocksdb tests
|
||||||
|
"-DWITH_TESTS=0"
|
||||||
|
# we use rust-rocksdb via C interface and dont need C++ RTTI
|
||||||
|
"-DUSE_RTTI=0"
|
||||||
|
];
|
||||||
|
|
||||||
|
# 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 = "";
|
||||||
});
|
});
|
||||||
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
|
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
|
||||||
liburing = pkgs.liburing.overrideAttrs (old: {
|
liburing = pkgs.liburing.overrideAttrs (old: {
|
||||||
@@ -50,16 +80,6 @@
|
|||||||
configureFlags = pkgs.lib.subtractLists
|
configureFlags = pkgs.lib.subtractLists
|
||||||
[ "--enable-static" "--disable-shared" ]
|
[ "--enable-static" "--disable-shared" ]
|
||||||
old.configureFlags;
|
old.configureFlags;
|
||||||
|
|
||||||
postInstall = old.postInstall + ''
|
|
||||||
# we remove the extra outputs
|
|
||||||
#
|
|
||||||
# we need to do this to prevent rocksdb from trying to link the
|
|
||||||
# static library in a dynamic stdenv
|
|
||||||
rm $out/lib/liburing*${
|
|
||||||
if pkgs.stdenv.hostPlatform.isStatic then ".so*" else ".a"
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -99,6 +119,9 @@
|
|||||||
# Needed for producing Debian packages
|
# Needed for producing Debian packages
|
||||||
cargo-deb
|
cargo-deb
|
||||||
|
|
||||||
|
# Needed for CI to check validity of produced Debian packages (dpkg-deb)
|
||||||
|
dpkg
|
||||||
|
|
||||||
# Needed for Complement
|
# Needed for Complement
|
||||||
go
|
go
|
||||||
|
|
||||||
@@ -108,8 +131,13 @@
|
|||||||
# Needed for finding broken markdown links
|
# Needed for finding broken markdown links
|
||||||
lychee
|
lychee
|
||||||
|
|
||||||
|
# Needed for linting markdown files
|
||||||
|
markdownlint-cli
|
||||||
|
|
||||||
# Useful for editing the book locally
|
# Useful for editing the book locally
|
||||||
mdbook
|
mdbook
|
||||||
|
|
||||||
|
sccache
|
||||||
])
|
])
|
||||||
++ scope.main.buildInputs
|
++ scope.main.buildInputs
|
||||||
++ scope.main.propagatedBuildInputs
|
++ scope.main.propagatedBuildInputs
|
||||||
@@ -121,9 +149,64 @@
|
|||||||
{
|
{
|
||||||
packages = {
|
packages = {
|
||||||
default = scopeHost.main;
|
default = scopeHost.main;
|
||||||
|
default-debug = scopeHost.main.override {
|
||||||
|
profile = "dev";
|
||||||
|
# debug build users expect full logs
|
||||||
|
disable_release_max_log_level = true;
|
||||||
|
};
|
||||||
|
default-test = scopeHost.main.override {
|
||||||
|
profile = "test";
|
||||||
|
disable_release_max_log_level = true;
|
||||||
|
};
|
||||||
|
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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
all-features-debug = scopeHost.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"
|
||||||
|
];
|
||||||
|
};
|
||||||
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
|
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
|
||||||
|
|
||||||
oci-image = scopeHost.oci-image;
|
oci-image = scopeHost.oci-image;
|
||||||
|
oci-image-all-features = scopeHost.oci-image.override {
|
||||||
|
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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
oci-image-all-features-debug = scopeHost.oci-image.override {
|
||||||
|
main = scopeHost.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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
oci-image-hmalloc = scopeHost.oci-image.override {
|
oci-image-hmalloc = scopeHost.oci-image.override {
|
||||||
main = scopeHost.main.override {
|
main = scopeHost.main.override {
|
||||||
features = ["hardened_malloc"];
|
features = ["hardened_malloc"];
|
||||||
@@ -158,6 +241,57 @@
|
|||||||
value = scopeCrossStatic.main;
|
value = scopeCrossStatic.main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# An output for a statically-linked unstripped debug ("dev") binary
|
||||||
|
{
|
||||||
|
name = "${binaryName}-debug";
|
||||||
|
value = scopeCrossStatic.main.override {
|
||||||
|
profile = "dev";
|
||||||
|
# debug build users expect full logs
|
||||||
|
disable_release_max_log_level = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# An output for a statically-linked unstripped debug binary with the
|
||||||
|
# "test" profile (for CI usage only)
|
||||||
|
{
|
||||||
|
name = "${binaryName}-test";
|
||||||
|
value = scopeCrossStatic.main.override {
|
||||||
|
profile = "test";
|
||||||
|
disable_release_max_log_level = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# An output for a statically-linked binary with `--all-features`
|
||||||
|
{
|
||||||
|
name = "${binaryName}-all-features";
|
||||||
|
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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# An output for a statically-linked unstripped debug ("dev") binary with `--all-features`
|
||||||
|
{
|
||||||
|
name = "${binaryName}-all-features-debug";
|
||||||
|
value = 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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
# An output for a statically-linked binary with hardened_malloc
|
# An output for a statically-linked binary with hardened_malloc
|
||||||
{
|
{
|
||||||
name = "${binaryName}-hmalloc";
|
name = "${binaryName}-hmalloc";
|
||||||
@@ -172,6 +306,53 @@
|
|||||||
value = scopeCrossStatic.oci-image;
|
value = scopeCrossStatic.oci-image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# An output for an OCI image based on that unstripped debug ("dev") binary
|
||||||
|
{
|
||||||
|
name = "oci-image-${crossSystem}-debug";
|
||||||
|
value = scopeCrossStatic.oci-image.override {
|
||||||
|
main = scopeCrossStatic.main.override {
|
||||||
|
profile = "dev";
|
||||||
|
# debug build users expect full logs
|
||||||
|
disable_release_max_log_level = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# An output for an OCI image based on that binary with `--all-features`
|
||||||
|
{
|
||||||
|
name = "oci-image-${crossSystem}-all-features";
|
||||||
|
value = scopeCrossStatic.oci-image.override {
|
||||||
|
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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
# An output for an OCI image based on that binary with hardened_malloc
|
# An output for an OCI image based on that binary with hardened_malloc
|
||||||
{
|
{
|
||||||
name = "oci-image-${crossSystem}-hmalloc";
|
name = "oci-image-${crossSystem}-hmalloc";
|
||||||
@@ -193,7 +374,15 @@
|
|||||||
devShells.default = mkDevShell scopeHostStatic;
|
devShells.default = mkDevShell scopeHostStatic;
|
||||||
devShells.all-features = mkDevShell
|
devShells.all-features = mkDevShell
|
||||||
(scopeHostStatic.overrideScope (final: prev: {
|
(scopeHostStatic.overrideScope (final: prev: {
|
||||||
main = prev.main.override { all_features = true; };
|
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"
|
||||||
|
];
|
||||||
|
};
|
||||||
}));
|
}));
|
||||||
devShells.no-features = mkDevShell
|
devShells.no-features = mkDevShell
|
||||||
(scopeHostStatic.overrideScope (final: prev: {
|
(scopeHostStatic.overrideScope (final: prev: {
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ allow_guest_registration = true
|
|||||||
allow_public_room_directory_over_federation = true
|
allow_public_room_directory_over_federation = true
|
||||||
allow_public_room_directory_without_auth = true
|
allow_public_room_directory_without_auth = true
|
||||||
allow_registration = true
|
allow_registration = true
|
||||||
allow_unstable_room_versions = true
|
|
||||||
database_backend = "rocksdb"
|
|
||||||
database_path = "/database"
|
database_path = "/database"
|
||||||
log = "trace"
|
log = "trace,h2=warn,hyper=warn"
|
||||||
port = [8008, 8448]
|
port = [8008, 8448]
|
||||||
trusted_servers = []
|
trusted_servers = []
|
||||||
|
query_trusted_key_servers_first = false
|
||||||
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
|
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
|
||||||
|
ip_range_denylist = []
|
||||||
|
url_preview_domain_contains_allowlist = ["*"]
|
||||||
|
media_compat_file_link = false
|
||||||
|
media_startup_check = false
|
||||||
|
rocksdb_direct_io = false
|
||||||
|
|
||||||
[global.tls]
|
[global.tls]
|
||||||
certs = "/certificate.crt"
|
certs = "/certificate.crt"
|
||||||
|
|||||||
@@ -14,8 +14,15 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
main' = main.override {
|
main' = main.override {
|
||||||
profile = "dev";
|
profile = "test";
|
||||||
features = ["axum_dual_protocol"];
|
all_features = true;
|
||||||
|
disable_release_max_log_level = true;
|
||||||
|
disable_features = [
|
||||||
|
# this is non-functional on nix for some reason
|
||||||
|
"hardened_malloc"
|
||||||
|
# dont include experimental features
|
||||||
|
"experimental"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
start = writeShellScriptBin "start" ''
|
start = writeShellScriptBin "start" ''
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
|||||||
lib.concatStringsSep
|
lib.concatStringsSep
|
||||||
" "
|
" "
|
||||||
([]
|
([]
|
||||||
|
++ lib.optionals
|
||||||
|
stdenv.targetPlatform.isx86_64
|
||||||
|
[ "-C" "target-cpu=x86-64-v2" ]
|
||||||
|
++ lib.optionals
|
||||||
|
stdenv.targetPlatform.isAarch64
|
||||||
|
[ "-C" "target-cpu=cortex-a55" ] # cortex-a55 == ARMv8.2-a
|
||||||
# This disables PIE for static builds, which isn't great in terms
|
# This disables PIE for static builds, which isn't great in terms
|
||||||
# of security. Unfortunately, my hand is forced because nixpkgs'
|
# of security. Unfortunately, my hand is forced because nixpkgs'
|
||||||
# `libstdc++.a` is built without `-fPIE`, which precludes us from
|
# `libstdc++.a` is built without `-fPIE`, which precludes us from
|
||||||
|
|||||||
+38
-10
@@ -25,11 +25,7 @@ let
|
|||||||
# on the nix side depend on feature values.
|
# on the nix side depend on feature values.
|
||||||
crateFeatures = path:
|
crateFeatures = path:
|
||||||
let manifest = lib.importTOML "${path}/Cargo.toml"; in
|
let manifest = lib.importTOML "${path}/Cargo.toml"; in
|
||||||
lib.remove "default" (lib.attrNames manifest.features) ++
|
lib.remove "default" (lib.attrNames manifest.features);
|
||||||
lib.attrNames
|
|
||||||
(lib.filterAttrs
|
|
||||||
(_: dependency: dependency.optional or false)
|
|
||||||
manifest.dependencies);
|
|
||||||
crateDefaultFeatures = path:
|
crateDefaultFeatures = path:
|
||||||
(lib.importTOML "${path}/Cargo.toml").features.default;
|
(lib.importTOML "${path}/Cargo.toml").features.default;
|
||||||
allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main";
|
allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main";
|
||||||
@@ -43,7 +39,7 @@ features'' = lib.subtractLists disable_features' features';
|
|||||||
|
|
||||||
featureEnabled = feature : builtins.elem feature features'';
|
featureEnabled = feature : builtins.elem feature features'';
|
||||||
|
|
||||||
enableLiburing = featureEnabled "io_uring" && stdenv.isLinux;
|
enableLiburing = featureEnabled "io_uring" && !stdenv.isDarwin;
|
||||||
|
|
||||||
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
|
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
|
||||||
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
|
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
|
||||||
@@ -55,6 +51,10 @@ rust-jemalloc-sys' = (rust-jemalloc-sys.override {
|
|||||||
unprefixed = true;
|
unprefixed = true;
|
||||||
}).overrideAttrs (old: {
|
}).overrideAttrs (old: {
|
||||||
configureFlags = old.configureFlags ++
|
configureFlags = old.configureFlags ++
|
||||||
|
# we dont need docs
|
||||||
|
[ "--disable-doc" ] ++
|
||||||
|
# we dont need cxx/C++ integration
|
||||||
|
[ "--disable-cxx" ] ++
|
||||||
# tikv-jemalloc-sys/profiling feature
|
# tikv-jemalloc-sys/profiling feature
|
||||||
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof";
|
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof";
|
||||||
});
|
});
|
||||||
@@ -70,12 +70,34 @@ buildDepsOnlyEnv =
|
|||||||
#
|
#
|
||||||
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
|
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
|
||||||
enableJemalloc = featureEnabled "jemalloc" && !stdenv.isDarwin;
|
enableJemalloc = featureEnabled "jemalloc" && !stdenv.isDarwin;
|
||||||
|
|
||||||
|
# for some reason enableLiburing in nixpkgs rocksdb is default true
|
||||||
|
# which breaks Darwin entirely
|
||||||
|
enableLiburing = enableLiburing;
|
||||||
}).overrideAttrs (old: {
|
}).overrideAttrs (old: {
|
||||||
# TODO: static rocksdb fails to build on darwin
|
# TODO: static rocksdb fails to build on darwin, also see <https://github.com/NixOS/nixpkgs/issues/320448>
|
||||||
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
|
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
|
||||||
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
|
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
|
||||||
# TODO: switch to enableUring option once https://github.com/NixOS/nixpkgs/pull/314945 is available
|
|
||||||
buildInputs = old.buildInputs ++ lib.optional enableLiburing liburing;
|
enableLiburing = enableLiburing;
|
||||||
|
|
||||||
|
sse42Support = stdenv.targetPlatform.isx86_64;
|
||||||
|
|
||||||
|
cmakeFlags = if stdenv.targetPlatform.isx86_64
|
||||||
|
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
|
||||||
|
++ lib.optionals stdenv.targetPlatform.isx86_64 [
|
||||||
|
"-DPORTABLE=x86-64-v2"
|
||||||
|
"-DUSE_SSE=1"
|
||||||
|
"-DHAVE_SSE=1"
|
||||||
|
"-DHAVE_SSE42=1"
|
||||||
|
]
|
||||||
|
else if stdenv.targetPlatform.isAarch64
|
||||||
|
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
|
||||||
|
++ lib.optionals stdenv.targetPlatform.isAarch64 [
|
||||||
|
# cortex-a55 == ARMv8.2-a
|
||||||
|
"-DPORTABLE=armv8.2-a"
|
||||||
|
]
|
||||||
|
else old.cmakeFlags;
|
||||||
});
|
});
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -102,7 +124,11 @@ buildPackageEnv = {
|
|||||||
# Only needed in static stdenv because these are transitive dependencies of rocksdb
|
# Only needed in static stdenv because these are transitive dependencies of rocksdb
|
||||||
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
|
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
|
||||||
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
|
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
|
||||||
" -L${lib.getLib liburing}/lib -luring";
|
" -L${lib.getLib liburing}/lib -luring"
|
||||||
|
+ lib.optionalString stdenv.targetPlatform.isx86_64
|
||||||
|
" -Ctarget-cpu=x86-64-v2"
|
||||||
|
+ lib.optionalString stdenv.targetPlatform.isAarch64
|
||||||
|
" -Ctarget-cpu=cortex-a55"; # cortex-a55 == ARMv8.2-a
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -127,6 +153,8 @@ commonAttrs = {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
dontStrip = profile == "dev" || profile == "test";
|
||||||
|
|
||||||
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
|
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
# If you're having trouble making the relevant changes, bug a maintainer.
|
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||||
|
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.77.0"
|
channel = "1.80.0"
|
||||||
components = [
|
components = [
|
||||||
# For rust-analyzer
|
# For rust-analyzer
|
||||||
"rust-src",
|
"rust-src",
|
||||||
|
|||||||
+3
-19
@@ -17,41 +17,25 @@ crate-type = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dev_release_log_level = []
|
#dev_release_log_level = []
|
||||||
release_max_log_level = [
|
release_max_log_level = [
|
||||||
"tracing/max_level_trace",
|
"tracing/max_level_trace",
|
||||||
"tracing/release_max_level_info",
|
"tracing/release_max_level_info",
|
||||||
"log/max_level_trace",
|
"log/max_level_trace",
|
||||||
"log/release_max_level_info",
|
"log/release_max_level_info",
|
||||||
]
|
]
|
||||||
rocksdb = [
|
|
||||||
"dep:rust-rocksdb",
|
|
||||||
]
|
|
||||||
jemalloc = [
|
|
||||||
"rust-rocksdb/jemalloc",
|
|
||||||
]
|
|
||||||
io_uring = [
|
|
||||||
"rust-rocksdb/io-uring",
|
|
||||||
]
|
|
||||||
zstd_compression = [
|
|
||||||
"rust-rocksdb/zstd",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
conduit-api.workspace = true
|
conduit-api.workspace = true
|
||||||
conduit-core.workspace = true
|
conduit-core.workspace = true
|
||||||
conduit-database.workspace = true
|
conduit-macros.workspace = true
|
||||||
conduit-service.workspace = true
|
conduit-service.workspace = true
|
||||||
|
const-str.workspace = true
|
||||||
futures-util.workspace = true
|
futures-util.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
loole.workspace = true
|
|
||||||
regex.workspace = true
|
|
||||||
ruma.workspace = true
|
ruma.workspace = true
|
||||||
rust-rocksdb.optional = true
|
|
||||||
rust-rocksdb.workspace = true
|
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde.workspace = true
|
|
||||||
serde_yaml.workspace = true
|
serde_yaml.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
|
||||||
|
pub(super) enum AdminCommand {
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for managing appservices
|
||||||
|
Appservices(AppserviceCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for managing local users
|
||||||
|
Users(UserCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for managing rooms
|
||||||
|
Rooms(RoomCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for managing federation
|
||||||
|
Federation(FederationCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for managing the server
|
||||||
|
Server(ServerCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for managing media
|
||||||
|
Media(MediaCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for checking integrity
|
||||||
|
Check(CheckCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Commands for debugging things
|
||||||
|
Debug(DebugCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// - Low-level queries for database getters and iterators
|
||||||
|
Query(QueryCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, name = "command")]
|
||||||
|
pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
|
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?,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent};
|
use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent};
|
||||||
|
|
||||||
use crate::{services, Result};
|
use crate::{admin_command, Result};
|
||||||
|
|
||||||
pub(super) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
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() != "```"
|
||||||
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Expected code block in command body. Add --help for details.",
|
"Expected code block in command body. Add --help for details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n");
|
let appservice_config = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||||
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
|
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
|
||||||
match parsed_config {
|
match parsed_config {
|
||||||
Ok(yaml) => match services().appservice.register_appservice(yaml).await {
|
Ok(yaml) => match self.services.appservice.register_appservice(yaml).await {
|
||||||
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
|
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
"Appservice registered with ID: {id}."
|
"Appservice registered with ID: {id}."
|
||||||
))),
|
))),
|
||||||
@@ -26,8 +28,10 @@ pub(super) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn unregister(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
match services()
|
pub(super) async fn unregister(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
|
||||||
|
match self
|
||||||
|
.services
|
||||||
.appservice
|
.appservice
|
||||||
.unregister_appservice(&appservice_identifier)
|
.unregister_appservice(&appservice_identifier)
|
||||||
.await
|
.await
|
||||||
@@ -39,8 +43,10 @@ pub(super) async fn unregister(_body: Vec<&str>, appservice_identifier: String)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
match services()
|
pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
|
||||||
|
match self
|
||||||
|
.services
|
||||||
.appservice
|
.appservice
|
||||||
.get_registration(&appservice_identifier)
|
.get_registration(&appservice_identifier)
|
||||||
.await
|
.await
|
||||||
@@ -54,8 +60,9 @@ pub(super) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Res
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let appservices = services().appservice.iter_ids().await;
|
pub(super) async fn list_registered(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
let appservices = self.services.appservice.iter_ids().await;
|
||||||
let output = format!("Appservices ({}): {}", appservices.len(), appservices.join(", "));
|
let output = format!("Appservices ({}): {}", appservices.len(), appservices.join(", "));
|
||||||
Ok(RoomMessageEventContent::text_plain(output))
|
Ok(RoomMessageEventContent::text_plain(output))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ mod commands;
|
|||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
|
||||||
|
|
||||||
use self::commands::*;
|
use crate::admin_command_dispatch;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[derive(Debug, Subcommand)]
|
||||||
#[derive(Subcommand)]
|
#[admin_command_dispatch]
|
||||||
pub(super) enum AppserviceCommand {
|
pub(super) enum AppserviceCommand {
|
||||||
/// - Register an appservice using its registration YAML
|
/// - Register an appservice using its registration YAML
|
||||||
///
|
///
|
||||||
@@ -29,24 +28,13 @@ pub(super) enum AppserviceCommand {
|
|||||||
/// - Show an appservice's config using its ID
|
/// - Show an appservice's config using its ID
|
||||||
///
|
///
|
||||||
/// You can find the ID using the `list-appservices` command.
|
/// You can find the ID using the `list-appservices` command.
|
||||||
Show {
|
#[clap(alias("show"))]
|
||||||
|
ShowAppserviceConfig {
|
||||||
/// The appservice to show
|
/// The appservice to show
|
||||||
appservice_identifier: String,
|
appservice_identifier: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// - List all the currently registered appservices
|
/// - List all the currently registered appservices
|
||||||
List,
|
#[clap(alias("list"))]
|
||||||
}
|
ListRegistered,
|
||||||
|
|
||||||
pub(super) async fn process(command: AppserviceCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
AppserviceCommand::Register => register(body).await?,
|
|
||||||
AppserviceCommand::Unregister {
|
|
||||||
appservice_identifier,
|
|
||||||
} => unregister(body, appservice_identifier).await?,
|
|
||||||
AppserviceCommand::Show {
|
|
||||||
appservice_identifier,
|
|
||||||
} => show(body, appservice_identifier).await?,
|
|
||||||
AppserviceCommand::List => list(body).await?,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
|
use conduit_macros::implement;
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
use crate::services;
|
use crate::Command;
|
||||||
|
|
||||||
/// Uses the iterator in `src/database/key_value/users.rs` to iterator over
|
/// Uses the iterator in `src/database/key_value/users.rs` to iterator over
|
||||||
/// every user in our database (remote and local). Reports total count, any
|
/// every user in our database (remote and local). Reports total count, any
|
||||||
/// errors if there were any, etc
|
/// errors if there were any, etc
|
||||||
pub(super) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[implement(Command, params = "<'_>")]
|
||||||
|
pub(super) async fn check_all_users(&self) -> Result<RoomMessageEventContent> {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().users.db.iter();
|
let results = self.services.users.db.iter();
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
let users = results.collect::<Vec<_>>();
|
let users = results.collect::<Vec<_>>();
|
||||||
@@ -19,7 +21,7 @@ pub(super) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEvent
|
|||||||
|
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"Database query completed in {query_time:?}:\n\n```\nTotal entries: {total:?}\nFailure/Invalid user count: \
|
"Database query completed in {query_time:?}:\n\n```\nTotal entries: {total:?}\nFailure/Invalid user count: \
|
||||||
{err_count:?}\nSuccess/Valid user count: {ok_count:?}```"
|
{err_count:?}\nSuccess/Valid user count: {ok_count:?}\n```"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(message))
|
Ok(RoomMessageEventContent::notice_markdown(message))
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
mod commands;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
|
use crate::Command;
|
||||||
|
|
||||||
|
#[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?,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
use service::Services;
|
||||||
|
|
||||||
|
pub(crate) struct Command<'a> {
|
||||||
|
pub(crate) services: &'a Services,
|
||||||
|
pub(crate) body: &'a [&'a str],
|
||||||
|
}
|
||||||
+294
-117
@@ -1,33 +1,37 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
|
fmt::Write,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
time::Instant,
|
time::{Instant, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
use api::client::validate_and_add_event_id;
|
use api::client::validate_and_add_event_id;
|
||||||
use conduit::{
|
use conduit::{
|
||||||
debug, info, log,
|
debug, debug_error, err, info, log,
|
||||||
log::{capture, Capture},
|
log::{capture, Capture},
|
||||||
warn, Error, Result,
|
utils, warn, Error, PduEvent, Result,
|
||||||
};
|
};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{client::error::ErrorKind, federation::event::get_room_state},
|
api::{client::error::ErrorKind, federation::event::get_room_state},
|
||||||
events::room::message::RoomMessageEventContent,
|
events::room::message::RoomMessageEventContent,
|
||||||
CanonicalJsonObject, EventId, RoomId, RoomVersionId, ServerName,
|
CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName,
|
||||||
};
|
};
|
||||||
use service::{rooms::event_handler::parse_incoming_pdu, sending::resolve::resolve_actual_dest, services, PduEvent};
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
pub(super) async fn echo(_body: Vec<&str>, message: Vec<String>) -> Result<RoomMessageEventContent> {
|
use crate::admin_command;
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn echo(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
|
||||||
let message = message.join(" ");
|
let message = message.join(" ");
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_plain(message))
|
Ok(RoomMessageEventContent::notice_plain(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
|
pub(super) async fn get_auth_chain(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
||||||
let event_id = Arc::<EventId>::from(event_id);
|
let event_id = Arc::<EventId>::from(event_id);
|
||||||
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? {
|
if let Some(event) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
|
||||||
let room_id_str = event
|
let room_id_str = event
|
||||||
.get("room_id")
|
.get("room_id")
|
||||||
.and_then(|val| val.as_str())
|
.and_then(|val| val.as_str())
|
||||||
@@ -35,13 +39,16 @@ pub(super) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
|
|||||||
|
|
||||||
let room_id = <&RoomId>::try_from(room_id_str)
|
let room_id = <&RoomId>::try_from(room_id_str)
|
||||||
.map_err(|_| Error::bad_database("Invalid room id field in event in database"))?;
|
.map_err(|_| Error::bad_database("Invalid room id field in event in database"))?;
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let count = services()
|
let count = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.auth_chain
|
.auth_chain
|
||||||
.event_ids_iter(room_id, vec![event_id])
|
.event_ids_iter(room_id, vec![event_id])
|
||||||
.await?
|
.await?
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
let elapsed = start.elapsed();
|
let elapsed = start.elapsed();
|
||||||
Ok(RoomMessageEventContent::text_plain(format!(
|
Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
"Loaded auth chain with length {count} in {elapsed:?}"
|
"Loaded auth chain with length {count} in {elapsed:?}"
|
||||||
@@ -51,14 +58,16 @@ pub(super) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
pub(super) async fn parse_pdu(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||||
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Expected code block in command body. Add --help for details.",
|
"Expected code block in command body. Add --help for details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let string = body[1..body.len() - 1].join("\n");
|
let string = self.body[1..self.body.len().saturating_sub(1)].join("\n");
|
||||||
match serde_json::from_str(&string) {
|
match serde_json::from_str(&string) {
|
||||||
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||||
Ok(hash) => {
|
Ok(hash) => {
|
||||||
@@ -79,15 +88,17 @@ pub(super) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn get_pdu(_body: Vec<&str>, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
|
pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
||||||
let mut outlier = false;
|
let mut outlier = false;
|
||||||
let mut pdu_json = services()
|
let mut pdu_json = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.get_non_outlier_pdu_json(&event_id)?;
|
.get_non_outlier_pdu_json(&event_id)?;
|
||||||
if pdu_json.is_none() {
|
if pdu_json.is_none() {
|
||||||
outlier = true;
|
outlier = true;
|
||||||
pdu_json = services().rooms.timeline.get_pdu_json(&event_id)?;
|
pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id)?;
|
||||||
}
|
}
|
||||||
match pdu_json {
|
match pdu_json {
|
||||||
Some(json) => {
|
Some(json) => {
|
||||||
@@ -106,38 +117,42 @@ pub(super) async fn get_pdu(_body: Vec<&str>, event_id: Box<EventId>) -> Result<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
pub(super) async fn get_remote_pdu_list(
|
pub(super) async fn get_remote_pdu_list(
|
||||||
body: Vec<&str>, server: Box<ServerName>, force: bool,
|
&self, server: Box<ServerName>, force: bool,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
if !services().globals.config.allow_federation {
|
if !self.services.globals.config.allow_federation {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Federation is disabled on this homeserver.",
|
"Federation is disabled on this homeserver.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if server == services().globals.server_name() {
|
if server == self.services.globals.server_name() {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
|
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
|
||||||
the database.",
|
the database.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if body.len() < 2 || !body[0].trim().starts_with("```") || 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(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Expected code block in command body. Add --help for details.",
|
"Expected code block in command body. Add --help for details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let list = body
|
let list = self
|
||||||
.clone()
|
.body
|
||||||
.drain(1..body.len().checked_sub(1).unwrap())
|
.iter()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.drain(1..self.body.len().saturating_sub(1))
|
||||||
.filter_map(|pdu| EventId::parse(pdu).ok())
|
.filter_map(|pdu| EventId::parse(pdu).ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for pdu in list {
|
for pdu in list {
|
||||||
if force {
|
if force {
|
||||||
if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await {
|
if let Err(e) = self.get_remote_pdu(Box::from(pdu), server.clone()).await {
|
||||||
services()
|
self.services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||||
"Failed to get remote PDU, ignoring error: {e}"
|
"Failed to get remote PDU, ignoring error: {e}"
|
||||||
@@ -146,29 +161,31 @@ pub(super) async fn get_remote_pdu_list(
|
|||||||
warn!(%e, "Failed to get remote PDU, ignoring error");
|
warn!(%e, "Failed to get remote PDU, ignoring error");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?;
|
self.get_remote_pdu(Box::from(pdu), server.clone()).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."))
|
Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
pub(super) async fn get_remote_pdu(
|
pub(super) async fn get_remote_pdu(
|
||||||
_body: Vec<&str>, event_id: Box<EventId>, server: Box<ServerName>,
|
&self, event_id: Box<EventId>, server: Box<ServerName>,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
if !services().globals.config.allow_federation {
|
if !self.services.globals.config.allow_federation {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Federation is disabled on this homeserver.",
|
"Federation is disabled on this homeserver.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if server == services().globals.server_name() {
|
if server == self.services.globals.server_name() {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
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 services()
|
match self
|
||||||
|
.services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
&server,
|
&server,
|
||||||
@@ -189,7 +206,11 @@ pub(super) async fn get_remote_pdu(
|
|||||||
|
|
||||||
debug!("Attempting to parse PDU: {:?}", &response.pdu);
|
debug!("Attempting to parse PDU: {:?}", &response.pdu);
|
||||||
let parsed_pdu = {
|
let parsed_pdu = {
|
||||||
let parsed_result = parse_incoming_pdu(&response.pdu);
|
let parsed_result = self
|
||||||
|
.services
|
||||||
|
.rooms
|
||||||
|
.event_handler
|
||||||
|
.parse_incoming_pdu(&response.pdu);
|
||||||
let (event_id, value, room_id) = match parsed_result {
|
let (event_id, value, room_id) = match parsed_result {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -207,9 +228,8 @@ pub(super) async fn get_remote_pdu(
|
|||||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||||
|
|
||||||
debug!("Attempting to fetch homeserver signing keys for {server}");
|
debug!("Attempting to fetch homeserver signing keys for {server}");
|
||||||
services()
|
self.services
|
||||||
.rooms
|
.server_keys
|
||||||
.event_handler
|
|
||||||
.fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
|
.fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
@@ -217,7 +237,7 @@ pub(super) async fn get_remote_pdu(
|
|||||||
});
|
});
|
||||||
|
|
||||||
info!("Attempting to handle event ID {event_id} as backfilled PDU");
|
info!("Attempting to handle event ID {event_id} as backfilled PDU");
|
||||||
services()
|
self.services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.backfill_pdu(&server, response.pdu, &pub_key_map)
|
.backfill_pdu(&server, response.pdu, &pub_key_map)
|
||||||
@@ -236,8 +256,11 @@ pub(super) async fn get_remote_pdu(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn get_room_state(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let room_state = services()
|
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 = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_full(&room_id)
|
.room_state_full(&room_id)
|
||||||
@@ -252,21 +275,19 @@ pub(super) async fn get_room_state(_body: Vec<&str>, room_id: Box<RoomId>) -> Re
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let json_text = serde_json::to_string_pretty(&room_state).map_err(|e| {
|
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}");
|
warn!("Failed converting room state vector in our database to pretty JSON: {e}");
|
||||||
Error::bad_database(
|
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",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json}\n```")))
|
||||||
"{}\n```json\n{}\n```",
|
|
||||||
"Found full room state", json_text
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if server == services().globals.server_name() {
|
pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
|
||||||
|
if server == self.services.globals.server_name() {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Not allowed to send federation requests to ourselves.",
|
"Not allowed to send federation requests to ourselves.",
|
||||||
));
|
));
|
||||||
@@ -274,7 +295,8 @@ pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<Ro
|
|||||||
|
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
|
|
||||||
match services()
|
match self
|
||||||
|
.services
|
||||||
.sending
|
.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
|
.await
|
||||||
@@ -303,21 +325,23 @@ pub(super) async fn ping(_body: Vec<&str>, server: Box<ServerName>) -> Result<Ro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn force_device_list_updates(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
|
pub(super) async fn force_device_list_updates(&self) -> Result<RoomMessageEventContent> {
|
||||||
// Force E2EE device list updates for all users
|
// Force E2EE device list updates for all users
|
||||||
for user_id in services().users.iter().filter_map(Result::ok) {
|
for user_id in self.services.users.iter().filter_map(Result::ok) {
|
||||||
services().users.mark_device_key_update(&user_id)?;
|
self.services.users.mark_device_key_update(&user_id)?;
|
||||||
}
|
}
|
||||||
Ok(RoomMessageEventContent::text_plain(
|
Ok(RoomMessageEventContent::text_plain(
|
||||||
"Marked all devices for all users as having new keys to update",
|
"Marked all devices for all users as having new keys to update",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn change_log_level(
|
#[admin_command]
|
||||||
_body: Vec<&str>, filter: Option<String>, reset: bool,
|
pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result<RoomMessageEventContent> {
|
||||||
) -> Result<RoomMessageEventContent> {
|
let handles = &["console"];
|
||||||
|
|
||||||
if reset {
|
if reset {
|
||||||
let old_filter_layer = match EnvFilter::try_new(&services().globals.config.log) {
|
let old_filter_layer = match EnvFilter::try_new(&self.services.globals.config.log) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
@@ -326,11 +350,17 @@ pub(super) async fn change_log_level(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match services().server.log.reload.reload(&old_filter_layer) {
|
match self
|
||||||
|
.services
|
||||||
|
.server
|
||||||
|
.log
|
||||||
|
.reload
|
||||||
|
.reload(&old_filter_layer, Some(handles))
|
||||||
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
"Successfully changed log level back to config value {}",
|
"Successfully changed log level back to config value {}",
|
||||||
services().globals.config.log
|
self.services.globals.config.log
|
||||||
)));
|
)));
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -351,7 +381,13 @@ pub(super) async fn change_log_level(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match services().server.log.reload.reload(&new_filter_layer) {
|
match self
|
||||||
|
.services
|
||||||
|
.server
|
||||||
|
.log
|
||||||
|
.reload
|
||||||
|
.reload(&new_filter_layer, Some(handles))
|
||||||
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
return Ok(RoomMessageEventContent::text_plain("Successfully changed log level"));
|
return Ok(RoomMessageEventContent::text_plain("Successfully changed log level"));
|
||||||
},
|
},
|
||||||
@@ -366,19 +402,21 @@ pub(super) async fn change_log_level(
|
|||||||
Ok(RoomMessageEventContent::text_plain("No log level was specified."))
|
Ok(RoomMessageEventContent::text_plain("No log level was specified."))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
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() != "```"
|
||||||
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Expected code block in command body. Add --help for details.",
|
"Expected code block in command body. Add --help for details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
|
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||||
match serde_json::from_str(&string) {
|
match serde_json::from_str(&string) {
|
||||||
Ok(mut value) => {
|
Ok(mut value) => {
|
||||||
ruma::signatures::sign_json(
|
ruma::signatures::sign_json(
|
||||||
services().globals.server_name().as_str(),
|
self.services.globals.server_name().as_str(),
|
||||||
services().globals.keypair(),
|
self.services.globals.keypair(),
|
||||||
&mut value,
|
&mut value,
|
||||||
)
|
)
|
||||||
.expect("our request json is what ruma expects");
|
.expect("our request json is what ruma expects");
|
||||||
@@ -389,21 +427,22 @@ pub(super) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
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() != "```"
|
||||||
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Expected code block in command body. Add --help for details.",
|
"Expected code block in command body. Add --help for details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
|
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||||
match serde_json::from_str(&string) {
|
match serde_json::from_str(&string) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||||
|
|
||||||
services()
|
self.services
|
||||||
.rooms
|
.server_keys
|
||||||
.event_handler
|
|
||||||
.fetch_required_signing_keys([&value], &pub_key_map)
|
.fetch_required_signing_keys([&value], &pub_key_map)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -419,19 +458,22 @@ pub(super) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventConte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(_body))]
|
#[admin_command]
|
||||||
pub(super) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
#[tracing::instrument(skip(self))]
|
||||||
if !services()
|
pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||||
|
if !self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.server_in_room(&services().globals.config.server_name, &room_id)?
|
.server_in_room(&self.services.globals.config.server_name, &room_id)?
|
||||||
{
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"We are not participating in the room / we don't know about the room ID.",
|
"We are not participating in the room / we don't know about the room ID.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let first_pdu = services()
|
let first_pdu = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.first_pdu_in_room(&room_id)?
|
.first_pdu_in_room(&room_id)?
|
||||||
@@ -440,19 +482,22 @@ pub(super) async fn first_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) ->
|
|||||||
Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}")))
|
Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(_body))]
|
#[admin_command]
|
||||||
pub(super) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
#[tracing::instrument(skip(self))]
|
||||||
if !services()
|
pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||||
|
if !self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.server_in_room(&services().globals.config.server_name, &room_id)?
|
.server_in_room(&self.services.globals.config.server_name, &room_id)?
|
||||||
{
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"We are not participating in the room / we don't know about the room ID.",
|
"We are not participating in the room / we don't know about the room ID.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let latest_pdu = services()
|
let latest_pdu = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.latest_pdu_in_room(&room_id)?
|
.latest_pdu_in_room(&room_id)?
|
||||||
@@ -461,32 +506,36 @@ pub(super) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -
|
|||||||
Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}")))
|
Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(_body))]
|
#[admin_command]
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
pub(super) async fn force_set_room_state_from_server(
|
pub(super) async fn force_set_room_state_from_server(
|
||||||
_body: Vec<&str>, server_name: Box<ServerName>, room_id: Box<RoomId>,
|
&self, room_id: Box<RoomId>, server_name: Box<ServerName>,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
if !services()
|
if !self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.server_in_room(&services().globals.config.server_name, &room_id)?
|
.server_in_room(&self.services.globals.config.server_name, &room_id)?
|
||||||
{
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"We are not participating in the room / we don't know about the room ID.",
|
"We are not participating in the room / we don't know about the room ID.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let first_pdu = services()
|
let first_pdu = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.latest_pdu_in_room(&room_id)?
|
.latest_pdu_in_room(&room_id)?
|
||||||
.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
|
.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
|
||||||
|
|
||||||
let room_version = services().rooms.state.get_room_version(&room_id)?;
|
let room_version = self.services.rooms.state.get_room_version(&room_id)?;
|
||||||
|
|
||||||
let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
|
let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
|
||||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||||
|
|
||||||
let remote_state_response = services()
|
let remote_state_response = self
|
||||||
|
.services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
&server_name,
|
&server_name,
|
||||||
@@ -500,7 +549,7 @@ pub(super) async fn force_set_room_state_from_server(
|
|||||||
let mut events = Vec::with_capacity(remote_state_response.pdus.len());
|
let mut events = Vec::with_capacity(remote_state_response.pdus.len());
|
||||||
|
|
||||||
for pdu in remote_state_response.pdus.clone() {
|
for pdu in remote_state_response.pdus.clone() {
|
||||||
events.push(match parse_incoming_pdu(&pdu) {
|
events.push(match self.services.rooms.event_handler.parse_incoming_pdu(&pdu) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Could not parse PDU, ignoring: {e}");
|
warn!("Could not parse PDU, ignoring: {e}");
|
||||||
@@ -510,9 +559,8 @@ pub(super) async fn force_set_room_state_from_server(
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("Fetching required signing keys for all the state events we got");
|
info!("Fetching required signing keys for all the state events we got");
|
||||||
services()
|
self.services
|
||||||
.rooms
|
.server_keys
|
||||||
.event_handler
|
|
||||||
.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
|
.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -520,23 +568,24 @@ pub(super) async fn force_set_room_state_from_server(
|
|||||||
for result in remote_state_response
|
for result in remote_state_response
|
||||||
.pdus
|
.pdus
|
||||||
.iter()
|
.iter()
|
||||||
.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map))
|
.map(|pdu| validate_and_add_event_id(self.services, pdu, &room_version, &pub_key_map))
|
||||||
{
|
{
|
||||||
let Ok((event_id, value)) = result.await else {
|
let Ok((event_id, value)) = result.await else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
|
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
|
||||||
warn!("Invalid PDU in fetching remote room state PDUs response: {} {:?}", e, value);
|
debug_error!("Invalid PDU in fetching remote room state PDUs response: {value:#?}");
|
||||||
Error::BadServerResponse("Invalid PDU in send_join response.")
|
err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}")))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
services()
|
self.services
|
||||||
.rooms
|
.rooms
|
||||||
.outlier
|
.outlier
|
||||||
.add_pdu_outlier(&event_id, &value)?;
|
.add_pdu_outlier(&event_id, &value)?;
|
||||||
if let Some(state_key) = &pdu.state_key {
|
if let Some(state_key) = &pdu.state_key {
|
||||||
let shortstatekey = services()
|
let shortstatekey = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.short
|
.short
|
||||||
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
|
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
|
||||||
@@ -548,32 +597,34 @@ pub(super) async fn force_set_room_state_from_server(
|
|||||||
for result in remote_state_response
|
for result in remote_state_response
|
||||||
.auth_chain
|
.auth_chain
|
||||||
.iter()
|
.iter()
|
||||||
.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map))
|
.map(|pdu| validate_and_add_event_id(self.services, pdu, &room_version, &pub_key_map))
|
||||||
{
|
{
|
||||||
let Ok((event_id, value)) = result.await else {
|
let Ok((event_id, value)) = result.await else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
services()
|
self.services
|
||||||
.rooms
|
.rooms
|
||||||
.outlier
|
.outlier
|
||||||
.add_pdu_outlier(&event_id, &value)?;
|
.add_pdu_outlier(&event_id, &value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_room_state = services()
|
let new_room_state = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.event_handler
|
.event_handler
|
||||||
.resolve_state(room_id.clone().as_ref(), &room_version, state)
|
.resolve_state(room_id.clone().as_ref(), &room_version, state)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
info!("Forcing new room state");
|
info!("Forcing new room state");
|
||||||
let (short_state_hash, new, removed) = services()
|
let (short_state_hash, new, removed) = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_compressor
|
.state_compressor
|
||||||
.save_state(room_id.clone().as_ref(), new_room_state)?;
|
.save_state(room_id.clone().as_ref(), new_room_state)?;
|
||||||
|
|
||||||
let state_lock = services().globals.roomid_mutex_state.lock(&room_id).await;
|
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
|
||||||
services()
|
self.services
|
||||||
.rooms
|
.rooms
|
||||||
.state
|
.state
|
||||||
.force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock)
|
.force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock)
|
||||||
@@ -583,7 +634,10 @@ pub(super) async fn force_set_room_state_from_server(
|
|||||||
"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
|
"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
|
||||||
m.room.member state"
|
m.room.member state"
|
||||||
);
|
);
|
||||||
services().rooms.state_cache.update_joined_count(&room_id)?;
|
self.services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.update_joined_count(&room_id)?;
|
||||||
|
|
||||||
drop(state_lock);
|
drop(state_lock);
|
||||||
|
|
||||||
@@ -592,16 +646,63 @@ pub(super) async fn force_set_room_state_from_server(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn resolve_true_destination(
|
#[admin_command]
|
||||||
_body: Vec<&str>, server_name: Box<ServerName>, no_cache: bool,
|
pub(super) async fn get_signing_keys(
|
||||||
|
&self, server_name: Option<Box<ServerName>>, _cached: bool,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
if !services().globals.config.allow_federation {
|
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
|
||||||
|
let signing_keys = self.services.globals.signing_keys_for(&server_name)?;
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
|
"```rs\n{signing_keys:#?}\n```"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(super) async fn get_verify_keys(
|
||||||
|
&self, server_name: Option<Box<ServerName>>, cached: bool,
|
||||||
|
) -> Result<RoomMessageEventContent> {
|
||||||
|
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
if cached {
|
||||||
|
writeln!(out, "| Key ID | VerifyKey |")?;
|
||||||
|
writeln!(out, "| --- | --- |")?;
|
||||||
|
for (key_id, verify_key) in self.services.globals.verify_keys_for(&server_name)? {
|
||||||
|
writeln!(out, "| {key_id} | {verify_key:?} |")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(RoomMessageEventContent::notice_markdown(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
let signature_ids: Vec<String> = Vec::new();
|
||||||
|
let keys = self
|
||||||
|
.services
|
||||||
|
.server_keys
|
||||||
|
.fetch_signing_keys_for_server(&server_name, signature_ids)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
writeln!(out, "| Key ID | Public Key |")?;
|
||||||
|
writeln!(out, "| --- | --- |")?;
|
||||||
|
for (key_id, key) in keys {
|
||||||
|
writeln!(out, "| {key_id} | {key} |")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn resolve_true_destination(
|
||||||
|
&self, server_name: Box<ServerName>, no_cache: bool,
|
||||||
|
) -> Result<RoomMessageEventContent> {
|
||||||
|
if !self.services.globals.config.allow_federation {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Federation is disabled on this homeserver.",
|
"Federation is disabled on this homeserver.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if server_name == services().globals.config.server_name {
|
if server_name == self.services.globals.config.server_name {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
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.",
|
||||||
));
|
));
|
||||||
@@ -613,32 +714,108 @@ pub(super) async fn resolve_true_destination(
|
|||||||
&& matches!(data.span_name(), "actual" | "well-known" | "srv")
|
&& matches!(data.span_name(), "actual" | "well-known" | "srv")
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = &services().server.log.capture;
|
let state = &self.services.server.log.capture;
|
||||||
let logs = Arc::new(Mutex::new(String::new()));
|
let logs = Arc::new(Mutex::new(String::new()));
|
||||||
let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone()));
|
let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone()));
|
||||||
let (actual_dest, hostname_uri);
|
|
||||||
{
|
let capture_scope = capture.start();
|
||||||
let _capture_scope = capture.start();
|
let actual = self
|
||||||
(actual_dest, hostname_uri) = resolve_actual_dest(&server_name, !no_cache).await?;
|
.services
|
||||||
};
|
.resolver
|
||||||
|
.resolve_actual_dest(&server_name, !no_cache)
|
||||||
|
.await?;
|
||||||
|
drop(capture_scope);
|
||||||
|
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"{}\nDestination: {actual_dest}\nHostname URI: {hostname_uri}",
|
"{}\nDestination: {}\nHostname URI: {}",
|
||||||
logs.lock().expect("locked")
|
logs.lock().expect("locked"),
|
||||||
|
actual.dest,
|
||||||
|
actual.host,
|
||||||
);
|
);
|
||||||
Ok(RoomMessageEventContent::text_markdown(msg))
|
Ok(RoomMessageEventContent::text_markdown(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[admin_command]
|
||||||
pub(super) fn memory_stats() -> RoomMessageEventContent {
|
pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
|
||||||
let html_body = conduit::alloc::memory_stats();
|
let html_body = conduit::alloc::memory_stats();
|
||||||
|
|
||||||
if html_body.is_empty() {
|
if html_body.is_none() {
|
||||||
return RoomMessageEventContent::text_plain("malloc stats are not supported on your compiled malloc.");
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
|
"malloc stats are not supported on your compiled malloc.",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomMessageEventContent::text_html(
|
Ok(RoomMessageEventContent::text_html(
|
||||||
"This command's output can only be viewed by clients that render HTML.".to_owned(),
|
"This command's output can only be viewed by clients that render HTML.".to_owned(),
|
||||||
html_body,
|
html_body.expect("string result"),
|
||||||
)
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
#[admin_command]
|
||||||
|
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```"),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::text_markdown(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tokio_unstable))]
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
Ok(RoomMessageEventContent::text_markdown(
|
||||||
|
"Runtime metrics require building with `tokio_unstable`.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(tokio_unstable)]
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn runtime_interval(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
let out = self.services.server.metrics.runtime_interval().map_or_else(
|
||||||
|
|| "Runtime metrics are not available.".to_owned(),
|
||||||
|
|metrics| format!("```rs\n{metrics:#?}\n```"),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::text_markdown(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(tokio_unstable))]
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn runtime_interval(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
Ok(RoomMessageEventContent::text_markdown(
|
||||||
|
"Runtime metrics require building with `tokio_unstable`.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn time(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
Ok(RoomMessageEventContent::text_markdown(utils::time::format(now, "%+")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn list_dependencies(&self, names: bool) -> Result<RoomMessageEventContent> {
|
||||||
|
if names {
|
||||||
|
let out = info::cargo::dependencies_names().join(" ");
|
||||||
|
return Ok(RoomMessageEventContent::notice_markdown(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
let deps = info::cargo::dependencies();
|
||||||
|
let mut out = String::new();
|
||||||
|
writeln!(out, "| name | version | features |")?;
|
||||||
|
writeln!(out, "| ---- | ------- | -------- |")?;
|
||||||
|
for (name, dep) in deps {
|
||||||
|
let version = dep.try_req().unwrap_or("*");
|
||||||
|
let feats = dep.req_features();
|
||||||
|
let feats = if !feats.is_empty() {
|
||||||
|
feats.join(" ")
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
writeln!(out, "{name} | {version} | {feats}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-55
@@ -1,13 +1,15 @@
|
|||||||
mod commands;
|
mod commands;
|
||||||
|
pub(crate) mod tester;
|
||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, EventId, RoomId, ServerName};
|
use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName};
|
||||||
|
|
||||||
use self::commands::*;
|
use self::tester::TesterCommand;
|
||||||
|
use crate::admin_command_dispatch;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[admin_command_dispatch]
|
||||||
#[derive(Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(super) enum DebugCommand {
|
pub(super) enum DebugCommand {
|
||||||
/// - Echo input of admin command
|
/// - Echo input of admin command
|
||||||
Echo {
|
Echo {
|
||||||
@@ -71,7 +73,15 @@ pub(super) enum DebugCommand {
|
|||||||
/// Of course the check is still done on the actual client API.
|
/// Of course the check is still done on the actual client API.
|
||||||
GetRoomState {
|
GetRoomState {
|
||||||
/// Room ID
|
/// Room ID
|
||||||
room_id: Box<RoomId>,
|
room_id: OwnedRoomOrAliasId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Get and display signing keys from local cache or remote server.
|
||||||
|
GetSigningKeys {
|
||||||
|
server_name: Option<Box<ServerName>>,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
cached: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// - Sends a federation request to the remote server's
|
/// - Sends a federation request to the remote server's
|
||||||
@@ -157,55 +167,25 @@ pub(super) enum DebugCommand {
|
|||||||
|
|
||||||
/// - Print extended memory usage
|
/// - Print extended memory usage
|
||||||
MemoryStats,
|
MemoryStats,
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
/// - Print general tokio runtime metric totals.
|
||||||
Ok(match command {
|
RuntimeMetrics,
|
||||||
DebugCommand::Echo {
|
|
||||||
message,
|
/// - Print detailed tokio runtime metrics accumulated since last command
|
||||||
} => echo(body, message).await?,
|
/// invocation.
|
||||||
DebugCommand::GetAuthChain {
|
RuntimeInterval,
|
||||||
event_id,
|
|
||||||
} => get_auth_chain(body, event_id).await?,
|
/// - Print the current time
|
||||||
DebugCommand::ParsePdu => parse_pdu(body).await?,
|
Time,
|
||||||
DebugCommand::GetPdu {
|
|
||||||
event_id,
|
/// - List dependencies
|
||||||
} => get_pdu(body, event_id).await?,
|
ListDependencies {
|
||||||
DebugCommand::GetRemotePdu {
|
#[arg(short, long)]
|
||||||
event_id,
|
names: bool,
|
||||||
server,
|
},
|
||||||
} => get_remote_pdu(body, event_id, server).await?,
|
|
||||||
DebugCommand::GetRoomState {
|
/// - Developer test stubs
|
||||||
room_id,
|
#[command(subcommand)]
|
||||||
} => get_room_state(body, room_id).await?,
|
#[allow(non_snake_case)]
|
||||||
DebugCommand::Ping {
|
Tester(TesterCommand),
|
||||||
server,
|
|
||||||
} => ping(body, server).await?,
|
|
||||||
DebugCommand::ForceDeviceListUpdates => force_device_list_updates(body).await?,
|
|
||||||
DebugCommand::ChangeLogLevel {
|
|
||||||
filter,
|
|
||||||
reset,
|
|
||||||
} => change_log_level(body, filter, reset).await?,
|
|
||||||
DebugCommand::SignJson => sign_json(body).await?,
|
|
||||||
DebugCommand::VerifyJson => verify_json(body).await?,
|
|
||||||
DebugCommand::FirstPduInRoom {
|
|
||||||
room_id,
|
|
||||||
} => first_pdu_in_room(body, room_id).await?,
|
|
||||||
DebugCommand::LatestPduInRoom {
|
|
||||||
room_id,
|
|
||||||
} => latest_pdu_in_room(body, room_id).await?,
|
|
||||||
DebugCommand::GetRemotePduList {
|
|
||||||
server,
|
|
||||||
force,
|
|
||||||
} => get_remote_pdu_list(body, server, force).await?,
|
|
||||||
DebugCommand::ForceSetRoomStateFromServer {
|
|
||||||
room_id,
|
|
||||||
server_name,
|
|
||||||
} => force_set_room_state_from_server(body, server_name, room_id).await?,
|
|
||||||
DebugCommand::ResolveTrueDestination {
|
|
||||||
server_name,
|
|
||||||
no_cache,
|
|
||||||
} => resolve_true_destination(body, server_name, no_cache).await?,
|
|
||||||
DebugCommand::MemoryStats => memory_stats(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
|
use crate::{admin_command, admin_command_dispatch, Result};
|
||||||
|
|
||||||
|
#[admin_command_dispatch]
|
||||||
|
#[derive(Debug, clap::Subcommand)]
|
||||||
|
pub(crate) enum TesterCommand {
|
||||||
|
Tester,
|
||||||
|
Timer,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
#[admin_command]
|
||||||
|
async fn tester(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::notice_plain("completed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[admin_command]
|
||||||
|
async fn timer(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
let started = std::time::Instant::now();
|
||||||
|
timed(self.body);
|
||||||
|
|
||||||
|
let elapsed = started.elapsed();
|
||||||
|
Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn timed(body: &[&str]) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,36 +1,45 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use conduit::Result;
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
|
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
|
||||||
|
|
||||||
use crate::{escape_html, get_room_info, services, Result};
|
use crate::{admin_command, escape_html, get_room_info};
|
||||||
|
|
||||||
pub(super) async fn disable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
pub(super) async fn disable_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||||
|
self.services.rooms.metadata.disable_room(&room_id, true)?;
|
||||||
Ok(RoomMessageEventContent::text_plain("Room disabled."))
|
Ok(RoomMessageEventContent::text_plain("Room disabled."))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
services().rooms.metadata.disable_room(&room_id, false)?;
|
pub(super) async fn enable_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||||
|
self.services.rooms.metadata.disable_room(&room_id, false)?;
|
||||||
Ok(RoomMessageEventContent::text_plain("Room enabled."))
|
Ok(RoomMessageEventContent::text_plain("Room enabled."))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let map = services().globals.roomid_federationhandletime.read().await;
|
pub(super) async fn incoming_federation(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
let map = self
|
||||||
|
.services
|
||||||
|
.rooms
|
||||||
|
.event_handler
|
||||||
|
.federation_handletime
|
||||||
|
.read()
|
||||||
|
.expect("locked");
|
||||||
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
|
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
|
||||||
|
|
||||||
for (r, (e, i)) in map.iter() {
|
for (r, (e, i)) in map.iter() {
|
||||||
let elapsed = i.elapsed();
|
let elapsed = i.elapsed();
|
||||||
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60,)
|
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
|
||||||
.expect("should be able to write to string buffer");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::text_plain(&msg))
|
Ok(RoomMessageEventContent::text_plain(&msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn fetch_support_well_known(
|
#[admin_command]
|
||||||
_body: Vec<&str>, server_name: Box<ServerName>,
|
pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>) -> Result<RoomMessageEventContent> {
|
||||||
) -> Result<RoomMessageEventContent> {
|
let response = self
|
||||||
let response = services()
|
.services
|
||||||
.globals
|
|
||||||
.client
|
.client
|
||||||
.default
|
.default
|
||||||
.get(format!("https://{server_name}/.well-known/matrix/support"))
|
.get(format!("https://{server_name}/.well-known/matrix/support"))
|
||||||
@@ -68,25 +77,27 @@ pub(super) async fn fetch_support_well_known(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if user_id.server_name() == 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.globals.config.server_name {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
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.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !services().users.exists(&user_id)? {
|
if !self.services.users.exists(&user_id)? {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Remote user does not exist in our database.",
|
"Remote user does not exist in our database.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rooms: Vec<(OwnedRoomId, u64, String)> = services()
|
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(&user_id)
|
.rooms_joined(&user_id)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.map(|room_id| get_room_info(&room_id))
|
.map(|room_id| get_room_info(self.services, &room_id))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if rooms.is_empty() {
|
if rooms.is_empty() {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ mod commands;
|
|||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
|
use ruma::{RoomId, ServerName, UserId};
|
||||||
|
|
||||||
use self::commands::*;
|
use crate::admin_command_dispatch;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[admin_command_dispatch]
|
||||||
#[derive(Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(super) enum FederationCommand {
|
pub(super) enum FederationCommand {
|
||||||
/// - List all rooms we are currently handling an incoming pdu from
|
/// - List all rooms we are currently handling an incoming pdu from
|
||||||
IncomingFederation,
|
IncomingFederation,
|
||||||
@@ -40,21 +40,3 @@ pub(super) enum FederationCommand {
|
|||||||
user_id: Box<UserId>,
|
user_id: Box<UserId>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn process(command: FederationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
FederationCommand::DisableRoom {
|
|
||||||
room_id,
|
|
||||||
} => disable_room(body, room_id).await?,
|
|
||||||
FederationCommand::EnableRoom {
|
|
||||||
room_id,
|
|
||||||
} => enable_room(body, room_id).await?,
|
|
||||||
FederationCommand::IncomingFederation => incoming_federation(body).await?,
|
|
||||||
FederationCommand::FetchSupportWellKnown {
|
|
||||||
server_name,
|
|
||||||
} => fetch_support_well_known(body, server_name).await?,
|
|
||||||
FederationCommand::RemoteUserInRooms {
|
|
||||||
user_id,
|
|
||||||
} => remote_user_in_rooms(body, user_id).await?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
mod commands;
|
|
||||||
|
|
||||||
use clap::Subcommand;
|
|
||||||
use conduit::Result;
|
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
|
||||||
|
|
||||||
use self::commands::*;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub(super) enum FsckCommand {
|
|
||||||
CheckAllUsers,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn process(command: FsckCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
FsckCommand::CheckAllUsers => check_all_users(body).await?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
+156
-116
@@ -1,172 +1,212 @@
|
|||||||
use clap::Parser;
|
use std::{panic::AssertUnwindSafe, sync::Arc, time::Instant};
|
||||||
use conduit::trace;
|
|
||||||
use ruma::events::{
|
use clap::{CommandFactory, Parser};
|
||||||
|
use conduit::{checked, error, trace, utils::string::common_prefix, Error, Result};
|
||||||
|
use futures_util::future::FutureExt;
|
||||||
|
use ruma::{
|
||||||
|
events::{
|
||||||
relation::InReplyTo,
|
relation::InReplyTo,
|
||||||
room::message::{Relation::Reply, RoomMessageEventContent},
|
room::message::{Relation::Reply, RoomMessageEventContent},
|
||||||
|
},
|
||||||
|
OwnedEventId,
|
||||||
|
};
|
||||||
|
use service::{
|
||||||
|
admin::{CommandInput, CommandOutput, CommandResult, HandlerResult},
|
||||||
|
Services,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern crate conduit_service as service;
|
use crate::{admin, admin::AdminCommand, Command};
|
||||||
|
|
||||||
use conduit::Result;
|
|
||||||
pub(crate) use service::admin::{Command, Service};
|
|
||||||
use service::admin::{CommandOutput, CommandResult, HandlerResult};
|
|
||||||
|
|
||||||
use self::{fsck::FsckCommand, tester::TesterCommands};
|
|
||||||
use crate::{
|
|
||||||
appservice, appservice::AppserviceCommand, debug, debug::DebugCommand, federation, federation::FederationCommand,
|
|
||||||
fsck, media, media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand, server,
|
|
||||||
server::ServerCommand, services, tester, user, user::UserCommand,
|
|
||||||
};
|
|
||||||
pub(crate) const PAGE_SIZE: usize = 100;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
|
|
||||||
pub(crate) enum AdminCommand {
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Commands for managing appservices
|
|
||||||
Appservices(AppserviceCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Commands for managing local users
|
|
||||||
Users(UserCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Commands for managing rooms
|
|
||||||
Rooms(RoomCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Commands for managing federation
|
|
||||||
Federation(FederationCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Commands for managing the server
|
|
||||||
Server(ServerCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Commands for managing media
|
|
||||||
Media(MediaCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Commands for debugging things
|
|
||||||
Debug(DebugCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Query all the database getters and iterators
|
|
||||||
Query(QueryCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
/// - Query all the database getters and iterators
|
|
||||||
Fsck(FsckCommand),
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
Tester(TesterCommands),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn handle(command: Command) -> HandlerResult { Box::pin(handle_command(command)) }
|
pub(super) fn complete(line: &str) -> String { complete_command(AdminCommand::command(), line) }
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub(super) fn handle(services: Arc<Services>, command: CommandInput) -> HandlerResult {
|
||||||
|
Box::pin(handle_command(services, command))
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, name = "admin")]
|
#[tracing::instrument(skip_all, name = "admin")]
|
||||||
async fn handle_command(command: Command) -> CommandResult {
|
async fn handle_command(services: Arc<Services>, command: CommandInput) -> CommandResult {
|
||||||
let Some(mut content) = process_admin_message(command.command).await else {
|
AssertUnwindSafe(Box::pin(process_command(services, &command)))
|
||||||
return Ok(None);
|
.catch_unwind()
|
||||||
};
|
.await
|
||||||
|
.map_err(Error::from_panic)
|
||||||
|
.or_else(|error| handle_panic(&error, command))
|
||||||
|
}
|
||||||
|
|
||||||
content.relates_to = command.reply_id.map(|event_id| Reply {
|
async fn process_command(services: Arc<Services>, command: &CommandInput) -> CommandOutput {
|
||||||
|
process(services, &command.command)
|
||||||
|
.await
|
||||||
|
.and_then(|content| reply(content, command.reply_id.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_panic(error: &Error, command: CommandInput) -> CommandResult {
|
||||||
|
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:?}");
|
||||||
|
Ok(reply(content, command.reply_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reply(mut content: RoomMessageEventContent, reply_id: Option<OwnedEventId>) -> Option<RoomMessageEventContent> {
|
||||||
|
content.relates_to = reply_id.map(|event_id| Reply {
|
||||||
in_reply_to: InReplyTo {
|
in_reply_to: InReplyTo {
|
||||||
event_id,
|
event_id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Some(content))
|
Some(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse and process a message from the admin room
|
// Parse and process a message from the admin room
|
||||||
async fn process_admin_message(msg: String) -> CommandOutput {
|
async fn process(services: Arc<Services>, msg: &str) -> CommandOutput {
|
||||||
let mut lines = msg.lines().filter(|l| !l.trim().is_empty());
|
let lines = msg.lines().filter(|l| !l.trim().is_empty());
|
||||||
let command_line = lines.next().expect("each string has at least one line");
|
let command = lines
|
||||||
let body = lines.collect::<Vec<_>>();
|
.clone()
|
||||||
|
.next()
|
||||||
let admin_command = match parse_admin_command(command_line) {
|
.expect("each string has at least one line");
|
||||||
Ok(command) => command,
|
let (parsed, body) = match parse_command(command) {
|
||||||
|
Ok(parsed) => parsed,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let server_name = services().globals.server_name();
|
let server_name = services.globals.server_name();
|
||||||
let message = error.replace("server.name", server_name.as_str());
|
let message = error.replace("server.name", server_name.as_str());
|
||||||
return Some(RoomMessageEventContent::notice_markdown(message));
|
return Some(RoomMessageEventContent::notice_markdown(message));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match process_admin_command(admin_command, body).await {
|
let body = parse_body(AdminCommand::command(), &body, lines.skip(1).collect()).expect("trailing body parsed");
|
||||||
Ok(reply_message) => Some(reply_message),
|
let context = Command {
|
||||||
Err(error) => {
|
services: &services,
|
||||||
let markdown_message = format!("Encountered an error while handling the command:\n```\n{error}\n```",);
|
body: &body,
|
||||||
Some(RoomMessageEventContent::notice_markdown(markdown_message))
|
};
|
||||||
},
|
let timer = Instant::now();
|
||||||
|
let result = Box::pin(admin::process(parsed, &context)).await;
|
||||||
|
let elapsed = timer.elapsed();
|
||||||
|
conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}");
|
||||||
|
match result {
|
||||||
|
Ok(reply) => Some(reply),
|
||||||
|
Err(error) => Some(RoomMessageEventContent::notice_markdown(format!(
|
||||||
|
"Encountered an error while handling the command:\n```\n{error:#?}\n```"
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse chat messages from the admin room into an AdminCommand object
|
// Parse chat messages from the admin room into an AdminCommand object
|
||||||
fn parse_admin_command(command_line: &str) -> Result<AdminCommand, String> {
|
fn parse_command(command_line: &str) -> Result<(AdminCommand, Vec<String>), String> {
|
||||||
let mut argv = command_line.split_whitespace().collect::<Vec<_>>();
|
let argv = parse_line(command_line);
|
||||||
|
let com = AdminCommand::try_parse_from(&argv).map_err(|error| error.to_string())?;
|
||||||
|
Ok((com, argv))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_body<'a>(mut cmd: clap::Command, body: &'a [String], lines: Vec<&'a str>) -> Result<Vec<&'a str>> {
|
||||||
|
let mut start = 1;
|
||||||
|
'token: for token in body.iter().skip(1) {
|
||||||
|
let cmd_ = cmd.clone();
|
||||||
|
for sub in cmd_.get_subcommands() {
|
||||||
|
if sub.get_name() == *token {
|
||||||
|
start = checked!(start + 1)?;
|
||||||
|
cmd = sub.clone();
|
||||||
|
continue 'token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// positional arguments have to be skipped too
|
||||||
|
let num_posargs = cmd_.get_positionals().count();
|
||||||
|
start = checked!(start + num_posargs)?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(body
|
||||||
|
.iter()
|
||||||
|
.skip(start)
|
||||||
|
.map(String::as_str)
|
||||||
|
.chain(lines)
|
||||||
|
.collect::<Vec<&'a str>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_command(mut cmd: clap::Command, line: &str) -> String {
|
||||||
|
let argv = parse_line(line);
|
||||||
|
let mut ret = Vec::<String>::with_capacity(argv.len().saturating_add(1));
|
||||||
|
|
||||||
|
'token: for token in argv.into_iter().skip(1) {
|
||||||
|
let cmd_ = cmd.clone();
|
||||||
|
let mut choice = Vec::new();
|
||||||
|
|
||||||
|
for sub in cmd_.get_subcommands() {
|
||||||
|
let name = sub.get_name();
|
||||||
|
if *name == token {
|
||||||
|
// token already complete; recurse to subcommand
|
||||||
|
ret.push(token);
|
||||||
|
cmd.clone_from(sub);
|
||||||
|
continue 'token;
|
||||||
|
} else if name.starts_with(&token) {
|
||||||
|
// partial match; add to choices
|
||||||
|
choice.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if choice.len() == 1 {
|
||||||
|
// One choice. Add extra space because it's complete
|
||||||
|
let choice = *choice.first().expect("only choice");
|
||||||
|
ret.push(choice.to_owned());
|
||||||
|
ret.push(String::new());
|
||||||
|
} else if choice.is_empty() {
|
||||||
|
// Nothing found, return original string
|
||||||
|
ret.push(token);
|
||||||
|
} else {
|
||||||
|
// Find the common prefix
|
||||||
|
ret.push(common_prefix(&choice).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return from completion
|
||||||
|
return ret.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return from no completion. Needs a space though.
|
||||||
|
ret.push(String::new());
|
||||||
|
ret.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse chat messages from the admin room into an AdminCommand object
|
||||||
|
fn parse_line(command_line: &str) -> Vec<String> {
|
||||||
|
let mut argv = command_line
|
||||||
|
.split_whitespace()
|
||||||
|
.map(str::to_owned)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
// Remove any escapes that came with a server-side escape command
|
// Remove any escapes that came with a server-side escape command
|
||||||
if !argv.is_empty() && argv[0].ends_with("admin") {
|
if !argv.is_empty() && argv[0].ends_with("admin") {
|
||||||
argv[0] = argv[0].trim_start_matches('\\');
|
argv[0] = argv[0].trim_start_matches('\\').into();
|
||||||
}
|
}
|
||||||
|
|
||||||
// First indice has to be "admin" but for console convenience we add it here
|
// First indice has to be "admin" but for console convenience we add it here
|
||||||
let server_user = services().globals.server_user.as_str();
|
if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with('@') {
|
||||||
if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with(server_user) {
|
argv.insert(0, "admin".to_owned());
|
||||||
argv.insert(0, "admin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace `help command` with `command --help`
|
// Replace `help command` with `command --help`
|
||||||
// Clap has a help subcommand, but it omits the long help description.
|
// Clap has a help subcommand, but it omits the long help description.
|
||||||
if argv.len() > 1 && argv[1] == "help" {
|
if argv.len() > 1 && argv[1] == "help" {
|
||||||
argv.remove(1);
|
argv.remove(1);
|
||||||
argv.push("--help");
|
argv.push("--help".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backwards compatibility with `register_appservice`-style commands
|
// Backwards compatibility with `register_appservice`-style commands
|
||||||
let command_with_dashes_argv1;
|
|
||||||
if argv.len() > 1 && argv[1].contains('_') {
|
if argv.len() > 1 && argv[1].contains('_') {
|
||||||
command_with_dashes_argv1 = argv[1].replace('_', "-");
|
argv[1] = argv[1].replace('_', "-");
|
||||||
argv[1] = &command_with_dashes_argv1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backwards compatibility with `register_appservice`-style commands
|
// Backwards compatibility with `register_appservice`-style commands
|
||||||
let command_with_dashes_argv2;
|
|
||||||
if argv.len() > 2 && argv[2].contains('_') {
|
if argv.len() > 2 && argv[2].contains('_') {
|
||||||
command_with_dashes_argv2 = argv[2].replace('_', "-");
|
argv[2] = argv[2].replace('_', "-");
|
||||||
argv[2] = &command_with_dashes_argv2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the user is using the `query` command (argv[1]), replace the database
|
// if the user is using the `query` command (argv[1]), replace the database
|
||||||
// function/table calls with underscores to match the codebase
|
// function/table calls with underscores to match the codebase
|
||||||
let command_with_dashes_argv3;
|
|
||||||
if argv.len() > 3 && argv[1].eq("query") {
|
if argv.len() > 3 && argv[1].eq("query") {
|
||||||
command_with_dashes_argv3 = argv[3].replace('_', "-");
|
argv[3] = argv[3].replace('_', "-");
|
||||||
argv[3] = &command_with_dashes_argv3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!(?command_line, ?argv, "parse");
|
trace!(?command_line, ?argv, "parse");
|
||||||
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string())
|
argv
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, name = "command")]
|
|
||||||
async fn process_admin_command(command: AdminCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
let reply_message_content = match command {
|
|
||||||
AdminCommand::Appservices(command) => appservice::process(command, body).await?,
|
|
||||||
AdminCommand::Media(command) => media::process(command, body).await?,
|
|
||||||
AdminCommand::Users(command) => user::process(command, body).await?,
|
|
||||||
AdminCommand::Rooms(command) => room::process(command, body).await?,
|
|
||||||
AdminCommand::Federation(command) => federation::process(command, body).await?,
|
|
||||||
AdminCommand::Server(command) => server::process(command, body).await?,
|
|
||||||
AdminCommand::Debug(command) => debug::process(command, body).await?,
|
|
||||||
AdminCommand::Query(command) => query::process(command, body).await?,
|
|
||||||
AdminCommand::Fsck(command) => fsck::process(command, body).await?,
|
|
||||||
AdminCommand::Tester(command) => tester::process(command, body).await?,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(reply_message_content)
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-17
@@ -1,11 +1,11 @@
|
|||||||
use conduit::Result;
|
use conduit::{debug, info, Result};
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
|
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
|
||||||
use tracing::{debug, info};
|
|
||||||
|
|
||||||
use crate::services;
|
use crate::admin_command;
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
pub(super) async fn delete(
|
pub(super) async fn delete(
|
||||||
_body: Vec<&str>, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
|
&self, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
if event_id.is_some() && mxc.is_some() {
|
if event_id.is_some() && mxc.is_some() {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
@@ -15,7 +15,7 @@ pub(super) async fn delete(
|
|||||||
|
|
||||||
if let Some(mxc) = mxc {
|
if let Some(mxc) = mxc {
|
||||||
debug!("Got MXC URL: {mxc}");
|
debug!("Got MXC URL: {mxc}");
|
||||||
services().media.delete(mxc.to_string()).await?;
|
self.services.media.delete(mxc.as_ref()).await?;
|
||||||
|
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Deleted the MXC from our database and on our filesystem.",
|
"Deleted the MXC from our database and on our filesystem.",
|
||||||
@@ -27,7 +27,7 @@ pub(super) async fn delete(
|
|||||||
let mut mxc_deletion_count: usize = 0;
|
let mut mxc_deletion_count: usize = 0;
|
||||||
|
|
||||||
// parsing the PDU for any MXC URLs begins here
|
// parsing the PDU for any MXC URLs begins here
|
||||||
if let Some(event_json) = services().rooms.timeline.get_pdu_json(&event_id)? {
|
if let Some(event_json) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
|
||||||
if let Some(content_key) = event_json.get("content") {
|
if let Some(content_key) = event_json.get("content") {
|
||||||
debug!("Event ID has \"content\".");
|
debug!("Event ID has \"content\".");
|
||||||
let content_obj = content_key.as_object();
|
let content_obj = content_key.as_object();
|
||||||
@@ -123,7 +123,7 @@ pub(super) async fn delete(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for mxc_url in mxc_urls {
|
for mxc_url in mxc_urls {
|
||||||
services().media.delete(mxc_url).await?;
|
self.services.media.delete(&mxc_url).await?;
|
||||||
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,23 +138,26 @@ pub(super) async fn delete(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
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() != "```"
|
||||||
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Expected code block in command body. Add --help for details.",
|
"Expected code block in command body. Add --help for details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mxc_list = body
|
let mxc_list = self
|
||||||
.clone()
|
.body
|
||||||
.drain(1..body.len().checked_sub(1).unwrap())
|
.to_vec()
|
||||||
|
.drain(1..self.body.len().checked_sub(1).unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut mxc_deletion_count: usize = 0;
|
let mut mxc_deletion_count: usize = 0;
|
||||||
|
|
||||||
for mxc in mxc_list {
|
for mxc in mxc_list {
|
||||||
debug!("Deleting MXC {mxc} in bulk");
|
debug!("Deleting MXC {mxc} in bulk");
|
||||||
services().media.delete(mxc.to_owned()).await?;
|
self.services.media.delete(mxc).await?;
|
||||||
mxc_deletion_count = mxc_deletion_count
|
mxc_deletion_count = mxc_deletion_count
|
||||||
.checked_add(1)
|
.checked_add(1)
|
||||||
.expect("mxc_deletion_count should not get this high");
|
.expect("mxc_deletion_count should not get this high");
|
||||||
@@ -165,10 +168,10 @@ pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventConte
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn delete_past_remote_media(
|
#[admin_command]
|
||||||
_body: Vec<&str>, duration: String, force: bool,
|
pub(super) async fn delete_past_remote_media(&self, duration: String, force: bool) -> Result<RoomMessageEventContent> {
|
||||||
) -> Result<RoomMessageEventContent> {
|
let deleted_count = self
|
||||||
let deleted_count = services()
|
.services
|
||||||
.media
|
.media
|
||||||
.delete_all_remote_media_at_after_time(duration, force)
|
.delete_all_remote_media_at_after_time(duration, force)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
+4
-18
@@ -2,12 +2,12 @@ mod commands;
|
|||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
|
use ruma::{EventId, MxcUri};
|
||||||
|
|
||||||
use self::commands::*;
|
use crate::admin_command_dispatch;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[admin_command_dispatch]
|
||||||
#[derive(Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(super) enum MediaCommand {
|
pub(super) enum MediaCommand {
|
||||||
/// - Deletes a single media file from our database and on the filesystem
|
/// - Deletes a single media file from our database and on the filesystem
|
||||||
/// via a single MXC URL
|
/// via a single MXC URL
|
||||||
@@ -37,17 +37,3 @@ pub(super) enum MediaCommand {
|
|||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn process(command: MediaCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
MediaCommand::Delete {
|
|
||||||
mxc,
|
|
||||||
event_id,
|
|
||||||
} => delete(body, mxc, event_id).await?,
|
|
||||||
MediaCommand::DeleteList => delete_list(body).await?,
|
|
||||||
MediaCommand::DeletePastRemoteMedia {
|
|
||||||
duration,
|
|
||||||
force,
|
|
||||||
} => delete_past_remote_media(body, duration, force).await?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
+34
-34
@@ -1,57 +1,57 @@
|
|||||||
|
#![recursion_limit = "192"]
|
||||||
#![allow(clippy::wildcard_imports)]
|
#![allow(clippy::wildcard_imports)]
|
||||||
|
#![allow(clippy::enum_glob_use)]
|
||||||
|
|
||||||
|
pub(crate) mod admin;
|
||||||
|
pub(crate) mod command;
|
||||||
|
pub(crate) mod handler;
|
||||||
|
mod tests;
|
||||||
|
pub(crate) mod utils;
|
||||||
|
|
||||||
pub(crate) mod appservice;
|
pub(crate) mod appservice;
|
||||||
|
pub(crate) mod check;
|
||||||
pub(crate) mod debug;
|
pub(crate) mod debug;
|
||||||
pub(crate) mod federation;
|
pub(crate) mod federation;
|
||||||
pub(crate) mod fsck;
|
|
||||||
pub(crate) mod handler;
|
|
||||||
pub(crate) mod media;
|
pub(crate) mod media;
|
||||||
pub(crate) mod query;
|
pub(crate) mod query;
|
||||||
pub(crate) mod room;
|
pub(crate) mod room;
|
||||||
pub(crate) mod server;
|
pub(crate) mod server;
|
||||||
pub(crate) mod tester;
|
|
||||||
pub(crate) mod user;
|
pub(crate) mod user;
|
||||||
pub(crate) mod utils;
|
|
||||||
|
|
||||||
extern crate conduit_api as api;
|
extern crate conduit_api as api;
|
||||||
extern crate conduit_core as conduit;
|
extern crate conduit_core as conduit;
|
||||||
extern crate conduit_service as service;
|
extern crate conduit_service as service;
|
||||||
|
|
||||||
pub(crate) use conduit::{mod_ctor, mod_dtor, Result};
|
pub(crate) use conduit::Result;
|
||||||
pub use handler::handle;
|
pub(crate) use conduit_macros::{admin_command, admin_command_dispatch};
|
||||||
pub(crate) use service::{services, user_is_local};
|
|
||||||
|
|
||||||
pub(crate) use crate::{
|
pub(crate) use crate::{
|
||||||
handler::Service,
|
command::Command,
|
||||||
utils::{escape_html, get_room_info},
|
utils::{escape_html, get_room_info},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod_ctor! {}
|
pub(crate) const PAGE_SIZE: usize = 100;
|
||||||
mod_dtor! {}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
conduit::mod_ctor! {}
|
||||||
mod test {
|
conduit::mod_dtor! {}
|
||||||
use clap::Parser;
|
conduit::rustc_flags_capture! {}
|
||||||
|
|
||||||
use crate::handler::AdminCommand;
|
/// Install the admin command handler
|
||||||
|
pub async fn init(admin_service: &service::admin::Service) {
|
||||||
#[test]
|
_ = admin_service
|
||||||
fn get_help_short() { get_help_inner("-h"); }
|
.complete
|
||||||
|
.write()
|
||||||
#[test]
|
.expect("locked for writing")
|
||||||
fn get_help_long() { get_help_inner("--help"); }
|
.insert(handler::complete);
|
||||||
|
_ = admin_service.handle.write().await.insert(handler::handle);
|
||||||
#[test]
|
}
|
||||||
fn get_help_subcommand() { get_help_inner("help"); }
|
|
||||||
|
/// Uninstall the admin command handler
|
||||||
fn get_help_inner(input: &str) {
|
pub async fn fini(admin_service: &service::admin::Service) {
|
||||||
let error = AdminCommand::try_parse_from(["argv[0] doesn't matter", input])
|
_ = admin_service.handle.write().await.take();
|
||||||
.unwrap_err()
|
_ = admin_service
|
||||||
.to_string();
|
.complete
|
||||||
|
.write()
|
||||||
// Search for a handful of keywords that suggest the help printed properly
|
.expect("locked for writing")
|
||||||
assert!(error.contains("Usage:"));
|
.take();
|
||||||
assert!(error.contains("Commands:"));
|
|
||||||
assert!(error.contains("Options:"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,49 @@
|
|||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::{
|
||||||
|
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
|
||||||
|
RoomId, UserId,
|
||||||
|
};
|
||||||
|
|
||||||
use super::AccountData;
|
use crate::Command;
|
||||||
use crate::{services, Result};
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
/// All the getters and iterators from src/database/key_value/account_data.rs
|
||||||
|
pub(crate) enum AccountDataCommand {
|
||||||
|
/// - Returns all changes to the account data that happened after `since`.
|
||||||
|
ChangesSince {
|
||||||
|
/// Full user ID
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
/// UNIX timestamp since (u64)
|
||||||
|
since: u64,
|
||||||
|
/// Optional room ID of the account data
|
||||||
|
room_id: Option<Box<RoomId>>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Searches the account data for a specific kind.
|
||||||
|
Get {
|
||||||
|
/// Full user ID
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
/// Account data event type
|
||||||
|
kind: RoomAccountDataEventType,
|
||||||
|
/// Optional room ID of the account data
|
||||||
|
room_id: Option<Box<RoomId>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// All the getters and iterators from src/database/key_value/account_data.rs
|
/// All the getters and iterators from src/database/key_value/account_data.rs
|
||||||
pub(super) async fn account_data(subcommand: AccountData) -> Result<RoomMessageEventContent> {
|
pub(super) async fn process(subcommand: AccountDataCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
|
let services = context.services;
|
||||||
|
|
||||||
match subcommand {
|
match subcommand {
|
||||||
AccountData::ChangesSince {
|
AccountDataCommand::ChangesSince {
|
||||||
user_id,
|
user_id,
|
||||||
since,
|
since,
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services()
|
let results = services
|
||||||
.account_data
|
.account_data
|
||||||
.db
|
|
||||||
.changes_since(room_id.as_deref(), &user_id, since)?;
|
.changes_since(room_id.as_deref(), &user_id, since)?;
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
@@ -22,15 +51,14 @@ pub(super) async fn account_data(subcommand: AccountData) -> Result<RoomMessageE
|
|||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
AccountData::Get {
|
AccountDataCommand::Get {
|
||||||
user_id,
|
user_id,
|
||||||
kind,
|
kind,
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services()
|
let results = services
|
||||||
.account_data
|
.account_data
|
||||||
.db
|
|
||||||
.get(room_id.as_deref(), &user_id, kind)?;
|
.get(room_id.as_deref(), &user_id, kind)?;
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
use super::Appservice;
|
use crate::Command;
|
||||||
use crate::{services, Result};
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
/// All the getters and iterators from src/database/key_value/appservice.rs
|
||||||
|
pub(crate) enum AppserviceCommand {
|
||||||
|
/// - Gets the appservice registration info/details from the ID as a string
|
||||||
|
GetRegistration {
|
||||||
|
/// Appservice registration ID
|
||||||
|
appservice_id: Box<str>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Gets all appservice registrations with their ID and registration info
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
/// All the getters and iterators from src/database/key_value/appservice.rs
|
/// All the getters and iterators from src/database/key_value/appservice.rs
|
||||||
pub(super) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEventContent> {
|
pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
|
let services = context.services;
|
||||||
|
|
||||||
match subcommand {
|
match subcommand {
|
||||||
Appservice::GetRegistration {
|
AppserviceCommand::GetRegistration {
|
||||||
appservice_id,
|
appservice_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services()
|
let results = services
|
||||||
.appservice
|
.appservice
|
||||||
.db
|
.db
|
||||||
.get_registration(appservice_id.as_ref());
|
.get_registration(appservice_id.as_ref());
|
||||||
@@ -20,9 +36,9 @@ pub(super) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEven
|
|||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Appservice::All => {
|
AppserviceCommand::All => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().appservice.db.all();
|
let results = services.appservice.all();
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
|
|||||||
+35
-14
@@ -1,52 +1,73 @@
|
|||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::{events::room::message::RoomMessageEventContent, ServerName};
|
||||||
|
|
||||||
use super::Globals;
|
use crate::Command;
|
||||||
use crate::{services, Result};
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
/// All the getters and iterators from src/database/key_value/globals.rs
|
||||||
|
pub(crate) enum GlobalsCommand {
|
||||||
|
DatabaseVersion,
|
||||||
|
|
||||||
|
CurrentCount,
|
||||||
|
|
||||||
|
LastCheckForUpdatesId,
|
||||||
|
|
||||||
|
LoadKeypair,
|
||||||
|
|
||||||
|
/// - This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
|
||||||
|
/// for the server.
|
||||||
|
SigningKeysFor {
|
||||||
|
origin: Box<ServerName>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// All the getters and iterators from src/database/key_value/globals.rs
|
/// All the getters and iterators from src/database/key_value/globals.rs
|
||||||
pub(super) async fn globals(subcommand: Globals) -> Result<RoomMessageEventContent> {
|
pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
|
let services = context.services;
|
||||||
|
|
||||||
match subcommand {
|
match subcommand {
|
||||||
Globals::DatabaseVersion => {
|
GlobalsCommand::DatabaseVersion => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().globals.db.database_version();
|
let results = services.globals.db.database_version();
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Globals::CurrentCount => {
|
GlobalsCommand::CurrentCount => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().globals.db.current_count();
|
let results = services.globals.db.current_count();
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Globals::LastCheckForUpdatesId => {
|
GlobalsCommand::LastCheckForUpdatesId => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().globals.db.last_check_for_updates_id();
|
let results = services.updates.last_check_for_updates_id();
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Globals::LoadKeypair => {
|
GlobalsCommand::LoadKeypair => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().globals.db.load_keypair();
|
let results = services.globals.db.load_keypair();
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Globals::SigningKeysFor {
|
GlobalsCommand::SigningKeysFor {
|
||||||
origin,
|
origin,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().globals.db.signing_keys_for(&origin);
|
let results = services.globals.db.verify_keys_for(&origin);
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
|
|||||||
+19
-261
@@ -2,6 +2,7 @@ mod account_data;
|
|||||||
mod appservice;
|
mod appservice;
|
||||||
mod globals;
|
mod globals;
|
||||||
mod presence;
|
mod presence;
|
||||||
|
mod resolver;
|
||||||
mod room_alias;
|
mod room_alias;
|
||||||
mod room_state_cache;
|
mod room_state_cache;
|
||||||
mod sending;
|
mod sending;
|
||||||
@@ -9,294 +10,51 @@ mod users;
|
|||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
use room_state_cache::room_state_cache;
|
|
||||||
use ruma::{
|
|
||||||
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
|
|
||||||
RoomAliasId, RoomId, ServerName, UserId,
|
|
||||||
};
|
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
account_data::account_data, appservice::appservice, globals::globals, presence::presence, room_alias::room_alias,
|
account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
|
||||||
sending::sending, users::users,
|
presence::PresenceCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
|
||||||
|
room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand,
|
||||||
};
|
};
|
||||||
|
use crate::admin_command_dispatch;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[admin_command_dispatch]
|
||||||
#[derive(Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
/// Query tables from database
|
/// Query tables from database
|
||||||
pub(super) enum QueryCommand {
|
pub(super) enum QueryCommand {
|
||||||
/// - account_data.rs iterators and getters
|
/// - account_data.rs iterators and getters
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
AccountData(AccountData),
|
AccountData(AccountDataCommand),
|
||||||
|
|
||||||
/// - appservice.rs iterators and getters
|
/// - appservice.rs iterators and getters
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Appservice(Appservice),
|
Appservice(AppserviceCommand),
|
||||||
|
|
||||||
/// - presence.rs iterators and getters
|
/// - presence.rs iterators and getters
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Presence(Presence),
|
Presence(PresenceCommand),
|
||||||
|
|
||||||
/// - rooms/alias.rs iterators and getters
|
/// - rooms/alias.rs iterators and getters
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
RoomAlias(RoomAlias),
|
RoomAlias(RoomAliasCommand),
|
||||||
|
|
||||||
/// - rooms/state_cache iterators and getters
|
/// - rooms/state_cache iterators and getters
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
RoomStateCache(RoomStateCache),
|
RoomStateCache(RoomStateCacheCommand),
|
||||||
|
|
||||||
/// - globals.rs iterators and getters
|
/// - globals.rs iterators and getters
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Globals(Globals),
|
Globals(GlobalsCommand),
|
||||||
|
|
||||||
/// - sending.rs iterators and getters
|
/// - sending.rs iterators and getters
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Sending(Sending),
|
Sending(SendingCommand),
|
||||||
|
|
||||||
/// - users.rs iterators and getters
|
/// - users.rs iterators and getters
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Users(Users),
|
Users(UsersCommand),
|
||||||
}
|
|
||||||
|
/// - resolver service
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[command(subcommand)]
|
||||||
#[derive(Subcommand)]
|
Resolver(ResolverCommand),
|
||||||
/// All the getters and iterators from src/database/key_value/account_data.rs
|
|
||||||
pub(super) enum AccountData {
|
|
||||||
/// - Returns all changes to the account data that happened after `since`.
|
|
||||||
ChangesSince {
|
|
||||||
/// Full user ID
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
/// UNIX timestamp since (u64)
|
|
||||||
since: u64,
|
|
||||||
/// Optional room ID of the account data
|
|
||||||
room_id: Option<Box<RoomId>>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Searches the account data for a specific kind.
|
|
||||||
Get {
|
|
||||||
/// Full user ID
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
/// Account data event type
|
|
||||||
kind: RoomAccountDataEventType,
|
|
||||||
/// Optional room ID of the account data
|
|
||||||
room_id: Option<Box<RoomId>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
/// All the getters and iterators from src/database/key_value/appservice.rs
|
|
||||||
pub(super) enum Appservice {
|
|
||||||
/// - Gets the appservice registration info/details from the ID as a string
|
|
||||||
GetRegistration {
|
|
||||||
/// Appservice registration ID
|
|
||||||
appservice_id: Box<str>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Gets all appservice registrations with their ID and registration info
|
|
||||||
All,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
/// All the getters and iterators from src/database/key_value/presence.rs
|
|
||||||
pub(super) enum Presence {
|
|
||||||
/// - Returns the latest presence event for the given user.
|
|
||||||
GetPresence {
|
|
||||||
/// Full user ID
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Iterator of the most recent presence updates that happened after the
|
|
||||||
/// event with id `since`.
|
|
||||||
PresenceSince {
|
|
||||||
/// UNIX timestamp since (u64)
|
|
||||||
since: u64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
|
|
||||||
pub(super) enum RoomAlias {
|
|
||||||
ResolveLocalAlias {
|
|
||||||
/// Full room alias
|
|
||||||
alias: Box<RoomAliasId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Iterator of all our local room aliases for the room ID
|
|
||||||
LocalAliasesForRoom {
|
|
||||||
/// Full room ID
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Iterator of all our local aliases in our database with their room IDs
|
|
||||||
AllLocalAliases,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub(super) enum RoomStateCache {
|
|
||||||
ServerInRoom {
|
|
||||||
server: Box<ServerName>,
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomServers {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
ServerRooms {
|
|
||||||
server: Box<ServerName>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomMembers {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
LocalUsersInRoom {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
ActiveLocalUsersInRoom {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomJoinedCount {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomInvitedCount {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomUserOnceJoined {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomMembersInvited {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
GetInviteCount {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
GetLeftCount {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomsJoined {
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomsLeft {
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
RoomsInvited {
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
InviteState {
|
|
||||||
user_id: Box<UserId>,
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
/// All the getters and iterators from src/database/key_value/globals.rs
|
|
||||||
pub(super) enum Globals {
|
|
||||||
DatabaseVersion,
|
|
||||||
|
|
||||||
CurrentCount,
|
|
||||||
|
|
||||||
LastCheckForUpdatesId,
|
|
||||||
|
|
||||||
LoadKeypair,
|
|
||||||
|
|
||||||
/// - This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
|
|
||||||
/// for the server.
|
|
||||||
SigningKeysFor {
|
|
||||||
origin: Box<ServerName>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
/// All the getters and iterators from src/database/key_value/sending.rs
|
|
||||||
pub(super) enum Sending {
|
|
||||||
/// - Queries database for all `servercurrentevent_data`
|
|
||||||
ActiveRequests,
|
|
||||||
|
|
||||||
/// - Queries database for `servercurrentevent_data` but for a specific
|
|
||||||
/// destination
|
|
||||||
///
|
|
||||||
/// This command takes only *one* format of these arguments:
|
|
||||||
///
|
|
||||||
/// appservice_id
|
|
||||||
/// server_name
|
|
||||||
/// user_id AND push_key
|
|
||||||
///
|
|
||||||
/// See src/service/sending/mod.rs for the definition of the `Destination`
|
|
||||||
/// enum
|
|
||||||
ActiveRequestsFor {
|
|
||||||
#[arg(short, long)]
|
|
||||||
appservice_id: Option<String>,
|
|
||||||
#[arg(short, long)]
|
|
||||||
server_name: Option<Box<ServerName>>,
|
|
||||||
#[arg(short, long)]
|
|
||||||
user_id: Option<Box<UserId>>,
|
|
||||||
#[arg(short, long)]
|
|
||||||
push_key: Option<String>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Queries database for `servernameevent_data` which are the queued up
|
|
||||||
/// requests that will eventually be sent
|
|
||||||
///
|
|
||||||
/// This command takes only *one* format of these arguments:
|
|
||||||
///
|
|
||||||
/// appservice_id
|
|
||||||
/// server_name
|
|
||||||
/// user_id AND push_key
|
|
||||||
///
|
|
||||||
/// See src/service/sending/mod.rs for the definition of the `Destination`
|
|
||||||
/// enum
|
|
||||||
QueuedRequests {
|
|
||||||
#[arg(short, long)]
|
|
||||||
appservice_id: Option<String>,
|
|
||||||
#[arg(short, long)]
|
|
||||||
server_name: Option<Box<ServerName>>,
|
|
||||||
#[arg(short, long)]
|
|
||||||
user_id: Option<Box<UserId>>,
|
|
||||||
#[arg(short, long)]
|
|
||||||
push_key: Option<String>,
|
|
||||||
},
|
|
||||||
|
|
||||||
GetLatestEduCount {
|
|
||||||
server_name: Box<ServerName>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
/// All the getters and iterators from src/database/key_value/users.rs
|
|
||||||
pub(super) enum Users {
|
|
||||||
Iter,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Processes admin query commands
|
|
||||||
pub(super) async fn process(command: QueryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
QueryCommand::AccountData(command) => account_data(command).await?,
|
|
||||||
QueryCommand::Appservice(command) => appservice(command).await?,
|
|
||||||
QueryCommand::Presence(command) => presence(command).await?,
|
|
||||||
QueryCommand::RoomAlias(command) => room_alias(command).await?,
|
|
||||||
QueryCommand::RoomStateCache(command) => room_state_cache(command).await?,
|
|
||||||
QueryCommand::Globals(command) => globals(command).await?,
|
|
||||||
QueryCommand::Sending(command) => sending(command).await?,
|
|
||||||
QueryCommand::Users(command) => users(command).await?,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-10
@@ -1,30 +1,49 @@
|
|||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::{events::room::message::RoomMessageEventContent, UserId};
|
||||||
|
|
||||||
use super::Presence;
|
use crate::Command;
|
||||||
use crate::{services, Result};
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
/// All the getters and iterators from src/database/key_value/presence.rs
|
||||||
|
pub(crate) enum PresenceCommand {
|
||||||
|
/// - Returns the latest presence event for the given user.
|
||||||
|
GetPresence {
|
||||||
|
/// Full user ID
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Iterator of the most recent presence updates that happened after the
|
||||||
|
/// event with id `since`.
|
||||||
|
PresenceSince {
|
||||||
|
/// UNIX timestamp since (u64)
|
||||||
|
since: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// All the getters and iterators in key_value/presence.rs
|
/// All the getters and iterators in key_value/presence.rs
|
||||||
pub(super) async fn presence(subcommand: Presence) -> Result<RoomMessageEventContent> {
|
pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
|
let services = context.services;
|
||||||
|
|
||||||
match subcommand {
|
match subcommand {
|
||||||
Presence::GetPresence {
|
PresenceCommand::GetPresence {
|
||||||
user_id,
|
user_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().presence.db.get_presence(&user_id)?;
|
let results = services.presence.db.get_presence(&user_id)?;
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Presence::PresenceSince {
|
PresenceCommand::PresenceSince {
|
||||||
since,
|
since,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().presence.db.presence_since(since);
|
let results = services.presence.db.presence_since(since);
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
let presence_since: Vec<(_, _, _)> = results.collect();
|
let presence_since: Vec<(_, _, _)> = results.collect();
|
||||||
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{presence_since:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{presence_since:#?}\n```"
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
use conduit::{utils::time, Result};
|
||||||
|
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
|
||||||
|
|
||||||
|
use crate::{admin_command, admin_command_dispatch};
|
||||||
|
|
||||||
|
#[admin_command_dispatch]
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
/// Resolver service and caches
|
||||||
|
pub(crate) enum ResolverCommand {
|
||||||
|
/// Query the destinations cache
|
||||||
|
DestinationsCache {
|
||||||
|
server_name: Option<OwnedServerName>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Query the overrides cache
|
||||||
|
OverridesCache {
|
||||||
|
name: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
)| {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
},
|
||||||
|
)| {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||||
|
}
|
||||||
@@ -1,41 +1,60 @@
|
|||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
|
||||||
|
|
||||||
use super::RoomAlias;
|
use crate::Command;
|
||||||
use crate::{services, Result};
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
|
||||||
|
pub(crate) enum RoomAliasCommand {
|
||||||
|
ResolveLocalAlias {
|
||||||
|
/// Full room alias
|
||||||
|
alias: Box<RoomAliasId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Iterator of all our local room aliases for the room ID
|
||||||
|
LocalAliasesForRoom {
|
||||||
|
/// Full room ID
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Iterator of all our local aliases in our database with their room IDs
|
||||||
|
AllLocalAliases,
|
||||||
|
}
|
||||||
|
|
||||||
/// All the getters and iterators in src/database/key_value/rooms/alias.rs
|
/// All the getters and iterators in src/database/key_value/rooms/alias.rs
|
||||||
pub(super) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEventContent> {
|
pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
|
let services = context.services;
|
||||||
|
|
||||||
match subcommand {
|
match subcommand {
|
||||||
RoomAlias::ResolveLocalAlias {
|
RoomAliasCommand::ResolveLocalAlias {
|
||||||
alias,
|
alias,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().rooms.alias.db.resolve_local_alias(&alias);
|
let results = services.rooms.alias.resolve_local_alias(&alias);
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomAlias::LocalAliasesForRoom {
|
RoomAliasCommand::LocalAliasesForRoom {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().rooms.alias.db.local_aliases_for_room(&room_id);
|
let results = services.rooms.alias.local_aliases_for_room(&room_id);
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
let aliases: Vec<_> = results.collect();
|
let aliases: Vec<_> = results.collect();
|
||||||
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomAlias::AllLocalAliases => {
|
RoomAliasCommand::AllLocalAliases => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().rooms.alias.db.all_local_aliases();
|
let results = services.rooms.alias.all_local_aliases();
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
let aliases: Vec<_> = results.collect();
|
let aliases: Vec<_> = results.collect();
|
||||||
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
|
||||||
|
|||||||
@@ -1,71 +1,136 @@
|
|||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
|
||||||
|
|
||||||
use super::RoomStateCache;
|
use crate::Command;
|
||||||
use crate::{services, Result};
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub(crate) enum RoomStateCacheCommand {
|
||||||
|
ServerInRoom {
|
||||||
|
server: Box<ServerName>,
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomServers {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
ServerRooms {
|
||||||
|
server: Box<ServerName>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomMembers {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
LocalUsersInRoom {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
ActiveLocalUsersInRoom {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomJoinedCount {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomInvitedCount {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomUserOnceJoined {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomMembersInvited {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
GetInviteCount {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
GetLeftCount {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomsJoined {
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomsLeft {
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
RoomsInvited {
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
InviteState {
|
||||||
|
user_id: Box<UserId>,
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn process(
|
||||||
|
subcommand: RoomStateCacheCommand, context: &Command<'_>,
|
||||||
|
) -> Result<RoomMessageEventContent> {
|
||||||
|
let services = context.services;
|
||||||
|
|
||||||
pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomMessageEventContent> {
|
|
||||||
match subcommand {
|
match subcommand {
|
||||||
RoomStateCache::ServerInRoom {
|
RoomStateCacheCommand::ServerInRoom {
|
||||||
server,
|
server,
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let result = services()
|
let result = services.rooms.state_cache.server_in_room(&server, &room_id);
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.server_in_room(&server, &room_id);
|
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomServers {
|
RoomStateCacheCommand::RoomServers {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Result<Vec<_>> = services()
|
let results: Result<Vec<_>> = services.rooms.state_cache.room_servers(&room_id).collect();
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_servers(&room_id)
|
|
||||||
.collect();
|
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::ServerRooms {
|
RoomStateCacheCommand::ServerRooms {
|
||||||
server,
|
server,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Result<Vec<_>> = services().rooms.state_cache.server_rooms(&server).collect();
|
let results: Result<Vec<_>> = services.rooms.state_cache.server_rooms(&server).collect();
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomMembers {
|
RoomStateCacheCommand::RoomMembers {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Result<Vec<_>> = services()
|
let results: Result<Vec<_>> = services.rooms.state_cache.room_members(&room_id).collect();
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_members(&room_id)
|
|
||||||
.collect();
|
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::LocalUsersInRoom {
|
RoomStateCacheCommand::LocalUsersInRoom {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Vec<_> = services()
|
let results: Vec<_> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.local_users_in_room(&room_id)
|
.local_users_in_room(&room_id)
|
||||||
@@ -76,11 +141,11 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
|
|||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::ActiveLocalUsersInRoom {
|
RoomStateCacheCommand::ActiveLocalUsersInRoom {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Vec<_> = services()
|
let results: Vec<_> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.active_local_users_in_room(&room_id)
|
.active_local_users_in_room(&room_id)
|
||||||
@@ -91,33 +156,33 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
|
|||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomJoinedCount {
|
RoomStateCacheCommand::RoomJoinedCount {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().rooms.state_cache.room_joined_count(&room_id);
|
let results = services.rooms.state_cache.room_joined_count(&room_id);
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomInvitedCount {
|
RoomStateCacheCommand::RoomInvitedCount {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().rooms.state_cache.room_invited_count(&room_id);
|
let results = services.rooms.state_cache.room_invited_count(&room_id);
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomUserOnceJoined {
|
RoomStateCacheCommand::RoomUserOnceJoined {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Result<Vec<_>> = services()
|
let results: Result<Vec<_>> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_useroncejoined(&room_id)
|
.room_useroncejoined(&room_id)
|
||||||
@@ -128,11 +193,11 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
|
|||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomMembersInvited {
|
RoomStateCacheCommand::RoomMembersInvited {
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Result<Vec<_>> = services()
|
let results: Result<Vec<_>> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_members_invited(&room_id)
|
.room_members_invited(&room_id)
|
||||||
@@ -143,12 +208,12 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
|
|||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::GetInviteCount {
|
RoomStateCacheCommand::GetInviteCount {
|
||||||
room_id,
|
room_id,
|
||||||
user_id,
|
user_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services()
|
let results = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.get_invite_count(&room_id, &user_id);
|
.get_invite_count(&room_id, &user_id);
|
||||||
@@ -158,12 +223,12 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
|
|||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::GetLeftCount {
|
RoomStateCacheCommand::GetLeftCount {
|
||||||
room_id,
|
room_id,
|
||||||
user_id,
|
user_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services()
|
let results = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.get_left_count(&room_id, &user_id);
|
.get_left_count(&room_id, &user_id);
|
||||||
@@ -173,56 +238,45 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
|
|||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomsJoined {
|
RoomStateCacheCommand::RoomsJoined {
|
||||||
user_id,
|
user_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Result<Vec<_>> = services()
|
let results: Result<Vec<_>> = services.rooms.state_cache.rooms_joined(&user_id).collect();
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.rooms_joined(&user_id)
|
|
||||||
.collect();
|
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomsInvited {
|
RoomStateCacheCommand::RoomsInvited {
|
||||||
user_id,
|
user_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Result<Vec<_>> = services()
|
let results: Result<Vec<_>> = services.rooms.state_cache.rooms_invited(&user_id).collect();
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.rooms_invited(&user_id)
|
|
||||||
.collect();
|
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::RoomsLeft {
|
RoomStateCacheCommand::RoomsLeft {
|
||||||
user_id,
|
user_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results: Result<Vec<_>> = services().rooms.state_cache.rooms_left(&user_id).collect();
|
let results: Result<Vec<_>> = services.rooms.state_cache.rooms_left(&user_id).collect();
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
RoomStateCache::InviteState {
|
RoomStateCacheCommand::InviteState {
|
||||||
user_id,
|
user_id,
|
||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services()
|
let results = services.rooms.state_cache.invite_state(&user_id, &room_id);
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.invite_state(&user_id, &room_id);
|
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
|
|||||||
+89
-56
@@ -1,23 +1,81 @@
|
|||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::{events::room::message::RoomMessageEventContent, ServerName, UserId};
|
||||||
|
use service::sending::Destination;
|
||||||
|
|
||||||
use super::Sending;
|
use crate::Command;
|
||||||
use crate::{service::sending::Destination, services, Result};
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
/// All the getters and iterators from src/database/key_value/sending.rs
|
||||||
|
pub(crate) enum SendingCommand {
|
||||||
|
/// - Queries database for all `servercurrentevent_data`
|
||||||
|
ActiveRequests,
|
||||||
|
|
||||||
|
/// - Queries database for `servercurrentevent_data` but for a specific
|
||||||
|
/// destination
|
||||||
|
///
|
||||||
|
/// This command takes only *one* format of these arguments:
|
||||||
|
///
|
||||||
|
/// appservice_id
|
||||||
|
/// server_name
|
||||||
|
/// user_id AND push_key
|
||||||
|
///
|
||||||
|
/// See src/service/sending/mod.rs for the definition of the `Destination`
|
||||||
|
/// enum
|
||||||
|
ActiveRequestsFor {
|
||||||
|
#[arg(short, long)]
|
||||||
|
appservice_id: Option<String>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
server_name: Option<Box<ServerName>>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
user_id: Option<Box<UserId>>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
push_key: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Queries database for `servernameevent_data` which are the queued up
|
||||||
|
/// requests that will eventually be sent
|
||||||
|
///
|
||||||
|
/// This command takes only *one* format of these arguments:
|
||||||
|
///
|
||||||
|
/// appservice_id
|
||||||
|
/// server_name
|
||||||
|
/// user_id AND push_key
|
||||||
|
///
|
||||||
|
/// See src/service/sending/mod.rs for the definition of the `Destination`
|
||||||
|
/// enum
|
||||||
|
QueuedRequests {
|
||||||
|
#[arg(short, long)]
|
||||||
|
appservice_id: Option<String>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
server_name: Option<Box<ServerName>>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
user_id: Option<Box<UserId>>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
push_key: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
GetLatestEduCount {
|
||||||
|
server_name: Box<ServerName>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
/// All the getters and iterators in key_value/sending.rs
|
/// All the getters and iterators in key_value/sending.rs
|
||||||
pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventContent> {
|
pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
match subcommand {
|
let services = context.services;
|
||||||
Sending::ActiveRequests => {
|
|
||||||
let timer = tokio::time::Instant::now();
|
|
||||||
let results = services().sending.db.active_requests();
|
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
|
match subcommand {
|
||||||
|
SendingCommand::ActiveRequests => {
|
||||||
|
let timer = tokio::time::Instant::now();
|
||||||
|
let results = services.sending.db.active_requests();
|
||||||
let active_requests: Result<Vec<(_, _, _)>> = results.collect();
|
let active_requests: Result<Vec<(_, _, _)>> = results.collect();
|
||||||
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Sending::QueuedRequests {
|
SendingCommand::QueuedRequests {
|
||||||
appservice_id,
|
appservice_id,
|
||||||
server_name,
|
server_name,
|
||||||
user_id,
|
user_id,
|
||||||
@@ -29,8 +87,8 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
|||||||
--help for more details.",
|
--help for more details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let timer = tokio::time::Instant::now();
|
||||||
let (results, query_time) = match (appservice_id, server_name, user_id, push_key) {
|
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() {
|
if appservice_id.is_empty() {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
@@ -39,25 +97,15 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let timer = tokio::time::Instant::now();
|
services
|
||||||
let results = services()
|
|
||||||
.sending
|
.sending
|
||||||
.db
|
.db
|
||||||
.queued_requests(&Destination::Appservice(appservice_id));
|
.queued_requests(&Destination::Appservice(appservice_id))
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
(results, query_time)
|
|
||||||
},
|
},
|
||||||
(None, Some(server_name), None, None) => {
|
(None, Some(server_name), None, None) => services
|
||||||
let timer = tokio::time::Instant::now();
|
|
||||||
let results = services()
|
|
||||||
.sending
|
.sending
|
||||||
.db
|
.db
|
||||||
.queued_requests(&Destination::Normal(server_name.into()));
|
.queued_requests(&Destination::Normal(server_name.into())),
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
(results, query_time)
|
|
||||||
},
|
|
||||||
(None, None, Some(user_id), Some(push_key)) => {
|
(None, None, Some(user_id), Some(push_key)) => {
|
||||||
if push_key.is_empty() {
|
if push_key.is_empty() {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
@@ -66,14 +114,10 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let timer = tokio::time::Instant::now();
|
services
|
||||||
let results = services()
|
|
||||||
.sending
|
.sending
|
||||||
.db
|
.db
|
||||||
.queued_requests(&Destination::Push(user_id.into(), push_key));
|
.queued_requests(&Destination::Push(user_id.into(), push_key))
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
(results, query_time)
|
|
||||||
},
|
},
|
||||||
(Some(_), Some(_), Some(_), Some(_)) => {
|
(Some(_), Some(_), Some(_), Some(_)) => {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
@@ -90,12 +134,13 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
|||||||
};
|
};
|
||||||
|
|
||||||
let queued_requests = results.collect::<Result<Vec<(_, _)>>>();
|
let queued_requests = results.collect::<Result<Vec<(_, _)>>>();
|
||||||
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Sending::ActiveRequestsFor {
|
SendingCommand::ActiveRequestsFor {
|
||||||
appservice_id,
|
appservice_id,
|
||||||
server_name,
|
server_name,
|
||||||
user_id,
|
user_id,
|
||||||
@@ -108,7 +153,8 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (results, query_time) = match (appservice_id, server_name, user_id, push_key) {
|
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() {
|
if appservice_id.is_empty() {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
@@ -117,25 +163,15 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let timer = tokio::time::Instant::now();
|
services
|
||||||
let results = services()
|
|
||||||
.sending
|
.sending
|
||||||
.db
|
.db
|
||||||
.active_requests_for(&Destination::Appservice(appservice_id));
|
.active_requests_for(&Destination::Appservice(appservice_id))
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
(results, query_time)
|
|
||||||
},
|
},
|
||||||
(None, Some(server_name), None, None) => {
|
(None, Some(server_name), None, None) => services
|
||||||
let timer = tokio::time::Instant::now();
|
|
||||||
let results = services()
|
|
||||||
.sending
|
.sending
|
||||||
.db
|
.db
|
||||||
.active_requests_for(&Destination::Normal(server_name.into()));
|
.active_requests_for(&Destination::Normal(server_name.into())),
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
(results, query_time)
|
|
||||||
},
|
|
||||||
(None, None, Some(user_id), Some(push_key)) => {
|
(None, None, Some(user_id), Some(push_key)) => {
|
||||||
if push_key.is_empty() {
|
if push_key.is_empty() {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
@@ -144,14 +180,10 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let timer = tokio::time::Instant::now();
|
services
|
||||||
let results = services()
|
|
||||||
.sending
|
.sending
|
||||||
.db
|
.db
|
||||||
.active_requests_for(&Destination::Push(user_id.into(), push_key));
|
.active_requests_for(&Destination::Push(user_id.into(), push_key))
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
(results, query_time)
|
|
||||||
},
|
},
|
||||||
(Some(_), Some(_), Some(_), Some(_)) => {
|
(Some(_), Some(_), Some(_), Some(_)) => {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
@@ -168,16 +200,17 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
|||||||
};
|
};
|
||||||
|
|
||||||
let active_requests = results.collect::<Result<Vec<(_, _)>>>();
|
let active_requests = results.collect::<Result<Vec<(_, _)>>>();
|
||||||
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Sending::GetLatestEduCount {
|
SendingCommand::GetLatestEduCount {
|
||||||
server_name,
|
server_name,
|
||||||
} => {
|
} => {
|
||||||
let timer = tokio::time::Instant::now();
|
let timer = tokio::time::Instant::now();
|
||||||
let results = services().sending.db.get_latest_educount(&server_name);
|
let results = services.sending.db.get_latest_educount(&server_name);
|
||||||
let query_time = timer.elapsed();
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
use super::Users;
|
use crate::Command;
|
||||||
use crate::{services, Result};
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
/// All the getters and iterators from src/database/key_value/users.rs
|
||||||
|
pub(crate) enum UsersCommand {
|
||||||
|
Iter,
|
||||||
|
}
|
||||||
|
|
||||||
/// All the getters and iterators in key_value/users.rs
|
/// All the getters and iterators in key_value/users.rs
|
||||||
pub(super) async fn users(subcommand: Users) -> Result<RoomMessageEventContent> {
|
pub(super) async fn process(subcommand: UsersCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
match subcommand {
|
let services = context.services;
|
||||||
Users::Iter => {
|
|
||||||
let timer = tokio::time::Instant::now();
|
|
||||||
let results = services().users.db.iter();
|
|
||||||
let query_time = timer.elapsed();
|
|
||||||
|
|
||||||
|
match subcommand {
|
||||||
|
UsersCommand::Iter => {
|
||||||
|
let timer = tokio::time::Instant::now();
|
||||||
|
let results = services.users.db.iter();
|
||||||
let users = results.collect::<Vec<_>>();
|
let users = results.collect::<Vec<_>>();
|
||||||
|
let query_time = timer.elapsed();
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Query completed in {query_time:?}:\n\n```rs\n{users:#?}\n```"
|
"Query completed in {query_time:?}:\n\n```rs\n{users:#?}\n```"
|
||||||
|
|||||||
@@ -1,12 +1,49 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId};
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
|
||||||
|
|
||||||
use super::RoomAliasCommand;
|
use crate::{escape_html, Command};
|
||||||
use crate::{escape_html, services, Result};
|
|
||||||
|
|
||||||
pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[derive(Debug, Subcommand)]
|
||||||
let server_user = &services().globals.server_user;
|
pub(crate) enum RoomAliasCommand {
|
||||||
|
/// - Make an alias point to a room.
|
||||||
|
Set {
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// Set the alias even if a room is already using it
|
||||||
|
force: bool,
|
||||||
|
|
||||||
|
/// The room id to set the alias on
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
|
||||||
|
/// The alias localpart to use (`alias`, not `#alias:servername.tld`)
|
||||||
|
room_alias_localpart: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Remove a local alias
|
||||||
|
Remove {
|
||||||
|
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
|
||||||
|
room_alias_localpart: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Show which room is using an alias
|
||||||
|
Which {
|
||||||
|
/// The alias localpart to look up (`alias`, not
|
||||||
|
/// `#alias:servername.tld`)
|
||||||
|
room_alias_localpart: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - List aliases currently being used
|
||||||
|
List {
|
||||||
|
/// If set, only list the aliases for this room
|
||||||
|
room_id: Option<Box<RoomId>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
|
let services = context.services;
|
||||||
|
let server_user = &services.globals.server_user;
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
RoomAliasCommand::Set {
|
RoomAliasCommand::Set {
|
||||||
@@ -19,7 +56,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
|||||||
| RoomAliasCommand::Which {
|
| RoomAliasCommand::Which {
|
||||||
ref room_alias_localpart,
|
ref room_alias_localpart,
|
||||||
} => {
|
} => {
|
||||||
let room_alias_str = format!("#{}:{}", room_alias_localpart, services().globals.server_name());
|
let room_alias_str = format!("#{}:{}", room_alias_localpart, services.globals.server_name());
|
||||||
let room_alias = match RoomAliasId::parse_box(room_alias_str) {
|
let room_alias = match RoomAliasId::parse_box(room_alias_str) {
|
||||||
Ok(alias) => alias,
|
Ok(alias) => alias,
|
||||||
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))),
|
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))),
|
||||||
@@ -29,8 +66,8 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
|||||||
force,
|
force,
|
||||||
room_id,
|
room_id,
|
||||||
..
|
..
|
||||||
} => match (force, services().rooms.alias.resolve_local_alias(&room_alias)) {
|
} => match (force, services.rooms.alias.resolve_local_alias(&room_alias)) {
|
||||||
(true, Ok(Some(id))) => match services()
|
(true, Ok(Some(id))) => match services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.set_alias(&room_alias, &room_id, server_user)
|
.set_alias(&room_alias, &room_id, server_user)
|
||||||
@@ -43,7 +80,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
|||||||
(false, Ok(Some(id))) => Ok(RoomMessageEventContent::text_plain(format!(
|
(false, Ok(Some(id))) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
|
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
|
||||||
))),
|
))),
|
||||||
(_, Ok(None)) => match services()
|
(_, Ok(None)) => match services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.set_alias(&room_alias, &room_id, server_user)
|
.set_alias(&room_alias, &room_id, server_user)
|
||||||
@@ -55,8 +92,8 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
|||||||
},
|
},
|
||||||
RoomAliasCommand::Remove {
|
RoomAliasCommand::Remove {
|
||||||
..
|
..
|
||||||
} => match services().rooms.alias.resolve_local_alias(&room_alias) {
|
} => match services.rooms.alias.resolve_local_alias(&room_alias) {
|
||||||
Ok(Some(id)) => match services()
|
Ok(Some(id)) => match services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.remove_alias(&room_alias, server_user)
|
.remove_alias(&room_alias, server_user)
|
||||||
@@ -70,7 +107,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
|||||||
},
|
},
|
||||||
RoomAliasCommand::Which {
|
RoomAliasCommand::Which {
|
||||||
..
|
..
|
||||||
} => match services().rooms.alias.resolve_local_alias(&room_alias) {
|
} => match services.rooms.alias.resolve_local_alias(&room_alias) {
|
||||||
Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))),
|
Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))),
|
||||||
Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))),
|
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))),
|
||||||
@@ -84,7 +121,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
|||||||
room_id,
|
room_id,
|
||||||
} => {
|
} => {
|
||||||
if let Some(room_id) = room_id {
|
if let Some(room_id) = room_id {
|
||||||
let aliases = services()
|
let aliases = services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.local_aliases_for_room(&room_id)
|
.local_aliases_for_room(&room_id)
|
||||||
@@ -109,14 +146,14 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
|||||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {err}"))),
|
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {err}"))),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let aliases = services()
|
let aliases = services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.all_local_aliases()
|
.all_local_aliases()
|
||||||
.collect::<Result<Vec<_>, _>>();
|
.collect::<Result<Vec<_>, _>>();
|
||||||
match aliases {
|
match aliases {
|
||||||
Ok(aliases) => {
|
Ok(aliases) => {
|
||||||
let server_name = services().globals.server_name();
|
let server_name = services.globals.server_name();
|
||||||
let plain_list = aliases
|
let plain_list = aliases
|
||||||
.iter()
|
.iter()
|
||||||
.fold(String::new(), |mut output, (alias, id)| {
|
.fold(String::new(), |mut output, (alias, id)| {
|
||||||
@@ -1,18 +1,51 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
|
use conduit::Result;
|
||||||
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
|
use crate::{admin_command, escape_html, get_room_info, PAGE_SIZE};
|
||||||
|
|
||||||
pub(super) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
|
pub(super) async fn list_rooms(
|
||||||
|
&self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
|
||||||
|
) -> Result<RoomMessageEventContent> {
|
||||||
// TODO: i know there's a way to do this with clap, but i can't seem to find it
|
// 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 page = page.unwrap_or(1);
|
||||||
let mut rooms = services()
|
let mut rooms = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.metadata
|
.metadata
|
||||||
.iter_ids()
|
.iter_ids()
|
||||||
.filter_map(Result::ok)
|
.filter_map(|room_id| {
|
||||||
.map(|id: OwnedRoomId| get_room_info(&id))
|
room_id
|
||||||
|
.ok()
|
||||||
|
.filter(|room_id| {
|
||||||
|
if exclude_disabled
|
||||||
|
&& self
|
||||||
|
.services
|
||||||
|
.rooms
|
||||||
|
.metadata
|
||||||
|
.is_disabled(room_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if exclude_banned
|
||||||
|
&& self
|
||||||
|
.services
|
||||||
|
.rooms
|
||||||
|
.metadata
|
||||||
|
.is_banned(room_id)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.map(|room_id| get_room_info(self.services, &room_id))
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
rooms.sort_by_key(|r| r.1);
|
rooms.sort_by_key(|r| r.1);
|
||||||
rooms.reverse();
|
rooms.reverse();
|
||||||
@@ -1,21 +1,43 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
|
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId};
|
||||||
|
|
||||||
use super::RoomDirectoryCommand;
|
use crate::{escape_html, get_room_info, Command, PAGE_SIZE};
|
||||||
use crate::{escape_html, get_room_info, handler::PAGE_SIZE, services, Result};
|
|
||||||
|
|
||||||
pub(super) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub(crate) enum RoomDirectoryCommand {
|
||||||
|
/// - Publish a room to the room directory
|
||||||
|
Publish {
|
||||||
|
/// The room id of the room to publish
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Unpublish a room to the room directory
|
||||||
|
Unpublish {
|
||||||
|
/// The room id of the room to unpublish
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - List rooms that are published
|
||||||
|
List {
|
||||||
|
page: Option<usize>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||||
|
let services = context.services;
|
||||||
match command {
|
match command {
|
||||||
RoomDirectoryCommand::Publish {
|
RoomDirectoryCommand::Publish {
|
||||||
room_id,
|
room_id,
|
||||||
} => match services().rooms.directory.set_public(&room_id) {
|
} => match services.rooms.directory.set_public(&room_id) {
|
||||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room published")),
|
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room published")),
|
||||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
|
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
|
||||||
},
|
},
|
||||||
RoomDirectoryCommand::Unpublish {
|
RoomDirectoryCommand::Unpublish {
|
||||||
room_id,
|
room_id,
|
||||||
} => match services().rooms.directory.set_not_public(&room_id) {
|
} => match services.rooms.directory.set_not_public(&room_id) {
|
||||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room unpublished")),
|
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room unpublished")),
|
||||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
|
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
|
||||||
},
|
},
|
||||||
@@ -24,12 +46,12 @@ pub(super) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
|
|||||||
} => {
|
} => {
|
||||||
// TODO: i know there's a way to do this with clap, but i can't seem to find it
|
// 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 page = page.unwrap_or(1);
|
||||||
let mut rooms = services()
|
let mut rooms = services
|
||||||
.rooms
|
.rooms
|
||||||
.directory
|
.directory
|
||||||
.public_rooms()
|
.public_rooms()
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.map(|id: OwnedRoomId| get_room_info(&id))
|
.map(|id: OwnedRoomId| get_room_info(services, &id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
rooms.sort_by_key(|r| r.1);
|
rooms.sort_by_key(|r| r.1);
|
||||||
rooms.reverse();
|
rooms.reverse();
|
||||||
@@ -1,22 +1,30 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
use conduit::Result;
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
|
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
|
||||||
use service::services;
|
|
||||||
|
|
||||||
use super::RoomInfoCommand;
|
use crate::{admin_command, admin_command_dispatch};
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
pub(super) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command_dispatch]
|
||||||
match command {
|
#[derive(Debug, Subcommand)]
|
||||||
RoomInfoCommand::ListJoinedMembers {
|
pub(crate) enum RoomInfoCommand {
|
||||||
room_id,
|
/// - List joined members in a room
|
||||||
} => list_joined_members(body, room_id).await,
|
ListJoinedMembers {
|
||||||
RoomInfoCommand::ViewRoomTopic {
|
room_id: Box<RoomId>,
|
||||||
room_id,
|
},
|
||||||
} => view_room_topic(body, room_id).await,
|
|
||||||
}
|
/// - Displays room topic
|
||||||
|
///
|
||||||
|
/// Room topics can be huge, so this is in its
|
||||||
|
/// own separate command
|
||||||
|
ViewRoomTopic {
|
||||||
|
room_id: Box<RoomId>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let room_name = services()
|
async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||||
|
let room_name = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.get_name(&room_id)
|
.get_name(&room_id)
|
||||||
@@ -24,7 +32,8 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
|
|||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or_else(|| room_id.to_string());
|
.unwrap_or_else(|| room_id.to_string());
|
||||||
|
|
||||||
let members = services()
|
let members = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_members(&room_id)
|
.room_members(&room_id)
|
||||||
@@ -35,7 +44,7 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
|
|||||||
.map(|user_id| {
|
.map(|user_id| {
|
||||||
(
|
(
|
||||||
user_id.clone(),
|
user_id.clone(),
|
||||||
services()
|
self.services
|
||||||
.users
|
.users
|
||||||
.displayname(&user_id)
|
.displayname(&user_id)
|
||||||
.unwrap_or(None)
|
.unwrap_or(None)
|
||||||
@@ -58,12 +67,18 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
|
|||||||
Ok(RoomMessageEventContent::notice_markdown(output_plain))
|
Ok(RoomMessageEventContent::notice_markdown(output_plain))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let Some(room_topic) = services().rooms.state_accessor.get_room_topic(&room_id)? else {
|
async fn view_room_topic(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||||
|
let Some(room_topic) = self
|
||||||
|
.services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.get_room_topic(&room_id)?
|
||||||
|
else {
|
||||||
return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set."));
|
return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set."));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
"Room topic:\n\n```{room_topic}\n```"
|
"Room topic:\n```\n{room_topic}\n```"
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
+21
-158
@@ -1,21 +1,32 @@
|
|||||||
mod room_alias_commands;
|
mod alias;
|
||||||
mod room_commands;
|
mod commands;
|
||||||
mod room_directory_commands;
|
mod directory;
|
||||||
mod room_info_commands;
|
mod info;
|
||||||
mod room_moderation_commands;
|
mod moderation;
|
||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId, RoomOrAliasId};
|
|
||||||
|
|
||||||
use self::room_commands::list;
|
use self::{
|
||||||
|
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, moderation::RoomModerationCommand,
|
||||||
|
};
|
||||||
|
use crate::admin_command_dispatch;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[admin_command_dispatch]
|
||||||
#[derive(Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(super) enum RoomCommand {
|
pub(super) enum RoomCommand {
|
||||||
/// - List all rooms the server knows about
|
/// - List all rooms the server knows about
|
||||||
List {
|
#[clap(alias = "list")]
|
||||||
|
ListRooms {
|
||||||
page: Option<usize>,
|
page: Option<usize>,
|
||||||
|
|
||||||
|
/// Excludes rooms that we have federation disabled with
|
||||||
|
#[arg(long)]
|
||||||
|
exclude_disabled: bool,
|
||||||
|
|
||||||
|
/// Excludes rooms that we have banned
|
||||||
|
#[arg(long)]
|
||||||
|
exclude_banned: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
@@ -34,151 +45,3 @@ pub(super) enum RoomCommand {
|
|||||||
/// - Manage the room directory
|
/// - Manage the room directory
|
||||||
Directory(RoomDirectoryCommand),
|
Directory(RoomDirectoryCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub(super) enum RoomInfoCommand {
|
|
||||||
/// - List joined members in a room
|
|
||||||
ListJoinedMembers {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Displays room topic
|
|
||||||
///
|
|
||||||
/// Room topics can be huge, so this is in its
|
|
||||||
/// own separate command
|
|
||||||
ViewRoomTopic {
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub(super) enum RoomAliasCommand {
|
|
||||||
/// - Make an alias point to a room.
|
|
||||||
Set {
|
|
||||||
#[arg(short, long)]
|
|
||||||
/// Set the alias even if a room is already using it
|
|
||||||
force: bool,
|
|
||||||
|
|
||||||
/// The room id to set the alias on
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
|
|
||||||
/// The alias localpart to use (`alias`, not `#alias:servername.tld`)
|
|
||||||
room_alias_localpart: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Remove a local alias
|
|
||||||
Remove {
|
|
||||||
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
|
|
||||||
room_alias_localpart: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Show which room is using an alias
|
|
||||||
Which {
|
|
||||||
/// The alias localpart to look up (`alias`, not
|
|
||||||
/// `#alias:servername.tld`)
|
|
||||||
room_alias_localpart: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - List aliases currently being used
|
|
||||||
List {
|
|
||||||
/// If set, only list the aliases for this room
|
|
||||||
room_id: Option<Box<RoomId>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub(super) enum RoomDirectoryCommand {
|
|
||||||
/// - Publish a room to the room directory
|
|
||||||
Publish {
|
|
||||||
/// The room id of the room to publish
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Unpublish a room to the room directory
|
|
||||||
Unpublish {
|
|
||||||
/// The room id of the room to unpublish
|
|
||||||
room_id: Box<RoomId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - List rooms that are published
|
|
||||||
List {
|
|
||||||
page: Option<usize>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
pub(super) enum RoomModerationCommand {
|
|
||||||
/// - Bans a room from local users joining and evicts all our local users
|
|
||||||
/// from the room. Also blocks any invites (local and remote) for the
|
|
||||||
/// banned room.
|
|
||||||
///
|
|
||||||
/// Server admins (users in the conduwuit admin room) will not be evicted
|
|
||||||
/// and server admins can still join the room. To evict admins too, use
|
|
||||||
/// --force (also ignores errors) To disable incoming federation of the
|
|
||||||
/// room, use --disable-federation
|
|
||||||
BanRoom {
|
|
||||||
#[arg(short, long)]
|
|
||||||
/// Evicts admins out of the room and ignores any potential errors when
|
|
||||||
/// making our local users leave the room
|
|
||||||
force: bool,
|
|
||||||
|
|
||||||
#[arg(long)]
|
|
||||||
/// Disables incoming federation of the room after banning and evicting
|
|
||||||
/// users
|
|
||||||
disable_federation: bool,
|
|
||||||
|
|
||||||
/// The room in the format of `!roomid:example.com` or a room alias in
|
|
||||||
/// the format of `#roomalias:example.com`
|
|
||||||
room: Box<RoomOrAliasId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Bans a list of rooms (room IDs and room aliases) from a newline
|
|
||||||
/// delimited codeblock similar to `user deactivate-all`
|
|
||||||
BanListOfRooms {
|
|
||||||
#[arg(short, long)]
|
|
||||||
/// Evicts admins out of the room and ignores any potential errors when
|
|
||||||
/// making our local users leave the room
|
|
||||||
force: bool,
|
|
||||||
|
|
||||||
#[arg(long)]
|
|
||||||
/// Disables incoming federation of the room after banning and evicting
|
|
||||||
/// users
|
|
||||||
disable_federation: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Unbans a room to allow local users to join again
|
|
||||||
///
|
|
||||||
/// To re-enable incoming federation of the room, use --enable-federation
|
|
||||||
UnbanRoom {
|
|
||||||
#[arg(long)]
|
|
||||||
/// Enables incoming federation of the room after unbanning
|
|
||||||
enable_federation: bool,
|
|
||||||
|
|
||||||
/// The room in the format of `!roomid:example.com` or a room alias in
|
|
||||||
/// the format of `#roomalias:example.com`
|
|
||||||
room: Box<RoomOrAliasId>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - List of all rooms we have banned
|
|
||||||
ListBannedRooms,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
RoomCommand::Info(command) => room_info_commands::process(command, body).await?,
|
|
||||||
|
|
||||||
RoomCommand::Alias(command) => room_alias_commands::process(command, body).await?,
|
|
||||||
|
|
||||||
RoomCommand::Directory(command) => room_directory_commands::process(command, body).await?,
|
|
||||||
|
|
||||||
RoomCommand::Moderation(command) => room_moderation_commands::process(command, body).await?,
|
|
||||||
|
|
||||||
RoomCommand::List {
|
|
||||||
page,
|
|
||||||
} => list(body, page).await?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,39 +1,77 @@
|
|||||||
use api::client::{get_alias_helper, leave_room};
|
use api::client::leave_room;
|
||||||
use ruma::{
|
use clap::Subcommand;
|
||||||
events::room::message::RoomMessageEventContent, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId,
|
use conduit::{debug, error, info, warn, Result};
|
||||||
};
|
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
|
||||||
use tracing::{debug, error, info, warn};
|
|
||||||
|
|
||||||
use super::{super::Service, RoomModerationCommand};
|
use crate::{admin_command, admin_command_dispatch, get_room_info};
|
||||||
use crate::{get_room_info, services, user_is_local, Result};
|
|
||||||
|
|
||||||
pub(super) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command_dispatch]
|
||||||
match command {
|
#[derive(Debug, Subcommand)]
|
||||||
RoomModerationCommand::BanRoom {
|
pub(crate) enum RoomModerationCommand {
|
||||||
force,
|
/// - Bans a room from local users joining and evicts all our local users
|
||||||
room,
|
/// from the room. Also blocks any invites (local and remote) for the
|
||||||
disable_federation,
|
/// banned room.
|
||||||
} => ban_room(body, force, room, disable_federation).await,
|
///
|
||||||
RoomModerationCommand::BanListOfRooms {
|
/// Server admins (users in the conduwuit admin room) will not be evicted
|
||||||
force,
|
/// and server admins can still join the room. To evict admins too, use
|
||||||
disable_federation,
|
/// --force (also ignores errors) To disable incoming federation of the
|
||||||
} => ban_list_of_rooms(body, force, disable_federation).await,
|
/// room, use --disable-federation
|
||||||
RoomModerationCommand::UnbanRoom {
|
BanRoom {
|
||||||
room,
|
#[arg(short, long)]
|
||||||
enable_federation,
|
/// Evicts admins out of the room and ignores any potential errors when
|
||||||
} => unban_room(body, room, enable_federation).await,
|
/// making our local users leave the room
|
||||||
RoomModerationCommand::ListBannedRooms => list_banned_rooms(body).await,
|
force: bool,
|
||||||
}
|
|
||||||
|
#[arg(long)]
|
||||||
|
/// Disables incoming federation of the room after banning and evicting
|
||||||
|
/// users
|
||||||
|
disable_federation: bool,
|
||||||
|
|
||||||
|
/// The room in the format of `!roomid:example.com` or a room alias in
|
||||||
|
/// the format of `#roomalias:example.com`
|
||||||
|
room: Box<RoomOrAliasId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Bans a list of rooms (room IDs and room aliases) from a newline
|
||||||
|
/// delimited codeblock similar to `user deactivate-all`
|
||||||
|
BanListOfRooms {
|
||||||
|
#[arg(short, long)]
|
||||||
|
/// Evicts admins out of the room and ignores any potential errors when
|
||||||
|
/// making our local users leave the room
|
||||||
|
force: bool,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
/// Disables incoming federation of the room after banning and evicting
|
||||||
|
/// users
|
||||||
|
disable_federation: bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Unbans a room to allow local users to join again
|
||||||
|
///
|
||||||
|
/// To re-enable incoming federation of the room, use --enable-federation
|
||||||
|
UnbanRoom {
|
||||||
|
#[arg(long)]
|
||||||
|
/// Enables incoming federation of the room after unbanning
|
||||||
|
enable_federation: bool,
|
||||||
|
|
||||||
|
/// The room in the format of `!roomid:example.com` or a room alias in
|
||||||
|
/// the format of `#roomalias:example.com`
|
||||||
|
room: Box<RoomOrAliasId>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - List of all rooms we have banned
|
||||||
|
ListBannedRooms,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
async fn ban_room(
|
async fn ban_room(
|
||||||
_body: Vec<&str>, force: bool, room: Box<RoomOrAliasId>, disable_federation: bool,
|
&self, force: bool, disable_federation: bool, room: Box<RoomOrAliasId>,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
debug!("Got room alias or ID: {}", room);
|
debug!("Got room alias or ID: {}", room);
|
||||||
|
|
||||||
let admin_room_alias = &services().globals.admin_alias;
|
let admin_room_alias = &self.services.globals.admin_alias;
|
||||||
|
|
||||||
if let Some(admin_room_id) = Service::get_admin_room()? {
|
if let Some(admin_room_id) = self.services.admin.get_admin_room()? {
|
||||||
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
|
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
|
||||||
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
|
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
|
||||||
}
|
}
|
||||||
@@ -52,7 +90,7 @@ async fn ban_room(
|
|||||||
|
|
||||||
debug!("Room specified is a room ID, banning room ID");
|
debug!("Room specified is a room ID, banning room ID");
|
||||||
|
|
||||||
services().rooms.metadata.ban_room(&room_id, true)?;
|
self.services.rooms.metadata.ban_room(&room_id, true)?;
|
||||||
|
|
||||||
room_id
|
room_id
|
||||||
} else if room.is_room_alias_id() {
|
} else if room.is_room_alias_id() {
|
||||||
@@ -71,25 +109,31 @@ async fn ban_room(
|
|||||||
get_alias_helper to fetch room ID remotely"
|
get_alias_helper to fetch room ID remotely"
|
||||||
);
|
);
|
||||||
|
|
||||||
let room_id = if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
|
let room_id = if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
|
||||||
room_id
|
room_id
|
||||||
} else {
|
} 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 get_alias_helper(room_alias, None).await {
|
match self
|
||||||
Ok(response) => {
|
.services
|
||||||
debug!("Got federation response fetching room ID for room {room}: {:?}", response);
|
.rooms
|
||||||
response.room_id
|
.alias
|
||||||
|
.resolve_alias(&room_alias, None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((room_id, servers)) => {
|
||||||
|
debug!(?room_id, ?servers, "Got federation response fetching room ID for {room}");
|
||||||
|
room_id
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
return Ok(RoomMessageEventContent::notice_plain(format!(
|
||||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||||
)));
|
)));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
services().rooms.metadata.ban_room(&room_id, true)?;
|
self.services.rooms.metadata.ban_room(&room_id, true)?;
|
||||||
|
|
||||||
room_id
|
room_id
|
||||||
} else {
|
} else {
|
||||||
@@ -101,58 +145,56 @@ async fn ban_room(
|
|||||||
|
|
||||||
debug!("Making all users leave the room {}", &room);
|
debug!("Making all users leave the room {}", &room);
|
||||||
if force {
|
if force {
|
||||||
for local_user in services()
|
for local_user in self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_members(&room_id)
|
.room_members(&room_id)
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
user.ok().filter(|local_user| {
|
user.ok().filter(|local_user| {
|
||||||
user_is_local(local_user)
|
self.services.globals.user_is_local(local_user)
|
||||||
// additional wrapped check here is to avoid adding remote users
|
// additional wrapped check here is to avoid adding remote users
|
||||||
// who are in the admin room to the list of local users (would
|
// who are in the admin room to the list of local users (would
|
||||||
// fail auth check)
|
// fail auth check)
|
||||||
&& (user_is_local(local_user)
|
&& (self.services.globals.user_is_local(local_user)
|
||||||
// since this is a force operation, assume user is an admin
|
// since this is a force operation, assume user is an admin
|
||||||
// if somehow this fails
|
// if somehow this fails
|
||||||
&& services()
|
&& self.services
|
||||||
.users
|
.users
|
||||||
.is_admin(local_user)
|
.is_admin(local_user)
|
||||||
.unwrap_or(true))
|
.unwrap_or(true))
|
||||||
})
|
})
|
||||||
})
|
}) {
|
||||||
.collect::<Vec<OwnedUserId>>()
|
|
||||||
{
|
|
||||||
debug!(
|
debug!(
|
||||||
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
|
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
|
||||||
&local_user, &room_id
|
&local_user, &room_id
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(e) = leave_room(&local_user, &room_id, None).await {
|
if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
|
||||||
warn!(%e, "Failed to leave room");
|
warn!(%e, "Failed to leave room");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for local_user in services()
|
for local_user in self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_members(&room_id)
|
.room_members(&room_id)
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
user.ok().filter(|local_user| {
|
user.ok().filter(|local_user| {
|
||||||
local_user.server_name() == services().globals.server_name()
|
local_user.server_name() == self.services.globals.server_name()
|
||||||
// additional wrapped check here is to avoid adding remote users
|
// additional wrapped check here is to avoid adding remote users
|
||||||
// who are in the admin room to the list of local users (would fail auth check)
|
// who are in the admin room to the list of local users (would fail auth check)
|
||||||
&& (local_user.server_name()
|
&& (local_user.server_name()
|
||||||
== services().globals.server_name()
|
== self.services.globals.server_name()
|
||||||
&& !services()
|
&& !self.services
|
||||||
.users
|
.users
|
||||||
.is_admin(local_user)
|
.is_admin(local_user)
|
||||||
.unwrap_or(false))
|
.unwrap_or(false))
|
||||||
})
|
})
|
||||||
})
|
}) {
|
||||||
.collect::<Vec<OwnedUserId>>()
|
|
||||||
{
|
|
||||||
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
||||||
if let Err(e) = leave_room(&local_user, &room_id, None).await {
|
if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
|
||||||
error!(
|
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
|
&local_user, &room_id, e
|
||||||
@@ -167,7 +209,7 @@ async fn ban_room(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if disable_federation {
|
if disable_federation {
|
||||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
self.services.rooms.metadata.disable_room(&room_id, true)?;
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
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.",
|
||||||
));
|
));
|
||||||
@@ -179,16 +221,22 @@ async fn ban_room(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if body.len() < 2 || !body[0].trim().starts_with("```") || 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(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Expected code block in command body. Add --help for details.",
|
"Expected code block in command body. Add --help for details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let rooms_s = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
let rooms_s = self
|
||||||
|
.body
|
||||||
|
.to_vec()
|
||||||
|
.drain(1..self.body.len().saturating_sub(1))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let admin_room_alias = &services().globals.admin_alias;
|
let admin_room_alias = &self.services.globals.admin_alias;
|
||||||
|
|
||||||
let mut room_ban_count: usize = 0;
|
let mut room_ban_count: usize = 0;
|
||||||
let mut room_ids: Vec<OwnedRoomId> = Vec::new();
|
let mut room_ids: Vec<OwnedRoomId> = Vec::new();
|
||||||
@@ -196,7 +244,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
|
|||||||
for &room in &rooms_s {
|
for &room in &rooms_s {
|
||||||
match <&RoomOrAliasId>::try_from(room) {
|
match <&RoomOrAliasId>::try_from(room) {
|
||||||
Ok(room_alias_or_id) => {
|
Ok(room_alias_or_id) => {
|
||||||
if let Some(admin_room_id) = Service::get_admin_room()? {
|
if let Some(admin_room_id) = self.services.admin.get_admin_room()? {
|
||||||
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");
|
info!("User specified admin room in bulk ban list, ignoring");
|
||||||
continue;
|
continue;
|
||||||
@@ -229,7 +277,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
|
|||||||
match RoomAliasId::parse(room_alias_or_id) {
|
match RoomAliasId::parse(room_alias_or_id) {
|
||||||
Ok(room_alias) => {
|
Ok(room_alias) => {
|
||||||
let room_id =
|
let room_id =
|
||||||
if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
|
if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
|
||||||
room_id
|
room_id
|
||||||
} else {
|
} else {
|
||||||
debug!(
|
debug!(
|
||||||
@@ -237,13 +285,20 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
|
|||||||
ID over federation"
|
ID over federation"
|
||||||
);
|
);
|
||||||
|
|
||||||
match get_alias_helper(room_alias, None).await {
|
match self
|
||||||
Ok(response) => {
|
.services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.resolve_alias(&room_alias, None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((room_id, servers)) => {
|
||||||
debug!(
|
debug!(
|
||||||
"Got federation response fetching room ID for room {room}: {:?}",
|
?room_id,
|
||||||
response
|
?servers,
|
||||||
|
"Got federation response fetching room ID for {room}",
|
||||||
);
|
);
|
||||||
response.room_id
|
room_id
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// don't fail if force blocking
|
// don't fail if force blocking
|
||||||
@@ -295,66 +350,70 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
for room_id in room_ids {
|
for room_id in room_ids {
|
||||||
if services().rooms.metadata.ban_room(&room_id, true).is_ok() {
|
if self
|
||||||
|
.services
|
||||||
|
.rooms
|
||||||
|
.metadata
|
||||||
|
.ban_room(&room_id, true)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
debug!("Banned {room_id} successfully");
|
debug!("Banned {room_id} successfully");
|
||||||
room_ban_count = room_ban_count.saturating_add(1);
|
room_ban_count = room_ban_count.saturating_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Making all users leave the room {}", &room_id);
|
debug!("Making all users leave the room {}", &room_id);
|
||||||
if force {
|
if force {
|
||||||
for local_user in services()
|
for local_user in self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_members(&room_id)
|
.room_members(&room_id)
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
user.ok().filter(|local_user| {
|
user.ok().filter(|local_user| {
|
||||||
local_user.server_name() == services().globals.server_name()
|
local_user.server_name() == self.services.globals.server_name()
|
||||||
// additional wrapped check here is to avoid adding remote
|
// additional wrapped check here is to avoid adding remote
|
||||||
// users who are in the admin room to the list of local
|
// users who are in the admin room to the list of local
|
||||||
// users (would fail auth check)
|
// users (would fail auth check)
|
||||||
&& (local_user.server_name()
|
&& (local_user.server_name()
|
||||||
== services().globals.server_name()
|
== self.services.globals.server_name()
|
||||||
// since this is a force operation, assume user is an
|
// since this is a force operation, assume user is an
|
||||||
// admin if somehow this fails
|
// admin if somehow this fails
|
||||||
&& services()
|
&& self.services
|
||||||
.users
|
.users
|
||||||
.is_admin(local_user)
|
.is_admin(local_user)
|
||||||
.unwrap_or(true))
|
.unwrap_or(true))
|
||||||
})
|
})
|
||||||
})
|
}) {
|
||||||
.collect::<Vec<OwnedUserId>>()
|
|
||||||
{
|
|
||||||
debug!(
|
debug!(
|
||||||
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
|
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
|
||||||
&local_user, room_id
|
&local_user, room_id
|
||||||
);
|
);
|
||||||
if let Err(e) = leave_room(&local_user, &room_id, None).await {
|
if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
|
||||||
warn!(%e, "Failed to leave room");
|
warn!(%e, "Failed to leave room");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for local_user in services()
|
for local_user in self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_members(&room_id)
|
.room_members(&room_id)
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
user.ok().filter(|local_user| {
|
user.ok().filter(|local_user| {
|
||||||
local_user.server_name() == services().globals.server_name()
|
local_user.server_name() == self.services.globals.server_name()
|
||||||
// additional wrapped check here is to avoid adding remote
|
// additional wrapped check here is to avoid adding remote
|
||||||
// users who are in the admin room to the list of local
|
// users who are in the admin room to the list of local
|
||||||
// users (would fail auth check)
|
// users (would fail auth check)
|
||||||
&& (local_user.server_name()
|
&& (local_user.server_name()
|
||||||
== services().globals.server_name()
|
== self.services.globals.server_name()
|
||||||
&& !services()
|
&& !self.services
|
||||||
.users
|
.users
|
||||||
.is_admin(local_user)
|
.is_admin(local_user)
|
||||||
.unwrap_or(false))
|
.unwrap_or(false))
|
||||||
})
|
})
|
||||||
})
|
}) {
|
||||||
.collect::<Vec<OwnedUserId>>()
|
|
||||||
{
|
|
||||||
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
||||||
if let Err(e) = leave_room(&local_user, &room_id, None).await {
|
if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
|
||||||
error!(
|
error!(
|
||||||
"Error attempting to make local user {} leave room {} during bulk room banning: {}",
|
"Error attempting to make local user {} leave room {} during bulk room banning: {}",
|
||||||
&local_user, &room_id, e
|
&local_user, &room_id, e
|
||||||
@@ -370,7 +429,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if disable_federation {
|
if disable_federation {
|
||||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
self.services.rooms.metadata.disable_room(&room_id, true)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,9 +445,8 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unban_room(
|
#[admin_command]
|
||||||
_body: Vec<&str>, room: Box<RoomOrAliasId>, enable_federation: bool,
|
async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
|
||||||
) -> Result<RoomMessageEventContent> {
|
|
||||||
let room_id = if room.is_room_id() {
|
let room_id = if room.is_room_id() {
|
||||||
let room_id = match RoomId::parse(&room) {
|
let room_id = match RoomId::parse(&room) {
|
||||||
Ok(room_id) => room_id,
|
Ok(room_id) => room_id,
|
||||||
@@ -402,7 +460,7 @@ async fn unban_room(
|
|||||||
|
|
||||||
debug!("Room specified is a room ID, unbanning room ID");
|
debug!("Room specified is a room ID, unbanning room ID");
|
||||||
|
|
||||||
services().rooms.metadata.ban_room(&room_id, false)?;
|
self.services.rooms.metadata.ban_room(&room_id, false)?;
|
||||||
|
|
||||||
room_id
|
room_id
|
||||||
} else if room.is_room_alias_id() {
|
} else if room.is_room_alias_id() {
|
||||||
@@ -421,15 +479,21 @@ async fn unban_room(
|
|||||||
get_alias_helper to fetch room ID remotely"
|
get_alias_helper to fetch room ID remotely"
|
||||||
);
|
);
|
||||||
|
|
||||||
let room_id = if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
|
let room_id = if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
|
||||||
room_id
|
room_id
|
||||||
} else {
|
} 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 get_alias_helper(room_alias, None).await {
|
match self
|
||||||
Ok(response) => {
|
.services
|
||||||
debug!("Got federation response fetching room ID for room {room}: {:?}", response);
|
.rooms
|
||||||
response.room_id
|
.alias
|
||||||
|
.resolve_alias(&room_alias, None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
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!(
|
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
@@ -439,7 +503,7 @@ async fn unban_room(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
services().rooms.metadata.ban_room(&room_id, false)?;
|
self.services.rooms.metadata.ban_room(&room_id, false)?;
|
||||||
|
|
||||||
room_id
|
room_id
|
||||||
} else {
|
} else {
|
||||||
@@ -450,7 +514,7 @@ async fn unban_room(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if enable_federation {
|
if enable_federation {
|
||||||
services().rooms.metadata.disable_room(&room_id, false)?;
|
self.services.rooms.metadata.disable_room(&room_id, false)?;
|
||||||
return Ok(RoomMessageEventContent::text_plain("Room unbanned."));
|
return Ok(RoomMessageEventContent::text_plain("Room unbanned."));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,8 +524,10 @@ async fn unban_room(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let rooms = services()
|
async fn list_banned_rooms(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
let rooms = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.metadata
|
.metadata
|
||||||
.list_banned_rooms()
|
.list_banned_rooms()
|
||||||
@@ -475,7 +541,7 @@ async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent>
|
|||||||
|
|
||||||
let mut rooms = room_ids
|
let mut rooms = room_ids
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|room_id| get_room_info(&room_id))
|
.map(|room_id| get_room_info(self.services, &room_id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
rooms.sort_by_key(|r| r.1);
|
rooms.sort_by_key(|r| r.1);
|
||||||
rooms.reverse();
|
rooms.reverse();
|
||||||
+104
-69
@@ -1,60 +1,92 @@
|
|||||||
use conduit::{warn, Result};
|
use std::{fmt::Write, sync::Arc};
|
||||||
|
|
||||||
|
use conduit::{info, utils::time, warn, Err, Result};
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
use ruma::events::room::message::RoomMessageEventContent;
|
||||||
|
|
||||||
use crate::services;
|
use crate::admin_command;
|
||||||
|
|
||||||
pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let seconds = services()
|
pub(super) async fn uptime(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
let elapsed = self
|
||||||
|
.services
|
||||||
.server
|
.server
|
||||||
.started
|
.started
|
||||||
.elapsed()
|
.elapsed()
|
||||||
.expect("standard duration")
|
.expect("standard duration");
|
||||||
.as_secs();
|
|
||||||
let result = format!(
|
|
||||||
"up {} days, {} hours, {} minutes, {} seconds.",
|
|
||||||
seconds / 86400,
|
|
||||||
(seconds % 86400) / 60 / 60,
|
|
||||||
(seconds % 3600) / 60,
|
|
||||||
seconds % 60,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_plain(result))
|
let result = time::pretty(elapsed);
|
||||||
|
Ok(RoomMessageEventContent::notice_plain(format!("{result}.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
|
pub(super) async fn show_config(&self) -> Result<RoomMessageEventContent> {
|
||||||
// Construct and send the response
|
// Construct and send the response
|
||||||
Ok(RoomMessageEventContent::text_plain(format!("{}", services().globals.config)))
|
Ok(RoomMessageEventContent::text_plain(format!("{}", self.services.globals.config)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let response0 = services().memory_usage().await;
|
pub(super) async fn list_features(
|
||||||
let response1 = services().globals.db.memory_usage();
|
&self, available: bool, enabled: bool, comma: bool,
|
||||||
let response2 = conduit::alloc::memory_usage();
|
) -> Result<RoomMessageEventContent> {
|
||||||
|
let delim = if comma {
|
||||||
|
","
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
};
|
||||||
|
if enabled && !available {
|
||||||
|
let features = info::rustc::features().join(delim);
|
||||||
|
let out = format!("`\n{features}\n`");
|
||||||
|
return Ok(RoomMessageEventContent::text_markdown(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
if available && !enabled {
|
||||||
|
let features = info::cargo::features().join(delim);
|
||||||
|
let out = format!("`\n{features}\n`");
|
||||||
|
return Ok(RoomMessageEventContent::text_markdown(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut features = String::new();
|
||||||
|
let enabled = info::rustc::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 {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
writeln!(features, "{emoji} {feature} {remark}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::text_markdown(features))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
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}"));
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::text_plain(format!(
|
Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
"Services:\n{response0}\n\nDatabase:\n{response1}\n{}",
|
"Services:\n{services_usage}\nDatabase:\n{database_usage}{allocator_usage}",
|
||||||
if !response2.is_empty() {
|
|
||||||
format!("Allocator:\n {response2}")
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn clear_database_caches(_body: Vec<&str>, amount: u32) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
services().globals.db.clear_caches(amount);
|
pub(super) async fn clear_caches(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
self.services.clear_cache().await;
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::text_plain("Done."))
|
Ok(RoomMessageEventContent::text_plain("Done."))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn clear_service_caches(_body: Vec<&str>, amount: u32) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
services().clear_caches(amount).await;
|
pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
let result = self.services.globals.db.backup_list()?;
|
||||||
Ok(RoomMessageEventContent::text_plain("Done."))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
let result = services().globals.db.backup_list()?;
|
|
||||||
|
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
Ok(RoomMessageEventContent::text_plain("No backups found."))
|
Ok(RoomMessageEventContent::text_plain("No backups found."))
|
||||||
@@ -63,66 +95,69 @@ pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn backup_database(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
if !cfg!(feature = "rocksdb") {
|
pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
let globals = Arc::clone(&self.services.globals);
|
||||||
"Only RocksDB supports online backups in conduwuit.",
|
let mut result = self
|
||||||
));
|
.services
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = services()
|
|
||||||
.server
|
.server
|
||||||
.runtime()
|
.runtime()
|
||||||
.spawn_blocking(move || match services().globals.db.backup() {
|
.spawn_blocking(move || match globals.db.backup() {
|
||||||
Ok(()) => String::new(),
|
Ok(()) => String::new(),
|
||||||
Err(e) => (*e).to_string(),
|
Err(e) => (*e).to_string(),
|
||||||
})
|
})
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if result.is_empty() {
|
if result.is_empty() {
|
||||||
result = services().globals.db.backup_list()?;
|
result = self.services.globals.db.backup_list()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::text_plain(&result))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn list_database_files(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
if !cfg!(feature = "rocksdb") {
|
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
|
||||||
"Only RocksDB supports listing files in conduwuit.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = services().globals.db.file_list()?;
|
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(result))
|
Ok(RoomMessageEventContent::notice_markdown(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn admin_notice(_body: Vec<&str>, message: Vec<String>) -> Result<RoomMessageEventContent> {
|
#[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(" ");
|
let message = message.join(" ");
|
||||||
services().admin.send_text(&message).await;
|
self.services.admin.send_text(&message).await;
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins"))
|
Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(conduit_mods)]
|
#[admin_command]
|
||||||
pub(super) async fn reload(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
pub(super) async fn reload_mods(&self) -> Result<RoomMessageEventContent> {
|
||||||
services().server.reload()?;
|
self.services.server.reload()?;
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_plain("Reloading server..."))
|
Ok(RoomMessageEventContent::notice_plain("Reloading server..."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(super) async fn restart(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
pub(super) async fn restart(&self, force: bool) -> Result<RoomMessageEventContent> {
|
||||||
services().server.restart()?;
|
use conduit::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."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.services.server.restart()?;
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_plain("Restarting server..."))
|
Ok(RoomMessageEventContent::notice_plain("Restarting server..."))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn shutdown(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
|
pub(super) async fn shutdown(&self) -> Result<RoomMessageEventContent> {
|
||||||
warn!("shutdown command");
|
warn!("shutdown command");
|
||||||
services().server.shutdown()?;
|
self.services.server.shutdown()?;
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_plain("Shutting down server..."))
|
Ok(RoomMessageEventContent::notice_plain("Shutting down server..."))
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-43
@@ -2,12 +2,11 @@ mod commands;
|
|||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
use ruma::events::room::message::RoomMessageEventContent;
|
|
||||||
|
|
||||||
use self::commands::*;
|
use crate::admin_command_dispatch;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[admin_command_dispatch]
|
||||||
#[derive(Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(super) enum ServerCommand {
|
pub(super) enum ServerCommand {
|
||||||
/// - Time elapsed since startup
|
/// - Time elapsed since startup
|
||||||
Uptime,
|
Uptime,
|
||||||
@@ -15,20 +14,23 @@ pub(super) enum ServerCommand {
|
|||||||
/// - Show configuration values
|
/// - Show configuration values
|
||||||
ShowConfig,
|
ShowConfig,
|
||||||
|
|
||||||
|
/// - List the features built into the server
|
||||||
|
ListFeatures {
|
||||||
|
#[arg(short, long)]
|
||||||
|
available: bool,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
enabled: bool,
|
||||||
|
|
||||||
|
#[arg(short, long)]
|
||||||
|
comma: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// - Print database memory usage statistics
|
/// - Print database memory usage statistics
|
||||||
MemoryUsage,
|
MemoryUsage,
|
||||||
|
|
||||||
/// - Clears all of Conduit's database caches with index smaller than the
|
/// - Clears all of Conduwuit's caches
|
||||||
/// amount
|
ClearCaches,
|
||||||
ClearDatabaseCaches {
|
|
||||||
amount: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Clears all of Conduit's service caches with index smaller than the
|
|
||||||
/// amount
|
|
||||||
ClearServiceCaches {
|
|
||||||
amount: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// - Performs an online backup of the database (only available for RocksDB
|
/// - Performs an online backup of the database (only available for RocksDB
|
||||||
/// at the moment)
|
/// at the moment)
|
||||||
@@ -45,39 +47,17 @@ pub(super) enum ServerCommand {
|
|||||||
message: Vec<String>,
|
message: Vec<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(conduit_mods)]
|
|
||||||
/// - Hot-reload the server
|
/// - Hot-reload the server
|
||||||
Reload,
|
#[clap(alias = "reload")]
|
||||||
|
ReloadMods,
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
/// - Restart the server
|
/// - Restart the server
|
||||||
Restart,
|
Restart {
|
||||||
|
#[arg(short, long)]
|
||||||
|
force: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// - Shutdown the server
|
/// - Shutdown the server
|
||||||
Shutdown,
|
Shutdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn process(command: ServerCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
ServerCommand::Uptime => uptime(body).await?,
|
|
||||||
ServerCommand::ShowConfig => show_config(body).await?,
|
|
||||||
ServerCommand::MemoryUsage => memory_usage(body).await?,
|
|
||||||
ServerCommand::ClearDatabaseCaches {
|
|
||||||
amount,
|
|
||||||
} => clear_database_caches(body, amount).await?,
|
|
||||||
ServerCommand::ClearServiceCaches {
|
|
||||||
amount,
|
|
||||||
} => clear_service_caches(body, amount).await?,
|
|
||||||
ServerCommand::ListBackups => list_backups(body).await?,
|
|
||||||
ServerCommand::BackupDatabase => backup_database(body).await?,
|
|
||||||
ServerCommand::ListDatabaseFiles => list_database_files(body).await?,
|
|
||||||
ServerCommand::AdminNotice {
|
|
||||||
message,
|
|
||||||
} => admin_notice(body, message).await?,
|
|
||||||
#[cfg(conduit_mods)]
|
|
||||||
ServerCommand::Reload => reload(body).await?,
|
|
||||||
#[cfg(unix)]
|
|
||||||
ServerCommand::Restart => restart(body).await?,
|
|
||||||
ServerCommand::Shutdown => shutdown(body).await?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
use ruma::events::room::message::RoomMessageEventContent;
|
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
#[derive(clap::Subcommand)]
|
|
||||||
pub(super) enum TesterCommands {
|
|
||||||
Tester,
|
|
||||||
}
|
|
||||||
pub(super) async fn process(command: TesterCommands, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
TesterCommands::Tester => RoomMessageEventContent::notice_plain(String::from("completed")),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_help_short() { get_help_inner("-h"); }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_help_long() { get_help_inner("--help"); }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_help_subcommand() { get_help_inner("help"); }
|
||||||
|
|
||||||
|
fn get_help_inner(input: &str) {
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
use crate::admin::AdminCommand;
|
||||||
|
|
||||||
|
let Err(error) = AdminCommand::try_parse_from(["argv[0] doesn't matter", input]) else {
|
||||||
|
panic!("no error!");
|
||||||
|
};
|
||||||
|
|
||||||
|
let error = error.to_string();
|
||||||
|
// Search for a handful of keywords that suggest the help printed properly
|
||||||
|
assert!(error.contains("Usage:"));
|
||||||
|
assert!(error.contains("Commands:"));
|
||||||
|
assert!(error.contains("Options:"));
|
||||||
|
}
|
||||||
+137
-73
@@ -1,29 +1,29 @@
|
|||||||
use std::{collections::BTreeMap, fmt::Write as _};
|
use std::{collections::BTreeMap, fmt::Write as _};
|
||||||
|
|
||||||
use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname};
|
use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname};
|
||||||
use conduit::{utils, Result};
|
use conduit::{error, info, utils, warn, Result};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
events::{
|
events::{
|
||||||
room::message::RoomMessageEventContent,
|
room::message::RoomMessageEventContent,
|
||||||
tag::{TagEvent, TagEventContent, TagInfo},
|
tag::{TagEvent, TagEventContent, TagInfo},
|
||||||
RoomAccountDataEventType,
|
RoomAccountDataEventType,
|
||||||
},
|
},
|
||||||
OwnedRoomId, OwnedUserId, RoomId,
|
OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId,
|
||||||
};
|
};
|
||||||
use tracing::{error, info, warn};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
escape_html, get_room_info, services,
|
admin_command, escape_html, get_room_info,
|
||||||
utils::{parse_active_local_user_id, parse_local_user_id},
|
utils::{parse_active_local_user_id, parse_local_user_id},
|
||||||
};
|
};
|
||||||
|
|
||||||
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
|
const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
|
||||||
|
|
||||||
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
match services().users.list_local_users() {
|
pub(super) async fn list_users(&self) -> Result<RoomMessageEventContent> {
|
||||||
|
match self.services.users.list_local_users() {
|
||||||
Ok(users) => {
|
Ok(users) => {
|
||||||
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
|
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
|
||||||
plain_msg += &users.join("\n");
|
plain_msg += users.join("\n").as_str();
|
||||||
plain_msg += "\n```";
|
plain_msg += "\n```";
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::notice_markdown(plain_msg))
|
Ok(RoomMessageEventContent::notice_markdown(plain_msg))
|
||||||
@@ -32,43 +32,51 @@ pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn create(
|
#[admin_command]
|
||||||
_body: Vec<&str>, username: String, password: Option<String>,
|
pub(super) async fn create_user(&self, username: String, password: Option<String>) -> Result<RoomMessageEventContent> {
|
||||||
) -> Result<RoomMessageEventContent> {
|
|
||||||
// Validate user id
|
// Validate user id
|
||||||
let user_id = parse_local_user_id(&username)?;
|
let user_id = parse_local_user_id(self.services, &username)?;
|
||||||
|
|
||||||
if services().users.exists(&user_id)? {
|
if self.services.users.exists(&user_id)? {
|
||||||
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() {
|
||||||
|
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
|
"User ID {user_id} does not conform to new Matrix identifier spec"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
services().users.create(&user_id, Some(password.as_str()))?;
|
self.services
|
||||||
|
.users
|
||||||
|
.create(&user_id, Some(password.as_str()))?;
|
||||||
|
|
||||||
// Default to pretty displayname
|
// Default to pretty displayname
|
||||||
let mut displayname = user_id.localpart().to_owned();
|
let mut displayname = user_id.localpart().to_owned();
|
||||||
|
|
||||||
// If `new_user_displayname_suffix` is set, registration will push whatever
|
// 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
|
// content is set to the user's display name with a space before it
|
||||||
if !services()
|
if !self
|
||||||
|
.services
|
||||||
.globals
|
.globals
|
||||||
.config
|
.config
|
||||||
.new_user_displayname_suffix
|
.new_user_displayname_suffix
|
||||||
.is_empty()
|
.is_empty()
|
||||||
{
|
{
|
||||||
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix)
|
write!(displayname, " {}", self.services.globals.config.new_user_displayname_suffix)
|
||||||
.expect("should be able to write to string buffer");
|
.expect("should be able to write to string buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
self.services
|
||||||
.users
|
.users
|
||||||
.set_displayname(&user_id, Some(displayname))
|
.set_displayname(&user_id, Some(displayname))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Initial account data
|
// Initial account data
|
||||||
services().account_data.update(
|
self.services.account_data.update(
|
||||||
None,
|
None,
|
||||||
&user_id,
|
&user_id,
|
||||||
ruma::events::GlobalAccountDataEventType::PushRules
|
ruma::events::GlobalAccountDataEventType::PushRules
|
||||||
@@ -82,12 +90,13 @@ pub(super) async fn create(
|
|||||||
.expect("to json value always works"),
|
.expect("to json value always works"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !services().globals.config.auto_join_rooms.is_empty() {
|
if !self.services.globals.config.auto_join_rooms.is_empty() {
|
||||||
for room in &services().globals.config.auto_join_rooms {
|
for room in &self.services.globals.config.auto_join_rooms {
|
||||||
if !services()
|
if !self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.server_in_room(services().globals.server_name(), room)?
|
.server_in_room(self.services.globals.server_name(), room)?
|
||||||
{
|
{
|
||||||
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;
|
continue;
|
||||||
@@ -95,10 +104,11 @@ pub(super) async fn create(
|
|||||||
|
|
||||||
if let Some(room_id_server_name) = room.server_name() {
|
if let Some(room_id_server_name) = room.server_name() {
|
||||||
match join_room_by_id_helper(
|
match join_room_by_id_helper(
|
||||||
Some(&user_id),
|
self.services,
|
||||||
|
&user_id,
|
||||||
room,
|
room,
|
||||||
Some("Automatically joining this room upon registration".to_owned()),
|
Some("Automatically joining this room upon registration".to_owned()),
|
||||||
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
|
&[room_id_server_name.to_owned(), self.services.globals.server_name().to_owned()],
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -123,38 +133,38 @@ pub(super) async fn create(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn deactivate(
|
#[admin_command]
|
||||||
_body: Vec<&str>, no_leave_rooms: bool, user_id: String,
|
pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) -> Result<RoomMessageEventContent> {
|
||||||
) -> Result<RoomMessageEventContent> {
|
|
||||||
// Validate user id
|
// Validate user id
|
||||||
let user_id = parse_local_user_id(&user_id)?;
|
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||||
|
|
||||||
// don't deactivate the server service account
|
// don't deactivate the server service account
|
||||||
if user_id == services().globals.server_user {
|
if user_id == self.services.globals.server_user {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Not allowed to deactivate the server service account.",
|
"Not allowed to deactivate the server service account.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
services().users.deactivate_account(&user_id)?;
|
self.services.users.deactivate_account(&user_id)?;
|
||||||
|
|
||||||
if !no_leave_rooms {
|
if !no_leave_rooms {
|
||||||
services()
|
self.services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||||
"Making {user_id} leave all rooms after deactivation..."
|
"Making {user_id} leave all rooms after deactivation..."
|
||||||
)))
|
)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services()
|
let all_joined_rooms: Vec<OwnedRoomId> = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(&user_id)
|
.rooms_joined(&user_id)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
update_displayname(user_id.clone(), None, all_joined_rooms.clone()).await?;
|
update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
|
||||||
update_avatar_url(user_id.clone(), None, None, all_joined_rooms).await?;
|
update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
|
||||||
leave_all_rooms(&user_id).await;
|
leave_all_rooms(self.services, &user_id).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RoomMessageEventContent::text_plain(format!(
|
Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
@@ -162,10 +172,11 @@ pub(super) async fn deactivate(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
let user_id = parse_local_user_id(&username)?;
|
pub(super) async fn reset_password(&self, username: String) -> Result<RoomMessageEventContent> {
|
||||||
|
let user_id = parse_local_user_id(self.services, &username)?;
|
||||||
|
|
||||||
if user_id == services().globals.server_user {
|
if user_id == self.services.globals.server_user {
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
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.",
|
||||||
));
|
));
|
||||||
@@ -173,7 +184,8 @@ pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result
|
|||||||
|
|
||||||
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
|
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
|
||||||
|
|
||||||
match services()
|
match self
|
||||||
|
.services
|
||||||
.users
|
.users
|
||||||
.set_password(&user_id, Some(new_password.as_str()))
|
.set_password(&user_id, Some(new_password.as_str()))
|
||||||
{
|
{
|
||||||
@@ -186,25 +198,29 @@ pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn deactivate_all(
|
#[admin_command]
|
||||||
body: Vec<&str>, no_leave_rooms: bool, force: bool,
|
pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) -> Result<RoomMessageEventContent> {
|
||||||
) -> Result<RoomMessageEventContent> {
|
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||||
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
|
{
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
"Expected code block in command body. Add --help for details.",
|
"Expected code block in command body. Add --help for details.",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
let usernames = self
|
||||||
|
.body
|
||||||
|
.to_vec()
|
||||||
|
.drain(1..self.body.len().saturating_sub(1))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
|
let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
|
||||||
let mut admins = Vec::new();
|
let mut admins = Vec::new();
|
||||||
|
|
||||||
for username in usernames {
|
for username in usernames {
|
||||||
match parse_active_local_user_id(username) {
|
match parse_active_local_user_id(self.services, username) {
|
||||||
Ok(user_id) => {
|
Ok(user_id) => {
|
||||||
if services().users.is_admin(&user_id)? && !force {
|
if self.services.users.is_admin(&user_id)? && !force {
|
||||||
services()
|
self.services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||||
"{username} is an admin and --force is not set, skipping over"
|
"{username} is an admin and --force is not set, skipping over"
|
||||||
@@ -215,8 +231,8 @@ pub(super) async fn deactivate_all(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// don't deactivate the server service account
|
// don't deactivate the server service account
|
||||||
if user_id == services().globals.server_user {
|
if user_id == self.services.globals.server_user {
|
||||||
services()
|
self.services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||||
"{username} is the server service account, skipping over"
|
"{username} is the server service account, skipping over"
|
||||||
@@ -228,7 +244,7 @@ pub(super) async fn deactivate_all(
|
|||||||
user_ids.push(user_id);
|
user_ids.push(user_id);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
services()
|
self.services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||||
"{username} is not a valid username, skipping over: {e}"
|
"{username} is not a valid username, skipping over: {e}"
|
||||||
@@ -242,24 +258,25 @@ pub(super) async fn deactivate_all(
|
|||||||
let mut deactivation_count: usize = 0;
|
let mut deactivation_count: usize = 0;
|
||||||
|
|
||||||
for user_id in user_ids {
|
for user_id in user_ids {
|
||||||
match services().users.deactivate_account(&user_id) {
|
match self.services.users.deactivate_account(&user_id) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
deactivation_count = deactivation_count.saturating_add(1);
|
deactivation_count = deactivation_count.saturating_add(1);
|
||||||
if !no_leave_rooms {
|
if !no_leave_rooms {
|
||||||
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
|
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services()
|
let all_joined_rooms: Vec<OwnedRoomId> = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(&user_id)
|
.rooms_joined(&user_id)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
update_displayname(user_id.clone(), None, all_joined_rooms.clone()).await?;
|
update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
|
||||||
update_avatar_url(user_id.clone(), None, None, all_joined_rooms).await?;
|
update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
|
||||||
leave_all_rooms(&user_id).await;
|
leave_all_rooms(self.services, &user_id).await;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
services()
|
self.services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
|
.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
|
||||||
.await;
|
.await;
|
||||||
@@ -280,16 +297,18 @@ pub(super) async fn deactivate_all(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> {
|
#[admin_command]
|
||||||
|
pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result<RoomMessageEventContent> {
|
||||||
// Validate user id
|
// Validate user id
|
||||||
let user_id = parse_local_user_id(&user_id)?;
|
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||||
|
|
||||||
let mut rooms: Vec<(OwnedRoomId, u64, String)> = services()
|
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
|
||||||
|
.services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(&user_id)
|
.rooms_joined(&user_id)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.map(|room_id| get_room_info(&room_id))
|
.map(|room_id| get_room_info(self.services, &room_id))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if rooms.is_empty() {
|
if rooms.is_empty() {
|
||||||
@@ -331,12 +350,55 @@ pub(super) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
|
|||||||
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
|
Ok(RoomMessageEventContent::text_html(output_plain, output_html))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn put_room_tag(
|
#[admin_command]
|
||||||
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
|
pub(super) async fn force_join_room(
|
||||||
|
&self, user_id: String, room_id: OwnedRoomOrAliasId,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
let user_id = parse_active_local_user_id(&user_id)?;
|
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||||
|
let room_id = self.services.rooms.alias.resolve(&room_id).await?;
|
||||||
|
|
||||||
let event = services()
|
assert!(
|
||||||
|
self.services.globals.user_is_local(&user_id),
|
||||||
|
"Parsed user_id must be a local user"
|
||||||
|
);
|
||||||
|
join_room_by_id_helper(self.services, &user_id, &room_id, None, &[], None).await?;
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
|
"{user_id} has been joined to {room_id}.",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> {
|
||||||
|
let user_id = parse_local_user_id(self.services, &user_id)?;
|
||||||
|
let displayname = self
|
||||||
|
.services
|
||||||
|
.users
|
||||||
|
.displayname(&user_id)?
|
||||||
|
.unwrap_or_else(|| user_id.to_string());
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
self.services.globals.user_is_local(&user_id),
|
||||||
|
"Parsed user_id must be a local user"
|
||||||
|
);
|
||||||
|
self.services
|
||||||
|
.admin
|
||||||
|
.make_user_admin(&user_id, displayname)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||||
|
"{user_id} has been granted admin privileges.",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
|
pub(super) async fn put_room_tag(
|
||||||
|
&self, user_id: String, room_id: Box<RoomId>, tag: String,
|
||||||
|
) -> Result<RoomMessageEventContent> {
|
||||||
|
let user_id = parse_active_local_user_id(self.services, &user_id)?;
|
||||||
|
|
||||||
|
let event = self
|
||||||
|
.services
|
||||||
.account_data
|
.account_data
|
||||||
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
||||||
|
|
||||||
@@ -354,7 +416,7 @@ pub(super) async fn put_room_tag(
|
|||||||
.tags
|
.tags
|
||||||
.insert(tag.clone().into(), TagInfo::new());
|
.insert(tag.clone().into(), TagInfo::new());
|
||||||
|
|
||||||
services().account_data.update(
|
self.services.account_data.update(
|
||||||
Some(&room_id),
|
Some(&room_id),
|
||||||
&user_id,
|
&user_id,
|
||||||
RoomAccountDataEventType::Tag,
|
RoomAccountDataEventType::Tag,
|
||||||
@@ -366,12 +428,14 @@ pub(super) async fn put_room_tag(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[admin_command]
|
||||||
pub(super) async fn delete_room_tag(
|
pub(super) async fn delete_room_tag(
|
||||||
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
|
&self, user_id: String, room_id: Box<RoomId>, tag: String,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
let user_id = parse_active_local_user_id(&user_id)?;
|
let user_id = parse_active_local_user_id(self.services, &user_id)?;
|
||||||
|
|
||||||
let event = services()
|
let event = self
|
||||||
|
.services
|
||||||
.account_data
|
.account_data
|
||||||
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
||||||
|
|
||||||
@@ -386,7 +450,7 @@ pub(super) async fn delete_room_tag(
|
|||||||
|
|
||||||
tags_event.content.tags.remove(&tag.clone().into());
|
tags_event.content.tags.remove(&tag.clone().into());
|
||||||
|
|
||||||
services().account_data.update(
|
self.services.account_data.update(
|
||||||
Some(&room_id),
|
Some(&room_id),
|
||||||
&user_id,
|
&user_id,
|
||||||
RoomAccountDataEventType::Tag,
|
RoomAccountDataEventType::Tag,
|
||||||
@@ -398,12 +462,12 @@ pub(super) async fn delete_room_tag(
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn get_room_tags(
|
#[admin_command]
|
||||||
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>,
|
pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||||
) -> Result<RoomMessageEventContent> {
|
let user_id = parse_active_local_user_id(self.services, &user_id)?;
|
||||||
let user_id = parse_active_local_user_id(&user_id)?;
|
|
||||||
|
|
||||||
let event = services()
|
let event = self
|
||||||
|
.services
|
||||||
.account_data
|
.account_data
|
||||||
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
|
||||||
|
|
||||||
|
|||||||
+20
-45
@@ -2,15 +2,16 @@ mod commands;
|
|||||||
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use conduit::Result;
|
use conduit::Result;
|
||||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
|
use ruma::{OwnedRoomOrAliasId, RoomId};
|
||||||
|
|
||||||
use self::commands::*;
|
use crate::admin_command_dispatch;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[admin_command_dispatch]
|
||||||
#[derive(Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub(super) enum UserCommand {
|
pub(super) enum UserCommand {
|
||||||
/// - Create a new user
|
/// - Create a new user
|
||||||
Create {
|
#[clap(alias = "create")]
|
||||||
|
CreateUser {
|
||||||
/// Username of the new user
|
/// Username of the new user
|
||||||
username: String,
|
username: String,
|
||||||
/// Password of the new user, if unspecified one is generated
|
/// Password of the new user, if unspecified one is generated
|
||||||
@@ -49,7 +50,7 @@ pub(super) enum UserCommand {
|
|||||||
/// Markdown code block below the command.
|
/// Markdown code block below the command.
|
||||||
DeactivateAll {
|
DeactivateAll {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
/// Remove users from their joined rooms
|
/// Does not leave any rooms the user is in on deactivation
|
||||||
no_leave_rooms: bool,
|
no_leave_rooms: bool,
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
/// Also deactivate admin accounts and will assume leave all rooms too
|
/// Also deactivate admin accounts and will assume leave all rooms too
|
||||||
@@ -57,7 +58,8 @@ pub(super) enum UserCommand {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/// - List local users in the database
|
/// - List local users in the database
|
||||||
List,
|
#[clap(alias = "list")]
|
||||||
|
ListUsers,
|
||||||
|
|
||||||
/// - Lists all the rooms (local and remote) that the specified user is
|
/// - Lists all the rooms (local and remote) that the specified user is
|
||||||
/// joined in
|
/// joined in
|
||||||
@@ -65,6 +67,17 @@ pub(super) enum UserCommand {
|
|||||||
user_id: String,
|
user_id: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// - Manually join a local user to a room.
|
||||||
|
ForceJoinRoom {
|
||||||
|
user_id: String,
|
||||||
|
room_id: OwnedRoomOrAliasId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// - Grant server-admin privileges to a user.
|
||||||
|
MakeUserAdmin {
|
||||||
|
user_id: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// - Puts a room tag for the specified user and room ID.
|
/// - Puts a room tag for the specified user and room ID.
|
||||||
///
|
///
|
||||||
/// This is primarily useful if you'd like to set your admin room
|
/// This is primarily useful if you'd like to set your admin room
|
||||||
@@ -91,41 +104,3 @@ pub(super) enum UserCommand {
|
|||||||
room_id: Box<RoomId>,
|
room_id: Box<RoomId>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn process(command: UserCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
|
||||||
Ok(match command {
|
|
||||||
UserCommand::List => list(body).await?,
|
|
||||||
UserCommand::Create {
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
} => create(body, username, password).await?,
|
|
||||||
UserCommand::Deactivate {
|
|
||||||
no_leave_rooms,
|
|
||||||
user_id,
|
|
||||||
} => deactivate(body, no_leave_rooms, user_id).await?,
|
|
||||||
UserCommand::ResetPassword {
|
|
||||||
username,
|
|
||||||
} => reset_password(body, username).await?,
|
|
||||||
UserCommand::DeactivateAll {
|
|
||||||
no_leave_rooms,
|
|
||||||
force,
|
|
||||||
} => deactivate_all(body, no_leave_rooms, force).await?,
|
|
||||||
UserCommand::ListJoinedRooms {
|
|
||||||
user_id,
|
|
||||||
} => list_joined_rooms(body, user_id).await?,
|
|
||||||
UserCommand::PutRoomTag {
|
|
||||||
user_id,
|
|
||||||
room_id,
|
|
||||||
tag,
|
|
||||||
} => put_room_tag(body, user_id, room_id, tag).await?,
|
|
||||||
UserCommand::DeleteRoomTag {
|
|
||||||
user_id,
|
|
||||||
room_id,
|
|
||||||
tag,
|
|
||||||
} => delete_room_tag(body, user_id, room_id, tag).await?,
|
|
||||||
UserCommand::GetRoomTags {
|
|
||||||
user_id,
|
|
||||||
room_id,
|
|
||||||
} => get_room_tags(body, user_id, room_id).await?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
+18
-20
@@ -1,8 +1,6 @@
|
|||||||
use conduit_core::Error;
|
use conduit_core::{err, Err, Result};
|
||||||
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
|
use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
|
||||||
use service::user_is_local;
|
use service::Services;
|
||||||
|
|
||||||
use crate::{services, Result};
|
|
||||||
|
|
||||||
pub(crate) fn escape_html(s: &str) -> String {
|
pub(crate) fn escape_html(s: &str) -> String {
|
||||||
s.replace('&', "&")
|
s.replace('&', "&")
|
||||||
@@ -10,17 +8,17 @@ pub(crate) fn escape_html(s: &str) -> String {
|
|||||||
.replace('>', ">")
|
.replace('>', ">")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
|
pub(crate) fn get_room_info(services: &Services, id: &RoomId) -> (OwnedRoomId, u64, String) {
|
||||||
(
|
(
|
||||||
id.into(),
|
id.into(),
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_joined_count(id)
|
.room_joined_count(id)
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.get_name(id)
|
.get_name(id)
|
||||||
@@ -31,32 +29,32 @@ pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parses user ID
|
/// Parses user ID
|
||||||
pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> {
|
pub(crate) fn parse_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
|
||||||
UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name())
|
UserId::parse_with_server_name(user_id.to_lowercase(), services.globals.server_name())
|
||||||
.map_err(|e| Error::Err(format!("The supplied username is not a valid username: {e}")))
|
.map_err(|e| err!("The supplied username is not a valid username: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses user ID as our local user
|
/// Parses user ID as our local user
|
||||||
pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> {
|
pub(crate) fn parse_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
|
||||||
let user_id = parse_user_id(user_id)?;
|
let user_id = parse_user_id(services, user_id)?;
|
||||||
|
|
||||||
if !user_is_local(&user_id) {
|
if !services.globals.user_is_local(&user_id) {
|
||||||
return Err(Error::Err(String::from("User does not belong to our server.")));
|
return Err!("User {user_id:?} does not belong to our server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(user_id)
|
Ok(user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses user ID that is an active (not guest or deactivated) local user
|
/// Parses user ID that is an active (not guest or deactivated) local user
|
||||||
pub(crate) fn parse_active_local_user_id(user_id: &str) -> Result<OwnedUserId> {
|
pub(crate) fn parse_active_local_user_id(services: &Services, user_id: &str) -> Result<OwnedUserId> {
|
||||||
let user_id = parse_local_user_id(user_id)?;
|
let user_id = parse_local_user_id(services, user_id)?;
|
||||||
|
|
||||||
if !services().users.exists(&user_id)? {
|
if !services.users.exists(&user_id)? {
|
||||||
return Err(Error::Err(String::from("User does not exist on this server.")));
|
return Err!("User {user_id:?} does not exist on this server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if services().users.is_deactivated(&user_id)? {
|
if services.users.is_deactivated(&user_id)? {
|
||||||
return Err(Error::Err(String::from("User is deactivated.")));
|
return Err!("User {user_id:?} is deactivated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(user_id)
|
Ok(user_id)
|
||||||
|
|||||||
+3
-4
@@ -18,7 +18,7 @@ crate-type = [
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
element_hacks = []
|
element_hacks = []
|
||||||
dev_release_log_level = []
|
#dev_release_log_level = []
|
||||||
release_max_log_level = [
|
release_max_log_level = [
|
||||||
"tracing/max_level_trace",
|
"tracing/max_level_trace",
|
||||||
"tracing/release_max_level_info",
|
"tracing/release_max_level_info",
|
||||||
@@ -41,11 +41,12 @@ bytes.workspace = true
|
|||||||
conduit-core.workspace = true
|
conduit-core.workspace = true
|
||||||
conduit-database.workspace = true
|
conduit-database.workspace = true
|
||||||
conduit-service.workspace = true
|
conduit-service.workspace = true
|
||||||
|
const-str.workspace = true
|
||||||
futures-util.workspace = true
|
futures-util.workspace = true
|
||||||
hmac.workspace = true
|
hmac.workspace = true
|
||||||
http.workspace = true
|
http.workspace = true
|
||||||
|
http-body-util.workspace = true
|
||||||
hyper.workspace = true
|
hyper.workspace = true
|
||||||
image.workspace = true
|
|
||||||
ipaddress.workspace = true
|
ipaddress.workspace = true
|
||||||
jsonwebtoken.workspace = true
|
jsonwebtoken.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
@@ -56,10 +57,8 @@ serde_html_form.workspace = true
|
|||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
sha-1.workspace = true
|
sha-1.workspace = true
|
||||||
thiserror.workspace = true
|
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
webpage.workspace = true
|
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
+88
-79
@@ -1,7 +1,8 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduit::debug_info;
|
use conduit::{debug_info, error, info, utils, warn, Error, Result};
|
||||||
use register::RegistrationKind;
|
use register::RegistrationKind;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{
|
api::client::{
|
||||||
@@ -17,15 +18,9 @@ use ruma::{
|
|||||||
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
|
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
|
||||||
push, OwnedRoomId, UserId,
|
push, OwnedRoomId, UserId,
|
||||||
};
|
};
|
||||||
use tracing::{error, info, warn};
|
|
||||||
|
|
||||||
use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
|
||||||
use crate::{
|
use crate::Ruma;
|
||||||
service::user_is_local,
|
|
||||||
services,
|
|
||||||
utils::{self},
|
|
||||||
Error, Result, Ruma,
|
|
||||||
};
|
|
||||||
|
|
||||||
const RANDOM_USER_ID_LENGTH: usize = 10;
|
const RANDOM_USER_ID_LENGTH: usize = 10;
|
||||||
|
|
||||||
@@ -42,20 +37,21 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
|
|||||||
/// invalid when trying to register
|
/// invalid when trying to register
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
|
#[tracing::instrument(skip_all, fields(%client), name = "register_available")]
|
||||||
pub(crate) async fn get_register_available_route(
|
pub(crate) async fn get_register_available_route(
|
||||||
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_username_availability::v3::Request>,
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<get_username_availability::v3::Request>,
|
||||||
) -> Result<get_username_availability::v3::Response> {
|
) -> Result<get_username_availability::v3::Response> {
|
||||||
// Validate user id
|
// Validate user id
|
||||||
let user_id = UserId::parse_with_server_name(body.username.to_lowercase(), services().globals.server_name())
|
let user_id = UserId::parse_with_server_name(body.username.to_lowercase(), services.globals.server_name())
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|user_id| !user_id.is_historical() && user_is_local(user_id))
|
.filter(|user_id| !user_id.is_historical() && services.globals.user_is_local(user_id))
|
||||||
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
|
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
|
||||||
|
|
||||||
// Check if username is creative enough
|
// Check if username is creative enough
|
||||||
if services().users.exists(&user_id)? {
|
if services.users.exists(&user_id)? {
|
||||||
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()
|
if services
|
||||||
.globals
|
.globals
|
||||||
.forbidden_usernames()
|
.forbidden_usernames()
|
||||||
.is_match(user_id.localpart())
|
.is_match(user_id.localpart())
|
||||||
@@ -91,9 +87,9 @@ pub(crate) async fn get_register_available_route(
|
|||||||
#[allow(clippy::doc_markdown)]
|
#[allow(clippy::doc_markdown)]
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "register")]
|
#[tracing::instrument(skip_all, fields(%client), name = "register")]
|
||||||
pub(crate) async fn register_route(
|
pub(crate) async fn register_route(
|
||||||
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> {
|
) -> Result<register::v3::Response> {
|
||||||
if !services().globals.allow_registration() && body.appservice_info.is_none() {
|
if !services.globals.allow_registration() && body.appservice_info.is_none() {
|
||||||
info!(
|
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 \
|
||||||
{:?}",
|
{:?}",
|
||||||
@@ -105,8 +101,8 @@ pub(crate) async fn register_route(
|
|||||||
let is_guest = body.kind == RegistrationKind::Guest;
|
let is_guest = body.kind == RegistrationKind::Guest;
|
||||||
|
|
||||||
if is_guest
|
if is_guest
|
||||||
&& (!services().globals.allow_guest_registration()
|
&& (!services.globals.allow_guest_registration()
|
||||||
|| (services().globals.allow_registration() && services().globals.config.registration_token.is_some()))
|
|| (services.globals.allow_registration() && services.globals.config.registration_token.is_some()))
|
||||||
{
|
{
|
||||||
info!(
|
info!(
|
||||||
"Guest registration disabled / registration enabled with token configured, rejecting guest registration \
|
"Guest registration disabled / registration enabled with token configured, rejecting guest registration \
|
||||||
@@ -121,7 +117,7 @@ pub(crate) async fn register_route(
|
|||||||
|
|
||||||
// forbid guests from registering if there is not a real admin user yet. give
|
// forbid guests from registering if there is not a real admin user yet. give
|
||||||
// generic user error.
|
// generic user error.
|
||||||
if is_guest && services().users.count()? < 2 {
|
if is_guest && services.users.count()? < 2 {
|
||||||
warn!(
|
warn!(
|
||||||
"Guest account attempted to register before a real admin user has been registered, rejecting \
|
"Guest account attempted to register before a real admin user has been registered, rejecting \
|
||||||
registration. Guest's initial device name: {:?}",
|
registration. Guest's initial device name: {:?}",
|
||||||
@@ -133,16 +129,16 @@ pub(crate) async fn register_route(
|
|||||||
let user_id = match (&body.username, is_guest) {
|
let user_id = match (&body.username, is_guest) {
|
||||||
(Some(username), false) => {
|
(Some(username), false) => {
|
||||||
let proposed_user_id =
|
let proposed_user_id =
|
||||||
UserId::parse_with_server_name(username.to_lowercase(), services().globals.server_name())
|
UserId::parse_with_server_name(username.to_lowercase(), services.globals.server_name())
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|user_id| !user_id.is_historical() && user_is_local(user_id))
|
.filter(|user_id| !user_id.is_historical() && services.globals.user_is_local(user_id))
|
||||||
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
|
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
|
||||||
|
|
||||||
if services().users.exists(&proposed_user_id)? {
|
if services.users.exists(&proposed_user_id)? {
|
||||||
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()
|
if services
|
||||||
.globals
|
.globals
|
||||||
.forbidden_usernames()
|
.forbidden_usernames()
|
||||||
.is_match(proposed_user_id.localpart())
|
.is_match(proposed_user_id.localpart())
|
||||||
@@ -155,10 +151,10 @@ pub(crate) async fn register_route(
|
|||||||
_ => loop {
|
_ => loop {
|
||||||
let proposed_user_id = UserId::parse_with_server_name(
|
let proposed_user_id = UserId::parse_with_server_name(
|
||||||
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
|
||||||
services().globals.server_name(),
|
services.globals.server_name(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if !services().users.exists(&proposed_user_id)? {
|
if !services.users.exists(&proposed_user_id)? {
|
||||||
break proposed_user_id;
|
break proposed_user_id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -172,13 +168,13 @@ pub(crate) async fn register_route(
|
|||||||
} else {
|
} else {
|
||||||
return Err(Error::BadRequest(ErrorKind::MissingToken, "Missing appservice token."));
|
return Err(Error::BadRequest(ErrorKind::MissingToken, "Missing appservice token."));
|
||||||
}
|
}
|
||||||
} else if services().appservice.is_exclusive_user_id(&user_id).await {
|
} else if services.appservice.is_exclusive_user_id(&user_id).await {
|
||||||
return Err(Error::BadRequest(ErrorKind::Exclusive, "User ID reserved by appservice."));
|
return Err(Error::BadRequest(ErrorKind::Exclusive, "User ID reserved by appservice."));
|
||||||
}
|
}
|
||||||
|
|
||||||
// UIAA
|
// UIAA
|
||||||
let mut uiaainfo;
|
let mut uiaainfo;
|
||||||
let skip_auth = if services().globals.config.registration_token.is_some() {
|
let skip_auth = if services.globals.config.registration_token.is_some() {
|
||||||
// Registration token required
|
// Registration token required
|
||||||
uiaainfo = UiaaInfo {
|
uiaainfo = UiaaInfo {
|
||||||
flows: vec![AuthFlow {
|
flows: vec![AuthFlow {
|
||||||
@@ -206,8 +202,8 @@ pub(crate) async fn register_route(
|
|||||||
|
|
||||||
if !skip_auth {
|
if !skip_auth {
|
||||||
if let Some(auth) = &body.auth {
|
if let Some(auth) = &body.auth {
|
||||||
let (worked, uiaainfo) = services().uiaa.try_auth(
|
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(),
|
"".into(),
|
||||||
auth,
|
auth,
|
||||||
&uiaainfo,
|
&uiaainfo,
|
||||||
@@ -218,8 +214,8 @@ pub(crate) async fn register_route(
|
|||||||
// Success!
|
// Success!
|
||||||
} else if let Some(json) = body.json_body {
|
} else if let Some(json) = body.json_body {
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
services().uiaa.create(
|
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(),
|
"".into(),
|
||||||
&uiaainfo,
|
&uiaainfo,
|
||||||
&json,
|
&json,
|
||||||
@@ -237,25 +233,25 @@ pub(crate) async fn register_route(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create user
|
// Create user
|
||||||
services().users.create(&user_id, password)?;
|
services.users.create(&user_id, password)?;
|
||||||
|
|
||||||
// Default to pretty displayname
|
// Default to pretty displayname
|
||||||
let mut displayname = user_id.localpart().to_owned();
|
let mut displayname = user_id.localpart().to_owned();
|
||||||
|
|
||||||
// If `new_user_displayname_suffix` is set, registration will push whatever
|
// 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
|
// content is set to the user's display name with a space before it
|
||||||
if !services().globals.new_user_displayname_suffix().is_empty() {
|
if !services.globals.new_user_displayname_suffix().is_empty() {
|
||||||
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix)
|
write!(displayname, " {}", services.globals.config.new_user_displayname_suffix)
|
||||||
.expect("should be able to write to string buffer");
|
.expect("should be able to write to string buffer");
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_displayname(&user_id, Some(displayname.clone()))
|
.set_displayname(&user_id, Some(displayname.clone()))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Initial account data
|
// Initial account data
|
||||||
services().account_data.update(
|
services.account_data.update(
|
||||||
None,
|
None,
|
||||||
&user_id,
|
&user_id,
|
||||||
GlobalAccountDataEventType::PushRules.to_string().into(),
|
GlobalAccountDataEventType::PushRules.to_string().into(),
|
||||||
@@ -290,7 +286,7 @@ pub(crate) async fn register_route(
|
|||||||
let token = utils::random_string(TOKEN_LENGTH);
|
let token = utils::random_string(TOKEN_LENGTH);
|
||||||
|
|
||||||
// Create device for this account
|
// Create device for this account
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())?;
|
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())?;
|
||||||
|
|
||||||
@@ -299,7 +295,7 @@ pub(crate) async fn register_route(
|
|||||||
// log in conduit admin channel if a non-guest user registered
|
// log in conduit admin channel if a non-guest user registered
|
||||||
if body.appservice_info.is_none() && !is_guest {
|
if body.appservice_info.is_none() && !is_guest {
|
||||||
info!("New user \"{user_id}\" registered on this server.");
|
info!("New user \"{user_id}\" registered on this server.");
|
||||||
services()
|
services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||||
"New user \"{user_id}\" registered on this server from IP {client}."
|
"New user \"{user_id}\" registered on this server from IP {client}."
|
||||||
@@ -308,8 +304,8 @@ pub(crate) async fn register_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// log in conduit admin channel if a guest registered
|
// log in conduit admin channel if a guest registered
|
||||||
if body.appservice_info.is_none() && is_guest && services().globals.log_guest_registrations() {
|
if body.appservice_info.is_none() && is_guest && services.globals.log_guest_registrations() {
|
||||||
info!("New guest user \"{user_id}\" registered on this server from IP.");
|
info!("New guest user \"{user_id}\" registered on this server.");
|
||||||
|
|
||||||
if let Some(device_display_name) = &body.initial_device_display_name {
|
if let Some(device_display_name) = &body.initial_device_display_name {
|
||||||
if body
|
if body
|
||||||
@@ -317,7 +313,7 @@ pub(crate) async fn register_route(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|device_display_name| !device_display_name.is_empty())
|
.is_some_and(|device_display_name| !device_display_name.is_empty())
|
||||||
{
|
{
|
||||||
services()
|
services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||||
"Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \
|
"Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \
|
||||||
@@ -325,7 +321,7 @@ pub(crate) async fn register_route(
|
|||||||
)))
|
)))
|
||||||
.await;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
services()
|
services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||||
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
|
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
|
||||||
@@ -334,7 +330,7 @@ pub(crate) async fn register_route(
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
services()
|
services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
.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}.",
|
||||||
@@ -346,14 +342,12 @@ pub(crate) async fn register_route(
|
|||||||
// If this is the first real user, grant them admin privileges except for guest
|
// If this is the first real user, grant them admin privileges except for guest
|
||||||
// users Note: the server user, @conduit:servername, is generated first
|
// users Note: the server user, @conduit:servername, is generated first
|
||||||
if !is_guest {
|
if !is_guest {
|
||||||
if let Some(admin_room) = service::admin::Service::get_admin_room()? {
|
if let Some(admin_room) = services.admin.get_admin_room()? {
|
||||||
if services()
|
if services.rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
|
||||||
.rooms
|
services
|
||||||
.state_cache
|
.admin
|
||||||
.room_joined_count(&admin_room)?
|
.make_user_admin(&user_id, displayname)
|
||||||
== Some(1)
|
.await?;
|
||||||
{
|
|
||||||
service::admin::make_user_admin(&user_id, displayname).await?;
|
|
||||||
|
|
||||||
warn!("Granting {user_id} admin privileges as the first user");
|
warn!("Granting {user_id} admin privileges as the first user");
|
||||||
}
|
}
|
||||||
@@ -361,14 +355,14 @@ pub(crate) async fn register_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if body.appservice_info.is_none()
|
if body.appservice_info.is_none()
|
||||||
&& !services().globals.config.auto_join_rooms.is_empty()
|
&& !services.globals.config.auto_join_rooms.is_empty()
|
||||||
&& (services().globals.allow_guests_auto_join_rooms() || !is_guest)
|
&& (services.globals.allow_guests_auto_join_rooms() || !is_guest)
|
||||||
{
|
{
|
||||||
for room in &services().globals.config.auto_join_rooms {
|
for room in &services.globals.config.auto_join_rooms {
|
||||||
if !services()
|
if !services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.server_in_room(services().globals.server_name(), room)?
|
.server_in_room(services.globals.server_name(), room)?
|
||||||
{
|
{
|
||||||
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;
|
continue;
|
||||||
@@ -376,10 +370,11 @@ pub(crate) async fn register_route(
|
|||||||
|
|
||||||
if let Some(room_id_server_name) = room.server_name() {
|
if let Some(room_id_server_name) = room.server_name() {
|
||||||
if let Err(e) = join_room_by_id_helper(
|
if let Err(e) = join_room_by_id_helper(
|
||||||
Some(&user_id),
|
&services,
|
||||||
|
&user_id,
|
||||||
room,
|
room,
|
||||||
Some("Automatically joining this room upon registration".to_owned()),
|
Some("Automatically joining this room upon registration".to_owned()),
|
||||||
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()],
|
&[room_id_server_name.to_owned(), services.globals.server_name().to_owned()],
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -421,9 +416,15 @@ pub(crate) async fn register_route(
|
|||||||
/// - Triggers device list updates
|
/// - Triggers device list updates
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
|
#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
|
||||||
pub(crate) async fn change_password_route(
|
pub(crate) async fn change_password_route(
|
||||||
InsecureClientIp(client): InsecureClientIp, body: Ruma<change_password::v3::Request>,
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<change_password::v3::Request>,
|
||||||
) -> Result<change_password::v3::Response> {
|
) -> Result<change_password::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
// Authentication for this endpoint was made optional, but we need
|
||||||
|
// authentication currently
|
||||||
|
let sender_user = body
|
||||||
|
.sender_user
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))?;
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let mut uiaainfo = UiaaInfo {
|
let mut uiaainfo = UiaaInfo {
|
||||||
@@ -437,7 +438,7 @@ pub(crate) async fn change_password_route(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(auth) = &body.auth {
|
if let Some(auth) = &body.auth {
|
||||||
let (worked, uiaainfo) = services()
|
let (worked, uiaainfo) = services
|
||||||
.uiaa
|
.uiaa
|
||||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
||||||
if !worked {
|
if !worked {
|
||||||
@@ -446,7 +447,7 @@ pub(crate) async fn change_password_route(
|
|||||||
// Success!
|
// Success!
|
||||||
} else if let Some(json) = body.json_body {
|
} else if let Some(json) = body.json_body {
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
services()
|
services
|
||||||
.uiaa
|
.uiaa
|
||||||
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
@@ -454,24 +455,24 @@ pub(crate) async fn change_password_route(
|
|||||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_password(sender_user, Some(&body.new_password))?;
|
.set_password(sender_user, Some(&body.new_password))?;
|
||||||
|
|
||||||
if body.logout_devices {
|
if body.logout_devices {
|
||||||
// Logout all devices except the current one
|
// Logout all devices except the current one
|
||||||
for id in services()
|
for id in services
|
||||||
.users
|
.users
|
||||||
.all_device_ids(sender_user)
|
.all_device_ids(sender_user)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.filter(|id| id != sender_device)
|
.filter(|id| id != sender_device)
|
||||||
{
|
{
|
||||||
services().users.remove_device(sender_user, &id)?;
|
services.users.remove_device(sender_user, &id)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("User {sender_user} changed their password.");
|
info!("User {sender_user} changed their password.");
|
||||||
services()
|
services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||||
"User {sender_user} changed their password."
|
"User {sender_user} changed their password."
|
||||||
@@ -486,14 +487,16 @@ pub(crate) async fn change_password_route(
|
|||||||
/// Get `user_id` of the sender user.
|
/// Get `user_id` of the sender user.
|
||||||
///
|
///
|
||||||
/// Note: Also works for Application Services
|
/// Note: Also works for Application Services
|
||||||
pub(crate) async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoami::v3::Response> {
|
pub(crate) async fn whoami_route(
|
||||||
|
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 sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let device_id = body.sender_device.clone();
|
let device_id = body.sender_device.clone();
|
||||||
|
|
||||||
Ok(whoami::v3::Response {
|
Ok(whoami::v3::Response {
|
||||||
user_id: sender_user.clone(),
|
user_id: sender_user.clone(),
|
||||||
device_id,
|
device_id,
|
||||||
is_guest: services().users.is_deactivated(sender_user)? && body.appservice_info.is_none(),
|
is_guest: services.users.is_deactivated(sender_user)? && body.appservice_info.is_none(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,9 +513,15 @@ pub(crate) async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoa
|
|||||||
/// - Removes ability to log in again
|
/// - Removes ability to log in again
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
|
#[tracing::instrument(skip_all, fields(%client), name = "deactivate")]
|
||||||
pub(crate) async fn deactivate_route(
|
pub(crate) async fn deactivate_route(
|
||||||
InsecureClientIp(client): InsecureClientIp, body: Ruma<deactivate::v3::Request>,
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<deactivate::v3::Request>,
|
||||||
) -> Result<deactivate::v3::Response> {
|
) -> Result<deactivate::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
// Authentication for this endpoint was made optional, but we need
|
||||||
|
// authentication currently
|
||||||
|
let sender_user = body
|
||||||
|
.sender_user
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| Error::BadRequest(ErrorKind::MissingToken, "Missing access token."))?;
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let mut uiaainfo = UiaaInfo {
|
let mut uiaainfo = UiaaInfo {
|
||||||
@@ -526,7 +535,7 @@ pub(crate) async fn deactivate_route(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(auth) = &body.auth {
|
if let Some(auth) = &body.auth {
|
||||||
let (worked, uiaainfo) = services()
|
let (worked, uiaainfo) = services
|
||||||
.uiaa
|
.uiaa
|
||||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
||||||
if !worked {
|
if !worked {
|
||||||
@@ -535,7 +544,7 @@ pub(crate) async fn deactivate_route(
|
|||||||
// Success!
|
// Success!
|
||||||
} else if let Some(json) = body.json_body {
|
} else if let Some(json) = body.json_body {
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
services()
|
services
|
||||||
.uiaa
|
.uiaa
|
||||||
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
@@ -544,23 +553,23 @@ pub(crate) async fn deactivate_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove devices and mark account as deactivated
|
// Remove devices and mark account as deactivated
|
||||||
services().users.deactivate_account(sender_user)?;
|
services.users.deactivate_account(sender_user)?;
|
||||||
|
|
||||||
// Remove profile pictures and display name
|
// Remove profile pictures and display name
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services()
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(sender_user)
|
.rooms_joined(sender_user)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
super::update_displayname(sender_user.clone(), None, all_joined_rooms.clone()).await?;
|
super::update_displayname(&services, sender_user.clone(), None, all_joined_rooms.clone()).await?;
|
||||||
super::update_avatar_url(sender_user.clone(), None, None, all_joined_rooms).await?;
|
super::update_avatar_url(&services, sender_user.clone(), None, None, all_joined_rooms).await?;
|
||||||
|
|
||||||
// Make the user leave all rooms before deactivation
|
// Make the user leave all rooms before deactivation
|
||||||
super::leave_all_rooms(sender_user).await;
|
super::leave_all_rooms(&services, sender_user).await;
|
||||||
|
|
||||||
info!("User {sender_user} deactivated their account.");
|
info!("User {sender_user} deactivated their account.");
|
||||||
services()
|
services
|
||||||
.admin
|
.admin
|
||||||
.send_message(RoomMessageEventContent::notice_plain(format!(
|
.send_message(RoomMessageEventContent::notice_plain(format!(
|
||||||
"User {sender_user} deactivated their account."
|
"User {sender_user} deactivated their account."
|
||||||
@@ -622,9 +631,9 @@ pub(crate) async fn request_3pid_management_token_via_msisdn_route(
|
|||||||
/// Currently does not have any ratelimiting, and this isn't very practical as
|
/// Currently does not have any ratelimiting, and this isn't very practical as
|
||||||
/// there is only one registration token allowed.
|
/// there is only one registration token allowed.
|
||||||
pub(crate) async fn check_registration_token_validity(
|
pub(crate) async fn check_registration_token_validity(
|
||||||
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> {
|
) -> Result<check_registration_token_validity::v1::Response> {
|
||||||
let Some(reg_token) = services().globals.config.registration_token.clone() else {
|
let Some(reg_token) = services.globals.config.registration_token.clone() else {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::forbidden(),
|
ErrorKind::forbidden(),
|
||||||
"Server does not allow token registration.",
|
"Server does not allow token registration.",
|
||||||
|
|||||||
+41
-153
@@ -1,34 +1,34 @@
|
|||||||
|
use axum::extract::State;
|
||||||
|
use conduit::{debug, Error, Result};
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::client::{
|
||||||
appservice,
|
|
||||||
client::{
|
|
||||||
alias::{create_alias, delete_alias, get_alias},
|
alias::{create_alias, delete_alias, get_alias},
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
},
|
},
|
||||||
federation,
|
OwnedServerName, RoomAliasId, RoomId,
|
||||||
},
|
|
||||||
OwnedRoomAliasId, OwnedServerName, RoomAliasId, RoomId,
|
|
||||||
};
|
};
|
||||||
use tracing::debug;
|
use service::Services;
|
||||||
|
|
||||||
use crate::{
|
use crate::Ruma;
|
||||||
debug_info, debug_warn,
|
|
||||||
service::{appservice::RegistrationInfo, server_is_ours},
|
|
||||||
services, Error, Result, Ruma,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/v3/directory/room/{roomAlias}`
|
/// # `PUT /_matrix/client/v3/directory/room/{roomAlias}`
|
||||||
///
|
///
|
||||||
/// Creates a new room alias on this server.
|
/// Creates a new room alias on this server.
|
||||||
pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) -> Result<create_alias::v3::Response> {
|
pub(crate) async fn create_alias_route(
|
||||||
|
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");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
alias_checks(&body.room_alias, &body.appservice_info).await?;
|
services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.appservice_checks(&body.room_alias, &body.appservice_info)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// this isn't apart of alias_checks or delete alias route because we should
|
// this isn't apart of alias_checks or delete alias route because we should
|
||||||
// allow removing forbidden room aliases
|
// allow removing forbidden room aliases
|
||||||
if services()
|
if services
|
||||||
.globals
|
.globals
|
||||||
.forbidden_alias_names()
|
.forbidden_alias_names()
|
||||||
.is_match(body.room_alias.alias())
|
.is_match(body.room_alias.alias())
|
||||||
@@ -36,7 +36,7 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
|
|||||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room alias is forbidden."));
|
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room alias is forbidden."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if services()
|
if services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.resolve_local_alias(&body.room_alias)?
|
.resolve_local_alias(&body.room_alias)?
|
||||||
@@ -45,7 +45,7 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
|
|||||||
return Err(Error::Conflict("Alias already exists."));
|
return Err(Error::Conflict("Alias already exists."));
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.set_alias(&body.room_alias, &body.room_id, sender_user)?;
|
.set_alias(&body.room_alias, &body.room_id, sender_user)?;
|
||||||
@@ -58,12 +58,18 @@ pub(crate) async fn create_alias_route(body: Ruma<create_alias::v3::Request>) ->
|
|||||||
/// Deletes a room alias from this server.
|
/// Deletes a room alias from this server.
|
||||||
///
|
///
|
||||||
/// - TODO: Update canonical alias event
|
/// - TODO: Update canonical alias event
|
||||||
pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) -> Result<delete_alias::v3::Response> {
|
pub(crate) async fn delete_alias_route(
|
||||||
|
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");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
alias_checks(&body.room_alias, &body.appservice_info).await?;
|
services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.appservice_checks(&body.room_alias, &body.appservice_info)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if services()
|
if services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.resolve_local_alias(&body.room_alias)?
|
.resolve_local_alias(&body.room_alias)?
|
||||||
@@ -72,7 +78,7 @@ pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) ->
|
|||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Alias does not exist."));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Alias does not exist."));
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.remove_alias(&body.room_alias, sender_user)
|
.remove_alias(&body.room_alias, sender_user)
|
||||||
@@ -86,134 +92,32 @@ pub(crate) async fn delete_alias_route(body: Ruma<delete_alias::v3::Request>) ->
|
|||||||
/// # `GET /_matrix/client/v3/directory/room/{roomAlias}`
|
/// # `GET /_matrix/client/v3/directory/room/{roomAlias}`
|
||||||
///
|
///
|
||||||
/// Resolve an alias locally or over federation.
|
/// Resolve an alias locally or over federation.
|
||||||
pub(crate) async fn get_alias_route(body: Ruma<get_alias::v3::Request>) -> Result<get_alias::v3::Response> {
|
pub(crate) async fn get_alias_route(
|
||||||
get_alias_helper(body.body.room_alias, None).await
|
State(services): State<crate::State>, body: Ruma<get_alias::v3::Request>,
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_alias_helper(
|
|
||||||
room_alias: OwnedRoomAliasId, servers: Option<Vec<OwnedServerName>>,
|
|
||||||
) -> Result<get_alias::v3::Response> {
|
) -> Result<get_alias::v3::Response> {
|
||||||
debug!("get_alias_helper servers: {servers:?}");
|
let room_alias = body.body.room_alias;
|
||||||
if !server_is_ours(room_alias.server_name())
|
let servers = None;
|
||||||
&& (!servers
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|servers| servers.contains(&services().globals.server_name().to_owned()))
|
|
||||||
|| servers.as_ref().is_none())
|
|
||||||
{
|
|
||||||
let mut response = services()
|
|
||||||
.sending
|
|
||||||
.send_federation_request(
|
|
||||||
room_alias.server_name(),
|
|
||||||
federation::query::get_room_information::v1::Request {
|
|
||||||
room_alias: room_alias.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
debug!("room alias server_name get_alias_helper response: {response:?}");
|
let Ok((room_id, pre_servers)) = services
|
||||||
|
|
||||||
if let Err(ref e) = response {
|
|
||||||
debug_info!(
|
|
||||||
"Server {} of the original room alias failed to assist in resolving room alias: {e}",
|
|
||||||
room_alias.server_name()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.as_ref().is_ok_and(|resp| resp.servers.is_empty()) || response.as_ref().is_err() {
|
|
||||||
if let Some(servers) = servers {
|
|
||||||
for server in servers {
|
|
||||||
response = services()
|
|
||||||
.sending
|
|
||||||
.send_federation_request(
|
|
||||||
&server,
|
|
||||||
federation::query::get_room_information::v1::Request {
|
|
||||||
room_alias: room_alias.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
debug!("Got response from server {server} for room aliases: {response:?}");
|
|
||||||
|
|
||||||
if let Ok(ref response) = response {
|
|
||||||
if !response.servers.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
debug_warn!(
|
|
||||||
"Server {server} responded with room aliases, but was empty? Response: {response:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(response) = response {
|
|
||||||
let room_id = response.room_id;
|
|
||||||
|
|
||||||
let mut pre_servers = response.servers;
|
|
||||||
// since the room alis server responded, insert it into the list
|
|
||||||
pre_servers.push(room_alias.server_name().into());
|
|
||||||
|
|
||||||
let servers = room_available_servers(&room_id, &room_alias, &Some(pre_servers));
|
|
||||||
debug!(
|
|
||||||
"room alias servers from federation response for room ID {room_id} and room alias {room_alias}: \
|
|
||||||
{servers:?}"
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(get_alias::v3::Response::new(room_id, servers));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(Error::BadRequest(
|
|
||||||
ErrorKind::NotFound,
|
|
||||||
"No servers could assist in resolving the room alias",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut room_id = None;
|
|
||||||
match services().rooms.alias.resolve_local_alias(&room_alias)? {
|
|
||||||
Some(r) => room_id = Some(r),
|
|
||||||
None => {
|
|
||||||
for appservice in services().appservice.read().await.values() {
|
|
||||||
if appservice.aliases.is_match(room_alias.as_str())
|
|
||||||
&& matches!(
|
|
||||||
services()
|
|
||||||
.sending
|
|
||||||
.send_appservice_request(
|
|
||||||
appservice.registration.clone(),
|
|
||||||
appservice::query::query_room_alias::v1::Request {
|
|
||||||
room_alias: room_alias.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await,
|
|
||||||
Ok(Some(_opt_result))
|
|
||||||
) {
|
|
||||||
room_id = Some(
|
|
||||||
services()
|
|
||||||
.rooms
|
.rooms
|
||||||
.alias
|
.alias
|
||||||
.resolve_local_alias(&room_alias)?
|
.resolve_alias(&room_alias, servers.as_ref())
|
||||||
.ok_or_else(|| Error::bad_config("Room does not exist."))?,
|
.await
|
||||||
);
|
else {
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(room_id) = room_id else {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Room with alias not found."));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Room with alias not found."));
|
||||||
};
|
};
|
||||||
|
|
||||||
let servers = room_available_servers(&room_id, &room_alias, &None);
|
let servers = room_available_servers(&services, &room_id, &room_alias, &pre_servers);
|
||||||
|
debug!(?room_alias, ?room_id, "available servers: {servers:?}");
|
||||||
debug!("room alias servers for room ID {room_id} and room alias {room_alias}");
|
|
||||||
|
|
||||||
Ok(get_alias::v3::Response::new(room_id, servers))
|
Ok(get_alias::v3::Response::new(room_id, servers))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn room_available_servers(
|
fn room_available_servers(
|
||||||
room_id: &RoomId, room_alias: &RoomAliasId, pre_servers: &Option<Vec<OwnedServerName>>,
|
services: &Services, room_id: &RoomId, room_alias: &RoomAliasId, pre_servers: &Option<Vec<OwnedServerName>>,
|
||||||
) -> Vec<OwnedServerName> {
|
) -> Vec<OwnedServerName> {
|
||||||
// find active servers in room state cache to suggest
|
// find active servers in room state cache to suggest
|
||||||
let mut servers: Vec<OwnedServerName> = services()
|
let mut servers: Vec<OwnedServerName> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_servers(room_id)
|
.room_servers(room_id)
|
||||||
@@ -236,10 +140,10 @@ fn room_available_servers(
|
|||||||
// prefer the room alias server first
|
// prefer the room alias server first
|
||||||
if let Some(server_index) = servers
|
if let Some(server_index) = servers
|
||||||
.iter()
|
.iter()
|
||||||
.position(|server_name| server_is_ours(server_name))
|
.position(|server_name| services.globals.server_is_ours(server_name))
|
||||||
{
|
{
|
||||||
servers.swap_remove(server_index);
|
servers.swap_remove(server_index);
|
||||||
servers.insert(0, services().globals.server_name().to_owned());
|
servers.insert(0, services.globals.server_name().to_owned());
|
||||||
} else if let Some(alias_server_index) = servers
|
} else if let Some(alias_server_index) = servers
|
||||||
.iter()
|
.iter()
|
||||||
.position(|server| server == room_alias.server_name())
|
.position(|server| server == room_alias.server_name())
|
||||||
@@ -250,19 +154,3 @@ fn room_available_servers(
|
|||||||
|
|
||||||
servers
|
servers
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn alias_checks(room_alias: &RoomAliasId, appservice_info: &Option<RegistrationInfo>) -> Result<()> {
|
|
||||||
if !server_is_ours(room_alias.server_name()) {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::InvalidParam, "Alias is from another server."));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref info) = appservice_info {
|
|
||||||
if !info.aliases.is_match(room_alias.as_str()) {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias is not in namespace."));
|
|
||||||
}
|
|
||||||
} else if services().appservice.is_exclusive_alias(room_alias).await {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::Exclusive, "Room alias reserved by appservice."));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
+49
-62
@@ -1,3 +1,4 @@
|
|||||||
|
use axum::extract::State;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{
|
api::client::{
|
||||||
backup::{
|
backup::{
|
||||||
@@ -11,16 +12,16 @@ use ruma::{
|
|||||||
UInt,
|
UInt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{services, Error, Result, Ruma};
|
use crate::{Error, Result, Ruma};
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/room_keys/version`
|
/// # `POST /_matrix/client/r0/room_keys/version`
|
||||||
///
|
///
|
||||||
/// Creates a new backup.
|
/// Creates a new backup.
|
||||||
pub(crate) async fn create_backup_version_route(
|
pub(crate) async fn create_backup_version_route(
|
||||||
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> {
|
) -> Result<create_backup_version::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let version = services()
|
let version = services
|
||||||
.key_backups
|
.key_backups
|
||||||
.create_backup(sender_user, &body.algorithm)?;
|
.create_backup(sender_user, &body.algorithm)?;
|
||||||
|
|
||||||
@@ -34,10 +35,10 @@ pub(crate) async fn create_backup_version_route(
|
|||||||
/// Update information about an existing backup. Only `auth_data` can be
|
/// Update information about an existing backup. Only `auth_data` can be
|
||||||
/// modified.
|
/// modified.
|
||||||
pub(crate) async fn update_backup_version_route(
|
pub(crate) async fn update_backup_version_route(
|
||||||
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> {
|
) -> Result<update_backup_version::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.update_backup(sender_user, &body.version, &body.algorithm)?;
|
.update_backup(sender_user, &body.version, &body.algorithm)?;
|
||||||
|
|
||||||
@@ -48,20 +49,20 @@ pub(crate) async fn update_backup_version_route(
|
|||||||
///
|
///
|
||||||
/// Get information about the latest backup version.
|
/// Get information about the latest backup version.
|
||||||
pub(crate) async fn get_latest_backup_info_route(
|
pub(crate) async fn get_latest_backup_info_route(
|
||||||
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> {
|
) -> Result<get_latest_backup_info::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let (version, algorithm) = services()
|
let (version, algorithm) = services
|
||||||
.key_backups
|
.key_backups
|
||||||
.get_latest_backup(sender_user)?
|
.get_latest_backup(sender_user)?
|
||||||
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
|
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
|
||||||
|
|
||||||
Ok(get_latest_backup_info::v3::Response {
|
Ok(get_latest_backup_info::v3::Response {
|
||||||
algorithm,
|
algorithm,
|
||||||
count: (UInt::try_from(services().key_backups.count_keys(sender_user, &version)?)
|
count: (UInt::try_from(services.key_backups.count_keys(sender_user, &version)?)
|
||||||
.expect("user backup keys count should not be that high")),
|
.expect("user backup keys count should not be that high")),
|
||||||
etag: services().key_backups.get_etag(sender_user, &version)?,
|
etag: services.key_backups.get_etag(sender_user, &version)?,
|
||||||
version,
|
version,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -70,10 +71,10 @@ pub(crate) async fn get_latest_backup_info_route(
|
|||||||
///
|
///
|
||||||
/// Get information about an existing backup.
|
/// Get information about an existing backup.
|
||||||
pub(crate) async fn get_backup_info_route(
|
pub(crate) async fn get_backup_info_route(
|
||||||
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> {
|
) -> Result<get_backup_info::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let algorithm = services()
|
let algorithm = services
|
||||||
.key_backups
|
.key_backups
|
||||||
.get_backup(sender_user, &body.version)?
|
.get_backup(sender_user, &body.version)?
|
||||||
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
|
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
|
||||||
@@ -81,14 +82,12 @@ pub(crate) async fn get_backup_info_route(
|
|||||||
Ok(get_backup_info::v3::Response {
|
Ok(get_backup_info::v3::Response {
|
||||||
algorithm,
|
algorithm,
|
||||||
count: (UInt::try_from(
|
count: (UInt::try_from(
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.count_keys(sender_user, &body.version)?,
|
.count_keys(sender_user, &body.version)?,
|
||||||
)
|
)
|
||||||
.expect("user backup keys count should not be that high")),
|
.expect("user backup keys count should not be that high")),
|
||||||
etag: services()
|
etag: services.key_backups.get_etag(sender_user, &body.version)?,
|
||||||
.key_backups
|
|
||||||
.get_etag(sender_user, &body.version)?,
|
|
||||||
version: body.version.clone(),
|
version: body.version.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -100,11 +99,11 @@ pub(crate) async fn get_backup_info_route(
|
|||||||
/// - Deletes both information about the backup, as well as all key data related
|
/// - Deletes both information about the backup, as well as all key data related
|
||||||
/// to the backup
|
/// to the backup
|
||||||
pub(crate) async fn delete_backup_version_route(
|
pub(crate) async fn delete_backup_version_route(
|
||||||
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> {
|
) -> Result<delete_backup_version::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.delete_backup(sender_user, &body.version)?;
|
.delete_backup(sender_user, &body.version)?;
|
||||||
|
|
||||||
@@ -120,12 +119,12 @@ pub(crate) async fn delete_backup_version_route(
|
|||||||
/// - Adds the keys to the backup
|
/// - Adds the keys to the backup
|
||||||
/// - Returns the new number of keys in this backup and the etag
|
/// - Returns the new number of keys in this backup and the etag
|
||||||
pub(crate) async fn add_backup_keys_route(
|
pub(crate) async fn add_backup_keys_route(
|
||||||
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> {
|
) -> Result<add_backup_keys::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
if Some(&body.version)
|
if Some(&body.version)
|
||||||
!= services()
|
!= services
|
||||||
.key_backups
|
.key_backups
|
||||||
.get_latest_backup_version(sender_user)?
|
.get_latest_backup_version(sender_user)?
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -138,7 +137,7 @@ pub(crate) async fn add_backup_keys_route(
|
|||||||
|
|
||||||
for (room_id, room) in &body.rooms {
|
for (room_id, room) in &body.rooms {
|
||||||
for (session_id, key_data) in &room.sessions {
|
for (session_id, key_data) in &room.sessions {
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.add_key(sender_user, &body.version, room_id, session_id, key_data)?;
|
.add_key(sender_user, &body.version, room_id, session_id, key_data)?;
|
||||||
}
|
}
|
||||||
@@ -146,14 +145,12 @@ pub(crate) async fn add_backup_keys_route(
|
|||||||
|
|
||||||
Ok(add_backup_keys::v3::Response {
|
Ok(add_backup_keys::v3::Response {
|
||||||
count: (UInt::try_from(
|
count: (UInt::try_from(
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.count_keys(sender_user, &body.version)?,
|
.count_keys(sender_user, &body.version)?,
|
||||||
)
|
)
|
||||||
.expect("user backup keys count should not be that high")),
|
.expect("user backup keys count should not be that high")),
|
||||||
etag: services()
|
etag: services.key_backups.get_etag(sender_user, &body.version)?,
|
||||||
.key_backups
|
|
||||||
.get_etag(sender_user, &body.version)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,12 +163,12 @@ pub(crate) async fn add_backup_keys_route(
|
|||||||
/// - Adds the keys to the backup
|
/// - Adds the keys to the backup
|
||||||
/// - Returns the new number of keys in this backup and the etag
|
/// - Returns the new number of keys in this backup and the etag
|
||||||
pub(crate) async fn add_backup_keys_for_room_route(
|
pub(crate) async fn add_backup_keys_for_room_route(
|
||||||
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> {
|
) -> Result<add_backup_keys_for_room::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
if Some(&body.version)
|
if Some(&body.version)
|
||||||
!= services()
|
!= services
|
||||||
.key_backups
|
.key_backups
|
||||||
.get_latest_backup_version(sender_user)?
|
.get_latest_backup_version(sender_user)?
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -183,21 +180,19 @@ pub(crate) async fn add_backup_keys_for_room_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (session_id, key_data) in &body.sessions {
|
for (session_id, key_data) in &body.sessions {
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.add_key(sender_user, &body.version, &body.room_id, session_id, key_data)?;
|
.add_key(sender_user, &body.version, &body.room_id, session_id, key_data)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(add_backup_keys_for_room::v3::Response {
|
Ok(add_backup_keys_for_room::v3::Response {
|
||||||
count: (UInt::try_from(
|
count: (UInt::try_from(
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.count_keys(sender_user, &body.version)?,
|
.count_keys(sender_user, &body.version)?,
|
||||||
)
|
)
|
||||||
.expect("user backup keys count should not be that high")),
|
.expect("user backup keys count should not be that high")),
|
||||||
etag: services()
|
etag: services.key_backups.get_etag(sender_user, &body.version)?,
|
||||||
.key_backups
|
|
||||||
.get_etag(sender_user, &body.version)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,12 +205,12 @@ pub(crate) async fn add_backup_keys_for_room_route(
|
|||||||
/// - Adds the keys to the backup
|
/// - Adds the keys to the backup
|
||||||
/// - Returns the new number of keys in this backup and the etag
|
/// - Returns the new number of keys in this backup and the etag
|
||||||
pub(crate) async fn add_backup_keys_for_session_route(
|
pub(crate) async fn add_backup_keys_for_session_route(
|
||||||
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> {
|
) -> Result<add_backup_keys_for_session::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
if Some(&body.version)
|
if Some(&body.version)
|
||||||
!= services()
|
!= services
|
||||||
.key_backups
|
.key_backups
|
||||||
.get_latest_backup_version(sender_user)?
|
.get_latest_backup_version(sender_user)?
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -226,20 +221,18 @@ pub(crate) async fn add_backup_keys_for_session_route(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?;
|
.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?;
|
||||||
|
|
||||||
Ok(add_backup_keys_for_session::v3::Response {
|
Ok(add_backup_keys_for_session::v3::Response {
|
||||||
count: (UInt::try_from(
|
count: (UInt::try_from(
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.count_keys(sender_user, &body.version)?,
|
.count_keys(sender_user, &body.version)?,
|
||||||
)
|
)
|
||||||
.expect("user backup keys count should not be that high")),
|
.expect("user backup keys count should not be that high")),
|
||||||
etag: services()
|
etag: services.key_backups.get_etag(sender_user, &body.version)?,
|
||||||
.key_backups
|
|
||||||
.get_etag(sender_user, &body.version)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,11 +240,11 @@ pub(crate) async fn add_backup_keys_for_session_route(
|
|||||||
///
|
///
|
||||||
/// Retrieves all keys from the backup.
|
/// Retrieves all keys from the backup.
|
||||||
pub(crate) async fn get_backup_keys_route(
|
pub(crate) async fn get_backup_keys_route(
|
||||||
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> {
|
) -> Result<get_backup_keys::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let rooms = services().key_backups.get_all(sender_user, &body.version)?;
|
let rooms = services.key_backups.get_all(sender_user, &body.version)?;
|
||||||
|
|
||||||
Ok(get_backup_keys::v3::Response {
|
Ok(get_backup_keys::v3::Response {
|
||||||
rooms,
|
rooms,
|
||||||
@@ -262,11 +255,11 @@ pub(crate) async fn get_backup_keys_route(
|
|||||||
///
|
///
|
||||||
/// Retrieves all keys from the backup for a given room.
|
/// Retrieves all keys from the backup for a given room.
|
||||||
pub(crate) async fn get_backup_keys_for_room_route(
|
pub(crate) async fn get_backup_keys_for_room_route(
|
||||||
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> {
|
) -> Result<get_backup_keys_for_room::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let sessions = services()
|
let sessions = services
|
||||||
.key_backups
|
.key_backups
|
||||||
.get_room(sender_user, &body.version, &body.room_id)?;
|
.get_room(sender_user, &body.version, &body.room_id)?;
|
||||||
|
|
||||||
@@ -279,11 +272,11 @@ pub(crate) async fn get_backup_keys_for_room_route(
|
|||||||
///
|
///
|
||||||
/// Retrieves a key from the backup.
|
/// Retrieves a key from the backup.
|
||||||
pub(crate) async fn get_backup_keys_for_session_route(
|
pub(crate) async fn get_backup_keys_for_session_route(
|
||||||
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> {
|
) -> Result<get_backup_keys_for_session::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let key_data = services()
|
let key_data = services
|
||||||
.key_backups
|
.key_backups
|
||||||
.get_session(sender_user, &body.version, &body.room_id, &body.session_id)?
|
.get_session(sender_user, &body.version, &body.room_id, &body.session_id)?
|
||||||
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Backup key not found for this user's session."))?;
|
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Backup key not found for this user's session."))?;
|
||||||
@@ -297,24 +290,22 @@ pub(crate) async fn get_backup_keys_for_session_route(
|
|||||||
///
|
///
|
||||||
/// Delete the keys from the backup.
|
/// Delete the keys from the backup.
|
||||||
pub(crate) async fn delete_backup_keys_route(
|
pub(crate) async fn delete_backup_keys_route(
|
||||||
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> {
|
) -> Result<delete_backup_keys::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.delete_all_keys(sender_user, &body.version)?;
|
.delete_all_keys(sender_user, &body.version)?;
|
||||||
|
|
||||||
Ok(delete_backup_keys::v3::Response {
|
Ok(delete_backup_keys::v3::Response {
|
||||||
count: (UInt::try_from(
|
count: (UInt::try_from(
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.count_keys(sender_user, &body.version)?,
|
.count_keys(sender_user, &body.version)?,
|
||||||
)
|
)
|
||||||
.expect("user backup keys count should not be that high")),
|
.expect("user backup keys count should not be that high")),
|
||||||
etag: services()
|
etag: services.key_backups.get_etag(sender_user, &body.version)?,
|
||||||
.key_backups
|
|
||||||
.get_etag(sender_user, &body.version)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,24 +313,22 @@ pub(crate) async fn delete_backup_keys_route(
|
|||||||
///
|
///
|
||||||
/// Delete the keys from the backup for a given room.
|
/// Delete the keys from the backup for a given room.
|
||||||
pub(crate) async fn delete_backup_keys_for_room_route(
|
pub(crate) async fn delete_backup_keys_for_room_route(
|
||||||
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> {
|
) -> Result<delete_backup_keys_for_room::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.delete_room_keys(sender_user, &body.version, &body.room_id)?;
|
.delete_room_keys(sender_user, &body.version, &body.room_id)?;
|
||||||
|
|
||||||
Ok(delete_backup_keys_for_room::v3::Response {
|
Ok(delete_backup_keys_for_room::v3::Response {
|
||||||
count: (UInt::try_from(
|
count: (UInt::try_from(
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.count_keys(sender_user, &body.version)?,
|
.count_keys(sender_user, &body.version)?,
|
||||||
)
|
)
|
||||||
.expect("user backup keys count should not be that high")),
|
.expect("user backup keys count should not be that high")),
|
||||||
etag: services()
|
etag: services.key_backups.get_etag(sender_user, &body.version)?,
|
||||||
.key_backups
|
|
||||||
.get_etag(sender_user, &body.version)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,23 +336,21 @@ pub(crate) async fn delete_backup_keys_for_room_route(
|
|||||||
///
|
///
|
||||||
/// Delete a key from the backup.
|
/// Delete a key from the backup.
|
||||||
pub(crate) async fn delete_backup_keys_for_session_route(
|
pub(crate) async fn delete_backup_keys_for_session_route(
|
||||||
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> {
|
) -> Result<delete_backup_keys_for_session::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?;
|
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?;
|
||||||
|
|
||||||
Ok(delete_backup_keys_for_session::v3::Response {
|
Ok(delete_backup_keys_for_session::v3::Response {
|
||||||
count: (UInt::try_from(
|
count: (UInt::try_from(
|
||||||
services()
|
services
|
||||||
.key_backups
|
.key_backups
|
||||||
.count_keys(sender_user, &body.version)?,
|
.count_keys(sender_user, &body.version)?,
|
||||||
)
|
)
|
||||||
.expect("user backup keys count should not be that high")),
|
.expect("user backup keys count should not be that high")),
|
||||||
etag: services()
|
etag: services.key_backups.get_etag(sender_user, &body.version)?,
|
||||||
.key_backups
|
|
||||||
.get_etag(sender_user, &body.version)?,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,30 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
use ruma::api::client::discovery::get_capabilities::{
|
use ruma::api::client::discovery::get_capabilities::{
|
||||||
self, Capabilities, RoomVersionStability, RoomVersionsCapability, ThirdPartyIdChangesCapability,
|
self, Capabilities, RoomVersionStability, RoomVersionsCapability, ThirdPartyIdChangesCapability,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{services, Result, Ruma};
|
use crate::{Result, Ruma};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/v3/capabilities`
|
/// # `GET /_matrix/client/v3/capabilities`
|
||||||
///
|
///
|
||||||
/// Get information on the supported feature set and other relevent capabilities
|
/// Get information on the supported feature set and other relevent capabilities
|
||||||
/// of this server.
|
/// of this server.
|
||||||
pub(crate) async fn get_capabilities_route(
|
pub(crate) async fn get_capabilities_route(
|
||||||
_body: Ruma<get_capabilities::v3::Request>,
|
State(services): State<crate::State>, _body: Ruma<get_capabilities::v3::Request>,
|
||||||
) -> Result<get_capabilities::v3::Response> {
|
) -> Result<get_capabilities::v3::Response> {
|
||||||
let mut available = BTreeMap::new();
|
let mut available = BTreeMap::new();
|
||||||
for room_version in &services().globals.unstable_room_versions {
|
for room_version in &services.globals.unstable_room_versions {
|
||||||
available.insert(room_version.clone(), RoomVersionStability::Unstable);
|
available.insert(room_version.clone(), RoomVersionStability::Unstable);
|
||||||
}
|
}
|
||||||
for room_version in &services().globals.stable_room_versions {
|
for room_version in &services.globals.stable_room_versions {
|
||||||
available.insert(room_version.clone(), RoomVersionStability::Stable);
|
available.insert(room_version.clone(), RoomVersionStability::Stable);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut capabilities = Capabilities::default();
|
let mut capabilities = Capabilities::default();
|
||||||
capabilities.room_versions = RoomVersionsCapability {
|
capabilities.room_versions = RoomVersionsCapability {
|
||||||
default: services().globals.default_room_version(),
|
default: services.globals.default_room_version(),
|
||||||
available,
|
available,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+19
-10
@@ -1,3 +1,4 @@
|
|||||||
|
use axum::extract::State;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{
|
api::client::{
|
||||||
config::{get_global_account_data, get_room_account_data, set_global_account_data, set_room_account_data},
|
config::{get_global_account_data, get_room_account_data, set_global_account_data, set_room_account_data},
|
||||||
@@ -10,15 +11,21 @@ use ruma::{
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::{json, value::RawValue as RawJsonValue};
|
use serde_json::{json, value::RawValue as RawJsonValue};
|
||||||
|
|
||||||
use crate::{services, Error, Result, Ruma};
|
use crate::{service::Services, Error, Result, Ruma};
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
|
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
|
||||||
///
|
///
|
||||||
/// Sets some account data for the sender user.
|
/// Sets some account data for the sender user.
|
||||||
pub(crate) async fn set_global_account_data_route(
|
pub(crate) async fn set_global_account_data_route(
|
||||||
body: Ruma<set_global_account_data::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<set_global_account_data::v3::Request>,
|
||||||
) -> Result<set_global_account_data::v3::Response> {
|
) -> Result<set_global_account_data::v3::Response> {
|
||||||
set_account_data(None, &body.sender_user, &body.event_type.to_string(), body.data.json())?;
|
set_account_data(
|
||||||
|
&services,
|
||||||
|
None,
|
||||||
|
&body.sender_user,
|
||||||
|
&body.event_type.to_string(),
|
||||||
|
body.data.json(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(set_global_account_data::v3::Response {})
|
Ok(set_global_account_data::v3::Response {})
|
||||||
}
|
}
|
||||||
@@ -27,9 +34,10 @@ pub(crate) async fn set_global_account_data_route(
|
|||||||
///
|
///
|
||||||
/// Sets some room account data for the sender user.
|
/// Sets some room account data for the sender user.
|
||||||
pub(crate) async fn set_room_account_data_route(
|
pub(crate) async fn set_room_account_data_route(
|
||||||
body: Ruma<set_room_account_data::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<set_room_account_data::v3::Request>,
|
||||||
) -> Result<set_room_account_data::v3::Response> {
|
) -> Result<set_room_account_data::v3::Response> {
|
||||||
set_account_data(
|
set_account_data(
|
||||||
|
&services,
|
||||||
Some(&body.room_id),
|
Some(&body.room_id),
|
||||||
&body.sender_user,
|
&body.sender_user,
|
||||||
&body.event_type.to_string(),
|
&body.event_type.to_string(),
|
||||||
@@ -43,11 +51,11 @@ pub(crate) async fn set_room_account_data_route(
|
|||||||
///
|
///
|
||||||
/// Gets some account data for the sender user.
|
/// Gets some account data for the sender user.
|
||||||
pub(crate) async fn get_global_account_data_route(
|
pub(crate) async fn get_global_account_data_route(
|
||||||
body: Ruma<get_global_account_data::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<get_global_account_data::v3::Request>,
|
||||||
) -> Result<get_global_account_data::v3::Response> {
|
) -> Result<get_global_account_data::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let event: Box<RawJsonValue> = services()
|
let event: Box<RawJsonValue> = services
|
||||||
.account_data
|
.account_data
|
||||||
.get(None, sender_user, body.event_type.to_string().into())?
|
.get(None, sender_user, body.event_type.to_string().into())?
|
||||||
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||||
@@ -65,11 +73,11 @@ pub(crate) async fn get_global_account_data_route(
|
|||||||
///
|
///
|
||||||
/// Gets some room account data for the sender user.
|
/// Gets some room account data for the sender user.
|
||||||
pub(crate) async fn get_room_account_data_route(
|
pub(crate) async fn get_room_account_data_route(
|
||||||
body: Ruma<get_room_account_data::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<get_room_account_data::v3::Request>,
|
||||||
) -> Result<get_room_account_data::v3::Response> {
|
) -> Result<get_room_account_data::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let event: Box<RawJsonValue> = services()
|
let event: Box<RawJsonValue> = services
|
||||||
.account_data
|
.account_data
|
||||||
.get(Some(&body.room_id), sender_user, body.event_type.clone())?
|
.get(Some(&body.room_id), sender_user, body.event_type.clone())?
|
||||||
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
|
||||||
@@ -84,14 +92,15 @@ pub(crate) async fn get_room_account_data_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_account_data(
|
fn set_account_data(
|
||||||
room_id: Option<&RoomId>, sender_user: &Option<OwnedUserId>, event_type: &str, data: &RawJsonValue,
|
services: &Services, room_id: Option<&RoomId>, sender_user: &Option<OwnedUserId>, event_type: &str,
|
||||||
|
data: &RawJsonValue,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let sender_user = sender_user.as_ref().expect("user is authenticated");
|
let sender_user = sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let data: serde_json::Value =
|
let data: serde_json::Value =
|
||||||
serde_json::from_str(data.get()).map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
|
serde_json::from_str(data.get()).map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
|
||||||
|
|
||||||
services().account_data.update(
|
services.account_data.update(
|
||||||
room_id,
|
room_id,
|
||||||
sender_user,
|
sender_user,
|
||||||
event_type.into(),
|
event_type.into(),
|
||||||
|
|||||||
+21
-18
@@ -1,12 +1,13 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
|
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
|
||||||
events::StateEventType,
|
events::StateEventType,
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{services, Error, Result, Ruma};
|
use crate::{Error, Result, Ruma};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/rooms/{roomId}/context`
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/context`
|
||||||
///
|
///
|
||||||
@@ -14,7 +15,9 @@ use crate::{services, Error, Result, Ruma};
|
|||||||
///
|
///
|
||||||
/// - Only works if the user is joined (TODO: always allow, but only show events
|
/// - Only works if the user is joined (TODO: always allow, but only show events
|
||||||
/// if the user was joined, depending on history_visibility)
|
/// if the user was joined, depending on history_visibility)
|
||||||
pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> Result<get_context::v3::Response> {
|
pub(crate) async fn get_context_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<get_context::v3::Request>,
|
||||||
|
) -> Result<get_context::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
@@ -27,13 +30,13 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
|
|
||||||
let mut lazy_loaded = HashSet::new();
|
let mut lazy_loaded = HashSet::new();
|
||||||
|
|
||||||
let base_token = services()
|
let base_token = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.get_pdu_count(&body.event_id)?
|
.get_pdu_count(&body.event_id)?
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Base event id not found."))?;
|
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Base event id not found."))?;
|
||||||
|
|
||||||
let base_event = services()
|
let base_event = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.get_pdu(&body.event_id)?
|
.get_pdu(&body.event_id)?
|
||||||
@@ -41,7 +44,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
|
|
||||||
let room_id = base_event.room_id.clone();
|
let room_id = base_event.room_id.clone();
|
||||||
|
|
||||||
if !services()
|
if !services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_event(sender_user, &room_id, &body.event_id)?
|
.user_can_see_event(sender_user, &room_id, &body.event_id)?
|
||||||
@@ -52,7 +55,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !services().rooms.lazy_loading.lazy_load_was_sent_before(
|
if !services.rooms.lazy_loading.lazy_load_was_sent_before(
|
||||||
sender_user,
|
sender_user,
|
||||||
sender_device,
|
sender_device,
|
||||||
&room_id,
|
&room_id,
|
||||||
@@ -67,14 +70,14 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
|
|
||||||
let base_event = base_event.to_room_event();
|
let base_event = base_event.to_room_event();
|
||||||
|
|
||||||
let events_before: Vec<_> = services()
|
let events_before: Vec<_> = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.pdus_until(sender_user, &room_id, base_token)?
|
.pdus_until(sender_user, &room_id, base_token)?
|
||||||
.take(limit / 2)
|
.take(limit / 2)
|
||||||
.filter_map(Result::ok) // Remove buggy events
|
.filter_map(Result::ok) // Remove buggy events
|
||||||
.filter(|(_, pdu)| {
|
.filter(|(_, pdu)| {
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
|
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
|
||||||
@@ -83,7 +86,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (_, event) in &events_before {
|
for (_, event) in &events_before {
|
||||||
if !services().rooms.lazy_loading.lazy_load_was_sent_before(
|
if !services.rooms.lazy_loading.lazy_load_was_sent_before(
|
||||||
sender_user,
|
sender_user,
|
||||||
sender_device,
|
sender_device,
|
||||||
&room_id,
|
&room_id,
|
||||||
@@ -103,14 +106,14 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
.map(|(_, pdu)| pdu.to_room_event())
|
.map(|(_, pdu)| pdu.to_room_event())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let events_after: Vec<_> = services()
|
let events_after: Vec<_> = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.pdus_after(sender_user, &room_id, base_token)?
|
.pdus_after(sender_user, &room_id, base_token)?
|
||||||
.take(limit / 2)
|
.take(limit / 2)
|
||||||
.filter_map(Result::ok) // Remove buggy events
|
.filter_map(Result::ok) // Remove buggy events
|
||||||
.filter(|(_, pdu)| {
|
.filter(|(_, pdu)| {
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
|
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
|
||||||
@@ -119,7 +122,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for (_, event) in &events_after {
|
for (_, event) in &events_after {
|
||||||
if !services().rooms.lazy_loading.lazy_load_was_sent_before(
|
if !services.rooms.lazy_loading.lazy_load_was_sent_before(
|
||||||
sender_user,
|
sender_user,
|
||||||
sender_device,
|
sender_device,
|
||||||
&room_id,
|
&room_id,
|
||||||
@@ -130,7 +133,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let shortstatehash = services()
|
let shortstatehash = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.pdu_shortstatehash(
|
.pdu_shortstatehash(
|
||||||
@@ -139,7 +142,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
.map_or(&*body.event_id, |(_, e)| &*e.event_id),
|
.map_or(&*body.event_id, |(_, e)| &*e.event_id),
|
||||||
)?
|
)?
|
||||||
.map_or(
|
.map_or(
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.state
|
.state
|
||||||
.get_room_shortstatehash(&room_id)?
|
.get_room_shortstatehash(&room_id)?
|
||||||
@@ -147,7 +150,7 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
|hash| hash,
|
|hash| hash,
|
||||||
);
|
);
|
||||||
|
|
||||||
let state_ids = services()
|
let state_ids = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.state_full_ids(shortstatehash)
|
.state_full_ids(shortstatehash)
|
||||||
@@ -165,20 +168,20 @@ pub(crate) async fn get_context_route(body: Ruma<get_context::v3::Request>) -> R
|
|||||||
let mut state = Vec::with_capacity(state_ids.len());
|
let mut state = Vec::with_capacity(state_ids.len());
|
||||||
|
|
||||||
for (shortstatekey, id) in state_ids {
|
for (shortstatekey, id) in state_ids {
|
||||||
let (event_type, state_key) = services()
|
let (event_type, state_key) = services
|
||||||
.rooms
|
.rooms
|
||||||
.short
|
.short
|
||||||
.get_statekey_from_short(shortstatekey)?;
|
.get_statekey_from_short(shortstatekey)?;
|
||||||
|
|
||||||
if event_type != StateEventType::RoomMember {
|
if event_type != StateEventType::RoomMember {
|
||||||
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else {
|
let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
|
||||||
error!("Pdu in state not found: {}", id);
|
error!("Pdu in state not found: {}", id);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
state.push(pdu.to_state_event());
|
state.push(pdu.to_state_event());
|
||||||
} else if !lazy_load_enabled || lazy_loaded.contains(&state_key) {
|
} else if !lazy_load_enabled || lazy_loaded.contains(&state_key) {
|
||||||
let Some(pdu) = services().rooms.timeline.get_pdu(&id)? else {
|
let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
|
||||||
error!("Pdu in state not found: {}", id);
|
error!("Pdu in state not found: {}", id);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
+25
-18
@@ -1,3 +1,4 @@
|
|||||||
|
use axum::extract::State;
|
||||||
use ruma::api::client::{
|
use ruma::api::client::{
|
||||||
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
|
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
@@ -5,15 +6,17 @@ use ruma::api::client::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::SESSION_ID_LENGTH;
|
use super::SESSION_ID_LENGTH;
|
||||||
use crate::{services, utils, Error, Result, Ruma};
|
use crate::{utils, Error, Result, Ruma};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/devices`
|
/// # `GET /_matrix/client/r0/devices`
|
||||||
///
|
///
|
||||||
/// Get metadata on all devices of the sender user.
|
/// Get metadata on all devices of the sender user.
|
||||||
pub(crate) async fn get_devices_route(body: Ruma<get_devices::v3::Request>) -> Result<get_devices::v3::Response> {
|
pub(crate) async fn get_devices_route(
|
||||||
|
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");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let devices: Vec<device::Device> = services()
|
let devices: Vec<device::Device> = services
|
||||||
.users
|
.users
|
||||||
.all_devices_metadata(sender_user)
|
.all_devices_metadata(sender_user)
|
||||||
.filter_map(Result::ok) // Filter out buggy devices
|
.filter_map(Result::ok) // Filter out buggy devices
|
||||||
@@ -27,10 +30,12 @@ pub(crate) async fn get_devices_route(body: Ruma<get_devices::v3::Request>) -> R
|
|||||||
/// # `GET /_matrix/client/r0/devices/{deviceId}`
|
/// # `GET /_matrix/client/r0/devices/{deviceId}`
|
||||||
///
|
///
|
||||||
/// Get metadata on a single device of the sender user.
|
/// Get metadata on a single device of the sender user.
|
||||||
pub(crate) async fn get_device_route(body: Ruma<get_device::v3::Request>) -> Result<get_device::v3::Response> {
|
pub(crate) async fn get_device_route(
|
||||||
|
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");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let device = services()
|
let device = services
|
||||||
.users
|
.users
|
||||||
.get_device_metadata(sender_user, &body.body.device_id)?
|
.get_device_metadata(sender_user, &body.body.device_id)?
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
|
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
|
||||||
@@ -43,17 +48,19 @@ pub(crate) async fn get_device_route(body: Ruma<get_device::v3::Request>) -> Res
|
|||||||
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
|
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
|
||||||
///
|
///
|
||||||
/// Updates the metadata on a given device of the sender user.
|
/// Updates the metadata on a given device of the sender user.
|
||||||
pub(crate) async fn update_device_route(body: Ruma<update_device::v3::Request>) -> Result<update_device::v3::Response> {
|
pub(crate) async fn update_device_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<update_device::v3::Request>,
|
||||||
|
) -> Result<update_device::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let mut device = services()
|
let mut device = services
|
||||||
.users
|
.users
|
||||||
.get_device_metadata(sender_user, &body.device_id)?
|
.get_device_metadata(sender_user, &body.device_id)?
|
||||||
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
|
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
|
||||||
|
|
||||||
device.display_name.clone_from(&body.display_name);
|
device.display_name.clone_from(&body.display_name);
|
||||||
|
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.update_device_metadata(sender_user, &body.device_id, &device)?;
|
.update_device_metadata(sender_user, &body.device_id, &device)?;
|
||||||
|
|
||||||
@@ -70,7 +77,9 @@ pub(crate) async fn update_device_route(body: Ruma<update_device::v3::Request>)
|
|||||||
/// last seen ts)
|
/// last seen ts)
|
||||||
/// - Forgets to-device events
|
/// - Forgets to-device events
|
||||||
/// - Triggers device list updates
|
/// - Triggers device list updates
|
||||||
pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>) -> Result<delete_device::v3::Response> {
|
pub(crate) async fn delete_device_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<delete_device::v3::Request>,
|
||||||
|
) -> Result<delete_device::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
@@ -86,7 +95,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(auth) = &body.auth {
|
if let Some(auth) = &body.auth {
|
||||||
let (worked, uiaainfo) = services()
|
let (worked, uiaainfo) = services
|
||||||
.uiaa
|
.uiaa
|
||||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
||||||
if !worked {
|
if !worked {
|
||||||
@@ -95,7 +104,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
|
|||||||
// Success!
|
// Success!
|
||||||
} else if let Some(json) = body.json_body {
|
} else if let Some(json) = body.json_body {
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
services()
|
services
|
||||||
.uiaa
|
.uiaa
|
||||||
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
@@ -103,9 +112,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
|
|||||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services.users.remove_device(sender_user, &body.device_id)?;
|
||||||
.users
|
|
||||||
.remove_device(sender_user, &body.device_id)?;
|
|
||||||
|
|
||||||
Ok(delete_device::v3::Response {})
|
Ok(delete_device::v3::Response {})
|
||||||
}
|
}
|
||||||
@@ -123,7 +130,7 @@ pub(crate) async fn delete_device_route(body: Ruma<delete_device::v3::Request>)
|
|||||||
/// - Forgets to-device events
|
/// - Forgets to-device events
|
||||||
/// - Triggers device list updates
|
/// - Triggers device list updates
|
||||||
pub(crate) async fn delete_devices_route(
|
pub(crate) async fn delete_devices_route(
|
||||||
body: Ruma<delete_devices::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<delete_devices::v3::Request>,
|
||||||
) -> Result<delete_devices::v3::Response> {
|
) -> Result<delete_devices::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
@@ -140,7 +147,7 @@ pub(crate) async fn delete_devices_route(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(auth) = &body.auth {
|
if let Some(auth) = &body.auth {
|
||||||
let (worked, uiaainfo) = services()
|
let (worked, uiaainfo) = services
|
||||||
.uiaa
|
.uiaa
|
||||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
||||||
if !worked {
|
if !worked {
|
||||||
@@ -149,7 +156,7 @@ pub(crate) async fn delete_devices_route(
|
|||||||
// Success!
|
// Success!
|
||||||
} else if let Some(json) = body.json_body {
|
} else if let Some(json) = body.json_body {
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
services()
|
services
|
||||||
.uiaa
|
.uiaa
|
||||||
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
@@ -158,7 +165,7 @@ pub(crate) async fn delete_devices_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for device_id in &body.devices {
|
for device_id in &body.devices {
|
||||||
services().users.remove_device(sender_user, device_id)?;
|
services.users.remove_device(sender_user, device_id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(delete_devices::v3::Response {})
|
Ok(delete_devices::v3::Response {})
|
||||||
|
|||||||
+78
-52
@@ -1,4 +1,6 @@
|
|||||||
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
|
use conduit::{err, info, warn, Error, Result};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
client::{
|
client::{
|
||||||
@@ -11,17 +13,16 @@ use ruma::{
|
|||||||
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork},
|
directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork},
|
||||||
events::{
|
events::{
|
||||||
room::{
|
room::{
|
||||||
avatar::RoomAvatarEventContent,
|
|
||||||
create::RoomCreateEventContent,
|
|
||||||
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
join_rules::{JoinRule, RoomJoinRulesEventContent},
|
||||||
|
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
|
||||||
},
|
},
|
||||||
StateEventType,
|
StateEventType,
|
||||||
},
|
},
|
||||||
uint, ServerName, UInt,
|
uint, RoomId, ServerName, UInt, UserId,
|
||||||
};
|
};
|
||||||
use tracing::{error, info, warn};
|
use service::Services;
|
||||||
|
|
||||||
use crate::{service::server_is_ours, services, Error, Result, Ruma};
|
use crate::Ruma;
|
||||||
|
|
||||||
/// # `POST /_matrix/client/v3/publicRooms`
|
/// # `POST /_matrix/client/v3/publicRooms`
|
||||||
///
|
///
|
||||||
@@ -30,10 +31,11 @@ use crate::{service::server_is_ours, services, Error, Result, Ruma};
|
|||||||
/// - Rooms are ordered by the number of joined members
|
/// - Rooms are ordered by the number of joined members
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
||||||
pub(crate) async fn get_public_rooms_filtered_route(
|
pub(crate) async fn get_public_rooms_filtered_route(
|
||||||
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_public_rooms_filtered::v3::Request>,
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<get_public_rooms_filtered::v3::Request>,
|
||||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||||
if let Some(server) = &body.server {
|
if let Some(server) = &body.server {
|
||||||
if services()
|
if services
|
||||||
.globals
|
.globals
|
||||||
.forbidden_remote_room_directory_server_names()
|
.forbidden_remote_room_directory_server_names()
|
||||||
.contains(server)
|
.contains(server)
|
||||||
@@ -46,6 +48,7 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let response = get_public_rooms_filtered_helper(
|
let response = get_public_rooms_filtered_helper(
|
||||||
|
&services,
|
||||||
body.server.as_deref(),
|
body.server.as_deref(),
|
||||||
body.limit,
|
body.limit,
|
||||||
body.since.as_deref(),
|
body.since.as_deref(),
|
||||||
@@ -68,10 +71,11 @@ pub(crate) async fn get_public_rooms_filtered_route(
|
|||||||
/// - Rooms are ordered by the number of joined members
|
/// - Rooms are ordered by the number of joined members
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
#[tracing::instrument(skip_all, fields(%client), name = "publicrooms")]
|
||||||
pub(crate) async fn get_public_rooms_route(
|
pub(crate) async fn get_public_rooms_route(
|
||||||
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_public_rooms::v3::Request>,
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<get_public_rooms::v3::Request>,
|
||||||
) -> Result<get_public_rooms::v3::Response> {
|
) -> Result<get_public_rooms::v3::Response> {
|
||||||
if let Some(server) = &body.server {
|
if let Some(server) = &body.server {
|
||||||
if services()
|
if services
|
||||||
.globals
|
.globals
|
||||||
.forbidden_remote_room_directory_server_names()
|
.forbidden_remote_room_directory_server_names()
|
||||||
.contains(server)
|
.contains(server)
|
||||||
@@ -84,6 +88,7 @@ pub(crate) async fn get_public_rooms_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let response = get_public_rooms_filtered_helper(
|
let response = get_public_rooms_filtered_helper(
|
||||||
|
&services,
|
||||||
body.server.as_deref(),
|
body.server.as_deref(),
|
||||||
body.limit,
|
body.limit,
|
||||||
body.since.as_deref(),
|
body.since.as_deref(),
|
||||||
@@ -107,22 +112,28 @@ pub(crate) async fn get_public_rooms_route(
|
|||||||
/// # `PUT /_matrix/client/r0/directory/list/room/{roomId}`
|
/// # `PUT /_matrix/client/r0/directory/list/room/{roomId}`
|
||||||
///
|
///
|
||||||
/// Sets the visibility of a given room in the room directory.
|
/// Sets the visibility of a given room in the room directory.
|
||||||
///
|
|
||||||
/// - TODO: Access control checks
|
|
||||||
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
|
#[tracing::instrument(skip_all, fields(%client), name = "room_directory")]
|
||||||
pub(crate) async fn set_room_visibility_route(
|
pub(crate) async fn set_room_visibility_route(
|
||||||
InsecureClientIp(client): InsecureClientIp, body: Ruma<set_room_visibility::v3::Request>,
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<set_room_visibility::v3::Request>,
|
||||||
) -> Result<set_room_visibility::v3::Response> {
|
) -> Result<set_room_visibility::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
if !services().rooms.metadata.exists(&body.room_id)? {
|
if !services.rooms.metadata.exists(&body.room_id)? {
|
||||||
// Return 404 if the room doesn't exist
|
// Return 404 if the room doesn't exist
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !user_can_publish_room(&services, sender_user, &body.room_id)? {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::forbidden(),
|
||||||
|
"User is not allowed to publish this room",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
match &body.visibility {
|
match &body.visibility {
|
||||||
room::Visibility::Public => {
|
room::Visibility::Public => {
|
||||||
if services().globals.config.lockdown_public_room_directory && !services().users.is_admin(sender_user)? {
|
if services.globals.config.lockdown_public_room_directory && !services.users.is_admin(sender_user)? {
|
||||||
info!(
|
info!(
|
||||||
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
|
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
|
||||||
\"lockdown_public_room_directory\" is enabled",
|
\"lockdown_public_room_directory\" is enabled",
|
||||||
@@ -135,10 +146,10 @@ pub(crate) async fn set_room_visibility_route(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
services().rooms.directory.set_public(&body.room_id)?;
|
services.rooms.directory.set_public(&body.room_id)?;
|
||||||
info!("{sender_user} made {0} public", body.room_id);
|
info!("{sender_user} made {0} public", 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(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::InvalidParam,
|
ErrorKind::InvalidParam,
|
||||||
@@ -154,15 +165,15 @@ pub(crate) async fn set_room_visibility_route(
|
|||||||
///
|
///
|
||||||
/// Gets the visibility of a given room in the room directory.
|
/// Gets the visibility of a given room in the room directory.
|
||||||
pub(crate) async fn get_room_visibility_route(
|
pub(crate) async fn get_room_visibility_route(
|
||||||
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> {
|
) -> Result<get_room_visibility::v3::Response> {
|
||||||
if !services().rooms.metadata.exists(&body.room_id)? {
|
if !services.rooms.metadata.exists(&body.room_id)? {
|
||||||
// Return 404 if the room doesn't exist
|
// Return 404 if the room doesn't exist
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(get_room_visibility::v3::Response {
|
Ok(get_room_visibility::v3::Response {
|
||||||
visibility: if services().rooms.directory.is_public_room(&body.room_id)? {
|
visibility: if services.rooms.directory.is_public_room(&body.room_id)? {
|
||||||
room::Visibility::Public
|
room::Visibility::Public
|
||||||
} else {
|
} else {
|
||||||
room::Visibility::Private
|
room::Visibility::Private
|
||||||
@@ -171,10 +182,11 @@ pub(crate) async fn get_room_visibility_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn get_public_rooms_filtered_helper(
|
pub(crate) async fn get_public_rooms_filtered_helper(
|
||||||
server: Option<&ServerName>, limit: Option<UInt>, since: Option<&str>, filter: &Filter, _network: &RoomNetwork,
|
services: &Services, server: Option<&ServerName>, limit: Option<UInt>, since: Option<&str>, filter: &Filter,
|
||||||
|
_network: &RoomNetwork,
|
||||||
) -> Result<get_public_rooms_filtered::v3::Response> {
|
) -> Result<get_public_rooms_filtered::v3::Response> {
|
||||||
if let Some(other_server) = server.filter(|server_name| !server_is_ours(server_name)) {
|
if let Some(other_server) = server.filter(|server_name| !services.globals.server_is_ours(server_name)) {
|
||||||
let response = services()
|
let response = services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
other_server,
|
other_server,
|
||||||
@@ -220,7 +232,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut all_rooms: Vec<_> = services()
|
let mut all_rooms: Vec<_> = services
|
||||||
.rooms
|
.rooms
|
||||||
.directory
|
.directory
|
||||||
.public_rooms()
|
.public_rooms()
|
||||||
@@ -228,12 +240,12 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||||||
let room_id = room_id?;
|
let room_id = room_id?;
|
||||||
|
|
||||||
let chunk = PublicRoomsChunk {
|
let chunk = PublicRoomsChunk {
|
||||||
canonical_alias: services()
|
canonical_alias: services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.get_canonical_alias(&room_id)?,
|
.get_canonical_alias(&room_id)?,
|
||||||
name: services().rooms.state_accessor.get_name(&room_id)?,
|
name: services.rooms.state_accessor.get_name(&room_id)?,
|
||||||
num_joined_members: services()
|
num_joined_members: services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.room_joined_count(&room_id)?
|
.room_joined_count(&room_id)?
|
||||||
@@ -243,29 +255,24 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||||||
})
|
})
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("user count should not be that big"),
|
.expect("user count should not be that big"),
|
||||||
topic: services()
|
topic: services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.get_room_topic(&room_id)
|
.get_room_topic(&room_id)
|
||||||
.unwrap_or(None),
|
.unwrap_or(None),
|
||||||
world_readable: services().rooms.state_accessor.is_world_readable(&room_id)?,
|
world_readable: services.rooms.state_accessor.is_world_readable(&room_id)?,
|
||||||
guest_can_join: services()
|
guest_can_join: services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.guest_can_join(&room_id)?,
|
.guest_can_join(&room_id)?,
|
||||||
avatar_url: services()
|
avatar_url: services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_get(&room_id, &StateEventType::RoomAvatar, "")?
|
.get_avatar(&room_id)?
|
||||||
.map(|s| {
|
.into_option()
|
||||||
serde_json::from_str(s.content.get())
|
.unwrap_or_default()
|
||||||
.map(|c: RoomAvatarEventContent| c.url)
|
.url,
|
||||||
.map_err(|_| Error::bad_database("Invalid room avatar event in database."))
|
join_rule: services
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
// url is now an Option<String> so we must flatten
|
|
||||||
.flatten(),
|
|
||||||
join_rule: services()
|
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_get(&room_id, &StateEventType::RoomJoinRules, "")?
|
.room_state_get(&room_id, &StateEventType::RoomJoinRules, "")?
|
||||||
@@ -277,25 +284,16 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Invalid room join rule event in database: {}", e);
|
err!(Database(error!("Invalid room join rule event in database: {e}")))
|
||||||
Error::BadDatabase("Invalid room join rule event in database.")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.flatten()
|
.flatten()
|
||||||
.ok_or_else(|| Error::bad_database("Missing room join rule event for room."))?,
|
.ok_or_else(|| Error::bad_database("Missing room join rule event for room."))?,
|
||||||
room_type: services()
|
room_type: services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_get(&room_id, &StateEventType::RoomCreate, "")?
|
.get_room_type(&room_id)?,
|
||||||
.map(|s| {
|
|
||||||
serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(|e| {
|
|
||||||
error!("Invalid room create event in database: {}", e);
|
|
||||||
Error::BadDatabase("Invalid room create event in database.")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.and_then(|e| e.room_type),
|
|
||||||
room_id,
|
room_id,
|
||||||
};
|
};
|
||||||
Ok(chunk)
|
Ok(chunk)
|
||||||
@@ -368,3 +366,31 @@ pub(crate) async fn get_public_rooms_filtered_helper(
|
|||||||
total_room_count_estimate: Some(total_room_count_estimate),
|
total_room_count_estimate: Some(total_room_count_estimate),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether the user can publish to the room directory via power levels of
|
||||||
|
/// room history visibility event or room creator
|
||||||
|
fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
|
||||||
|
if let Some(event) = services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
} else if let Some(event) =
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
|
||||||
|
{
|
||||||
|
Ok(event.sender == user_id)
|
||||||
|
} else {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::forbidden(),
|
||||||
|
"User is not allowed to publish this room",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
|
use axum::extract::State;
|
||||||
use ruma::api::client::{
|
use ruma::api::client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
filter::{create_filter, get_filter},
|
filter::{create_filter, get_filter},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{services, Error, Result, Ruma};
|
use crate::{Error, Result, Ruma};
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}`
|
/// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}`
|
||||||
///
|
///
|
||||||
/// Loads a filter that was previously created.
|
/// Loads a filter that was previously created.
|
||||||
///
|
///
|
||||||
/// - A user can only access their own filters
|
/// - A user can only access their own filters
|
||||||
pub(crate) async fn get_filter_route(body: Ruma<get_filter::v3::Request>) -> Result<get_filter::v3::Response> {
|
pub(crate) async fn get_filter_route(
|
||||||
|
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");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let Some(filter) = services().users.get_filter(sender_user, &body.filter_id)? else {
|
let Some(filter) = services.users.get_filter(sender_user, &body.filter_id)? else {
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Filter not found."));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Filter not found."));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,9 +25,11 @@ pub(crate) async fn get_filter_route(body: Ruma<get_filter::v3::Request>) -> Res
|
|||||||
/// # `PUT /_matrix/client/r0/user/{userId}/filter`
|
/// # `PUT /_matrix/client/r0/user/{userId}/filter`
|
||||||
///
|
///
|
||||||
/// Creates a new filter to be used by other endpoints.
|
/// Creates a new filter to be used by other endpoints.
|
||||||
pub(crate) async fn create_filter_route(body: Ruma<create_filter::v3::Request>) -> Result<create_filter::v3::Response> {
|
pub(crate) async fn create_filter_route(
|
||||||
|
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");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
Ok(create_filter::v3::Response::new(
|
Ok(create_filter::v3::Response::new(
|
||||||
services().users.create_filter(sender_user, &body.filter)?,
|
services.users.create_filter(sender_user, &body.filter)?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-57
@@ -1,9 +1,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cmp,
|
|
||||||
collections::{hash_map, BTreeMap, HashMap, HashSet},
|
collections::{hash_map, BTreeMap, HashMap, HashSet},
|
||||||
time::{Duration, Instant},
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
|
use conduit::{utils, utils::math::continue_exponential_backoff_secs, Err, Error, Result};
|
||||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
@@ -18,15 +19,9 @@ use ruma::{
|
|||||||
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
|
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use super::SESSION_ID_LENGTH;
|
use super::SESSION_ID_LENGTH;
|
||||||
use crate::{
|
use crate::{service::Services, Ruma};
|
||||||
service::user_is_local,
|
|
||||||
services,
|
|
||||||
utils::{self},
|
|
||||||
Error, Result, Ruma,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/keys/upload`
|
/// # `POST /_matrix/client/r0/keys/upload`
|
||||||
///
|
///
|
||||||
@@ -35,12 +30,14 @@ use crate::{
|
|||||||
/// - Adds one time keys
|
/// - Adds one time keys
|
||||||
/// - If there are no device keys yet: Adds device keys (TODO: merge with
|
/// - If there are no device keys yet: Adds device keys (TODO: merge with
|
||||||
/// existing keys?)
|
/// existing keys?)
|
||||||
pub(crate) async fn upload_keys_route(body: Ruma<upload_keys::v3::Request>) -> Result<upload_keys::v3::Response> {
|
pub(crate) async fn upload_keys_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<upload_keys::v3::Request>,
|
||||||
|
) -> Result<upload_keys::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
for (key_key, key_value) in &body.one_time_keys {
|
for (key_key, key_value) in &body.one_time_keys {
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.add_one_time_key(sender_user, sender_device, key_key, key_value)?;
|
.add_one_time_key(sender_user, sender_device, key_key, key_value)?;
|
||||||
}
|
}
|
||||||
@@ -48,19 +45,19 @@ pub(crate) async fn upload_keys_route(body: Ruma<upload_keys::v3::Request>) -> R
|
|||||||
if let Some(device_keys) = &body.device_keys {
|
if let Some(device_keys) = &body.device_keys {
|
||||||
// TODO: merge this and the existing event?
|
// TODO: merge this and the existing event?
|
||||||
// This check is needed to assure that signatures are kept
|
// This check is needed to assure that signatures are kept
|
||||||
if services()
|
if services
|
||||||
.users
|
.users
|
||||||
.get_device_keys(sender_user, sender_device)?
|
.get_device_keys(sender_user, sender_device)?
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.add_device_keys(sender_user, sender_device, device_keys)?;
|
.add_device_keys(sender_user, sender_device, device_keys)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(upload_keys::v3::Response {
|
Ok(upload_keys::v3::Response {
|
||||||
one_time_key_counts: services()
|
one_time_key_counts: services
|
||||||
.users
|
.users
|
||||||
.count_one_time_keys(sender_user, sender_device)?,
|
.count_one_time_keys(sender_user, sender_device)?,
|
||||||
})
|
})
|
||||||
@@ -74,10 +71,13 @@ pub(crate) async fn upload_keys_route(body: Ruma<upload_keys::v3::Request>) -> R
|
|||||||
/// - Gets master keys, self-signing keys, user signing keys and device keys.
|
/// - Gets master keys, self-signing keys, user signing keys and device keys.
|
||||||
/// - The master and self-signing keys contain signatures that the user is
|
/// - The master and self-signing keys contain signatures that the user is
|
||||||
/// allowed to see
|
/// allowed to see
|
||||||
pub(crate) async fn get_keys_route(body: Ruma<get_keys::v3::Request>) -> Result<get_keys::v3::Response> {
|
pub(crate) async fn get_keys_route(
|
||||||
|
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");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
get_keys_helper(
|
get_keys_helper(
|
||||||
|
&services,
|
||||||
Some(sender_user),
|
Some(sender_user),
|
||||||
&body.device_keys,
|
&body.device_keys,
|
||||||
|u| u == sender_user,
|
|u| u == sender_user,
|
||||||
@@ -89,8 +89,10 @@ pub(crate) async fn get_keys_route(body: Ruma<get_keys::v3::Request>) -> Result<
|
|||||||
/// # `POST /_matrix/client/r0/keys/claim`
|
/// # `POST /_matrix/client/r0/keys/claim`
|
||||||
///
|
///
|
||||||
/// Claims one-time keys
|
/// Claims one-time keys
|
||||||
pub(crate) async fn claim_keys_route(body: Ruma<claim_keys::v3::Request>) -> Result<claim_keys::v3::Response> {
|
pub(crate) async fn claim_keys_route(
|
||||||
claim_keys_helper(&body.one_time_keys).await
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/keys/device_signing/upload`
|
/// # `POST /_matrix/client/r0/keys/device_signing/upload`
|
||||||
@@ -99,7 +101,7 @@ pub(crate) async fn claim_keys_route(body: Ruma<claim_keys::v3::Request>) -> Res
|
|||||||
///
|
///
|
||||||
/// - Requires UIAA to verify password
|
/// - Requires UIAA to verify password
|
||||||
pub(crate) async fn upload_signing_keys_route(
|
pub(crate) async fn upload_signing_keys_route(
|
||||||
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> {
|
) -> Result<upload_signing_keys::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
@@ -116,7 +118,7 @@ pub(crate) async fn upload_signing_keys_route(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(auth) = &body.auth {
|
if let Some(auth) = &body.auth {
|
||||||
let (worked, uiaainfo) = services()
|
let (worked, uiaainfo) = services
|
||||||
.uiaa
|
.uiaa
|
||||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
|
||||||
if !worked {
|
if !worked {
|
||||||
@@ -125,7 +127,7 @@ pub(crate) async fn upload_signing_keys_route(
|
|||||||
// Success!
|
// Success!
|
||||||
} else if let Some(json) = body.json_body {
|
} else if let Some(json) = body.json_body {
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
||||||
services()
|
services
|
||||||
.uiaa
|
.uiaa
|
||||||
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
.create(sender_user, sender_device, &uiaainfo, &json)?;
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
@@ -134,7 +136,7 @@ pub(crate) async fn upload_signing_keys_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(master_key) = &body.master_key {
|
if let Some(master_key) = &body.master_key {
|
||||||
services().users.add_cross_signing_keys(
|
services.users.add_cross_signing_keys(
|
||||||
sender_user,
|
sender_user,
|
||||||
master_key,
|
master_key,
|
||||||
&body.self_signing_key,
|
&body.self_signing_key,
|
||||||
@@ -150,7 +152,7 @@ pub(crate) async fn upload_signing_keys_route(
|
|||||||
///
|
///
|
||||||
/// Uploads end-to-end key signatures from the sender user.
|
/// Uploads end-to-end key signatures from the sender user.
|
||||||
pub(crate) async fn upload_signatures_route(
|
pub(crate) async fn upload_signatures_route(
|
||||||
body: Ruma<upload_signatures::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<upload_signatures::v3::Request>,
|
||||||
) -> Result<upload_signatures::v3::Response> {
|
) -> Result<upload_signatures::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
@@ -177,7 +179,7 @@ pub(crate) async fn upload_signatures_route(
|
|||||||
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature value."))?
|
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature value."))?
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
);
|
);
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.sign_key(user_id, key_id, signature, sender_user)?;
|
.sign_key(user_id, key_id, signature, sender_user)?;
|
||||||
}
|
}
|
||||||
@@ -196,14 +198,14 @@ pub(crate) async fn upload_signatures_route(
|
|||||||
///
|
///
|
||||||
/// - TODO: left users
|
/// - TODO: left users
|
||||||
pub(crate) async fn get_key_changes_route(
|
pub(crate) async fn get_key_changes_route(
|
||||||
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> {
|
) -> Result<get_key_changes::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let mut device_list_updates = HashSet::new();
|
let mut device_list_updates = HashSet::new();
|
||||||
|
|
||||||
device_list_updates.extend(
|
device_list_updates.extend(
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.keys_changed(
|
.keys_changed(
|
||||||
sender_user.as_str(),
|
sender_user.as_str(),
|
||||||
@@ -219,14 +221,14 @@ pub(crate) async fn get_key_changes_route(
|
|||||||
.filter_map(Result::ok),
|
.filter_map(Result::ok),
|
||||||
);
|
);
|
||||||
|
|
||||||
for room_id in services()
|
for room_id in services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(sender_user)
|
.rooms_joined(sender_user)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
{
|
{
|
||||||
device_list_updates.extend(
|
device_list_updates.extend(
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.keys_changed(
|
.keys_changed(
|
||||||
room_id.as_ref(),
|
room_id.as_ref(),
|
||||||
@@ -249,8 +251,8 @@ pub(crate) async fn get_key_changes_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
||||||
sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>, allowed_signatures: F,
|
services: &Services, sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
|
||||||
include_display_names: bool,
|
allowed_signatures: F, include_display_names: bool,
|
||||||
) -> Result<get_keys::v3::Response> {
|
) -> Result<get_keys::v3::Response> {
|
||||||
let mut master_keys = BTreeMap::new();
|
let mut master_keys = BTreeMap::new();
|
||||||
let mut self_signing_keys = BTreeMap::new();
|
let mut self_signing_keys = BTreeMap::new();
|
||||||
@@ -262,7 +264,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
|||||||
for (user_id, device_ids) in device_keys_input {
|
for (user_id, device_ids) in device_keys_input {
|
||||||
let user_id: &UserId = user_id;
|
let user_id: &UserId = user_id;
|
||||||
|
|
||||||
if !user_is_local(user_id) {
|
if !services.globals.user_is_local(user_id) {
|
||||||
get_over_federation
|
get_over_federation
|
||||||
.entry(user_id.server_name())
|
.entry(user_id.server_name())
|
||||||
.or_insert_with(Vec::new)
|
.or_insert_with(Vec::new)
|
||||||
@@ -272,10 +274,10 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
|||||||
|
|
||||||
if device_ids.is_empty() {
|
if device_ids.is_empty() {
|
||||||
let mut container = BTreeMap::new();
|
let mut container = BTreeMap::new();
|
||||||
for device_id in services().users.all_device_ids(user_id) {
|
for device_id in services.users.all_device_ids(user_id) {
|
||||||
let device_id = device_id?;
|
let device_id = device_id?;
|
||||||
if let Some(mut keys) = services().users.get_device_keys(user_id, &device_id)? {
|
if let Some(mut keys) = services.users.get_device_keys(user_id, &device_id)? {
|
||||||
let metadata = services()
|
let metadata = services
|
||||||
.users
|
.users
|
||||||
.get_device_metadata(user_id, &device_id)?
|
.get_device_metadata(user_id, &device_id)?
|
||||||
.ok_or_else(|| Error::bad_database("all_device_keys contained nonexistent device."))?;
|
.ok_or_else(|| Error::bad_database("all_device_keys contained nonexistent device."))?;
|
||||||
@@ -290,8 +292,8 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
|||||||
} else {
|
} else {
|
||||||
for device_id in device_ids {
|
for device_id in device_ids {
|
||||||
let mut container = BTreeMap::new();
|
let mut container = BTreeMap::new();
|
||||||
if let Some(mut keys) = services().users.get_device_keys(user_id, device_id)? {
|
if let Some(mut keys) = services.users.get_device_keys(user_id, device_id)? {
|
||||||
let metadata = services()
|
let metadata = services
|
||||||
.users
|
.users
|
||||||
.get_device_metadata(user_id, device_id)?
|
.get_device_metadata(user_id, device_id)?
|
||||||
.ok_or(Error::BadRequest(
|
.ok_or(Error::BadRequest(
|
||||||
@@ -307,21 +309,21 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(master_key) = services()
|
if let Some(master_key) = services
|
||||||
.users
|
.users
|
||||||
.get_master_key(sender_user, user_id, &allowed_signatures)?
|
.get_master_key(sender_user, user_id, &allowed_signatures)?
|
||||||
{
|
{
|
||||||
master_keys.insert(user_id.to_owned(), master_key);
|
master_keys.insert(user_id.to_owned(), master_key);
|
||||||
}
|
}
|
||||||
if let Some(self_signing_key) =
|
if let Some(self_signing_key) =
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.get_self_signing_key(sender_user, user_id, &allowed_signatures)?
|
.get_self_signing_key(sender_user, user_id, &allowed_signatures)?
|
||||||
{
|
{
|
||||||
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
|
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
|
||||||
}
|
}
|
||||||
if Some(user_id) == sender_user {
|
if Some(user_id) == sender_user {
|
||||||
if let Some(user_signing_key) = services().users.get_user_signing_key(user_id)? {
|
if let Some(user_signing_key) = services.users.get_user_signing_key(user_id)? {
|
||||||
user_signing_keys.insert(user_id.to_owned(), user_signing_key);
|
user_signing_keys.insert(user_id.to_owned(), user_signing_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,11 +332,11 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
|||||||
let mut failures = BTreeMap::new();
|
let mut failures = BTreeMap::new();
|
||||||
|
|
||||||
let back_off = |id| async {
|
let back_off = |id| async {
|
||||||
match services()
|
match services
|
||||||
.globals
|
.globals
|
||||||
.bad_query_ratelimiter
|
.bad_query_ratelimiter
|
||||||
.write()
|
.write()
|
||||||
.await
|
.expect("locked")
|
||||||
.entry(id)
|
.entry(id)
|
||||||
{
|
{
|
||||||
hash_map::Entry::Vacant(e) => {
|
hash_map::Entry::Vacant(e) => {
|
||||||
@@ -349,20 +351,18 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
|||||||
let mut futures: FuturesUnordered<_> = get_over_federation
|
let mut futures: FuturesUnordered<_> = get_over_federation
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(server, vec)| async move {
|
.map(|(server, vec)| async move {
|
||||||
if let Some((time, tries)) = services()
|
if let Some((time, tries)) = services
|
||||||
.globals
|
.globals
|
||||||
.bad_query_ratelimiter
|
.bad_query_ratelimiter
|
||||||
.read()
|
.read()
|
||||||
.await
|
.expect("locked")
|
||||||
.get(server)
|
.get(server)
|
||||||
{
|
{
|
||||||
// Exponential backoff
|
// Exponential backoff
|
||||||
const MAX_DURATION: Duration = Duration::from_secs(60 * 60 * 24);
|
const MIN: u64 = 5 * 60;
|
||||||
let min_elapsed_duration = cmp::min(MAX_DURATION, Duration::from_secs(5 * 60) * (*tries) * (*tries));
|
const MAX: u64 = 60 * 60 * 24;
|
||||||
|
if continue_exponential_backoff_secs(MIN, MAX, time.elapsed(), *tries) {
|
||||||
if time.elapsed() < min_elapsed_duration {
|
return (server, Err!(BadServerResponse("bad query from {server:?}, still backing off")));
|
||||||
debug!("Backing off query from {:?}", server);
|
|
||||||
return (server, Err(Error::BadServerResponse("bad query, still backing off")));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
|||||||
let request = federation::keys::get_keys::v1::Request {
|
let request = federation::keys::get_keys::v1::Request {
|
||||||
device_keys: device_keys_input_fed,
|
device_keys: device_keys_input_fed,
|
||||||
};
|
};
|
||||||
let response = services()
|
let response = services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(server, request)
|
.send_federation_request(server, request)
|
||||||
.await;
|
.await;
|
||||||
@@ -386,19 +386,19 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
|
|||||||
while let Some((server, response)) = futures.next().await {
|
while let Some((server, response)) = futures.next().await {
|
||||||
if let Ok(Ok(response)) = response {
|
if let Ok(Ok(response)) = response {
|
||||||
for (user, masterkey) in response.master_keys {
|
for (user, masterkey) in response.master_keys {
|
||||||
let (master_key_id, mut master_key) = services().users.parse_master_key(&user, &masterkey)?;
|
let (master_key_id, mut master_key) = services.users.parse_master_key(&user, &masterkey)?;
|
||||||
|
|
||||||
if let Some(our_master_key) =
|
if let Some(our_master_key) =
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.get_key(&master_key_id, sender_user, &user, &allowed_signatures)?
|
.get_key(&master_key_id, sender_user, &user, &allowed_signatures)?
|
||||||
{
|
{
|
||||||
let (_, our_master_key) = services().users.parse_master_key(&user, &our_master_key)?;
|
let (_, our_master_key) = services.users.parse_master_key(&user, &our_master_key)?;
|
||||||
master_key.signatures.extend(our_master_key.signatures);
|
master_key.signatures.extend(our_master_key.signatures);
|
||||||
}
|
}
|
||||||
let json = serde_json::to_value(master_key).expect("to_value always works");
|
let json = serde_json::to_value(master_key).expect("to_value always works");
|
||||||
let raw = serde_json::from_value(json).expect("Raw::from_value always works");
|
let raw = serde_json::from_value(json).expect("Raw::from_value always works");
|
||||||
services().users.add_cross_signing_keys(
|
services.users.add_cross_signing_keys(
|
||||||
&user, &raw, &None, &None,
|
&user, &raw, &None, &None,
|
||||||
false, /* Dont notify. A notification would trigger another key request resulting in an
|
false, /* Dont notify. A notification would trigger another key request resulting in an
|
||||||
* endless loop */
|
* endless loop */
|
||||||
@@ -449,14 +449,14 @@ fn add_unsigned_device_display_name(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn claim_keys_helper(
|
pub(crate) async fn claim_keys_helper(
|
||||||
one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, DeviceKeyAlgorithm>>,
|
services: &Services, one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, DeviceKeyAlgorithm>>,
|
||||||
) -> Result<claim_keys::v3::Response> {
|
) -> Result<claim_keys::v3::Response> {
|
||||||
let mut one_time_keys = BTreeMap::new();
|
let mut one_time_keys = BTreeMap::new();
|
||||||
|
|
||||||
let mut get_over_federation = BTreeMap::new();
|
let mut get_over_federation = BTreeMap::new();
|
||||||
|
|
||||||
for (user_id, map) in one_time_keys_input {
|
for (user_id, map) in one_time_keys_input {
|
||||||
if !user_is_local(user_id) {
|
if !services.globals.user_is_local(user_id) {
|
||||||
get_over_federation
|
get_over_federation
|
||||||
.entry(user_id.server_name())
|
.entry(user_id.server_name())
|
||||||
.or_insert_with(Vec::new)
|
.or_insert_with(Vec::new)
|
||||||
@@ -465,7 +465,7 @@ pub(crate) async fn claim_keys_helper(
|
|||||||
|
|
||||||
let mut container = BTreeMap::new();
|
let mut container = BTreeMap::new();
|
||||||
for (device_id, key_algorithm) in map {
|
for (device_id, key_algorithm) in map {
|
||||||
if let Some(one_time_keys) = services()
|
if let Some(one_time_keys) = services
|
||||||
.users
|
.users
|
||||||
.take_one_time_key(user_id, device_id, key_algorithm)?
|
.take_one_time_key(user_id, device_id, key_algorithm)?
|
||||||
{
|
{
|
||||||
@@ -488,7 +488,7 @@ pub(crate) async fn claim_keys_helper(
|
|||||||
}
|
}
|
||||||
(
|
(
|
||||||
server,
|
server,
|
||||||
services()
|
services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
server,
|
server,
|
||||||
|
|||||||
+110
-314
@@ -1,8 +1,18 @@
|
|||||||
use std::{io::Cursor, sync::Arc, time::Duration};
|
#![allow(deprecated)]
|
||||||
|
|
||||||
use image::io::Reader as ImgReader;
|
use std::time::Duration;
|
||||||
use ipaddress::IPAddress;
|
|
||||||
use reqwest::Url;
|
use axum::extract::State;
|
||||||
|
use axum_client_ip::InsecureClientIp;
|
||||||
|
use conduit::{
|
||||||
|
debug_warn, err, error,
|
||||||
|
utils::{
|
||||||
|
self,
|
||||||
|
content_disposition::{content_disposition_type, make_content_disposition, sanitise_filename},
|
||||||
|
math::ruma_from_usize,
|
||||||
|
},
|
||||||
|
warn, Err, Error, Result,
|
||||||
|
};
|
||||||
use ruma::api::client::{
|
use ruma::api::client::{
|
||||||
error::{ErrorKind, RetryAfter},
|
error::{ErrorKind, RetryAfter},
|
||||||
media::{
|
media::{
|
||||||
@@ -10,25 +20,12 @@ use ruma::api::client::{
|
|||||||
get_media_preview,
|
get_media_preview,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tracing::{debug, error, warn};
|
use service::{
|
||||||
use webpage::HTML;
|
media::{FileMeta, MXC_LENGTH},
|
||||||
|
Services,
|
||||||
use crate::{
|
|
||||||
debug_warn,
|
|
||||||
service::{
|
|
||||||
media::{FileMeta, UrlPreviewData},
|
|
||||||
server_is_ours,
|
|
||||||
},
|
|
||||||
services,
|
|
||||||
utils::{
|
|
||||||
self,
|
|
||||||
content_disposition::{content_disposition_type, make_content_disposition, sanitise_filename},
|
|
||||||
},
|
|
||||||
Error, Result, Ruma, RumaResponse,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// generated MXC ID (`media-id`) length
|
use crate::{Ruma, RumaResponse};
|
||||||
const MXC_LENGTH: usize = 32;
|
|
||||||
|
|
||||||
/// Cache control for immutable objects
|
/// Cache control for immutable objects
|
||||||
const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable";
|
const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable";
|
||||||
@@ -39,10 +36,10 @@ const CORP_CROSS_ORIGIN: &str = "cross-origin";
|
|||||||
///
|
///
|
||||||
/// Returns max upload size.
|
/// Returns max upload size.
|
||||||
pub(crate) async fn get_media_config_route(
|
pub(crate) async fn get_media_config_route(
|
||||||
_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> {
|
) -> Result<get_media_config::v3::Response> {
|
||||||
Ok(get_media_config::v3::Response {
|
Ok(get_media_config::v3::Response {
|
||||||
upload_size: services().globals.max_request_size().into(),
|
upload_size: ruma_from_usize(services.globals.config.max_request_size),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,26 +51,34 @@ pub(crate) async fn get_media_config_route(
|
|||||||
///
|
///
|
||||||
/// Returns max upload size.
|
/// Returns max upload size.
|
||||||
pub(crate) async fn get_media_config_v1_route(
|
pub(crate) async fn get_media_config_v1_route(
|
||||||
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>> {
|
) -> Result<RumaResponse<get_media_config::v3::Response>> {
|
||||||
get_media_config_route(body).await.map(RumaResponse)
|
get_media_config_route(State(services), body)
|
||||||
|
.await
|
||||||
|
.map(RumaResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/media/v3/preview_url`
|
/// # `GET /_matrix/media/v3/preview_url`
|
||||||
///
|
///
|
||||||
/// Returns URL preview.
|
/// Returns URL preview.
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
|
||||||
pub(crate) async fn get_media_preview_route(
|
pub(crate) async fn get_media_preview_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<get_media_preview::v3::Request>,
|
body: Ruma<get_media_preview::v3::Request>,
|
||||||
) -> Result<get_media_preview::v3::Response> {
|
) -> Result<get_media_preview::v3::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let url = &body.url;
|
let url = &body.url;
|
||||||
if !url_preview_allowed(url) {
|
if !services.media.url_preview_allowed(url) {
|
||||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "URL is not allowed to be previewed"));
|
return Err!(Request(Forbidden(
|
||||||
|
warn!(%sender_user, %url, "URL is not allowed to be previewed")
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
match get_url_preview(url).await {
|
match services.media.get_url_preview(url).await {
|
||||||
Ok(preview) => {
|
Ok(preview) => {
|
||||||
let res = serde_json::value::to_raw_value(&preview).map_err(|e| {
|
let res = serde_json::value::to_raw_value(&preview).map_err(|e| {
|
||||||
error!("Failed to convert UrlPreviewData into a serde json value: {}", e);
|
error!(%sender_user, "Failed to convert UrlPreviewData into a serde json value: {e}");
|
||||||
Error::BadRequest(
|
Error::BadRequest(
|
||||||
ErrorKind::LimitExceeded {
|
ErrorKind::LimitExceeded {
|
||||||
retry_after: Some(RetryAfter::Delay(Duration::from_secs(5))),
|
retry_after: Some(RetryAfter::Delay(Duration::from_secs(5))),
|
||||||
@@ -85,8 +90,7 @@ pub(crate) async fn get_media_preview_route(
|
|||||||
Ok(get_media_preview::v3::Response::from_raw_value(res))
|
Ok(get_media_preview::v3::Response::from_raw_value(res))
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to generate a URL preview: {e}");
|
warn!(%sender_user, "Failed to generate a URL preview: {e}");
|
||||||
|
|
||||||
// there doesn't seem to be an agreed-upon error code in the spec.
|
// there doesn't seem to be an agreed-upon error code in the spec.
|
||||||
// the only response codes in the preview_url spec page are 200 and 429.
|
// the only response codes in the preview_url spec page are 200 and 429.
|
||||||
Err(Error::BadRequest(
|
Err(Error::BadRequest(
|
||||||
@@ -106,10 +110,14 @@ pub(crate) async fn get_media_preview_route(
|
|||||||
/// See <https://spec.matrix.org/legacy/legacy/#id27>
|
/// See <https://spec.matrix.org/legacy/legacy/#id27>
|
||||||
///
|
///
|
||||||
/// Returns URL preview.
|
/// Returns URL preview.
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "url_preview")]
|
||||||
pub(crate) async fn get_media_preview_v1_route(
|
pub(crate) async fn get_media_preview_v1_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<get_media_preview::v3::Request>,
|
body: Ruma<get_media_preview::v3::Request>,
|
||||||
) -> Result<RumaResponse<get_media_preview::v3::Response>> {
|
) -> Result<RumaResponse<get_media_preview::v3::Response>> {
|
||||||
get_media_preview_route(body).await.map(RumaResponse)
|
get_media_preview_route(State(services), InsecureClientIp(client), body)
|
||||||
|
.await
|
||||||
|
.map(RumaResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `POST /_matrix/media/v3/upload`
|
/// # `POST /_matrix/media/v3/upload`
|
||||||
@@ -118,22 +126,20 @@ pub(crate) async fn get_media_preview_v1_route(
|
|||||||
///
|
///
|
||||||
/// - Some metadata will be saved in the database
|
/// - Some metadata will be saved in the database
|
||||||
/// - Media will be saved in the media/ directory
|
/// - Media will be saved in the media/ directory
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
|
||||||
pub(crate) async fn create_content_route(
|
pub(crate) async fn create_content_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<create_content::v3::Request>,
|
body: Ruma<create_content::v3::Request>,
|
||||||
) -> Result<create_content::v3::Response> {
|
) -> Result<create_content::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
let mxc = format!(
|
let mxc = format!("mxc://{}/{}", services.globals.server_name(), utils::random_string(MXC_LENGTH));
|
||||||
"mxc://{}/{}",
|
|
||||||
services().globals.server_name(),
|
|
||||||
utils::random_string(MXC_LENGTH)
|
|
||||||
);
|
|
||||||
|
|
||||||
services()
|
services
|
||||||
.media
|
.media
|
||||||
.create(
|
.create(
|
||||||
Some(sender_user.clone()),
|
Some(sender_user.clone()),
|
||||||
mxc.clone(),
|
&mxc,
|
||||||
body.filename
|
body.filename
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|filename| {
|
.map(|filename| {
|
||||||
@@ -165,10 +171,14 @@ pub(crate) async fn create_content_route(
|
|||||||
///
|
///
|
||||||
/// - Some metadata will be saved in the database
|
/// - Some metadata will be saved in the database
|
||||||
/// - Media will be saved in the media/ directory
|
/// - Media will be saved in the media/ directory
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "media_upload")]
|
||||||
pub(crate) async fn create_content_v1_route(
|
pub(crate) async fn create_content_v1_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<create_content::v3::Request>,
|
body: Ruma<create_content::v3::Request>,
|
||||||
) -> Result<RumaResponse<create_content::v3::Response>> {
|
) -> Result<RumaResponse<create_content::v3::Response>> {
|
||||||
create_content_route(body).await.map(RumaResponse)
|
create_content_route(State(services), InsecureClientIp(client), body)
|
||||||
|
.await
|
||||||
|
.map(RumaResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}`
|
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}`
|
||||||
@@ -179,16 +189,21 @@ pub(crate) async fn create_content_v1_route(
|
|||||||
/// - Only redirects if `allow_redirect` is true
|
/// - Only redirects if `allow_redirect` is true
|
||||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||||
/// seconds
|
/// seconds
|
||||||
pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> Result<get_content::v3::Response> {
|
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
|
||||||
|
pub(crate) async fn get_content_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
|
body: Ruma<get_content::v3::Request>,
|
||||||
|
) -> Result<get_content::v3::Response> {
|
||||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||||
|
|
||||||
if let Some(FileMeta {
|
if let Some(FileMeta {
|
||||||
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
file,
|
|
||||||
content_disposition,
|
content_disposition,
|
||||||
}) = services().media.get(mxc.clone()).await?
|
}) = services.media.get(&mxc).await?
|
||||||
{
|
{
|
||||||
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
|
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
|
||||||
|
let file = content.expect("content");
|
||||||
|
|
||||||
Ok(get_content::v3::Response {
|
Ok(get_content::v3::Response {
|
||||||
file,
|
file,
|
||||||
@@ -197,8 +212,9 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
|
|||||||
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
|
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
|
||||||
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
|
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
|
||||||
})
|
})
|
||||||
} else if !server_is_ours(&body.server_name) && body.allow_remote {
|
} else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
|
||||||
let response = get_remote_content(
|
let response = get_remote_content(
|
||||||
|
&services,
|
||||||
&mxc,
|
&mxc,
|
||||||
&body.server_name,
|
&body.server_name,
|
||||||
body.media_id.clone(),
|
body.media_id.clone(),
|
||||||
@@ -206,10 +222,7 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
|
|||||||
body.timeout_ms,
|
body.timeout_ms,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))))?;
|
||||||
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
|
|
||||||
Error::BadRequest(ErrorKind::NotFound, "Remote media error.")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let content_disposition = Some(make_content_disposition(
|
let content_disposition = Some(make_content_disposition(
|
||||||
&response.content_type,
|
&response.content_type,
|
||||||
@@ -225,7 +238,7 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
|
|||||||
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
|
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
Err!(Request(NotFound("Media not found.")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,10 +254,14 @@ pub(crate) async fn get_content_route(body: Ruma<get_content::v3::Request>) -> R
|
|||||||
/// - Only redirects if `allow_redirect` is true
|
/// - Only redirects if `allow_redirect` is true
|
||||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||||
/// seconds
|
/// seconds
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
|
||||||
pub(crate) async fn get_content_v1_route(
|
pub(crate) async fn get_content_v1_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<get_content::v3::Request>,
|
body: Ruma<get_content::v3::Request>,
|
||||||
) -> Result<RumaResponse<get_content::v3::Response>> {
|
) -> Result<RumaResponse<get_content::v3::Response>> {
|
||||||
get_content_route(body).await.map(RumaResponse)
|
get_content_route(State(services), InsecureClientIp(client), body)
|
||||||
|
.await
|
||||||
|
.map(RumaResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}`
|
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}`
|
||||||
@@ -255,16 +272,18 @@ pub(crate) async fn get_content_v1_route(
|
|||||||
/// - Only redirects if `allow_redirect` is true
|
/// - Only redirects if `allow_redirect` is true
|
||||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||||
/// seconds
|
/// seconds
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
|
||||||
pub(crate) async fn get_content_as_filename_route(
|
pub(crate) async fn get_content_as_filename_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<get_content_as_filename::v3::Request>,
|
body: Ruma<get_content_as_filename::v3::Request>,
|
||||||
) -> Result<get_content_as_filename::v3::Response> {
|
) -> Result<get_content_as_filename::v3::Response> {
|
||||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||||
|
|
||||||
if let Some(FileMeta {
|
if let Some(FileMeta {
|
||||||
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
file,
|
|
||||||
content_disposition,
|
content_disposition,
|
||||||
}) = services().media.get(mxc.clone()).await?
|
}) = services.media.get(&mxc).await?
|
||||||
{
|
{
|
||||||
let content_disposition = Some(make_content_disposition(
|
let content_disposition = Some(make_content_disposition(
|
||||||
&content_type,
|
&content_type,
|
||||||
@@ -272,6 +291,7 @@ pub(crate) async fn get_content_as_filename_route(
|
|||||||
Some(body.filename.clone()),
|
Some(body.filename.clone()),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let file = content.expect("content");
|
||||||
Ok(get_content_as_filename::v3::Response {
|
Ok(get_content_as_filename::v3::Response {
|
||||||
file,
|
file,
|
||||||
content_type,
|
content_type,
|
||||||
@@ -279,8 +299,9 @@ pub(crate) async fn get_content_as_filename_route(
|
|||||||
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
|
cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.to_owned()),
|
||||||
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
|
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
|
||||||
})
|
})
|
||||||
} else if !server_is_ours(&body.server_name) && body.allow_remote {
|
} else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
|
||||||
match get_remote_content(
|
match get_remote_content(
|
||||||
|
&services,
|
||||||
&mxc,
|
&mxc,
|
||||||
&body.server_name,
|
&body.server_name,
|
||||||
body.media_id.clone(),
|
body.media_id.clone(),
|
||||||
@@ -304,13 +325,10 @@ pub(crate) async fn get_content_as_filename_route(
|
|||||||
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
|
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => Err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))),
|
||||||
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
|
|
||||||
Err(Error::BadRequest(ErrorKind::NotFound, "Remote media error."))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
Err!(Request(NotFound("Media not found.")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,10 +344,14 @@ pub(crate) async fn get_content_as_filename_route(
|
|||||||
/// - Only redirects if `allow_redirect` is true
|
/// - Only redirects if `allow_redirect` is true
|
||||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||||
/// seconds
|
/// seconds
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "media_get")]
|
||||||
pub(crate) async fn get_content_as_filename_v1_route(
|
pub(crate) async fn get_content_as_filename_v1_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<get_content_as_filename::v3::Request>,
|
body: Ruma<get_content_as_filename::v3::Request>,
|
||||||
) -> Result<RumaResponse<get_content_as_filename::v3::Response>> {
|
) -> Result<RumaResponse<get_content_as_filename::v3::Response>> {
|
||||||
get_content_as_filename_route(body).await.map(RumaResponse)
|
get_content_as_filename_route(State(services), InsecureClientIp(client), body)
|
||||||
|
.await
|
||||||
|
.map(RumaResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}`
|
/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}`
|
||||||
@@ -340,29 +362,32 @@ pub(crate) async fn get_content_as_filename_v1_route(
|
|||||||
/// - Only redirects if `allow_redirect` is true
|
/// - Only redirects if `allow_redirect` is true
|
||||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||||
/// seconds
|
/// seconds
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
|
||||||
pub(crate) async fn get_content_thumbnail_route(
|
pub(crate) async fn get_content_thumbnail_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<get_content_thumbnail::v3::Request>,
|
body: Ruma<get_content_thumbnail::v3::Request>,
|
||||||
) -> Result<get_content_thumbnail::v3::Response> {
|
) -> Result<get_content_thumbnail::v3::Response> {
|
||||||
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||||
|
|
||||||
if let Some(FileMeta {
|
if let Some(FileMeta {
|
||||||
|
content,
|
||||||
content_type,
|
content_type,
|
||||||
file,
|
|
||||||
content_disposition,
|
content_disposition,
|
||||||
}) = services()
|
}) = services
|
||||||
.media
|
.media
|
||||||
.get_thumbnail(
|
.get_thumbnail(
|
||||||
mxc.clone(),
|
&mxc,
|
||||||
body.width
|
body.width
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
.map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?,
|
||||||
body.height
|
body.height
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Height is invalid."))?,
|
.map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
|
let content_disposition = Some(make_content_disposition(&content_type, content_disposition, None));
|
||||||
|
let file = content.expect("content");
|
||||||
|
|
||||||
Ok(get_content_thumbnail::v3::Response {
|
Ok(get_content_thumbnail::v3::Response {
|
||||||
file,
|
file,
|
||||||
@@ -371,8 +396,8 @@ pub(crate) async fn get_content_thumbnail_route(
|
|||||||
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
|
cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
|
||||||
content_disposition,
|
content_disposition,
|
||||||
})
|
})
|
||||||
} else if !server_is_ours(&body.server_name) && body.allow_remote {
|
} else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
|
||||||
if services()
|
if services
|
||||||
.globals
|
.globals
|
||||||
.prevent_media_downloads_from()
|
.prevent_media_downloads_from()
|
||||||
.contains(&body.server_name)
|
.contains(&body.server_name)
|
||||||
@@ -380,10 +405,10 @@ pub(crate) async fn get_content_thumbnail_route(
|
|||||||
// we'll lie to the client and say the blocked server's media was not found and
|
// we'll lie to the client and say the blocked server's media was not found and
|
||||||
// log. the client has no way of telling anyways so this is a security bonus.
|
// log. the client has no way of telling anyways so this is a security bonus.
|
||||||
debug_warn!("Received request for media `{}` on blocklisted server", mxc);
|
debug_warn!("Received request for media `{}` on blocklisted server", mxc);
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
|
return Err!(Request(NotFound("Media not found.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
match services()
|
match services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
&body.server_name,
|
&body.server_name,
|
||||||
@@ -396,16 +421,17 @@ pub(crate) async fn get_content_thumbnail_route(
|
|||||||
media_id: body.media_id.clone(),
|
media_id: body.media_id.clone(),
|
||||||
timeout_ms: body.timeout_ms,
|
timeout_ms: body.timeout_ms,
|
||||||
allow_redirect: body.allow_redirect,
|
allow_redirect: body.allow_redirect,
|
||||||
|
animated: body.animated,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(get_thumbnail_response) => {
|
Ok(get_thumbnail_response) => {
|
||||||
services()
|
services
|
||||||
.media
|
.media
|
||||||
.upload_thumbnail(
|
.upload_thumbnail(
|
||||||
None,
|
None,
|
||||||
mxc,
|
&mxc,
|
||||||
None,
|
None,
|
||||||
get_thumbnail_response.content_type.as_deref(),
|
get_thumbnail_response.content_type.as_deref(),
|
||||||
body.width.try_into().expect("all UInts are valid u32s"),
|
body.width.try_into().expect("all UInts are valid u32s"),
|
||||||
@@ -428,13 +454,10 @@ pub(crate) async fn get_content_thumbnail_route(
|
|||||||
content_disposition,
|
content_disposition,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => Err!(Request(NotFound(debug_warn!("Fetching media `{mxc}` failed: {e:?}")))),
|
||||||
debug_warn!("Fetching media `{}` failed: {:?}", mxc, e);
|
|
||||||
Err(Error::BadRequest(ErrorKind::NotFound, "Remote media error."))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
Err!(Request(NotFound("Media not found.")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,16 +473,21 @@ pub(crate) async fn get_content_thumbnail_route(
|
|||||||
/// - Only redirects if `allow_redirect` is true
|
/// - Only redirects if `allow_redirect` is true
|
||||||
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
/// - Uses client-provided `timeout_ms` if available, else defaults to 20
|
||||||
/// seconds
|
/// seconds
|
||||||
|
#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")]
|
||||||
pub(crate) async fn get_content_thumbnail_v1_route(
|
pub(crate) async fn get_content_thumbnail_v1_route(
|
||||||
|
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<get_content_thumbnail::v3::Request>,
|
body: Ruma<get_content_thumbnail::v3::Request>,
|
||||||
) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> {
|
) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> {
|
||||||
get_content_thumbnail_route(body).await.map(RumaResponse)
|
get_content_thumbnail_route(State(services), InsecureClientIp(client), body)
|
||||||
|
.await
|
||||||
|
.map(RumaResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_remote_content(
|
async fn get_remote_content(
|
||||||
mxc: &str, server_name: &ruma::ServerName, media_id: String, allow_redirect: bool, timeout_ms: Duration,
|
services: &Services, mxc: &str, server_name: &ruma::ServerName, media_id: String, allow_redirect: bool,
|
||||||
|
timeout_ms: Duration,
|
||||||
) -> Result<get_content::v3::Response, Error> {
|
) -> Result<get_content::v3::Response, Error> {
|
||||||
if services()
|
if services
|
||||||
.globals
|
.globals
|
||||||
.prevent_media_downloads_from()
|
.prevent_media_downloads_from()
|
||||||
.contains(&server_name.to_owned())
|
.contains(&server_name.to_owned())
|
||||||
@@ -467,10 +495,10 @@ async fn get_remote_content(
|
|||||||
// we'll lie to the client and say the blocked server's media was not found and
|
// we'll lie to the client and say the blocked server's media was not found and
|
||||||
// log. the client has no way of telling anyways so this is a security bonus.
|
// log. the client has no way of telling anyways so this is a security bonus.
|
||||||
debug_warn!("Received request for media `{mxc}` on blocklisted server");
|
debug_warn!("Received request for media `{mxc}` on blocklisted server");
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
|
return Err!(Request(NotFound("Media not found.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let content_response = services()
|
let content_response = services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
server_name,
|
server_name,
|
||||||
@@ -490,11 +518,11 @@ async fn get_remote_content(
|
|||||||
None,
|
None,
|
||||||
));
|
));
|
||||||
|
|
||||||
services()
|
services
|
||||||
.media
|
.media
|
||||||
.create(
|
.create(
|
||||||
None,
|
None,
|
||||||
mxc.to_owned(),
|
mxc,
|
||||||
content_disposition.as_deref(),
|
content_disposition.as_deref(),
|
||||||
content_response.content_type.as_deref(),
|
content_response.content_type.as_deref(),
|
||||||
&content_response.file,
|
&content_response.file,
|
||||||
@@ -509,235 +537,3 @@ async fn get_remote_content(
|
|||||||
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
|
cache_control: Some(CACHE_CONTROL_IMMUTABLE.to_owned()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
|
|
||||||
let image = client.get(url).send().await?.bytes().await?;
|
|
||||||
let mxc = format!(
|
|
||||||
"mxc://{}/{}",
|
|
||||||
services().globals.server_name(),
|
|
||||||
utils::random_string(MXC_LENGTH)
|
|
||||||
);
|
|
||||||
|
|
||||||
services()
|
|
||||||
.media
|
|
||||||
.create(None, mxc.clone(), None, None, &image)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let (width, height) = match ImgReader::new(Cursor::new(&image)).with_guessed_format() {
|
|
||||||
Err(_) => (None, None),
|
|
||||||
Ok(reader) => match reader.into_dimensions() {
|
|
||||||
Err(_) => (None, None),
|
|
||||||
Ok((width, height)) => (Some(width), Some(height)),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(UrlPreviewData {
|
|
||||||
image: Some(mxc),
|
|
||||||
image_size: Some(image.len()),
|
|
||||||
image_width: width,
|
|
||||||
image_height: height,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn download_html(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
|
|
||||||
let mut response = client.get(url).send().await?;
|
|
||||||
|
|
||||||
let mut bytes: Vec<u8> = Vec::new();
|
|
||||||
while let Some(chunk) = response.chunk().await? {
|
|
||||||
bytes.extend_from_slice(&chunk);
|
|
||||||
if bytes.len() > services().globals.url_preview_max_spider_size() {
|
|
||||||
debug!(
|
|
||||||
"Response body from URL {} exceeds url_preview_max_spider_size ({}), not processing the rest of the \
|
|
||||||
response body and assuming our necessary data is in this range.",
|
|
||||||
url,
|
|
||||||
services().globals.url_preview_max_spider_size()
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let body = String::from_utf8_lossy(&bytes);
|
|
||||||
let Ok(html) = HTML::from_string(body.to_string(), Some(url.to_owned())) else {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::Unknown, "Failed to parse HTML"));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut data = match html.opengraph.images.first() {
|
|
||||||
None => UrlPreviewData::default(),
|
|
||||||
Some(obj) => download_image(client, &obj.url).await?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let props = html.opengraph.properties;
|
|
||||||
|
|
||||||
/* use OpenGraph title/description, but fall back to HTML if not available */
|
|
||||||
data.title = props.get("title").cloned().or(html.title);
|
|
||||||
data.description = props.get("description").cloned().or(html.description);
|
|
||||||
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn request_url_preview(url: &str) -> Result<UrlPreviewData> {
|
|
||||||
if let Ok(ip) = IPAddress::parse(url) {
|
|
||||||
if !services().globals.valid_cidr_range(&ip) {
|
|
||||||
return Err(Error::BadServerResponse("Requesting from this address is forbidden"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = &services().globals.client.url_preview;
|
|
||||||
let response = client.head(url).send().await?;
|
|
||||||
|
|
||||||
if let Some(remote_addr) = response.remote_addr() {
|
|
||||||
if let Ok(ip) = IPAddress::parse(remote_addr.ip().to_string()) {
|
|
||||||
if !services().globals.valid_cidr_range(&ip) {
|
|
||||||
return Err(Error::BadServerResponse("Requesting from this address is forbidden"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(content_type) = response
|
|
||||||
.headers()
|
|
||||||
.get(reqwest::header::CONTENT_TYPE)
|
|
||||||
.and_then(|x| x.to_str().ok())
|
|
||||||
else {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::Unknown, "Unknown Content-Type"));
|
|
||||||
};
|
|
||||||
let data = match content_type {
|
|
||||||
html if html.starts_with("text/html") => download_html(client, url).await?,
|
|
||||||
img if img.starts_with("image/") => download_image(client, url).await?,
|
|
||||||
_ => return Err(Error::BadRequest(ErrorKind::Unknown, "Unsupported Content-Type")),
|
|
||||||
};
|
|
||||||
|
|
||||||
services().media.set_url_preview(url, &data).await?;
|
|
||||||
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_url_preview(url: &str) -> Result<UrlPreviewData> {
|
|
||||||
if let Some(preview) = services().media.get_url_preview(url).await {
|
|
||||||
return Ok(preview);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure that only one request is made per URL
|
|
||||||
let mutex_request = Arc::clone(
|
|
||||||
services()
|
|
||||||
.media
|
|
||||||
.url_preview_mutex
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.entry(url.to_owned())
|
|
||||||
.or_default(),
|
|
||||||
);
|
|
||||||
let _request_lock = mutex_request.lock().await;
|
|
||||||
|
|
||||||
match services().media.get_url_preview(url).await {
|
|
||||||
Some(preview) => Ok(preview),
|
|
||||||
None => request_url_preview(url).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn url_preview_allowed(url_str: &str) -> bool {
|
|
||||||
let url: Url = match Url::parse(url_str) {
|
|
||||||
Ok(u) => u,
|
|
||||||
Err(e) => {
|
|
||||||
warn!("Failed to parse URL from a str: {}", e);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if ["http", "https"]
|
|
||||||
.iter()
|
|
||||||
.all(|&scheme| scheme != url.scheme().to_lowercase())
|
|
||||||
{
|
|
||||||
debug!("Ignoring non-HTTP/HTTPS URL to preview: {}", url);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let host = match url.host_str() {
|
|
||||||
None => {
|
|
||||||
debug!("Ignoring URL preview for a URL that does not have a host (?): {}", url);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
Some(h) => h.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let allowlist_domain_contains = services().globals.url_preview_domain_contains_allowlist();
|
|
||||||
let allowlist_domain_explicit = services().globals.url_preview_domain_explicit_allowlist();
|
|
||||||
let denylist_domain_explicit = services().globals.url_preview_domain_explicit_denylist();
|
|
||||||
let allowlist_url_contains = services().globals.url_preview_url_contains_allowlist();
|
|
||||||
|
|
||||||
if allowlist_domain_contains.contains(&"*".to_owned())
|
|
||||||
|| allowlist_domain_explicit.contains(&"*".to_owned())
|
|
||||||
|| allowlist_url_contains.contains(&"*".to_owned())
|
|
||||||
{
|
|
||||||
debug!("Config key contains * which is allowing all URL previews. Allowing URL {}", url);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !host.is_empty() {
|
|
||||||
if denylist_domain_explicit.contains(&host) {
|
|
||||||
debug!(
|
|
||||||
"Host {} is not allowed by url_preview_domain_explicit_denylist (check 1/4)",
|
|
||||||
&host
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if allowlist_domain_explicit.contains(&host) {
|
|
||||||
debug!("Host {} is allowed by url_preview_domain_explicit_allowlist (check 2/4)", &host);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if allowlist_domain_contains
|
|
||||||
.iter()
|
|
||||||
.any(|domain_s| domain_s.contains(&host.clone()))
|
|
||||||
{
|
|
||||||
debug!("Host {} is allowed by url_preview_domain_contains_allowlist (check 3/4)", &host);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if allowlist_url_contains
|
|
||||||
.iter()
|
|
||||||
.any(|url_s| url.to_string().contains(&url_s.to_string()))
|
|
||||||
{
|
|
||||||
debug!("URL {} is allowed by url_preview_url_contains_allowlist (check 4/4)", &host);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check root domain if available and if user has root domain checks
|
|
||||||
if services().globals.url_preview_check_root_domain() {
|
|
||||||
debug!("Checking root domain");
|
|
||||||
match host.split_once('.') {
|
|
||||||
None => return false,
|
|
||||||
Some((_, root_domain)) => {
|
|
||||||
if denylist_domain_explicit.contains(&root_domain.to_owned()) {
|
|
||||||
debug!(
|
|
||||||
"Root domain {} is not allowed by url_preview_domain_explicit_denylist (check 1/3)",
|
|
||||||
&root_domain
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if allowlist_domain_explicit.contains(&root_domain.to_owned()) {
|
|
||||||
debug!(
|
|
||||||
"Root domain {} is allowed by url_preview_domain_explicit_allowlist (check 2/3)",
|
|
||||||
&root_domain
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if allowlist_domain_contains
|
|
||||||
.iter()
|
|
||||||
.any(|domain_s| domain_s.contains(&root_domain.to_owned()))
|
|
||||||
{
|
|
||||||
debug!(
|
|
||||||
"Root domain {} is allowed by url_preview_domain_contains_allowlist (check 3/3)",
|
|
||||||
&root_domain
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|||||||
+286
-292
File diff suppressed because it is too large
Load Diff
+30
-31
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
use conduit::PduCount;
|
use conduit::PduCount;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{
|
api::client::{
|
||||||
@@ -12,7 +13,10 @@ use ruma::{
|
|||||||
};
|
};
|
||||||
use serde_json::{from_str, Value};
|
use serde_json::{from_str, Value};
|
||||||
|
|
||||||
use crate::{service::pdu::PduBuilder, services, utils, Error, PduEvent, Result, Ruma};
|
use crate::{
|
||||||
|
service::{pdu::PduBuilder, Services},
|
||||||
|
utils, Error, PduEvent, Result, Ruma,
|
||||||
|
};
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
|
/// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
|
||||||
///
|
///
|
||||||
@@ -24,25 +28,19 @@ use crate::{service::pdu::PduBuilder, services, utils, Error, PduEvent, Result,
|
|||||||
/// - Tries to send the event into the room, auth rules will determine if it is
|
/// - Tries to send the event into the room, auth rules will determine if it is
|
||||||
/// allowed
|
/// allowed
|
||||||
pub(crate) async fn send_message_event_route(
|
pub(crate) async fn send_message_event_route(
|
||||||
body: Ruma<send_message_event::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<send_message_event::v3::Request>,
|
||||||
) -> Result<send_message_event::v3::Response> {
|
) -> Result<send_message_event::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let sender_device = body.sender_device.as_deref();
|
let sender_device = body.sender_device.as_deref();
|
||||||
|
|
||||||
let state_lock = services()
|
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
|
||||||
.globals
|
|
||||||
.roomid_mutex_state
|
|
||||||
.lock(&body.room_id)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Forbid m.room.encrypted if encryption is disabled
|
// Forbid m.room.encrypted if encryption is disabled
|
||||||
if MessageLikeEventType::RoomEncrypted == body.event_type && !services().globals.allow_encryption() {
|
if MessageLikeEventType::RoomEncrypted == body.event_type && !services.globals.allow_encryption() {
|
||||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Encryption has been disabled"));
|
return Err(Error::BadRequest(ErrorKind::forbidden(), "Encryption has been disabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if body.event_type == MessageLikeEventType::CallInvite
|
if body.event_type == MessageLikeEventType::CallInvite && services.rooms.directory.is_public_room(&body.room_id)? {
|
||||||
&& services().rooms.directory.is_public_room(&body.room_id)?
|
|
||||||
{
|
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
ErrorKind::forbidden(),
|
ErrorKind::forbidden(),
|
||||||
"Room call invites are not allowed in public rooms",
|
"Room call invites are not allowed in public rooms",
|
||||||
@@ -50,7 +48,7 @@ pub(crate) async fn send_message_event_route(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a new transaction id
|
// Check if this is a new transaction id
|
||||||
if let Some(response) = services()
|
if let Some(response) = services
|
||||||
.transaction_ids
|
.transaction_ids
|
||||||
.existing_txnid(sender_user, sender_device, &body.txn_id)?
|
.existing_txnid(sender_user, sender_device, &body.txn_id)?
|
||||||
{
|
{
|
||||||
@@ -75,7 +73,7 @@ pub(crate) async fn send_message_event_route(
|
|||||||
let mut unsigned = BTreeMap::new();
|
let mut unsigned = BTreeMap::new();
|
||||||
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
|
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
|
||||||
|
|
||||||
let event_id = services()
|
let event_id = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.build_and_append_pdu(
|
.build_and_append_pdu(
|
||||||
@@ -93,7 +91,7 @@ pub(crate) async fn send_message_event_route(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
services()
|
services
|
||||||
.transaction_ids
|
.transaction_ids
|
||||||
.add_txnid(sender_user, sender_device, &body.txn_id, event_id.as_bytes())?;
|
.add_txnid(sender_user, sender_device, &body.txn_id, event_id.as_bytes())?;
|
||||||
|
|
||||||
@@ -109,7 +107,7 @@ pub(crate) async fn send_message_event_route(
|
|||||||
/// - Only works if the user is joined (TODO: always allow, but only show events
|
/// - Only works if the user is joined (TODO: always allow, but only show events
|
||||||
/// where the user was joined, depending on `history_visibility`)
|
/// where the user was joined, depending on `history_visibility`)
|
||||||
pub(crate) async fn get_message_events_route(
|
pub(crate) async fn get_message_events_route(
|
||||||
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> {
|
) -> Result<get_message_events::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
|
||||||
@@ -127,7 +125,7 @@ pub(crate) async fn get_message_events_route(
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|t| PduCount::try_from_string(t).ok());
|
.and_then(|t| PduCount::try_from_string(t).ok());
|
||||||
|
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.lazy_loading
|
.lazy_loading
|
||||||
.lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from)
|
.lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from)
|
||||||
@@ -143,12 +141,12 @@ pub(crate) async fn get_message_events_route(
|
|||||||
|
|
||||||
match body.dir {
|
match body.dir {
|
||||||
ruma::api::Direction::Forward => {
|
ruma::api::Direction::Forward => {
|
||||||
let events_after: Vec<_> = services()
|
let events_after: Vec<_> = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.pdus_after(sender_user, &body.room_id, from)?
|
.pdus_after(sender_user, &body.room_id, from)?
|
||||||
.filter_map(Result::ok) // Filter out buggy events
|
.filter_map(Result::ok) // Filter out buggy events
|
||||||
.filter(|(_, pdu)| { contains_url_filter(pdu, &body.filter) && visibility_filter(pdu, sender_user, &body.room_id)
|
.filter(|(_, pdu)| { contains_url_filter(pdu, &body.filter) && visibility_filter(&services, pdu, sender_user, &body.room_id)
|
||||||
|
|
||||||
})
|
})
|
||||||
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
||||||
@@ -161,7 +159,7 @@ pub(crate) async fn get_message_events_route(
|
|||||||
* https://github.com/vector-im/element-web/issues/21034
|
* https://github.com/vector-im/element-web/issues/21034
|
||||||
*/
|
*/
|
||||||
if !cfg!(feature = "element_hacks")
|
if !cfg!(feature = "element_hacks")
|
||||||
&& !services().rooms.lazy_loading.lazy_load_was_sent_before(
|
&& !services.rooms.lazy_loading.lazy_load_was_sent_before(
|
||||||
sender_user,
|
sender_user,
|
||||||
sender_device,
|
sender_device,
|
||||||
&body.room_id,
|
&body.room_id,
|
||||||
@@ -185,17 +183,17 @@ pub(crate) async fn get_message_events_route(
|
|||||||
resp.chunk = events_after;
|
resp.chunk = events_after;
|
||||||
},
|
},
|
||||||
ruma::api::Direction::Backward => {
|
ruma::api::Direction::Backward => {
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.backfill_if_required(&body.room_id, from)
|
.backfill_if_required(&body.room_id, from)
|
||||||
.await?;
|
.await?;
|
||||||
let events_before: Vec<_> = services()
|
let events_before: Vec<_> = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.pdus_until(sender_user, &body.room_id, from)?
|
.pdus_until(sender_user, &body.room_id, from)?
|
||||||
.filter_map(Result::ok) // Filter out buggy events
|
.filter_map(Result::ok) // Filter out buggy events
|
||||||
.filter(|(_, pdu)| {contains_url_filter(pdu, &body.filter) && visibility_filter(pdu, sender_user, &body.room_id)})
|
.filter(|(_, pdu)| {contains_url_filter(pdu, &body.filter) && visibility_filter(&services, pdu, sender_user, &body.room_id)})
|
||||||
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
|
||||||
.take(limit)
|
.take(limit)
|
||||||
.collect();
|
.collect();
|
||||||
@@ -206,7 +204,7 @@ pub(crate) async fn get_message_events_route(
|
|||||||
* https://github.com/vector-im/element-web/issues/21034
|
* https://github.com/vector-im/element-web/issues/21034
|
||||||
*/
|
*/
|
||||||
if !cfg!(feature = "element_hacks")
|
if !cfg!(feature = "element_hacks")
|
||||||
&& !services().rooms.lazy_loading.lazy_load_was_sent_before(
|
&& !services.rooms.lazy_loading.lazy_load_was_sent_before(
|
||||||
sender_user,
|
sender_user,
|
||||||
sender_device,
|
sender_device,
|
||||||
&body.room_id,
|
&body.room_id,
|
||||||
@@ -233,11 +231,12 @@ pub(crate) async fn get_message_events_route(
|
|||||||
|
|
||||||
resp.state = Vec::new();
|
resp.state = Vec::new();
|
||||||
for ll_id in &lazy_loaded {
|
for ll_id in &lazy_loaded {
|
||||||
if let Some(member_event) = services().rooms.state_accessor.room_state_get(
|
if let Some(member_event) =
|
||||||
&body.room_id,
|
services
|
||||||
&StateEventType::RoomMember,
|
.rooms
|
||||||
ll_id.as_str(),
|
.state_accessor
|
||||||
)? {
|
.room_state_get(&body.room_id, &StateEventType::RoomMember, ll_id.as_str())?
|
||||||
|
{
|
||||||
resp.state.push(member_event.to_state_event());
|
resp.state.push(member_event.to_state_event());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +244,7 @@ pub(crate) async fn get_message_events_route(
|
|||||||
// remove the feature check when we are sure clients like element can handle it
|
// remove the feature check when we are sure clients like element can handle it
|
||||||
if !cfg!(feature = "element_hacks") {
|
if !cfg!(feature = "element_hacks") {
|
||||||
if let Some(next_token) = next_token {
|
if let Some(next_token) = next_token {
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.lazy_loading
|
.lazy_loading
|
||||||
.lazy_load_mark_sent(sender_user, sender_device, &body.room_id, lazy_loaded, next_token)
|
.lazy_load_mark_sent(sender_user, sender_device, &body.room_id, lazy_loaded, next_token)
|
||||||
@@ -256,8 +255,8 @@ pub(crate) async fn get_message_events_route(
|
|||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visibility_filter(pdu: &PduEvent, user_id: &UserId, room_id: &RoomId) -> bool {
|
fn visibility_filter(services: &Services, pdu: &PduEvent, user_id: &UserId, room_id: &RoomId) -> bool {
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.user_can_see_event(user_id, room_id, &pdu.event_id)
|
.user_can_see_event(user_id, room_id, &pdu.event_id)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub(super) mod keys;
|
|||||||
pub(super) mod media;
|
pub(super) mod media;
|
||||||
pub(super) mod membership;
|
pub(super) mod membership;
|
||||||
pub(super) mod message;
|
pub(super) mod message;
|
||||||
|
pub(super) mod openid;
|
||||||
pub(super) mod presence;
|
pub(super) mod presence;
|
||||||
pub(super) mod profile;
|
pub(super) mod profile;
|
||||||
pub(super) mod push;
|
pub(super) mod push;
|
||||||
@@ -35,7 +36,6 @@ pub(super) mod user_directory;
|
|||||||
pub(super) mod voip;
|
pub(super) mod voip;
|
||||||
|
|
||||||
pub(super) use account::*;
|
pub(super) use account::*;
|
||||||
pub use alias::get_alias_helper;
|
|
||||||
pub(super) use alias::*;
|
pub(super) use alias::*;
|
||||||
pub(super) use backup::*;
|
pub(super) use backup::*;
|
||||||
pub(super) use capabilities::*;
|
pub(super) use capabilities::*;
|
||||||
@@ -49,6 +49,7 @@ pub(super) use media::*;
|
|||||||
pub(super) use membership::*;
|
pub(super) use membership::*;
|
||||||
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, validate_and_add_event_id};
|
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, validate_and_add_event_id};
|
||||||
pub(super) use message::*;
|
pub(super) use message::*;
|
||||||
|
pub(super) use openid::*;
|
||||||
pub(super) use presence::*;
|
pub(super) use presence::*;
|
||||||
pub(super) use profile::*;
|
pub(super) use profile::*;
|
||||||
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
|
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
|
use conduit::utils;
|
||||||
|
use ruma::{
|
||||||
|
api::client::{account, error::ErrorKind},
|
||||||
|
authentication::TokenType,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::TOKEN_LENGTH;
|
||||||
|
use crate::{Error, Result, Ruma};
|
||||||
|
|
||||||
|
/// # `POST /_matrix/client/v3/user/{userId}/openid/request_token`
|
||||||
|
///
|
||||||
|
/// Request an OpenID token to verify identity with third-party services.
|
||||||
|
///
|
||||||
|
/// - The token generated is only valid for the OpenID API
|
||||||
|
pub(crate) async fn create_openid_token_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<account::request_openid_token::v3::Request>,
|
||||||
|
) -> Result<account::request_openid_token::v3::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if sender_user != &body.user_id {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Not allowed to request OpenID tokens on behalf of other users",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let access_token = utils::random_string(TOKEN_LENGTH);
|
||||||
|
|
||||||
|
let expires_in = services
|
||||||
|
.users
|
||||||
|
.create_openid_token(&body.user_id, &access_token)?;
|
||||||
|
|
||||||
|
Ok(account::request_openid_token::v3::Response {
|
||||||
|
access_token,
|
||||||
|
token_type: TokenType::Bearer,
|
||||||
|
matrix_server_name: services.globals.config.server_name.clone(),
|
||||||
|
expires_in: Duration::from_secs(expires_in),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,22 +1,32 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
use ruma::api::client::{
|
use ruma::api::client::{
|
||||||
error::ErrorKind,
|
error::ErrorKind,
|
||||||
presence::{get_presence, set_presence},
|
presence::{get_presence, set_presence},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{services, Error, Result, Ruma};
|
use crate::{Error, Result, Ruma};
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
|
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
|
||||||
///
|
///
|
||||||
/// Sets the presence state of the sender user.
|
/// Sets the presence state of the sender user.
|
||||||
pub(crate) async fn set_presence_route(body: Ruma<set_presence::v3::Request>) -> Result<set_presence::v3::Response> {
|
pub(crate) async fn set_presence_route(
|
||||||
if !services().globals.allow_local_presence() {
|
State(services): State<crate::State>, body: Ruma<set_presence::v3::Request>,
|
||||||
|
) -> Result<set_presence::v3::Response> {
|
||||||
|
if !services.globals.allow_local_presence() {
|
||||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server"));
|
return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
services()
|
if sender_user != &body.user_id && body.appservice_info.is_none() {
|
||||||
|
return Err(Error::BadRequest(
|
||||||
|
ErrorKind::InvalidParam,
|
||||||
|
"Not allowed to set presence of other users",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
services
|
||||||
.presence
|
.presence
|
||||||
.set_presence(sender_user, &body.presence, None, None, body.status_msg.clone())?;
|
.set_presence(sender_user, &body.presence, None, None, body.status_msg.clone())?;
|
||||||
|
|
||||||
@@ -28,8 +38,10 @@ pub(crate) async fn set_presence_route(body: Ruma<set_presence::v3::Request>) ->
|
|||||||
/// Gets the presence state of the given user.
|
/// Gets the presence state of the given user.
|
||||||
///
|
///
|
||||||
/// - Only works if you share a room with the user
|
/// - Only works if you share a room with the user
|
||||||
pub(crate) async fn get_presence_route(body: Ruma<get_presence::v3::Request>) -> Result<get_presence::v3::Response> {
|
pub(crate) async fn get_presence_route(
|
||||||
if !services().globals.allow_local_presence() {
|
State(services): State<crate::State>, body: Ruma<get_presence::v3::Request>,
|
||||||
|
) -> Result<get_presence::v3::Response> {
|
||||||
|
if !services.globals.allow_local_presence() {
|
||||||
return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server"));
|
return Err(Error::BadRequest(ErrorKind::forbidden(), "Presence is disabled on this server"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,12 +49,12 @@ pub(crate) async fn get_presence_route(body: Ruma<get_presence::v3::Request>) ->
|
|||||||
|
|
||||||
let mut presence_event = None;
|
let mut presence_event = None;
|
||||||
|
|
||||||
for _room_id in services()
|
for _room_id in services
|
||||||
.rooms
|
.rooms
|
||||||
.user
|
.user
|
||||||
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
|
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
|
||||||
{
|
{
|
||||||
if let Some(presence) = services().presence.get_presence(&body.user_id)? {
|
if let Some(presence) = services.presence.get_presence(&body.user_id)? {
|
||||||
presence_event = Some(presence);
|
presence_event = Some(presence);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+64
-59
@@ -1,3 +1,5 @@
|
|||||||
|
use axum::extract::State;
|
||||||
|
use conduit::{pdu::PduBuilder, warn, Error, Result};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
client::{
|
client::{
|
||||||
@@ -11,12 +13,9 @@ use ruma::{
|
|||||||
OwnedMxcUri, OwnedRoomId, OwnedUserId,
|
OwnedMxcUri, OwnedRoomId, OwnedUserId,
|
||||||
};
|
};
|
||||||
use serde_json::value::to_raw_value;
|
use serde_json::value::to_raw_value;
|
||||||
use tracing::warn;
|
use service::Services;
|
||||||
|
|
||||||
use crate::{
|
use crate::Ruma;
|
||||||
service::{pdu::PduBuilder, user_is_local},
|
|
||||||
services, Error, Result, Ruma,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// # `PUT /_matrix/client/r0/profile/{userId}/displayname`
|
/// # `PUT /_matrix/client/r0/profile/{userId}/displayname`
|
||||||
///
|
///
|
||||||
@@ -24,21 +23,21 @@ use crate::{
|
|||||||
///
|
///
|
||||||
/// - Also makes sure other users receive the update using presence EDUs
|
/// - Also makes sure other users receive the update using presence EDUs
|
||||||
pub(crate) async fn set_displayname_route(
|
pub(crate) async fn set_displayname_route(
|
||||||
body: Ruma<set_display_name::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<set_display_name::v3::Request>,
|
||||||
) -> Result<set_display_name::v3::Response> {
|
) -> Result<set_display_name::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services()
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(sender_user)
|
.rooms_joined(sender_user)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
update_displayname(sender_user.clone(), body.displayname.clone(), all_joined_rooms).await?;
|
update_displayname(&services, sender_user.clone(), body.displayname.clone(), all_joined_rooms).await?;
|
||||||
|
|
||||||
if services().globals.allow_local_presence() {
|
if services.globals.allow_local_presence() {
|
||||||
// Presence update
|
// Presence update
|
||||||
services()
|
services
|
||||||
.presence
|
.presence
|
||||||
.ping_presence(sender_user, &PresenceState::Online)?;
|
.ping_presence(sender_user, &PresenceState::Online)?;
|
||||||
}
|
}
|
||||||
@@ -53,11 +52,11 @@ pub(crate) async fn set_displayname_route(
|
|||||||
/// - If user is on another server and we do not have a local copy already fetch
|
/// - If user is on another server and we do not have a local copy already fetch
|
||||||
/// displayname over federation
|
/// displayname over federation
|
||||||
pub(crate) async fn get_displayname_route(
|
pub(crate) async fn get_displayname_route(
|
||||||
body: Ruma<get_display_name::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<get_display_name::v3::Request>,
|
||||||
) -> Result<get_display_name::v3::Response> {
|
) -> Result<get_display_name::v3::Response> {
|
||||||
if !user_is_local(&body.user_id) {
|
if !services.globals.user_is_local(&body.user_id) {
|
||||||
// Create and update our local copy of the user
|
// Create and update our local copy of the user
|
||||||
if let Ok(response) = services()
|
if let Ok(response) = services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
body.user_id.server_name(),
|
body.user_id.server_name(),
|
||||||
@@ -68,19 +67,19 @@ pub(crate) async fn get_displayname_route(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
if !services().users.exists(&body.user_id)? {
|
if !services.users.exists(&body.user_id)? {
|
||||||
services().users.create(&body.user_id, None)?;
|
services.users.create(&body.user_id, None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_displayname(&body.user_id, response.displayname.clone())
|
.set_displayname(&body.user_id, response.displayname.clone())
|
||||||
.await?;
|
.await?;
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
||||||
.await?;
|
.await?;
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_blurhash(&body.user_id, response.blurhash.clone())
|
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -91,14 +90,14 @@ pub(crate) async fn get_displayname_route(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !services().users.exists(&body.user_id)? {
|
if !services.users.exists(&body.user_id)? {
|
||||||
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
||||||
// federation
|
// federation
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(get_display_name::v3::Response {
|
Ok(get_display_name::v3::Response {
|
||||||
displayname: services().users.displayname(&body.user_id)?,
|
displayname: services.users.displayname(&body.user_id)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,10 +107,10 @@ pub(crate) async fn get_displayname_route(
|
|||||||
///
|
///
|
||||||
/// - Also makes sure other users receive the update using presence EDUs
|
/// - Also makes sure other users receive the update using presence EDUs
|
||||||
pub(crate) async fn set_avatar_url_route(
|
pub(crate) async fn set_avatar_url_route(
|
||||||
body: Ruma<set_avatar_url::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<set_avatar_url::v3::Request>,
|
||||||
) -> Result<set_avatar_url::v3::Response> {
|
) -> Result<set_avatar_url::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services()
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(sender_user)
|
.rooms_joined(sender_user)
|
||||||
@@ -119,6 +118,7 @@ pub(crate) async fn set_avatar_url_route(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
update_avatar_url(
|
update_avatar_url(
|
||||||
|
&services,
|
||||||
sender_user.clone(),
|
sender_user.clone(),
|
||||||
body.avatar_url.clone(),
|
body.avatar_url.clone(),
|
||||||
body.blurhash.clone(),
|
body.blurhash.clone(),
|
||||||
@@ -126,9 +126,9 @@ pub(crate) async fn set_avatar_url_route(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if services().globals.allow_local_presence() {
|
if services.globals.allow_local_presence() {
|
||||||
// Presence update
|
// Presence update
|
||||||
services()
|
services
|
||||||
.presence
|
.presence
|
||||||
.ping_presence(sender_user, &PresenceState::Online)?;
|
.ping_presence(sender_user, &PresenceState::Online)?;
|
||||||
}
|
}
|
||||||
@@ -143,11 +143,11 @@ pub(crate) async fn set_avatar_url_route(
|
|||||||
/// - If user is on another server and we do not have a local copy already fetch
|
/// - If user is on another server and we do not have a local copy already fetch
|
||||||
/// `avatar_url` and blurhash over federation
|
/// `avatar_url` and blurhash over federation
|
||||||
pub(crate) async fn get_avatar_url_route(
|
pub(crate) async fn get_avatar_url_route(
|
||||||
body: Ruma<get_avatar_url::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<get_avatar_url::v3::Request>,
|
||||||
) -> Result<get_avatar_url::v3::Response> {
|
) -> Result<get_avatar_url::v3::Response> {
|
||||||
if !user_is_local(&body.user_id) {
|
if !services.globals.user_is_local(&body.user_id) {
|
||||||
// Create and update our local copy of the user
|
// Create and update our local copy of the user
|
||||||
if let Ok(response) = services()
|
if let Ok(response) = services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
body.user_id.server_name(),
|
body.user_id.server_name(),
|
||||||
@@ -158,19 +158,19 @@ pub(crate) async fn get_avatar_url_route(
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
if !services().users.exists(&body.user_id)? {
|
if !services.users.exists(&body.user_id)? {
|
||||||
services().users.create(&body.user_id, None)?;
|
services.users.create(&body.user_id, None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_displayname(&body.user_id, response.displayname.clone())
|
.set_displayname(&body.user_id, response.displayname.clone())
|
||||||
.await?;
|
.await?;
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
||||||
.await?;
|
.await?;
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_blurhash(&body.user_id, response.blurhash.clone())
|
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -182,15 +182,15 @@ pub(crate) async fn get_avatar_url_route(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !services().users.exists(&body.user_id)? {
|
if !services.users.exists(&body.user_id)? {
|
||||||
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
||||||
// federation
|
// federation
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(get_avatar_url::v3::Response {
|
Ok(get_avatar_url::v3::Response {
|
||||||
avatar_url: services().users.avatar_url(&body.user_id)?,
|
avatar_url: services.users.avatar_url(&body.user_id)?,
|
||||||
blurhash: services().users.blurhash(&body.user_id)?,
|
blurhash: services.users.blurhash(&body.user_id)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,10 +200,12 @@ pub(crate) async fn get_avatar_url_route(
|
|||||||
///
|
///
|
||||||
/// - If user is on another server and we do not have a local copy already,
|
/// - If user is on another server and we do not have a local copy already,
|
||||||
/// fetch profile over federation.
|
/// fetch profile over federation.
|
||||||
pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> Result<get_profile::v3::Response> {
|
pub(crate) async fn get_profile_route(
|
||||||
if !user_is_local(&body.user_id) {
|
State(services): State<crate::State>, body: Ruma<get_profile::v3::Request>,
|
||||||
|
) -> Result<get_profile::v3::Response> {
|
||||||
|
if !services.globals.user_is_local(&body.user_id) {
|
||||||
// Create and update our local copy of the user
|
// Create and update our local copy of the user
|
||||||
if let Ok(response) = services()
|
if let Ok(response) = services
|
||||||
.sending
|
.sending
|
||||||
.send_federation_request(
|
.send_federation_request(
|
||||||
body.user_id.server_name(),
|
body.user_id.server_name(),
|
||||||
@@ -214,19 +216,19 @@ pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> R
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
if !services().users.exists(&body.user_id)? {
|
if !services.users.exists(&body.user_id)? {
|
||||||
services().users.create(&body.user_id, None)?;
|
services.users.create(&body.user_id, None)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_displayname(&body.user_id, response.displayname.clone())
|
.set_displayname(&body.user_id, response.displayname.clone())
|
||||||
.await?;
|
.await?;
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
||||||
.await?;
|
.await?;
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_blurhash(&body.user_id, response.blurhash.clone())
|
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -239,23 +241,23 @@ pub(crate) async fn get_profile_route(body: Ruma<get_profile::v3::Request>) -> R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !services().users.exists(&body.user_id)? {
|
if !services.users.exists(&body.user_id)? {
|
||||||
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
||||||
// federation
|
// federation
|
||||||
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(get_profile::v3::Response {
|
Ok(get_profile::v3::Response {
|
||||||
avatar_url: services().users.avatar_url(&body.user_id)?,
|
avatar_url: services.users.avatar_url(&body.user_id)?,
|
||||||
blurhash: services().users.blurhash(&body.user_id)?,
|
blurhash: services.users.blurhash(&body.user_id)?,
|
||||||
displayname: services().users.displayname(&body.user_id)?,
|
displayname: services.users.displayname(&body.user_id)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_displayname(
|
pub async fn update_displayname(
|
||||||
user_id: OwnedUserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>,
|
services: &Services, user_id: OwnedUserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_displayname(&user_id, displayname.clone())
|
.set_displayname(&user_id, displayname.clone())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -271,7 +273,7 @@ pub async fn update_displayname(
|
|||||||
displayname: displayname.clone(),
|
displayname: displayname.clone(),
|
||||||
join_authorized_via_users_server: None,
|
join_authorized_via_users_server: None,
|
||||||
..serde_json::from_str(
|
..serde_json::from_str(
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
|
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
|
||||||
@@ -294,19 +296,20 @@ pub async fn update_displayname(
|
|||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
update_all_rooms(all_joined_rooms, user_id).await;
|
update_all_rooms(services, all_joined_rooms, user_id).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_avatar_url(
|
pub async fn update_avatar_url(
|
||||||
user_id: OwnedUserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>, all_joined_rooms: Vec<OwnedRoomId>,
|
services: &Services, user_id: OwnedUserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>,
|
||||||
|
all_joined_rooms: Vec<OwnedRoomId>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_avatar_url(&user_id, avatar_url.clone())
|
.set_avatar_url(&user_id, avatar_url.clone())
|
||||||
.await?;
|
.await?;
|
||||||
services()
|
services
|
||||||
.users
|
.users
|
||||||
.set_blurhash(&user_id, blurhash.clone())
|
.set_blurhash(&user_id, blurhash.clone())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -323,7 +326,7 @@ pub async fn update_avatar_url(
|
|||||||
blurhash: blurhash.clone(),
|
blurhash: blurhash.clone(),
|
||||||
join_authorized_via_users_server: None,
|
join_authorized_via_users_server: None,
|
||||||
..serde_json::from_str(
|
..serde_json::from_str(
|
||||||
services()
|
services
|
||||||
.rooms
|
.rooms
|
||||||
.state_accessor
|
.state_accessor
|
||||||
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
|
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
|
||||||
@@ -346,15 +349,17 @@ pub async fn update_avatar_url(
|
|||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
update_all_rooms(all_joined_rooms, user_id).await;
|
update_all_rooms(services, all_joined_rooms, user_id).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_all_rooms(all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: OwnedUserId) {
|
pub async fn update_all_rooms(
|
||||||
|
services: &Services, all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: OwnedUserId,
|
||||||
|
) {
|
||||||
for (pdu_builder, room_id) in all_joined_rooms {
|
for (pdu_builder, room_id) in all_joined_rooms {
|
||||||
let state_lock = services().globals.roomid_mutex_state.lock(room_id).await;
|
let state_lock = services.rooms.state.mutex.lock(room_id).await;
|
||||||
if let Err(e) = services()
|
if let Err(e) = services
|
||||||
.rooms
|
.rooms
|
||||||
.timeline
|
.timeline
|
||||||
.build_and_append_pdu(pdu_builder, &user_id, room_id, &state_lock)
|
.build_and_append_pdu(pdu_builder, &user_id, room_id, &state_lock)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user