Compare commits

...

61 Commits

Author SHA1 Message Date
strawberry 16014e1594 remove ProcSubset=pid from systemd units for now
they appear to cause strange rust malloc issues on Debian systems

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 23:51:02 -04:00
strawberry 7e828440f9 allow conduit database version 16
Conduit bumped the database version to 16, but did not introduce any
breaking changes. Their database migrations are extremely fragile and risky,
and also do not really apply to us, so just to retain Conduit -> conduwuit
compatibility we'll check for both versions.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 23:44:15 -04:00
strawberry f6918833d7 remove -unknown- from nix flake
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 23:04:14 -04:00
strawberry 4d7bbe9fb4 tiny micro-optimisations in some config stuff
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 22:37:40 -04:00
strawberry 75be68fa61 add config option to control sending admin notices of alerts
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 22:37:34 -04:00
strawberry 0760150822 cache all 3 x86_64 nix devshells in CI
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 22:08:23 -04:00
strawberry 37a2ba59d0 improve UX of admin media deletion commands, ignore errors by default, support deleting local media too
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 22:02:58 -04:00
strawberry 724711218a add note that ko-fi takes a fee
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 22:02:58 -04:00
strawberry 359fb25262 add missing feat_sha256_media to fresh database creations
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 22:02:51 -04:00
strawberry 9761e2f10c fix lockdown_public_room_directory bypass, add appservice exclusion
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 21:38:26 -04:00
strawberry 30e3e45f9f misc CI improvements, build macOS binaries, flake improvements/fixes
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 21:29:17 -04:00
strawberry e5efd55838 feature-gate direct TLS mode to make rustls/aws-lc-rs optional
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:54:21 -04:00
strawberry 87734a074f add m.call and m.call.member to list of permissions to set on public rooms
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:53:31 -04:00
strawberry a7c4a7933d disable log colours in the complement config
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:52:14 -04:00
strawberry 83becf013c add config option to disable ANSI log colours
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:51:54 -04:00
strawberry acb9eae707 add back server name to error sending PDU to remote server
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:51:50 -04:00
strawberry 2eee454a18 docs: nixos and unix socket fail, jemalloc and hardened.nix
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:50:52 -04:00
strawberry e0b2595905 support reading TURN secret from a file (turn_secret_file)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:49:23 -04:00
strawberry 73afc1fd8f allow taking multiple --config arguments to "include"/merge more config files
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:49:17 -04:00
strawberry 6acdd0d947 improve some general documentation
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:49:13 -04:00
strawberry e38c37d9e7 allow users to respond to polls by default (org.matrix.msc3381.poll.response)
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:49:08 -04:00
strawberry 45254638b1 drop target-cpu optimised builds
this seems too broken.

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:48:59 -04:00
strawberry 2d54264fbe bump rust to 1.82.0, rocksdb v9.7.3, ruwuma, and a few nix pkgs
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-24 20:47:51 -04:00
Toby Murray 6c1c7b35a5 Separate command lines
I don't think this works if the commands are invoked on the same line with no thing joining them, so separate them on to separate lines.
2024-10-24 15:09:28 -04:00
nisbet-hubbard 8428e7cdf7 Update generic.md 2024-10-19 12:35:20 -04:00
strawberry e589464954 bump cargo.lock and deps
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-10-05 22:33:58 -04:00
Jason Volk 0413037246 fix lints
Signed-off-by: Jason Volk <jason@zemos.net>
2024-10-05 17:07:37 -07:00
Jacob Taylor b9a8f8e6c7 automatically scale conduwuit caches by CPU-core-count 2024-10-05 17:07:37 -07:00
strawberry 032b199129 add db query command to get all pushers for a user
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-15 19:56:29 -04:00
strawberry e9e5fe2176 implement MSC4165, removing own power levels on deactivation
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-15 14:20:01 -04:00
strawberry 17fd34eb12 dedupe some account deactivation steps, remove all profile fields on deactivation
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-15 12:08:07 -04:00
strawberry 895b178720 add admin command to force demote a local user from a room
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-15 11:36:47 -04:00
strawberry a65dd6dfb3 dont allow guests to publish to room directories
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-14 12:37:29 -04:00
strawberry e146c75279 dont include appservices in room guest access enforcement check
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-14 12:27:22 -04:00
strawberry d75aebc373 implement generic K-V support for MSC4133, GET/PUT/DELETE
no PATCH still yet

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-14 11:16:49 -04:00
morguldir 80b72637e2 ci: enable cachix after restoring the cache
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-09-13 05:09:10 +02:00
morguldir a41e63b40e Add back allow_check_for_updates
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-09-12 22:51:31 -04:00
strawberry cf9b72ce3f remove a few unnecessary muts
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-09 21:41:57 -04:00
strawberry 38552b36e9 make the first user admin if created from CLI / --execute
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-09 21:13:37 -04:00
strawberry 9de780b56c remove unnecessary displayname requirement on making user admin
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-09 21:10:56 -04:00
strawberry 55f71d3912 bump conduwuit to 0.4.7
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-08 19:56:53 -04:00
strawberry 61347bee06 advertise support for MSC4133 and MSC4175
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-08 19:53:33 -04:00
strawberry 38cd88e1e8 remove unnecessary cloning on account deactivation profile updates
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-08 19:33:21 -04:00
strawberry b44f7f5476 remove MSC4175 timezone on account deactivation
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-08 19:23:54 -04:00
strawberry e888810e67 update complement results for TestFederationThumbnail
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-08 16:22:02 -04:00
morguldir 02aee2f174 ci: retry attic push 3 times, and continue on errors
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-09-08 20:51:34 +02:00
morguldir 24c408f4c6 bump rocksdb to 9.6.1, add binutils to devshell 2024-09-08 19:21:23 +02:00
morguldir 1c1f300efe ci: avoid propagating bash errors immidiately
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-09-08 17:41:02 +02:00
morguldir 8dccc04b40 nix: explicitly include liburing in the devshell
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-09-08 17:40:02 +02:00
strawberry 96ab59b5b0 bump cargo.lock
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-08 10:28:21 -04:00
strawberry c47337f3db docs: ignore development.md and contributing.md from lychee
and update 2 other nix references

Signed-off-by: strawberry <strawberry@puppygock.gay>
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-09-08 10:24:38 -04:00
morguldir 3e0d404fb4 syncv3: use RoomTypeFilter struct instead of Option<RoomType>
Signed-off-by: morguldir <morguldir@protonmail.com>
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-07 18:09:21 +02:00
morguldir 593d3bb321 nix: update flake.lock, skip building tests for liburing
Signed-off-by: morguldir <morguldir@protonmail.com>
2024-09-08 05:03:30 +02:00
strawberry f14a253664 add local_only arg to list joined members in room admin cmd
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-07 12:46:59 -04:00
strawberry b3974c569d log device display name on normal user registrations too
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-07 10:10:46 -04:00
strawberry f163ebf3bb implement MSC4133 only with MSC4175 for GET/PUT/DELETE
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-07 09:27:35 -04:00
strawberry 5ae9a5ff31 bump nix lockfile, and ruma
• Updated input 'attic':
    'github:zhaofengli/attic/6d9aeaef0a067d664cb11bb7704f7ec373d47fb2' (2024-08-21)
  → 'github:zhaofengli/attic/bea72d75b6165dfb529ba0c39cc6c7e9c7f0d234' (2024-09-02)
• Added input 'attic/flake-parts':
    'github:hercules-ci/flake-parts/8471fe90ad337a8074e957b69ca4d0089218391d' (2024-08-01)
• Added input 'attic/flake-parts/nixpkgs-lib':
    follows 'attic/nixpkgs'
• Updated input 'attic/nixpkgs':
    'github:NixOS/nixpkgs/d4a7a4d0e066278bfb0d77bd2a7adde1c0ec9e3d' (2024-08-16)
  → 'github:NixOS/nixpkgs/b96f849e725333eb2b1c7f1cb84ff102062468ba' (2024-08-30)
• Updated input 'attic/nixpkgs-stable':
    'github:NixOS/nixpkgs/205fd4226592cc83fd4c0885a3e4c9c400efabb5' (2024-07-09)
  → 'github:NixOS/nixpkgs/797f7dc49e0bc7fab4b57c021cdf68f595e47841' (2024-08-22)
• Updated input 'complement':
    'github:matrix-org/complement/6e4426a9e63233f9821a4d2382bfed145244183f' (2024-07-30)
  → 'github:matrix-org/complement/39733c1b2f8314800776748cc7164f9a34650686' (2024-08-22)
• Updated input 'crane':
    'github:ipetkov/crane/7ce92819802bc583b7e82ebc08013a530f22209f' (2024-08-18)
  → 'github:ipetkov/crane/7e4586bad4e3f8f97a9271def747cf58c4b68f3c' (2024-09-04)
• Removed input 'crane/nixpkgs'
• Updated input 'fenix':
    'github:nix-community/fenix/e88b38a5a3834e039d413a88f8150a75ef6453ef' (2024-08-21)
  → 'github:nix-community/fenix/d9afdb4465ba2f20bb73b0ff5d2c2837cafc2e14' (2024-09-06)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/3723e5910c14f0ffbd13de474b8a8fcc74db04ce' (2024-08-20)
  → 'github:rust-lang/rust-analyzer/124c7482167ff6eea4f7663c0be87ea568ccd8c6' (2024-09-05)
• Updated input 'liburing':
    'github:axboe/liburing/2d4e799017d64cd2f8304503eef9064931bb3fbd' (2024-08-21)
  → 'github:axboe/liburing/0fe5c09195c0918f89582dd6ff098a58a0bdf62a' (2024-09-06)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/36a9aeaaa17a2d4348498275f9fe530cd4f9e519' (2024-08-21)
  → 'github:NixOS/nixpkgs/ad416d066ca1222956472ab7d0555a6946746a80' (2024-09-04)

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-07 09:27:29 -04:00
strawberry 6f643a4b06 bump rust to 1.81.0
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-06 21:08:21 -04:00
strawberry 80698c0b17 docs: add some more conduwuit development info
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-06 20:33:02 -04:00
strawberry 909eeac5b0 drop target CPU for aarch64 to cortex-a53 instead of cortex-a73
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-06 19:02:04 -04:00
strawberry f521f88daf docs: mildly update the NixOS page to ref https://github.com/NixOS/nixpkgs/pull/339260
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-09-06 19:00:38 -04:00
74 changed files with 2576 additions and 1187 deletions
+141 -116
View File
@@ -16,7 +16,6 @@ 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
@@ -24,7 +23,7 @@ on:
concurrency: concurrency:
group: ${{ github.head_ref || github.ref_name }} group: ${{ github.head_ref || github.ref_name }}
cancel-in-progress: true cancel-in-progress: false
env: env:
# sccache only on main repo # sccache only on main repo
@@ -51,8 +50,11 @@ env:
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps # Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
NIX_CONFIG: | NIX_CONFIG: |
show-trace = true show-trace = true
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg= extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
accept-flake-config = true
# complement uses libolm # complement uses libolm
NIXPKGS_ALLOW_INSECURE: 1 NIXPKGS_ALLOW_INSECURE: 1
@@ -64,12 +66,23 @@ 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) - name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main uses: jlumbroso/free-disk-space@main
- name: Free up more runner space
run: |
set +o pipefail
# large docker images
sudo docker image prune --all --force || true
# large packages
sudo apt-get purge -y '^llvm-.*' 'php.*' '^mongodb-.*' '^mysql-.*' azure-cli google-cloud-cli google-chrome-stable firefox powershell microsoft-edge-stable || true
sudo apt-get autoremove -y
sudo apt-get clean
# large folders
sudo rm -rf /var/lib/apt/lists/* /usr/local/games /usr/local/sqlpackage /usr/local/.ghcup /usr/local/share/powershell /usr/local/share/edge_driver /usr/local/share/gecko_driver /usr/local/share/chromium /usr/local/share/chromedriver-linux64 /usr/local/share/vcpkg /usr/local/lib/python* /usr/local/lib/node_modules /usr/local/julia* /opt/mssql-tools /etc/skel /usr/share/vim /usr/share/postgresql /usr/share/man /usr/share/apache-maven-* /usr/share/R /usr/share/alsa /usr/share/miniconda /usr/share/grub /usr/share/gradle-* /usr/share/locale /usr/share/texinfo /usr/share/kotlinc /usr/share/swift /usr/share/doc /usr/share/az_9.3.0 /usr/share/sbt /usr/share/ri /usr/share/icons /usr/share/java /usr/share/fonts /usr/lib/google-cloud-sdk /usr/lib/jvm /usr/lib/mono /usr/lib/R /usr/lib/postgresql /usr/lib/heroku /usr/lib/gcc
set -o pipefail
- name: Sync repository - name: Sync repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -85,13 +98,7 @@ jobs:
exit 1 exit 1
fi fi
- uses: nixbuild/nix-quick-install-action@v28 - uses: nixbuild/nix-quick-install-action@master
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Restore and cache Nix store - name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v5.1.0 uses: nix-community/cache-nix-action@v5.1.0
@@ -114,11 +121,20 @@ jobs:
# always save the cache # always save the cache
save-always: true save-always: true
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Apply Nix binary cache configuration - name: Apply Nix binary cache configuration
run: | run: |
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/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/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg= extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
accept-flake-config = true
EOF EOF
- name: Use alternative Nix binary caches if specified - name: Use alternative Nix binary caches if specified
@@ -132,29 +148,16 @@ 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 --impure 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 bin/nix-build-and-cache ci
ATTEMPTS=3 bin/nix-build-and-cache just '.#devShells.x86_64-linux.default'
SUCCESS=false bin/nix-build-and-cache just '.#devShells.x86_64-linux.all-features'
while (( ATTEMPTS-- > 0 )) bin/nix-build-and-cache just '.#devShells.x86_64-linux.dynamic'
do
bin/nix-build-and-cache ci
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
# use sccache for Rust # use sccache for Rust
- name: Run sccache-cache - name: Run sccache-cache
@@ -167,10 +170,14 @@ jobs:
cache-all-crates: "true" cache-all-crates: "true"
- name: Run CI tests - name: Run CI tests
env:
CARGO_PROFILE: "test"
run: | run: |
direnv exec . engage > >(tee -a test_output.log) direnv exec . engage > >(tee -a test_output.log)
- name: Run Complement tests - name: Run Complement tests
env:
CARGO_PROFILE: "test"
run: | run: |
# the nix devshell sets $COMPLEMENT_SRC, so "/dev/null" is no-op # 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) direnv exec . bin/complement "/dev/null" complement_test_logs.jsonl complement_test_results.jsonl > >(tee -a test_output.log)
@@ -218,7 +225,7 @@ jobs:
echo '```' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY
fi fi
- name: Run cargo clean test artifacts - name: Run cargo clean test artifacts to free up space
run: | run: |
cargo clean --profile test cargo clean --profile test
@@ -229,8 +236,8 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- target: aarch64-unknown-linux-musl - target: aarch64-linux-musl
- target: x86_64-unknown-linux-musl - target: x86_64-linux-musl
steps: steps:
- name: Free Disk Space (Ubuntu) - name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main uses: jlumbroso/free-disk-space@main
@@ -240,12 +247,6 @@ jobs:
- uses: nixbuild/nix-quick-install-action@v28 - uses: nixbuild/nix-quick-install-action@v28
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Restore and cache Nix store - name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v5.1.0 uses: nix-community/cache-nix-action@v5.1.0
with: with:
@@ -267,11 +268,20 @@ jobs:
# always save the cache # always save the cache
save-always: true save-always: true
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Apply Nix binary cache configuration - name: Apply Nix binary cache configuration
run: | run: |
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/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/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg= extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
accept-flake-config = true
EOF EOF
- name: Use alternative Nix binary caches if specified - name: Use alternative Nix binary caches if specified
@@ -301,26 +311,17 @@ jobs:
- name: Build static ${{ matrix.target }} - name: Build static ${{ matrix.target }}
run: | run: |
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*') if [[ ${{ matrix.target }} == "x86_64-linux-musl" ]]
then
CARGO_DEB_TARGET_TUPLE="x86_64-unknown-linux-musl"
elif [[ ${{ matrix.target }} == "aarch64-linux-musl" ]]
then
CARGO_DEB_TARGET_TUPLE="aarch64-unknown-linux-musl"
fi
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) 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 bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features
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/
@@ -341,26 +342,17 @@ jobs:
- name: Build static debug ${{ matrix.target }} - name: Build static debug ${{ matrix.target }}
run: | run: |
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*') if [[ ${{ matrix.target }} == "x86_64-linux-musl" ]]
then
CARGO_DEB_TARGET_TUPLE="x86_64-unknown-linux-musl"
elif [[ ${{ matrix.target }} == "aarch64-linux-musl" ]]
then
CARGO_DEB_TARGET_TUPLE="aarch64-unknown-linux-musl"
fi
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) 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 bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features-debug
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. # > 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 # so we need to coerce cargo-deb into thinking this is a release binary
@@ -423,45 +415,13 @@ jobs:
- name: Build OCI image ${{ matrix.target }} - name: Build OCI image ${{ matrix.target }}
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 bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
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 }} - name: Build debug OCI image ${{ matrix.target }}
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 bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features-debug
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 cp -v -f result oci-image-${{ matrix.target }}-debug.tar.gz
@@ -481,6 +441,71 @@ jobs:
if-no-files-found: error if-no-files-found: error
compression-level: 0 compression-level: 0
build_mac_binaries:
name: Build MacOS Binaries
strategy:
matrix:
os: [macos-latest, macos-13]
runs-on: ${{ matrix.os }}
steps:
- name: Sync repository
uses: actions/checkout@v4
- name: Tag comparison check
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
run: |
# Tag mismatch with latest repo tag check to prevent potential downgrades
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
if [ $LATEST_TAG != ${{ github.ref_name }} ]; then
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.'
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.' >> $GITHUB_STEP_SUMMARY
exit 1
fi
# use sccache for Rust
- name: Run sccache-cache
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
uses: mozilla-actions/sccache-action@main
# use rust-cache
- uses: Swatinem/rust-cache@v2
with:
cache-all-crates: "true"
# Nix can't do portable macOS builds yet
- name: Build macOS x86_64 binary
if: ${{ matrix.os == 'macos-13' }}
run: |
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short HEAD)" cargo build --release
cp -v -f target/release/conduit conduwuit-macos-x86_64
otool -L conduwuit-macos-x86_64
# quick smoke test of the x86_64 macOS binary
- name: Run x86_64 macOS release binary
if: ${{ matrix.os == 'macos-13' }}
run: |
./conduwuit-macos-x86_64 --version
- name: Build macOS arm64 binary
if: ${{ matrix.os == 'macos-latest' }}
run: |
CONDUWUIT_VERSION_EXTRA="$(git rev-parse --short HEAD)" cargo build --release
cp -v -f target/release/conduit conduwuit-macos-arm64
otool -L conduwuit-macos-arm64
# quick smoke test of the arm64 macOS binary
- name: Run arm64 macOS release binary
if: ${{ matrix.os == 'macos-latest' }}
run: |
./conduwuit-macos-arm64 --version
- name: Upload macOS x86_64 binary
if: ${{ matrix.os == 'macos-13' }}
uses: actions/upload-artifact@v4
with:
name: conduwuit-macos-x86_64
path: conduwuit-macos-x86_64
if-no-files-found: error
- name: Upload macOS arm64 binary
if: ${{ matrix.os == 'macos-latest' }}
uses: actions/upload-artifact@v4
with:
name: conduwuit-macos-arm64
path: conduwuit-macos-arm64
if-no-files-found: error
docker: docker:
name: Docker publish name: Docker publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -531,10 +556,10 @@ jobs:
- name: Move OCI images into position - name: Move OCI images into position
run: | run: |
mv -v oci-image-x86_64-unknown-linux-musl/*.tar.gz oci-image-amd64.tar.gz mv -v oci-image-x86_64-linux-musl/*.tar.gz oci-image-amd64.tar.gz
mv -v oci-image-aarch64-unknown-linux-musl/*.tar.gz oci-image-arm64v8.tar.gz mv -v oci-image-aarch64-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-x86_64-linux-musl-debug/*.tar.gz oci-image-amd64-debug.tar.gz
mv -v oci-image-aarch64-unknown-linux-musl-debug/*.tar.gz oci-image-arm64v8-debug.tar.gz mv -v oci-image-aarch64-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 != '') }}
+17 -43
View File
@@ -24,8 +24,11 @@ env:
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps # Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
NIX_CONFIG: | NIX_CONFIG: |
show-trace = true show-trace = true
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org extra-substituters = extra-substituters = https://attic.kennel.juneis.dog/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg= extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
accept-flake-config = true
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # 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.
@@ -57,13 +60,7 @@ jobs:
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: actions/configure-pages@v5 uses: actions/configure-pages@v5
- uses: nixbuild/nix-quick-install-action@v28 - uses: nixbuild/nix-quick-install-action@master
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Restore and cache Nix store - name: Restore and cache Nix store
uses: nix-community/cache-nix-action@v5.1.0 uses: nix-community/cache-nix-action@v5.1.0
@@ -86,11 +83,20 @@ jobs:
# always save the cache # always save the cache
save-always: true save-always: true
- name: Enable Cachix binary cache
run: |
nix profile install nixpkgs#cachix
cachix use crane
cachix use nix-community
- name: Apply Nix binary cache configuration - name: Apply Nix binary cache configuration
run: | run: |
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/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/conduwuit https://attic.kennel.juneis.dog/conduit https://cache.lix.systems https://conduwuit.cachix.org https://aseipp-nix-cache.freetls.fastly.net
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg= extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
accept-flake-config = true
EOF EOF
- name: Use alternative Nix binary caches if specified - name: Use alternative Nix binary caches if specified
@@ -110,23 +116,7 @@ jobs:
- 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 bin/nix-build-and-cache ci
ATTEMPTS=3
SUCCESS=false
while (( ATTEMPTS-- > 0 ))
do
bin/nix-build-and-cache ci
if [[ $? == 0 ]]; then
SUCCESS=true
break
else
sleep 3
fi
done
if [[ $SUCCESS == "false" ]]; then
exit 1
fi
- name: Run lychee and markdownlint - name: Run lychee and markdownlint
run: | run: |
@@ -135,23 +125,7 @@ jobs:
- name: Build documentation (book) - name: Build documentation (book)
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 bin/nix-build-and-cache just .#book
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
+2 -2
View File
@@ -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.24.0 uses: aquasecurity/trivy-action@0.28.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.24.0 uses: aquasecurity/trivy-action@0.28.0
with: with:
scan-type: fs scan-type: fs
format: sarif format: sarif
+24 -12
View File
@@ -10,6 +10,13 @@ variables:
FF_USE_FASTZIP: true FF_USE_FASTZIP: true
# Print progress reports for cache and artifact transfers # Print progress reports for cache and artifact transfers
TRANSFER_METER_FREQUENCY: 5s TRANSFER_METER_FREQUENCY: 5s
NIX_CONFIG: |
show-trace = true
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
experimental-features = nix-command flakes
extra-experimental-features = nix-command flakes
accept-flake-config = true
# Avoid duplicate pipelines # Avoid duplicate pipelines
# See: https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines # See: https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines
@@ -23,6 +30,9 @@ workflow:
before_script: before_script:
# Enable nix-command and flakes # Enable nix-command and flakes
- if command -v nix > /dev/null; then echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf; fi - if command -v nix > /dev/null; then echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-experimental-features = nix-command flakes" >> /etc/nix/nix.conf; fi
# Accept flake config from "untrusted" users
- if command -v nix > /dev/null; then echo "accept-flake-config = true" >> /etc/nix/nix.conf; fi
# Add conduwuit binary cache # Add conduwuit binary cache
- if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduwuit" >> /etc/nix/nix.conf; fi - if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduwuit" >> /etc/nix/nix.conf; fi
@@ -47,6 +57,8 @@ before_script:
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix-community.cachix.org" >> /etc/nix/nix.conf; fi - if command -v nix > /dev/null; then echo "extra-substituters = https://nix-community.cachix.org" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" >> /etc/nix/nix.conf; fi - if command -v nix > /dev/null; then echo "extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-substituters = https://aseipp-nix-cache.freetls.fastly.net" >> /etc/nix/nix.conf; fi
# Install direnv and nix-direnv # Install direnv and nix-direnv
- if command -v nix > /dev/null; then nix-env -iA nixpkgs.direnv nixpkgs.nix-direnv; fi - if command -v nix > /dev/null; then nix-env -iA nixpkgs.direnv nixpkgs.nix-direnv; fi
@@ -58,7 +70,7 @@ before_script:
ci: ci:
stage: ci stage: ci
image: nixos/nix:2.24.4 image: nixos/nix:2.24.9
script: script:
# Cache CI dependencies # Cache CI dependencies
- ./bin/nix-build-and-cache ci - ./bin/nix-build-and-cache ci
@@ -83,31 +95,31 @@ ci:
artifacts: artifacts:
stage: artifacts stage: artifacts
image: nixos/nix:2.24.4 image: nixos/nix:2.24.9
script: script:
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl - ./bin/nix-build-and-cache just .#static-x86_64-linux-musl
- cp result/bin/conduit x86_64-unknown-linux-musl - cp result/bin/conduit x86_64-linux-musl
- mkdir -p target/release - mkdir -p target/release
- cp result/bin/conduit target/release - cp result/bin/conduit target/release
- direnv exec . cargo deb --no-build --no-strip - direnv exec . cargo deb --no-build --no-strip
- mv target/debian/*.deb x86_64-unknown-linux-musl.deb - mv target/debian/*.deb x86_64-linux-musl.deb
# Since the OCI image package is based on the binary package, this has the # Since the OCI image package is based on the binary package, this has the
# fun side effect of uploading the normal binary too. Conduit users who are # fun side effect of uploading the normal binary too. Conduit users who are
# deploying with Nix can leverage this fact by adding our binary cache to # deploying with Nix can leverage this fact by adding our binary cache to
# their systems. # their systems.
# #
# Note that although we have an `oci-image-x86_64-unknown-linux-musl` # Note that although we have an `oci-image-x86_64-linux-musl`
# output, we don't build it because it would be largely redundant to this # output, we don't build it because it would be largely redundant to this
# one since it's all containerized anyway. # one since it's all containerized anyway.
- ./bin/nix-build-and-cache just .#oci-image - ./bin/nix-build-and-cache just .#oci-image
- cp result oci-image-amd64.tar.gz - cp result oci-image-amd64.tar.gz
- ./bin/nix-build-and-cache just .#static-aarch64-unknown-linux-musl - ./bin/nix-build-and-cache just .#static-aarch64-linux-musl
- cp result/bin/conduit aarch64-unknown-linux-musl - cp result/bin/conduit aarch64-linux-musl
- ./bin/nix-build-and-cache just .#oci-image-aarch64-unknown-linux-musl - ./bin/nix-build-and-cache just .#oci-image-aarch64-linux-musl
- cp result oci-image-arm64v8.tar.gz - cp result oci-image-arm64v8.tar.gz
- ./bin/nix-build-and-cache just .#book - ./bin/nix-build-and-cache just .#book
@@ -115,9 +127,9 @@ artifacts:
- cp -r --dereference result public - cp -r --dereference result public
artifacts: artifacts:
paths: paths:
- x86_64-unknown-linux-musl - x86_64-linux-musl
- aarch64-unknown-linux-musl - aarch64-linux-musl
- x86_64-unknown-linux-musl.deb - x86_64-linux-musl.deb
- oci-image-amd64.tar.gz - oci-image-amd64.tar.gz
- oci-image-arm64v8.tar.gz - oci-image-arm64v8.tar.gz
- public - public
+6 -3
View File
@@ -1,7 +1,7 @@
# Contributing guide # Contributing guide
This page is for about contributing to conduwuit. The This page is for about contributing to conduwuit. The
[development](development.md) page may be of interest for you as well. [development](./development.md) page may be of interest for you as well.
If you would like to work on an [issue][issues] that is not assigned, preferably 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], ask in the Matrix room first at [#conduwuit:puppygock.gay][conduwuit-matrix],
@@ -67,7 +67,7 @@ failing from your changes, please review the logs (they are uploaded as
artifacts) and determine if they're intended or not. artifacts) and determine if they're intended or not.
If you'd like to run Complement locally using Nix, see the If you'd like to run Complement locally using Nix, see the
[testing](docs/development/testing.md) page. [testing](development/testing.md) page.
[Sytest][sytest] support will come soon. [Sytest][sytest] support will come soon.
@@ -128,7 +128,10 @@ Direct all PRs/MRs to the `main` branch.
By sending a pull request or patch, you are agreeing that your changes are By sending a pull request or patch, you are agreeing that your changes are
allowed to be licenced under the Apache-2.0 licence and all of your conduct is allowed to be licenced under the Apache-2.0 licence and all of your conduct is
in line with the Contributor's Covenant. in line with the Contributor's Covenant, and conduwuit's Code of Conduct.
Contribution by users who violate either of these code of conducts will not have
their contributions accepted.
[issues]: https://github.com/girlbossceo/conduwuit/issues [issues]: https://github.com/girlbossceo/conduwuit/issues
[conduwuit-matrix]: https://matrix.to/#/#conduwuit:puppygock.gay [conduwuit-matrix]: https://matrix.to/#/#conduwuit:puppygock.gay
Generated
+341 -390
View File
File diff suppressed because it is too large Load Diff
+28 -19
View File
@@ -19,8 +19,8 @@ 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.80.1" rust-version = "1.82.0"
version = "0.4.6" version = "0.4.7"
[workspace.metadata.crane] [workspace.metadata.crane]
name = "conduit" name = "conduit"
@@ -69,7 +69,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.7.1" version = "1.7.2"
[workspace.dependencies.http-body-util] [workspace.dependencies.http-body-util]
version = "0.1.1" version = "0.1.1"
@@ -101,22 +101,21 @@ features = ["typed-header", "tracing"]
[workspace.dependencies.axum-server] [workspace.dependencies.axum-server]
version = "0.7.1" version = "0.7.1"
default-features = false default-features = false
features = ["tls-rustls"]
# to listen on both HTTP and HTTPS if listening on TLS dierctly from conduwuit for complement or sytest # to listen on both HTTP and HTTPS if listening on TLS dierctly from conduwuit for complement or sytest
[workspace.dependencies.axum-server-dual-protocol] [workspace.dependencies.axum-server-dual-protocol]
version = "0.7" version = "0.7"
[workspace.dependencies.axum-client-ip] [workspace.dependencies.axum-client-ip]
version = "0.6.0" version = "0.6.1"
[workspace.dependencies.tower] [workspace.dependencies.tower]
version = "0.5.0" version = "0.5.1"
default-features = false default-features = false
features = ["util"] features = ["util"]
[workspace.dependencies.tower-http] [workspace.dependencies.tower-http]
version = "0.5.2" version = "0.6.0"
default-features = false default-features = false
features = [ features = [
"add-extension", "add-extension",
@@ -129,10 +128,10 @@ features = [
] ]
[workspace.dependencies.rustls] [workspace.dependencies.rustls]
version = "0.23.12" version = "0.23.13"
[workspace.dependencies.reqwest] [workspace.dependencies.reqwest]
version = "0.12.7" version = "0.12.8"
default-features = false default-features = false
features = [ features = [
"rustls-tls-native-roots", "rustls-tls-native-roots",
@@ -200,7 +199,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.15" version = "4.5.20"
default-features = false default-features = false
features = [ features = [
"std", "std",
@@ -248,7 +247,7 @@ features = ["alloc", "std"]
default-features = false default-features = false
[workspace.dependencies.hyper] [workspace.dependencies.hyper]
version = "1.4.1" version = "1.5.0"
default-features = false default-features = false
features = [ features = [
"server", "server",
@@ -257,7 +256,8 @@ features = [
] ]
[workspace.dependencies.hyper-util] [workspace.dependencies.hyper-util]
version = "0.1.6" # 0.1.9 causes DNS issues
version = "=0.1.8"
default-features = false default-features = false
features = [ features = [
"client", "client",
@@ -314,7 +314,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 = "d7ddcd036f81edb257ab9371f9cadd46444e8a90" rev = "9900d0676564883cfade556d6e8da2a2c9061efd"
features = [ features = [
"compat", "compat",
"rand", "rand",
@@ -329,18 +329,22 @@ features = [
"ring-compat", "ring-compat",
"identifiers-validation", "identifiers-validation",
"unstable-unspecified", "unstable-unspecified",
"unstable-msc2409",
"unstable-msc2448", "unstable-msc2448",
"unstable-msc2666", "unstable-msc2666",
"unstable-msc2867", "unstable-msc2867",
"unstable-msc2870", "unstable-msc2870",
"unstable-msc3026", "unstable-msc3026",
"unstable-msc3061", "unstable-msc3061",
"unstable-msc3245",
"unstable-msc3266", "unstable-msc3266",
"unstable-msc3381", # polls "unstable-msc3381", # polls
"unstable-msc3489", # beacon / live location "unstable-msc3489", # beacon / live location
"unstable-msc3575", "unstable-msc3575",
"unstable-msc4075",
"unstable-msc4121", "unstable-msc4121",
"unstable-msc4125", "unstable-msc4125",
"unstable-msc4186",
"unstable-extensible-events", "unstable-extensible-events",
] ]
@@ -403,17 +407,17 @@ version = "0.34.0"
# jemalloc usage # jemalloc usage
[workspace.dependencies.tikv-jemalloc-sys] [workspace.dependencies.tikv-jemalloc-sys]
git = "https://github.com/girlbossceo/jemallocator" git = "https://github.com/girlbossceo/jemallocator"
rev = "c32af15f3b440ae5e46c3404f78b19093bbd5294" rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
default-features = false default-features = false
features = ["unprefixed_malloc_on_supported_platforms"] features = ["unprefixed_malloc_on_supported_platforms"]
[workspace.dependencies.tikv-jemallocator] [workspace.dependencies.tikv-jemallocator]
git = "https://github.com/girlbossceo/jemallocator" git = "https://github.com/girlbossceo/jemallocator"
rev = "c32af15f3b440ae5e46c3404f78b19093bbd5294" rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
default-features = false default-features = false
features = ["unprefixed_malloc_on_supported_platforms"] features = ["unprefixed_malloc_on_supported_platforms"]
[workspace.dependencies.tikv-jemalloc-ctl] [workspace.dependencies.tikv-jemalloc-ctl]
git = "https://github.com/girlbossceo/jemallocator" git = "https://github.com/girlbossceo/jemallocator"
rev = "c32af15f3b440ae5e46c3404f78b19093bbd5294" rev = "d87938bfddc26377dd7fdf14bbcd345f3ab19442"
default-features = false default-features = false
features = ["use_std"] features = ["use_std"]
@@ -442,7 +446,7 @@ version = "0.4.3"
default-features = false default-features = false
[workspace.dependencies.termimad] [workspace.dependencies.termimad]
version = "0.30.0" version = "0.30.1"
default-features = false default-features = false
[workspace.dependencies.checked_ops] [workspace.dependencies.checked_ops]
@@ -457,7 +461,7 @@ features = ["full", "extra-traits"]
version = "1.0.36" version = "1.0.36"
[workspace.dependencies.proc-macro2] [workspace.dependencies.proc-macro2]
version = "1.0.86" version = "1.0.89"
# #
@@ -608,7 +612,7 @@ inherits = "release"
# and can be raised if build times are tolerable. # and can be raised if build times are tolerable.
[profile.dev] [profile.dev]
debug = 1 debug = "full"
opt-level = 0 opt-level = 0
panic = "unwind" panic = "unwind"
debug-assertions = true debug-assertions = true
@@ -715,12 +719,16 @@ opt-level = 'z'
# primarily used for CI # primarily used for CI
[profile.test] [profile.test]
inherits = "dev" inherits = "dev"
strip = false
opt-level = 0
codegen-units = 16 codegen-units = 16
incremental = false incremental = false
[profile.test.package.'*'] [profile.test.package.'*']
inherits = "dev" inherits = "dev"
debug = 0 debug = 0
strip = false
opt-level = 0
codegen-units = 16 codegen-units = 16
incremental = false incremental = false
@@ -808,6 +816,7 @@ significant_drop_tightening = { level = "allow", priority = 1 } # TODO
pedantic = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 }
## some sadness ## some sadness
too_long_first_doc_paragraph = { level = "allow", priority = 1 }
doc_markdown = { level = "allow", priority = 1 } doc_markdown = { level = "allow", priority = 1 }
enum_glob_use = { level = "allow", priority = 1 } enum_glob_use = { level = "allow", priority = 1 }
if_not_else = { level = "allow", priority = 1 } if_not_else = { level = "allow", priority = 1 }
+31 -8
View File
@@ -9,7 +9,7 @@ Artifacts](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml/bad
<!-- ANCHOR_END: catchphrase --> <!-- ANCHOR_END: catchphrase -->
Visit the [Conduwuit documentation](https://conduwuit.puppyirl.gay/) for more Visit the [conduwuit documentation](https://conduwuit.puppyirl.gay/) for more
information. information.
<!-- ANCHOR: body --> <!-- ANCHOR: body -->
@@ -23,9 +23,9 @@ to communicate with users outside of Matrix, like a community on Discord.
#### What is the goal? #### What is the goal?
An efficient Matrix homeserver that's easy to set up and just works. You can A high-performance and efficient Matrix homeserver that's easy to set up and
install it on a mini-computer like the Raspberry Pi to host Matrix for your just works. You can install it on a mini-computer like the Raspberry Pi to
family, friends or company. host Matrix for your family, friends or company.
#### Can I try it out? #### Can I try it out?
@@ -42,9 +42,28 @@ transfem.dev is also listed at
#### What is the current status? #### What is the current status?
conduwuit is a hard fork of Conduit which is in beta, meaning you can join and conduwuit is technically a hard fork of Conduit, which is in Beta. The Beta status
participate in most Matrix rooms, but not all features are supported and you initially was inherited from Conduit, however overtime this Beta status is rapidly
might run into bugs from time to time. becoming less and less relevant as our codebase significantly diverges more and more.
conduwuit is quite stable and very usable as a daily driver and for a low-medium
sized homeserver. There is still a lot of more work to be done, but it is in a far
better place than the project was in early 2024.
#### How is conduwuit funded? Is conduwuit sustainable?
conduwuit has no external funding. This is made possible purely in my freetime with
contributors, also in their free time, and only by user-curated donations.
conduwuit has existed since around November 2023, but [only became more publicly known
in March/April 2024](https://matrix.org/blog/2024/04/26/this-week-in-matrix-2024-04-26/#conduwuit-website)
and we have no plans in stopping or slowing down any time soon!
#### Can I migrate or switch from Conduit?
conduwuit is a complete drop-in replacement for Conduit. As long as you are using RocksDB,
the only "migration" you need to do is replace the binary or container image. There
is no harm or additional steps required for using conduwuit.
<!-- ANCHOR_END: body --> <!-- ANCHOR_END: body -->
@@ -59,8 +78,12 @@ If you run into any question, feel free to
#### Donate #### Donate
conduwuit development is purely made possible by myself and contributors. I do
not get paid to work on this, and I work on it in my free time. Donations are
heavily appreciated! 💜🥺
- Liberapay: <https://liberapay.com/girlbossceo> - Liberapay: <https://liberapay.com/girlbossceo>
- Ko-fi: <https://ko-fi.com/puppygock> - Ko-fi (note they take a fee): <https://ko-fi.com/puppygock>
- GitHub Sponsors: <https://github.com/sponsors/girlbossceo> - GitHub Sponsors: <https://github.com/sponsors/girlbossceo>
#### Logo #### Logo
+1 -1
View File
@@ -15,7 +15,7 @@ DevicePolicy=closed
LockPersonality=yes LockPersonality=yes
MemoryDenyWriteExecute=yes MemoryDenyWriteExecute=yes
NoNewPrivileges=yes NoNewPrivileges=yes
ProcSubset=pid #ProcSubset=pid
ProtectClock=yes ProtectClock=yes
ProtectControlGroups=yes ProtectControlGroups=yes
ProtectHome=yes ProtectHome=yes
+2 -2
View File
@@ -15,7 +15,7 @@ LOG_FILE="$2"
# A `.jsonl` file to write test results to # A `.jsonl` file to write test results to
RESULTS_FILE="$3" RESULTS_FILE="$3"
OCI_IMAGE="complement-conduit:main" OCI_IMAGE="complement-conduwuit:main"
# Complement tests that are skipped due to flakiness/reliability issues # Complement tests that are skipped due to flakiness/reliability issues
SKIPPED_COMPLEMENT_TESTS='-skip=TestClientSpacesSummary.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestJumpToDateEndpoint.*' SKIPPED_COMPLEMENT_TESTS='-skip=TestClientSpacesSummary.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestJumpToDateEndpoint.*'
@@ -34,7 +34,7 @@ toplevel="$(git rev-parse --show-toplevel)"
pushd "$toplevel" > /dev/null pushd "$toplevel" > /dev/null
bin/nix-build-and-cache just .#static-complement bin/nix-build-and-cache just .#linux-complement
docker load < result docker load < result
popd > /dev/null popd > /dev/null
+13 -7
View File
@@ -26,7 +26,12 @@ just() {
"$ATTIC_TOKEN" "$ATTIC_TOKEN"
# Find all output paths of the installables and their build dependencies # Find all output paths of the installables and their build dependencies
readarray -t derivations < <(nix path-info --derivation "$@") #readarray -t derivations < <(nix path-info --derivation "$@")
derivations=()
while IFS=$'\n' read derivation; do
derivations+=("$derivation")
done < <(nix path-info --derivation "$@")
cache=() cache=()
for derivation in "${derivations[@]}"; do for derivation in "${derivations[@]}"; do
cache+=( cache+=(
@@ -34,6 +39,9 @@ just() {
) )
done done
withattic() {
nix shell --inputs-from "$toplevel" attic --command xargs attic push "$@" <<< "${cache[*]}"
}
# Upload them to Attic (conduit store) # Upload them to Attic (conduit store)
# #
# Use `xargs` and a here-string because something would probably explode if # Use `xargs` and a here-string because something would probably explode if
@@ -41,8 +49,7 @@ just() {
# store paths include a newline in them. # store paths include a newline in them.
( (
IFS=$'\n' IFS=$'\n'
nix shell --inputs-from "$toplevel" attic -c xargs \ withattic conduit || withattic conduit || withattic conduit || true
attic push conduit <<< "${cache[*]}"
) )
# main "conduwuit" store # main "conduwuit" store
@@ -59,8 +66,7 @@ just() {
# store paths include a newline in them. # store paths include a newline in them.
( (
IFS=$'\n' IFS=$'\n'
nix shell --inputs-from "$toplevel" attic -c xargs \ withattic conduwuit || withattic conduwuit || withattic conduwuit || true
attic push conduwuit <<< "${cache[*]}"
# push to cachix if available # push to cachix if available
if [ "$CACHIX_AUTH_TOKEN" ]; then if [ "$CACHIX_AUTH_TOKEN" ]; then
@@ -76,8 +82,8 @@ ci() {
--inputs-from "$toplevel" --inputs-from "$toplevel"
# Keep sorted # Keep sorted
"$toplevel#devShells.x86_64-linux.default" #"$toplevel#devShells.x86_64-linux.default"
"$toplevel#devShells.x86_64-linux.all-features" #"$toplevel#devShells.x86_64-linux.all-features"
attic#default attic#default
cachix#default cachix#default
nixpkgs#direnv nixpkgs#direnv
+26 -1
View File
@@ -351,6 +351,14 @@ allow_profile_lookup_federation_requests = true
# defaults to true # defaults to true
#admin_escape_commands = true #admin_escape_commands = true
# Controls whether admin room notices like account registrations, password changes, account deactivations,
# room directory publications, etc will be sent to the admin room.
#
# Update notices and normal admin command responses will still be sent.
#
# defaults to true
#admin_room_notices = true
### Misc ### Misc
@@ -363,6 +371,11 @@ allow_profile_lookup_federation_requests = true
# Defaults to "info" # Defaults to "info"
#log = "info" #log = "info"
# controls whether logs will be outputted with ANSI colours
#
# defaults to true
#log_colors = true
# controls whether encrypted rooms and events are allowed (default true) # controls whether encrypted rooms and events are allowed (default true)
#allow_encryption = false #allow_encryption = false
@@ -481,6 +494,7 @@ allow_profile_lookup_federation_requests = true
### 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.
# By default, the caches scale automatically with cpu-core-count.
# 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` # This was previously called `conduit_cache_capacity_modifier`
@@ -490,7 +504,7 @@ allow_profile_lookup_federation_requests = true
# Set this to any float value in megabytes for conduwuit to tell the database engine that this much memory is available for database-related caches. # 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.
# Defaults to 256.0 # Defaults to 128.0 + (64.0 * CPU core count).
#db_cache_capacity_mb = 256.0 #db_cache_capacity_mb = 256.0
@@ -844,9 +858,20 @@ allow_profile_lookup_federation_requests = true
# vector list of TURN URIs/servers to use # vector list of TURN URIs/servers to use
# #
# replace "example.turn.uri" with your TURN domain, such as the coturn "realm".
# if using TURN over TLS, replace "turn:" with "turns:"
#
# No default # No default
#turn_uris = ["turn:example.turn.uri?transport=udp", "turn:example.turn.uri?transport=tcp"] #turn_uris = ["turn:example.turn.uri?transport=udp", "turn:example.turn.uri?transport=tcp"]
# TURN secret to use that's read from the file path specified
#
# this takes priority over "turn_secret" first, and falls back to "turn_secret" if invalid or
# failed to open.
#
# no default
#turn_secret_file = "/path/to/secret.txt"
# TURN secret to use for generating the HMAC-SHA1 hash apart of username and password generation # TURN secret to use for generating the HMAC-SHA1 hash apart of username and password generation
# #
# this is more secure, but if needed you can use traditional username/password below. # this is more secure, but if needed you can use traditional username/password below.
+1 -1
View File
@@ -22,7 +22,7 @@ DevicePolicy=closed
LockPersonality=yes LockPersonality=yes
MemoryDenyWriteExecute=yes MemoryDenyWriteExecute=yes
NoNewPrivileges=yes NoNewPrivileges=yes
ProcSubset=pid #ProcSubset=pid
ProtectClock=yes ProtectClock=yes
ProtectControlGroups=yes ProtectControlGroups=yes
ProtectHome=yes ProtectHome=yes
+1 -1
View File
@@ -27,7 +27,7 @@ malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
[dependencies.rust-rocksdb] [dependencies.rust-rocksdb]
git = "https://github.com/girlbossceo/rust-rocksdb-zaidoon1" git = "https://github.com/girlbossceo/rust-rocksdb-zaidoon1"
rev = "5383ca8173299066b516406e3a2cf945ead891cb" rev = "c1e5523eae095a893deaf9056128c7dbc2d5fd73"
#branch = "master" #branch = "master"
default-features = false default-features = false
+1
View File
@@ -0,0 +1 @@
docs/development.md
-1
View File
@@ -1 +0,0 @@
{{#include ../CONTRIBUTING.md}}
+1
View File
@@ -0,0 +1 @@
../CONTRIBUTING.md
+54 -8
View File
@@ -13,18 +13,38 @@ what you need.
Prebuilt fully static musl binaries can be downloaded from the latest tagged Prebuilt fully static musl binaries can be downloaded from the latest tagged
release [here](https://github.com/girlbossceo/conduwuit/releases/latest) or release [here](https://github.com/girlbossceo/conduwuit/releases/latest) or
`main` CI branch workflow artifact output. These also include Debian packages. `main` CI branch workflow artifact output. These also include Debian/Ubuntu packages.
These binaries have jemalloc and io_uring statically linked and included with These binaries have jemalloc and io_uring statically linked and included with
them. them, so no additional dynamic dependencies need to be installed.
Alternatively, you may compile the binary yourself. We recommend using Alternatively, you may compile the binary yourself. We recommend using
[Lix](https://lix.systems) to build conduwuit as this has the most guaranteed Nix (or [Lix](https://lix.systems)) to build conduwuit as this has the most guaranteed
reproducibiltiy and easiest to get a build environment and output going. reproducibiltiy and easiest to get a build environment and output going. This also
allows easy cross-compilation.
You can run the `nix build -L .#static-x86_64-linux-musl-all-features` or
`nix build -L .#static-aarch64-linux-musl-all-features` commands based
on architecture to cross-compile the necessary static binary located at
`result/bin/conduit`. This is reproducible with the static binaries produced in our CI.
Otherwise, follow standard Rust project build guides (installing git and cloning Otherwise, follow standard Rust project build guides (installing git and cloning
the repo, getting the Rust toolchain via rustup, installing LLVM toolchain + the repo, getting the Rust toolchain via rustup, installing LLVM toolchain +
libclang for RocksDB, installing liburing for io_uring and RocksDB, etc). libclang for RocksDB, installing liburing for io_uring and RocksDB, etc).
## Migrating from Conduit
As mentioned in the README, there is little to no steps needed to migrate
from Conduit. As long as you are using the RocksDB database backend, just
replace the binary / container image / etc.
**Note**: If you are relying on Conduit's "automatic delegation" feature,
this will **NOT** work on conduwuit and you must configure delegation manually.
This is not a mistake and no support for this feature will be added.
See the `[global.well_known]` config section, or configure your web server
appropriately to send the delegation responses.
## Adding a conduwuit user ## Adding a conduwuit user
While conduwuit can run as any user it is better to use dedicated users for While conduwuit can run as any user it is better to use dedicated users for
@@ -58,6 +78,8 @@ The systemd unit for conduwuit can be found
[here](../configuration/examples.md#example-systemd-unit-file). You may need to [here](../configuration/examples.md#example-systemd-unit-file). You may need to
change the `ExecStart=` path to where you placed the conduwuit binary. change the `ExecStart=` path to where you placed the conduwuit binary.
On systems where rsyslog is used alongside journald (i.e. Red Hat-based distros and OpenSUSE), put `$EscapeControlCharactersOnReceive off` inside `/etc/rsyslog.conf` to allow color in logs.
## Creating the conduwuit configuration file ## Creating the conduwuit configuration file
Now we need to create the conduwuit's config file in Now we need to create the conduwuit's config file in
@@ -74,26 +96,47 @@ 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: read the config. To do that you can run this:
```bash ```bash
sudo chown -R root:root /etc/conduwuit sudo chmod -R 755 /etc/conduwuit sudo chown -R root:root /etc/conduwuit
sudo chmod -R 755 /etc/conduwuit
``` ```
If you use the default database path you also need to run this: If you use the default database path you also need to run this:
```bash ```bash
sudo mkdir -p /var/lib/conduwuit/ sudo chown -R conduwuit:conduwuit /var/lib/conduwuit/ sudo mkdir -p /var/lib/conduwuit/
sudo chown -R conduwuit:conduwuit /var/lib/conduwuit/
sudo chmod 700 /var/lib/conduwuit/ sudo chmod 700 /var/lib/conduwuit/
``` ```
## Setting up the Reverse Proxy ## Setting up the Reverse Proxy
Refer to the documentation or various guides online of your chosen reverse proxy Refer to the documentation or various guides online of your chosen reverse proxy
software. A [Caddy](https://caddyserver.com/) example will be provided as this software. There are many examples of basic Apache/Nginx reverse proxy setups
out there.
A [Caddy](https://caddyserver.com/) example will be provided as this
is the recommended reverse proxy for new users and is very trivial to use is the recommended reverse proxy for new users and is very trivial to use
(handles TLS, reverse proxy headers, etc transparently with proper defaults). (handles TLS, reverse proxy headers, etc transparently with proper defaults).
Lighttpd is not supported as it seems to mess with the `X-Matrix` Authorization Lighttpd is not supported as it seems to mess with the `X-Matrix` Authorization
header, making federation non-functional. If using Apache, you need to use header, making federation non-functional. If using Apache, you need to use
`nocanon` to prevent this. `nocanon` in your `ProxyPass` directive to prevent this (note that Apache
isn't very good as a general reverse proxy).
Nginx users may need to set `proxy_buffering off;` if there are issues with
uploading media like images.
You will need to reverse proxy everything under following routes:
- `/_matrix/` - core Matrix C-S and S-S APIs
- `/_conduwuit/` - ad-hoc conduwuit routes such as `/local_user_count` and
`/server_version`
You can optionally reverse proxy the following individual routes:
- `/.well-known/matrix/client` and `/.well-known/matrix/server` if using
conduwuit to perform delegation
- `/.well-known/matrix/support` if using conduwuit to send the homeserver admin
contact and support page (formerly known as MSC1929)
- `/` if you would like to see `hewwo from conduwuit woof!` at the root
### Caddy ### Caddy
@@ -142,6 +185,9 @@ curl https://your.server.name/_conduwuit/server_version
# If using port 8448 # If using port 8448
curl https://your.server.name:8448/_conduwuit/server_version curl https://your.server.name:8448/_conduwuit/server_version
# If federation is enabled
curl https://your.server.name:8448/_matrix/federation/v1/version
``` ```
- To check if your server can talk with other homeservers, you can use the - To check if your server can talk with other homeservers, you can use the
+47 -11
View File
@@ -1,11 +1,15 @@
# conduwuit for NixOS # conduwuit for NixOS
conduwuit can be acquired by [Lix][lix] from various places: conduwuit can be acquired by Nix (or [Lix][lix]) from various places:
* The `flake.nix` at the root of the repo * The `flake.nix` at the root of the repo
* The `default.nix` at the root of the repo * The `default.nix` at the root of the repo
* From conduwuit's binary cache * From conduwuit's binary cache
A community maintained NixOS package is available at [`conduwuit`](https://search.nixos.org/packages?channel=unstable&show=conduwuit&from=0&size=50&sort=relevance&type=packages&query=conduwuit)
### Binary cache
A binary cache for conduwuit that the CI/CD publishes to is available at the 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):
@@ -17,25 +21,57 @@ https://attic.kennel.juneis.dog/conduwuit
conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=
``` ```
The binary caches have been recreated recently due to attic issues. The old The binary caches were recreated some months ago due to attic issues. The old public
public keys were: keys were:
``` ```
conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg= conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw= conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
``` ```
If specifying a URL in your flake, please use the GitHub remote:
`github:girlbossceo/conduwuit`
The `flake.nix` and `default.nix` do not (currently) provide a NixOS module, so If specifying a Git remote URL in your flake, you can use any remotes that
(for now) [`services.matrix-conduit`][module] from Nixpkgs should be used to are specified on the README (the mirrors), such as the GitHub: `github:girlbossceo/conduwuit`
configure conduwuit.
If you want to run the latest code, you should get conduwuit from the ### NixOS module
`flake.nix` or `default.nix` and set
[`services.matrix-conduit.package`][package] appropriately. The `flake.nix` and `default.nix` do not currently provide a NixOS module (contributions
welcome!), so [`services.matrix-conduit`][module] from Nixpkgs can be used to configure
conduwuit.
If you want to run the latest code, you should get conduwuit from the `flake.nix`
or `default.nix` and set [`services.matrix-conduit.package`][package]
appropriately to use conduwuit instead of Conduit.
### UNIX sockets
Due to the lack of a conduwuit NixOS module, when using the `services.matrix-conduit` module
it is not possible to use UNIX sockets. This is because the UNIX socket option does not exist
in Conduit, and their module forces listening on `[::1]:6167` by default if unspecified.
Additionally, the [`matrix-conduit` systemd unit][systemd-unit] in the module does not allow
the `AF_UNIX` socket address family in their systemd unit's `RestrictAddressFamilies=` which
disallows the namespace from accessing or creating UNIX sockets.
There is no known workaround these. A conduwuit NixOS configuration module must be developed and
published by the community.
### jemalloc and hardened profile
conduwuit uses jemalloc by default. This may interfere with the [`hardened.nix` profile][hardened.nix]
due to them using `scudo` by default. You must either disable/hide `scudo` from conduwuit, or
disable jemalloc like so:
```nix
let
conduwuit = pkgs.unstable.conduwuit.override {
enableJemalloc = false;
};
in
```
[lix]: https://lix.systems/ [lix]: https://lix.systems/
[module]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit [module]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit
[package]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit.package [package]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit.package
[hardened.nix]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/profiles/hardened.nix#L22
[systemd-unit]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/matrix/conduit.nix#L132
+75 -3
View File
@@ -1,10 +1,74 @@
# Development # Development
Information about developing the project. If you are only interested in using Information about developing the project. If you are only interested in using
it, you can safely ignore this section. If you plan on contributing, see the it, you can safely ignore this page. If you plan on contributing, see the
[contributor's guide](contributing.md). [contributor's guide](./contributing.md).
## List of forked dependencies During conduwuit development, we have had to fork ## conduwuit project layout
conduwuit uses a collection of sub-crates, packages, or workspace members
that indicate what each general area of code is for. All of the workspace
members are under `src/`. The workspace definition is at the top level / root
`Cargo.toml`.
The crate names are generally self-explanatory:
- `admin` is the admin room
- `api` is the HTTP API, Matrix C-S and S-S endpoints, etc
- `core` is core conduwuit functionality like config loading, error definitions,
global utilities, logging infrastructure, etc
- `database` is RocksDB methods, helpers, RocksDB config, and general database definitions,
utilities, or functions
- `macros` are conduwuit Rust [macros][macros] like general helper macros, logging
and error handling macros, and [syn][syn] and [procedural macros][proc-macro]
used for admin room commands and others
- `main` is the "primary" sub-crate. This is where the `main()` function lives,
tokio worker and async initialisation, Sentry initialisation, [clap][clap] init,
and signal handling. If you are adding new [Rust features][features], they *must*
go here.
- `router` is the webserver and request handling bits, using axum, tower, tower-http,
hyper, etc, and the [global server state][state] to access `services`.
- `service` is the high-level database definitions and functions for data,
outbound/sending code, and other business logic such as media fetching.
It is highly unlikely you will ever need to add a new workspace member, but
if you truly find yourself needing to, we recommend reaching out to us in
the Matrix room for discussions about it beforehand.
The primary inspiration for this design was apart of hot reloadable development,
to support "conduwuit as a library" where specific parts can simply be swapped out.
There is evidence Conduit wanted to go this route too as `axum` is technically an
optional feature in Conduit, and can be compiled without the binary or axum library
for handling inbound web requests; but it was never completed or worked.
See the Rust documentation on [Workspaces][workspaces] for general questions
and information on Cargo workspaces.
## Adding compile-time [features][features]
If you'd like to add a compile-time feature, you must first define it in
the `main` workspace crate located in `src/main/Cargo.toml`. The feature must
enable a feature in the other workspace crate(s) you intend to use it in. Then
the said workspace crate(s) must define the feature there in its `Cargo.toml`.
So, if this is adding a feature to the API such as `woof`, you define the feature
in the `api` crate's `Cargo.toml` as `woof = []`. The feature definition in `main`'s
`Cargo.toml` will be `woof = ["conduit-api/woof"]`.
The rationale for this is due to Rust / Cargo not supporting
["workspace level features"][9], we must make a choice of; either scattering
features all over the workspace crates, making it difficult for anyone to add
or remove default features; or define all the features in one central workspace
crate that propagate down/up to the other workspace crates. It is a Cargo pitfall,
and we'd like to see better developer UX in Rust's Workspaces.
Additionally, the definition of one single place makes "feature collection" in our
Nix flake a million times easier instead of collecting and deduping them all from
searching in all the workspace crates' `Cargo.toml`s. Though we wouldn't need to
do this if Rust supported workspace-level features to begin with.
## List of forked dependencies
During conduwuit development, we have had to fork
some dependencies to support our use-cases in some areas. This ranges from some dependencies to support our use-cases in some areas. This ranges from
things said upstream project won't accept for any reason, faster-paced things said upstream project won't accept for any reason, faster-paced
development (unresponsive or slow upstream), conduwuit-specific usecases, or development (unresponsive or slow upstream), conduwuit-specific usecases, or
@@ -53,3 +117,11 @@ RUSTFLAGS="--cfg tokio_unstable" cargo build \
[6]: https://github.com/tokio-rs/tracing/ [6]: https://github.com/tokio-rs/tracing/
[7]: https://docs.rs/tokio-console/latest/tokio_console/ [7]: https://docs.rs/tokio-console/latest/tokio_console/
[8]: https://github.com/zaidoon1/ [8]: https://github.com/zaidoon1/
[9]: https://github.com/rust-lang/cargo/issues/12162
[workspaces]: https://doc.rust-lang.org/cargo/reference/workspaces.html
[macros]: https://doc.rust-lang.org/book/ch19-06-macros.html
[syn]: https://docs.rs/syn/latest/syn/
[proc-macro]: https://doc.rust-lang.org/reference/procedural-macros.html
[clap]: https://docs.rs/clap/latest/clap/
[features]: https://doc.rust-lang.org/cargo/reference/features.html
[state]: https://docs.rs/axum/latest/axum/extract/struct.State.html
+2 -3
View File
@@ -5,8 +5,8 @@
Have a look at [Complement's repository][complement] for an explanation of what Have a look at [Complement's repository][complement] for an explanation of what
it is. it is.
To test against Complement, with [Lix][lix] and direnv installed and set up, you To test against Complement, with Nix (or [Lix](https://lix.systems) and direnv installed
can: and set up, you can:
* Run `./bin/complement "$COMPLEMENT_SRC" ./path/to/logs.jsonl * Run `./bin/complement "$COMPLEMENT_SRC" ./path/to/logs.jsonl
./path/to/results.jsonl` to build a Complement image, run the tests, and output ./path/to/results.jsonl` to build a Complement image, run the tests, and output
@@ -18,6 +18,5 @@ Complement OCI image outputted to `result` (it's a `.tar.gz` file)
output from the commit/revision you want to test (e.g. from main) output from the commit/revision you want to test (e.g. from main)
[here][ci-workflows] [here][ci-workflows]
[lix]: https://lix.systems/
[ci-workflows]: https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml?query=event%3Apush+is%3Asuccess+actor%3Agirlbossceo [ci-workflows]: https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml?query=event%3Apush+is%3Asuccess+actor%3Agirlbossceo
[complement]: https://github.com/matrix-org/complement [complement]: https://github.com/matrix-org/complement
+16
View File
@@ -21,6 +21,22 @@ These same values need to be set in conduwuit. See the [example
config](configuration/examples.md) in the TURN section for configuring these and config](configuration/examples.md) in the TURN section for configuring these and
restart conduwuit after. restart conduwuit after.
`turn_secret` or a path to `turn_secret_file` must have a value of your
coturn `static-auth-secret`, or use `turn_username` and `turn_password`
if using legacy username:password TURN authentication (not preferred).
`turn_uris` must be the list of TURN URIs you would like to send to the client.
Typically you will just replace the example domain `example.turn.uri` with the
`realm` you set from the example config.
If you are using TURN over TLS, you can replace `turn:` with `turns:` in the
`turn_uris` config option to instruct clients to attempt to connect to
TURN over TLS. This is highly recommended.
If you need unauthenticated access to the TURN URIs, or some clients may be
having trouble, you can enable `turn_guest_access` in conduwuit which disables
authentication for the TURN URI endpoint `/_matrix/client/v3/voip/turnServer`
### Run ### Run
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
+1 -1
View File
@@ -152,7 +152,7 @@ cargo clippy \
[[task]] [[task]]
name = "lychee" name = "lychee"
group = "lints" group = "lints"
script = "lychee --verbose --offline docs *.md --exclude development.md" script = "lychee --verbose --offline docs *.md --exclude development.md --exclude contributing.md --exclude testing.md"
[[task]] [[task]]
name = "markdownlint" name = "markdownlint"
Generated
+325 -81
View File
@@ -4,16 +4,16 @@
"inputs": { "inputs": {
"crane": "crane", "crane": "crane",
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-utils": "flake-utils", "flake-parts": "flake-parts",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nixpkgs-stable": "nixpkgs-stable" "nixpkgs-stable": "nixpkgs-stable"
}, },
"locked": { "locked": {
"lastModified": 1724226964, "lastModified": 1729116596,
"narHash": "sha256-cltFh4su2vcFidxKp7LuEgX3ZGLfPy0DCdrQZ/QTe68=", "narHash": "sha256-NnLMLIXGZtAscUF4dCShksuQ1nOGF6Y2dEeyj0rBbUg=",
"owner": "zhaofengli", "owner": "zhaofengli",
"repo": "attic", "repo": "attic",
"rev": "6d9aeaef0a067d664cb11bb7704f7ec373d47fb2", "rev": "2b05b7d986cf6009b1c1ef7daa4961cd1a658782",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -28,14 +28,14 @@
"devenv": "devenv", "devenv": "devenv",
"flake-compat": "flake-compat_3", "flake-compat": "flake-compat_3",
"git-hooks": "git-hooks", "git-hooks": "git-hooks",
"nixpkgs": "nixpkgs_3" "nixpkgs": "nixpkgs_4"
}, },
"locked": { "locked": {
"lastModified": 1724232775, "lastModified": 1728672398,
"narHash": "sha256-6u2DycIEgrgNYlLxyGqdFVmBNiKIitnQKJ1pbRP5oko=", "narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
"owner": "cachix", "owner": "cachix",
"repo": "cachix", "repo": "cachix",
"rev": "03b6cb3f953097bff378fb8b9ea094bd091a4ec7", "rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -53,12 +53,51 @@
"devenv", "devenv",
"flake-compat" "flake-compat"
], ],
"git-hooks": [
"cachix",
"devenv",
"pre-commit-hooks"
],
"nixpkgs": [ "nixpkgs": [
"cachix", "cachix",
"devenv", "devenv",
"nixpkgs" "nixpkgs"
]
},
"locked": {
"lastModified": 1726520618,
"narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=",
"owner": "cachix",
"repo": "cachix",
"rev": "695525f9086542dfb09fde0871dbf4174abbf634",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "cachix",
"type": "github"
}
},
"cachix_3": {
"inputs": {
"devenv": "devenv_3",
"flake-compat": [
"cachix",
"devenv",
"cachix",
"devenv",
"flake-compat"
],
"nixpkgs": [
"cachix",
"devenv",
"cachix",
"devenv",
"nixpkgs"
], ],
"pre-commit-hooks": [ "pre-commit-hooks": [
"cachix",
"devenv",
"cachix", "cachix",
"devenv", "devenv",
"pre-commit-hooks" "pre-commit-hooks"
@@ -81,11 +120,11 @@
"complement": { "complement": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1722323564, "lastModified": 1724347376,
"narHash": "sha256-6w6/N8walz4Ayc9zu7iySqJRmGFukhkaICLn4dweAcA=", "narHash": "sha256-y0e/ULDJ92IhNQZsS/06g0s+AYZ82aJfrIO9qEse94c=",
"owner": "matrix-org", "owner": "matrix-org",
"repo": "complement", "repo": "complement",
"rev": "6e4426a9e63233f9821a4d2382bfed145244183f", "rev": "39733c1b2f8314800776748cc7164f9a34650686",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -117,17 +156,12 @@
} }
}, },
"crane_2": { "crane_2": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": { "locked": {
"lastModified": 1724006180, "lastModified": 1729741221,
"narHash": "sha256-PVxPj0Ga2fMYMtcT9ARCthF+4U71YkOT7ZjgD/vf1Aw=", "narHash": "sha256-8AHZZXs1lFkERfBY0C8cZGElSo33D/et7NKEpLRmvzo=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "7ce92819802bc583b7e82ebc08013a530f22209f", "rev": "f235b656ee5b2bfd6d94c3bfd67896a575d4a6ed",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -144,7 +178,7 @@
"cachix", "cachix",
"flake-compat" "flake-compat"
], ],
"nix": "nix_2", "nix": "nix_3",
"nixpkgs": [ "nixpkgs": [
"cachix", "cachix",
"nixpkgs" "nixpkgs"
@@ -154,6 +188,43 @@
"git-hooks" "git-hooks"
] ]
}, },
"locked": {
"lastModified": 1727963652,
"narHash": "sha256-os0EDjn7QVXL6RtHNb9TrZLXVm2Tc5/nZKk3KpbTzd8=",
"owner": "cachix",
"repo": "devenv",
"rev": "cb0052e25dbcc8267b3026160dc73cddaac7d5fd",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"devenv_2": {
"inputs": {
"cachix": "cachix_3",
"flake-compat": [
"cachix",
"devenv",
"cachix",
"flake-compat"
],
"nix": "nix_2",
"nixpkgs": [
"cachix",
"devenv",
"cachix",
"nixpkgs"
],
"pre-commit-hooks": [
"cachix",
"devenv",
"cachix",
"git-hooks"
]
},
"locked": { "locked": {
"lastModified": 1723156315, "lastModified": 1723156315,
"narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=", "narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=",
@@ -168,9 +239,11 @@
"type": "github" "type": "github"
} }
}, },
"devenv_2": { "devenv_3": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
"cachix",
"devenv",
"cachix", "cachix",
"devenv", "devenv",
"cachix", "cachix",
@@ -180,6 +253,8 @@
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_2",
"poetry2nix": "poetry2nix", "poetry2nix": "poetry2nix",
"pre-commit-hooks": [ "pre-commit-hooks": [
"cachix",
"devenv",
"cachix", "cachix",
"devenv", "devenv",
"cachix", "cachix",
@@ -209,11 +284,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1724221791, "lastModified": 1729751566,
"narHash": "sha256-mKX67QPnUybOopVph/LhOV1G/H4EvPxDIfSmbufrVdA=", "narHash": "sha256-99u/hrgBdi8bxSXZc9ZbNkR5EL1htrkbd3lsbKzS60g=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "e88b38a5a3834e039d413a88f8150a75ef6453ef", "rev": "f32a2d484091a6dc98220b1f4a2c2d60b7c97c64",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -288,27 +363,53 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils": { "flake-parts": {
"inputs": { "inputs": {
"systems": "systems" "nixpkgs-lib": [
"attic",
"nixpkgs"
]
}, },
"locked": { "locked": {
"lastModified": 1710146030, "lastModified": 1722555600,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "numtide", "owner": "hercules-ci",
"repo": "flake-utils", "repo": "flake-parts",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "numtide", "owner": "hercules-ci",
"repo": "flake-utils", "repo": "flake-parts",
"type": "github" "type": "github"
} }
}, },
"flake-utils_2": { "flake-parts_2": {
"inputs": { "inputs": {
"systems": "systems_2" "nixpkgs-lib": [
"cachix",
"devenv",
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1689068808, "lastModified": 1689068808,
@@ -324,9 +425,24 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils_2": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": { "flake-utils_3": {
"inputs": { "inputs": {
"systems": "systems_3" "systems": "systems_2"
}, },
"locked": { "locked": {
"lastModified": 1710146030, "lastModified": 1710146030,
@@ -357,11 +473,11 @@
"nixpkgs-stable": "nixpkgs-stable_2" "nixpkgs-stable": "nixpkgs-stable_2"
}, },
"locked": { "locked": {
"lastModified": 1723202784, "lastModified": 1727854478,
"narHash": "sha256-qbhjc/NEGaDbyy0ucycubq4N3//gDFFH3DOmp1D3u1Q=", "narHash": "sha256-/odH2nUMAwkMgOS2nG2z0exLQNJS4S2LfMW0teqU7co=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "c7012d0c18567c889b948781bc74a501e92275d1", "rev": "5f58871c9657b5fc0a7f65670fe2ba99c26c1d79",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -392,14 +508,30 @@
"type": "github" "type": "github"
} }
}, },
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"liburing": { "liburing": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1724199144, "lastModified": 1725659644,
"narHash": "sha256-MVjnwO6EbKzzSrU51dSseLarZ1fRp+6SagAf/nE/XZU=", "narHash": "sha256-WjnpmopfvFoUbubIu9bki+Y6P4YXDfvnW4+72hniq3g=",
"owner": "axboe", "owner": "axboe",
"repo": "liburing", "repo": "liburing",
"rev": "2d4e799017d64cd2f8304503eef9064931bb3fbd", "rev": "0fe5c09195c0918f89582dd6ff098a58a0bdf62a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -417,6 +549,8 @@
"devenv", "devenv",
"cachix", "cachix",
"devenv", "devenv",
"cachix",
"devenv",
"nixpkgs" "nixpkgs"
], ],
"nixpkgs-regression": "nixpkgs-regression" "nixpkgs-regression": "nixpkgs-regression"
@@ -459,6 +593,8 @@
"devenv", "devenv",
"cachix", "cachix",
"devenv", "devenv",
"cachix",
"devenv",
"poetry2nix", "poetry2nix",
"nixpkgs" "nixpkgs"
] ]
@@ -480,11 +616,15 @@
"nix_2": { "nix_2": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
"cachix",
"devenv",
"cachix", "cachix",
"devenv", "devenv",
"flake-compat" "flake-compat"
], ],
"nixpkgs": [ "nixpkgs": [
"cachix",
"devenv",
"cachix", "cachix",
"devenv", "devenv",
"nixpkgs" "nixpkgs"
@@ -506,13 +646,42 @@
"type": "github" "type": "github"
} }
}, },
"nix_3": {
"inputs": {
"flake-compat": [
"cachix",
"devenv",
"flake-compat"
],
"flake-parts": "flake-parts_2",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_3",
"nixpkgs-23-11": "nixpkgs-23-11",
"nixpkgs-regression": "nixpkgs-regression_3",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1727438425,
"narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=",
"owner": "domenkozar",
"repo": "nix",
"rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1723827930, "lastModified": 1726042813,
"narHash": "sha256-EU+W5F6y2CVNxGrGIMpY7nSVYq72WRChYxF4zpjx0y4=", "narHash": "sha256-LnNKCCxnwgF+575y0pxUdlGZBO/ru1CtGHIqQVfvjlA=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d4a7a4d0e066278bfb0d77bd2a7adde1c0ec9e3d", "rev": "159be5db480d1df880a0135ca0bfed84c2f88353",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -522,6 +691,22 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-23-11": {
"locked": {
"lastModified": 1717159533,
"narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
"type": "github"
}
},
"nixpkgs-regression": { "nixpkgs-regression": {
"locked": { "locked": {
"lastModified": 1643052045, "lastModified": 1643052045,
@@ -554,18 +739,34 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-stable": { "nixpkgs-regression_3": {
"locked": { "locked": {
"lastModified": 1720535198, "lastModified": 1643052045,
"narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=", "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5", "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-23.11", "repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1724316499,
"narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@@ -604,27 +805,27 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1722813957, "lastModified": 1717432640,
"narHash": "sha256-IAoYyYnED7P8zrBFMnmp7ydaJfwTnwcnqxUElC1I26Y=", "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "cb9a96f23c491c081b38eab96d22fa958043c9fa", "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-unstable", "ref": "release-24.05",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1724271409, "lastModified": 1727802920,
"narHash": "sha256-z4nw9HxkaXEn+5OT8ljLVL2oataHvAzUQ1LEi8Fp+SY=", "narHash": "sha256-HP89HZOT0ReIbI7IJZJQoJgxvB2Tn28V6XS3MNKnfLs=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "36a9aeaaa17a2d4348498275f9fe530cd4f9e519", "rev": "27e30d177e57d912d614c88c622dcfdb2e6e6515",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -634,15 +835,33 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_5": {
"locked": {
"lastModified": 1725534445,
"narHash": "sha256-Yd0FK9SkWy+ZPuNqUgmVPXokxDgMJoGuNpMEtkfcf84=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9bb1e7571aadf31ddb4af77fc64b2d59580f9a39",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": { "poetry2nix": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions", "nix-github-actions": "nix-github-actions",
"nixpkgs": [ "nixpkgs": [
"cachix", "cachix",
"devenv", "devenv",
"cachix", "cachix",
"devenv", "devenv",
"cachix",
"devenv",
"nixpkgs" "nixpkgs"
] ]
}, },
@@ -660,19 +879,59 @@
"type": "github" "type": "github"
} }
}, },
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
"cachix",
"devenv",
"nix"
],
"flake-utils": "flake-utils_2",
"gitignore": [
"cachix",
"devenv",
"nix"
],
"nixpkgs": [
"cachix",
"devenv",
"nix",
"nixpkgs"
],
"nixpkgs-stable": [
"cachix",
"devenv",
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712897695,
"narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"rocksdb": { "rocksdb": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1724285323, "lastModified": 1729712930,
"narHash": "sha256-k60kreKQ0v+bQ16yBd2SfLYpuNjMw2qoRmZL/S3k6CU=", "narHash": "sha256-jlp4kPkRTpoJaUdobEoHd8rCGAQNBy4ZHZ6y5zL/ibw=",
"owner": "girlbossceo", "owner": "girlbossceo",
"repo": "rocksdb", "repo": "rocksdb",
"rev": "5a67ad7ce46328578ee5587fb0c23faa03d14e67", "rev": "871eda6953c3f399aae39808dcfccdd014885beb",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "girlbossceo", "owner": "girlbossceo",
"ref": "v9.5.2", "ref": "v9.7.3",
"repo": "rocksdb", "repo": "rocksdb",
"type": "github" "type": "github"
} }
@@ -688,18 +947,18 @@
"flake-utils": "flake-utils_3", "flake-utils": "flake-utils_3",
"liburing": "liburing", "liburing": "liburing",
"nix-filter": "nix-filter", "nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_4", "nixpkgs": "nixpkgs_5",
"rocksdb": "rocksdb" "rocksdb": "rocksdb"
} }
}, },
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1724153119, "lastModified": 1729715509,
"narHash": "sha256-WxpvDJDttkINkXmUA/W5o11lwLPYhATAgu0QUAacZ2g=", "narHash": "sha256-jUDN4e1kObbksb4sc+57NEeujBEDRdLCOu9wiE3RZdM=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "3723e5910c14f0ffbd13de474b8a8fcc74db04ce", "rev": "40492e15d49b89cf409e2c5536444131fac49429",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -738,21 +997,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",
+60 -26
View File
@@ -3,13 +3,13 @@
attic.url = "github:zhaofengli/attic?ref=main"; attic.url = "github:zhaofengli/attic?ref=main";
cachix.url = "github:cachix/cachix?ref=master"; cachix.url = "github:cachix/cachix?ref=master";
complement = { url = "github:matrix-org/complement?ref=main"; flake = false; }; complement = { url = "github:matrix-org/complement?ref=main"; flake = false; };
crane = { url = "github:ipetkov/crane?ref=master"; inputs.nixpkgs.follows = "nixpkgs"; }; crane = { url = "github:ipetkov/crane?ref=master"; };
fenix = { url = "github:nix-community/fenix?ref=main"; inputs.nixpkgs.follows = "nixpkgs"; }; fenix = { url = "github:nix-community/fenix?ref=main"; inputs.nixpkgs.follows = "nixpkgs"; };
flake-compat = { url = "github:edolstra/flake-compat?ref=master"; flake = false; }; flake-compat = { url = "github:edolstra/flake-compat?ref=master"; flake = false; };
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=nixpkgs-unstable";
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.5.2"; flake = false; }; rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.7.3"; flake = false; };
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; }; liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
}; };
@@ -27,7 +27,7 @@
file = ./rust-toolchain.toml; file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
sha256 = "sha256-3jVIIf5XPnUU1CRaTyAiO0XHVbJl12MSx3eucTXCjtE="; sha256 = "sha256-yMuSb5eQPO/bHv+Bcf/US8LVMbf/G/0MSfiPwBhiPpk=";
}; };
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: { mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
@@ -38,7 +38,23 @@
inherit inputs; inherit inputs;
main = self.callPackage ./nix/pkgs/main {}; main = self.callPackage ./nix/pkgs/main {};
oci-image = self.callPackage ./nix/pkgs/oci-image {}; oci-image = self.callPackage ./nix/pkgs/oci-image {};
rocksdb = pkgs.rocksdb.overrideAttrs (old: { tini = pkgs.tini.overrideAttrs {
# newer clang/gcc is unhappy with tini-static: <https://3.dog/~strawberry/pb/c8y4>
patches = [ (pkgs.fetchpatch {
url = "https://patch-diff.githubusercontent.com/raw/krallin/tini/pull/224.patch";
hash = "sha256-4bTfAhRyIT71VALhHY13hUgbjLEUyvgkIJMt3w9ag3k=";
})
];
};
liburing = pkgs.liburing.overrideAttrs {
# Tests weren't building
outputs = [ "out" "dev" "man" ];
buildFlags = [ "library" ];
src = inputs.liburing;
};
rocksdb = (pkgs.rocksdb.override {
liburing = self.liburing;
}).overrideAttrs (old: {
src = inputs.rocksdb; src = inputs.rocksdb;
version = pkgs.lib.removePrefix version = pkgs.lib.removePrefix
"v" "v"
@@ -76,18 +92,20 @@
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use # preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
preInstall = ""; preInstall = "";
}); });
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
liburing = pkgs.liburing.overrideAttrs (old: {
# the configure script doesn't support these, and unconditionally
# builds both static and dynamic libraries.
configureFlags = pkgs.lib.subtractLists
[ "--enable-static" "--disable-shared" ]
old.configureFlags;
});
}); });
scopeHost = mkScope pkgsHost; scopeHost = mkScope pkgsHost;
scopeHostStatic = mkScope pkgsHostStatic; scopeHostStatic = mkScope pkgsHostStatic;
scopeCrossLinux = mkScope pkgsHost.pkgsLinux.pkgsStatic;
mkCrossScope = crossSystem:
let pkgsCrossStatic = (import inputs.nixpkgs {
inherit system;
crossSystem = {
config = crossSystem;
};
}).pkgsStatic;
in
mkScope pkgsCrossStatic;
mkDevShell = scope: scope.pkgs.mkShell { mkDevShell = scope: scope.pkgs.mkShell {
env = scope.main.env // { env = scope.main.env // {
@@ -119,6 +137,9 @@
engage engage
cargo-audit cargo-audit
# Required by hardened-malloc.rs dep
binutils
# Needed for producing Debian packages # Needed for producing Debian packages
cargo-deb cargo-deb
@@ -145,12 +166,21 @@
# needed so we can get rid of gcc and other unused deps that bloat OCI images # needed so we can get rid of gcc and other unused deps that bloat OCI images
removeReferencesTo removeReferencesTo
]) ]
# liburing is Linux-exclusive
++ lib.optional stdenv.hostPlatform.isLinux liburing
# needed to build Rust applications on macOS
++ lib.optionals stdenv.hostPlatform.isDarwin [
# https://github.com/NixOS/nixpkgs/issues/206242
# ld: library not found for -liconv
libiconv
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
])
++ scope.main.buildInputs ++ scope.main.buildInputs
++ scope.main.propagatedBuildInputs ++ scope.main.propagatedBuildInputs
++ scope.main.nativeBuildInputs; ++ scope.main.nativeBuildInputs;
meta.broken = scope.main.meta.broken;
}; };
in in
{ {
@@ -224,6 +254,8 @@
complement = scopeHost.complement; complement = scopeHost.complement;
static-complement = scopeHostStatic.complement; static-complement = scopeHostStatic.complement;
# macOS containers don't exist, so the complement images must be forced to linux
linux-complement = (mkCrossScope "${pkgsHost.hostPlatform.qemuArch}-linux-musl").complement;
} }
// //
builtins.listToAttrs builtins.listToAttrs
@@ -232,14 +264,7 @@
(crossSystem: (crossSystem:
let let
binaryName = "static-${crossSystem}"; binaryName = "static-${crossSystem}";
pkgsCrossStatic = scopeCrossStatic = mkCrossScope crossSystem;
(import inputs.nixpkgs {
inherit system;
crossSystem = {
config = crossSystem;
};
}).pkgsStatic;
scopeCrossStatic = mkScope pkgsCrossStatic;
in in
[ [
# An output for a statically-linked binary # An output for a statically-linked binary
@@ -369,11 +394,20 @@
}; };
}; };
} }
# An output for a complement OCI image for the specified platform
{
name = "complement-${crossSystem}";
value = scopeCrossStatic.complement;
}
] ]
) )
[ [
"x86_64-unknown-linux-musl" #"x86_64-apple-darwin"
"aarch64-unknown-linux-musl" #"aarch64-apple-darwin"
"x86_64-linux-gnu"
"x86_64-linux-musl"
"aarch64-linux-musl"
] ]
) )
); );
+2
View File
@@ -14,8 +14,10 @@ stdenv.mkDerivation {
include = [ include = [
"book.toml" "book.toml"
"conduwuit-example.toml" "conduwuit-example.toml"
"CODE_OF_CONDUCT.md"
"CONTRIBUTING.md" "CONTRIBUTING.md"
"README.md" "README.md"
"development.md"
"debian/conduwuit.service" "debian/conduwuit.service"
"debian/README.md" "debian/README.md"
"arch/conduwuit.service" "arch/conduwuit.service"
+1
View File
@@ -16,6 +16,7 @@ url_preview_domain_contains_allowlist = ["*"]
media_compat_file_link = false media_compat_file_link = false
media_startup_check = false media_startup_check = false
rocksdb_direct_io = false rocksdb_direct_io = false
log_colors = false
[global.tls] [global.tls]
certs = "/certificate.crt" certs = "/certificate.crt"
+11 -2
View File
@@ -18,6 +18,15 @@ let
all_features = true; all_features = true;
disable_release_max_log_level = true; disable_release_max_log_level = true;
disable_features = [ disable_features = [
# no reason to use jemalloc for complement, just has compatibility/build issues
"jemalloc"
# console/CLI stuff isn't used or relevant for complement
"console"
"tokio_console"
# sentry telemetry isn't useful for complement, disabled by default anyways
"sentry_telemetry"
# the containers don't use or need systemd signal support
"systemd"
# this is non-functional on nix for some reason # this is non-functional on nix for some reason
"hardened_malloc" "hardened_malloc"
# dont include experimental features # dont include experimental features
@@ -57,7 +66,7 @@ let
in in
dockerTools.buildImage { dockerTools.buildImage {
name = "complement-${main.pname}"; name = "complement-conduwuit";
tag = "main"; tag = "main";
copyToRoot = buildEnv { copyToRoot = buildEnv {
@@ -78,7 +87,7 @@ dockerTools.buildImage {
"${lib.getExe start}" "${lib.getExe start}"
]; ];
Entrypoint = if !stdenv.isDarwin Entrypoint = if !stdenv.hostPlatform.isDarwin
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT) # Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
# are handled as expected # are handled as expected
then [ "${lib.getExe' tini "tini"}" "--" ] then [ "${lib.getExe' tini "tini"}" "--" ]
+15 -12
View File
@@ -1,5 +1,6 @@
{ lib { lib
, pkgsBuildHost , pkgsBuildHost
, pkgsBuildTarget
, rust , rust
, stdenv , stdenv
}: }:
@@ -13,12 +14,6 @@ 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
@@ -41,7 +36,7 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
# including it here. Linkers are weird. # including it here. Linkers are weird.
(stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isx86_64) (stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isx86_64)
&& stdenv.hostPlatform.isStatic && stdenv.hostPlatform.isStatic
&& !stdenv.isDarwin && !stdenv.hostPlatform.isDarwin
&& !stdenv.cc.bintools.isLLVM && !stdenv.cc.bintools.isLLVM
) )
[ [
@@ -58,11 +53,12 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
# even covers the case of build scripts that need native code compiled and # even covers the case of build scripts that need native code compiled and
# run on the build platform (I think). # run on the build platform (I think).
# #
# [0]: https://github.com/NixOS/nixpkgs/blob/5cdb38bb16c6d0a38779db14fcc766bc1b2394d6/pkgs/build-support/rust/lib/default.nix#L57-L80 # [0]: https://github.com/NixOS/nixpkgs/blob/nixpkgs-unstable/pkgs/build-support/rust/lib/default.nix#L48-L68
// //
( (
let let
inherit (rust.lib) envVars; inherit (rust.lib) envVars;
shouldUseLLD = platform: platform.isAarch64 && platform.isStatic && !stdenv.hostPlatform.isDarwin;
in in
lib.optionalAttrs lib.optionalAttrs
(stdenv.targetPlatform.rust.rustcTarget (stdenv.targetPlatform.rust.rustcTarget
@@ -70,23 +66,30 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
( (
let let
inherit (stdenv.targetPlatform.rust) cargoEnvVarTarget; inherit (stdenv.targetPlatform.rust) cargoEnvVarTarget;
linkerForTarget = if shouldUseLLD stdenv.targetPlatform
&& !stdenv.cc.bintools.isLLVM # whether stdenv's linker is lld already
then "${pkgsBuildTarget.llvmPackages.bintools}/bin/${stdenv.cc.targetPrefix}ld.lld"
else envVars.ccForTarget;
in in
{ {
"CC_${cargoEnvVarTarget}" = envVars.ccForTarget; "CC_${cargoEnvVarTarget}" = envVars.ccForTarget;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget; "CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = "CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = linkerForTarget;
envVars.linkerForTarget;
} }
) )
// //
( (
let let
inherit (stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget; inherit (stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget;
linkerForHost = if shouldUseLLD stdenv.targetPlatform
&& !stdenv.cc.bintools.isLLVM
then "${pkgsBuildHost.llvmPackages.bintools}/bin/${stdenv.cc.targetPrefix}ld.lld"
else envVars.ccForHost;
in in
{ {
"CC_${cargoEnvVarTarget}" = envVars.ccForHost; "CC_${cargoEnvVarTarget}" = envVars.ccForHost;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForHost; "CXX_${cargoEnvVarTarget}" = envVars.cxxForHost;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForHost; "CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = linkerForHost;
CARGO_BUILD_TARGET = rustcTarget; CARGO_BUILD_TARGET = rustcTarget;
} }
) )
@@ -98,7 +101,7 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
{ {
"CC_${cargoEnvVarTarget}" = envVars.ccForBuild; "CC_${cargoEnvVarTarget}" = envVars.ccForBuild;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForBuild; "CXX_${cargoEnvVarTarget}" = envVars.cxxForBuild;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForBuild; "CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForBuild;
HOST_CC = "${pkgsBuildHost.stdenv.cc}/bin/cc"; HOST_CC = "${pkgsBuildHost.stdenv.cc}/bin/cc";
HOST_CXX = "${pkgsBuildHost.stdenv.cc}/bin/c++"; HOST_CXX = "${pkgsBuildHost.stdenv.cc}/bin/c++";
} }
+19 -32
View File
@@ -6,6 +6,7 @@
, libiconv , libiconv
, liburing , liburing
, pkgsBuildHost , pkgsBuildHost
, pkgsBuildTarget
, rocksdb , rocksdb
, removeReferencesTo , removeReferencesTo
, rust , rust
@@ -40,7 +41,7 @@ features'' = lib.subtractLists disable_features' features';
featureEnabled = feature : builtins.elem feature features''; featureEnabled = feature : builtins.elem feature features'';
enableLiburing = featureEnabled "io_uring" && !stdenv.isDarwin; enableLiburing = featureEnabled "io_uring" && !stdenv.hostPlatform.isDarwin;
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the # 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
@@ -72,35 +73,13 @@ buildDepsOnlyEnv =
# jemalloc symbols are prefixed. # jemalloc symbols are prefixed.
# #
# [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.hostPlatform.isDarwin;
# for some reason enableLiburing in nixpkgs rocksdb is default true # for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely # which breaks Darwin entirely
enableLiburing = enableLiburing; enableLiburing = enableLiburing;
}).overrideAttrs (old: { }).overrideAttrs (old: {
# TODO: static rocksdb fails to build on darwin, also see <https://github.com/NixOS/nixpkgs/issues/320448>
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
enableLiburing = enableLiburing; enableLiburing = enableLiburing;
sse42Support = stdenv.targetPlatform.isx86_64;
cmakeFlags = if stdenv.targetPlatform.isx86_64
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
++ lib.optionals stdenv.targetPlatform.isx86_64 [
"-DPORTABLE=x86-64-v2"
"-DUSE_SSE=1"
"-DHAVE_SSE=1"
"-DHAVE_SSE42=1"
]
else if stdenv.targetPlatform.isAarch64
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
++ lib.optionals stdenv.targetPlatform.isAarch64 [
# cortex-a73 == ARMv8-A
"-DPORTABLE=armv8-a"
]
else old.cmakeFlags;
}); });
in in
{ {
@@ -117,6 +96,7 @@ buildDepsOnlyEnv =
inherit inherit
lib lib
pkgsBuildHost pkgsBuildHost
pkgsBuildTarget
rust rust
stdenv; stdenv;
}); });
@@ -127,11 +107,7 @@ 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-a73"; # cortex-a73 == ARMv8-A
}; };
@@ -159,7 +135,16 @@ commonAttrs = {
dontStrip = profile == "dev" || profile == "test"; dontStrip = profile == "dev" || profile == "test";
dontPatchELF = profile == "dev" || profile == "test"; dontPatchELF = profile == "dev" || profile == "test";
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys'; buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys'
# needed to build Rust applications on macOS
++ lib.optionals stdenv.hostPlatform.isDarwin [
# https://github.com/NixOS/nixpkgs/issues/206242
# ld: library not found for -liconv
libiconv
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
];
nativeBuildInputs = [ nativeBuildInputs = [
# bindgen needs the build platform's libclang. Apparently due to "splicing # bindgen needs the build platform's libclang. Apparently due to "splicing
@@ -176,8 +161,10 @@ commonAttrs = {
# needed so we can get rid of gcc and other unused deps that bloat OCI images # needed so we can get rid of gcc and other unused deps that bloat OCI images
removeReferencesTo removeReferencesTo
] ]
++ lib.optionals stdenv.isDarwin [ # needed to build Rust applications on macOS
++ lib.optionals stdenv.hostPlatform.isDarwin [
# https://github.com/NixOS/nixpkgs/issues/206242 # https://github.com/NixOS/nixpkgs/issues/206242
# ld: library not found for -liconv
libiconv libiconv
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell # https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
@@ -189,7 +176,7 @@ commonAttrs = {
# #
# <https://github.com/input-output-hk/haskell.nix/issues/829> # <https://github.com/input-output-hk/haskell.nix/issues/829>
postInstall = with pkgsBuildHost; '' postInstall = with pkgsBuildHost; ''
find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${libgcc} -t ${linuxHeaders} -t ${libidn2} -t ${libunistring} '{}' + find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${rustc.unwrapped} -t ${rustc} -t ${libidn2} -t ${libunistring} '{}' +
''; '';
}; };
in in
+1 -1
View File
@@ -16,7 +16,7 @@ dockerTools.buildLayeredImage {
dockerTools.caCertificates dockerTools.caCertificates
]; ];
config = { config = {
Entrypoint = if !stdenv.isDarwin Entrypoint = if !stdenv.hostPlatform.isDarwin
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT) # Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
# are handled as expected # are handled as expected
then [ "${lib.getExe' tini "tini"}" "--" ] then [ "${lib.getExe' tini "tini"}" "--" ]
+8 -3
View File
@@ -2,8 +2,6 @@
# #
# Other files that need upkeep when this changes: # Other files that need upkeep when this changes:
# #
# * `.gitlab-ci.yml`
# * `.github/workflows/ci.yml`
# * `Cargo.toml` # * `Cargo.toml`
# * `flake.nix` # * `flake.nix`
# #
@@ -11,13 +9,20 @@
# 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.80.1" channel = "1.82.0"
profile = "minimal"
components = [ components = [
# For rust-analyzer # For rust-analyzer
"rust-src", "rust-src",
"rust-analyzer",
# For CI and editors
"rustfmt",
"clippy",
] ]
targets = [ targets = [
#"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu", "x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl", "x86_64-unknown-linux-musl",
"aarch64-unknown-linux-musl", "aarch64-unknown-linux-musl",
#"aarch64-apple-darwin",
] ]
+83 -50
View File
@@ -1,6 +1,6 @@
use std::time::Duration; use std::time::Duration;
use conduit::{debug, info, trace, utils::time::parse_timepoint_ago, warn, Result}; use conduit::{debug, debug_info, debug_warn, error, info, trace, utils::time::parse_timepoint_ago, Result};
use conduit_service::media::Dim; use conduit_service::media::Dim;
use ruma::{ use ruma::{
events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, OwnedMxcUri, OwnedServerName, ServerName, events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, OwnedMxcUri, OwnedServerName, ServerName,
@@ -19,7 +19,7 @@ pub(super) async fn delete(
} }
if let Some(mxc) = mxc { if let Some(mxc) = mxc {
debug!("Got MXC URL: {mxc}"); trace!("Got MXC URL: {mxc}");
self.services self.services
.media .media
.delete(&mxc.as_str().try_into()?) .delete(&mxc.as_str().try_into()?)
@@ -28,11 +28,12 @@ pub(super) async fn delete(
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.",
)); ));
} else if let Some(event_id) = event_id { }
debug!("Got event ID to delete media from: {event_id}");
let mut mxc_urls = vec![]; if let Some(event_id) = event_id {
let mut mxc_deletion_count: usize = 0; trace!("Got event ID to delete media from: {event_id}");
let mut mxc_urls = Vec::with_capacity(4);
// parsing the PDU for any MXC URLs begins here // parsing the PDU for any MXC URLs begins here
if let Some(event_json) = self.services.rooms.timeline.get_pdu_json(&event_id)? { if let Some(event_json) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
@@ -124,18 +125,28 @@ pub(super) async fn delete(
} }
if mxc_urls.is_empty() { if mxc_urls.is_empty() {
// we shouldn't get here (should have errored earlier) but just in case for
// whatever reason we do...
info!("Parsed event ID {event_id} but did not contain any MXC URLs."); info!("Parsed event ID {event_id} but did not contain any MXC URLs.");
return Ok(RoomMessageEventContent::text_plain("Parsed event ID but found no MXC URLs.")); return Ok(RoomMessageEventContent::text_plain("Parsed event ID but found no MXC URLs."));
} }
let mut mxc_deletion_count: usize = 0;
for mxc_url in mxc_urls { for mxc_url in mxc_urls {
self.services match self
.services
.media .media
.delete(&mxc_url.as_str().try_into()?) .delete(&mxc_url.as_str().try_into()?)
.await?; .await
mxc_deletion_count = mxc_deletion_count.saturating_add(1); {
Ok(()) => {
debug_info!("Successfully deleted {mxc_url} from filesystem and database");
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
},
Err(e) => {
debug_warn!("Failed to delete {mxc_url}, ignoring error and skipping: {e}");
continue;
},
}
} }
return Ok(RoomMessageEventContent::text_plain(format!( return Ok(RoomMessageEventContent::text_plain(format!(
@@ -158,34 +169,62 @@ pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
)); ));
} }
let mut failed_parsed_mxcs: usize = 0;
let mxc_list = self let mxc_list = self
.body .body
.to_vec() .to_vec()
.drain(1..self.body.len().checked_sub(1).unwrap()) .drain(1..self.body.len().checked_sub(1).unwrap())
.collect::<Vec<_>>(); .filter_map(|mxc_s| {
mxc_s
.try_into()
.inspect_err(|e| {
debug_warn!("Failed to parse user-provided MXC URI: {e}");
failed_parsed_mxcs = failed_parsed_mxcs.saturating_add(1);
})
.ok()
})
.collect::<Vec<Mxc<'_>>>();
let mut mxc_deletion_count: usize = 0; let mut mxc_deletion_count: usize = 0;
for mxc in mxc_list { for mxc in &mxc_list {
debug!("Deleting MXC {mxc} in bulk"); trace!(%failed_parsed_mxcs, %mxc_deletion_count, "Deleting MXC {mxc} in bulk");
self.services.media.delete(&mxc.try_into()?).await?; match self.services.media.delete(mxc).await {
mxc_deletion_count = mxc_deletion_count Ok(()) => {
.checked_add(1) debug_info!("Successfully deleted {mxc} from filesystem and database");
.expect("mxc_deletion_count should not get this high"); mxc_deletion_count = mxc_deletion_count.saturating_add(1);
},
Err(e) => {
debug_warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
continue;
},
}
} }
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.", "Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem. \
{failed_parsed_mxcs} MXCs failed to be parsed from the database.",
))) )))
} }
#[admin_command] #[admin_command]
pub(super) async fn delete_past_remote_media(&self, duration: String, force: bool) -> Result<RoomMessageEventContent> { pub(super) async fn delete_past_remote_media(
&self, duration: String, before: bool, after: bool, yes_i_want_to_delete_local_media: bool,
) -> Result<RoomMessageEventContent> {
if before && after {
return Ok(RoomMessageEventContent::text_plain(
"Please only pick one argument, --before or --after.",
));
}
assert!(!(before && after), "--before and --after should not be specified together");
let duration = parse_timepoint_ago(&duration)?; let duration = parse_timepoint_ago(&duration)?;
let deleted_count = self let deleted_count = self
.services .services
.media .media
.delete_all_remote_media_at_after_time(duration, force) .delete_all_remote_media_at_after_time(duration, before, after, yes_i_want_to_delete_local_media)
.await?; .await?;
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
@@ -194,14 +233,10 @@ pub(super) async fn delete_past_remote_media(&self, duration: String, force: boo
} }
#[admin_command] #[admin_command]
pub(super) async fn delete_all_from_user(&self, username: String, force: bool) -> Result<RoomMessageEventContent> { pub(super) async fn delete_all_from_user(&self, username: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &username)?; let user_id = parse_local_user_id(self.services, &username)?;
let deleted_count = self let deleted_count = self.services.media.delete_from_user(&user_id).await?;
.services
.media
.delete_from_user(&user_id, force)
.await?;
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
"Deleted {deleted_count} total files.", "Deleted {deleted_count} total files.",
@@ -210,34 +245,36 @@ pub(super) async fn delete_all_from_user(&self, username: String, force: bool) -
#[admin_command] #[admin_command]
pub(super) async fn delete_all_from_server( pub(super) async fn delete_all_from_server(
&self, server_name: Box<ServerName>, force: bool, &self, server_name: Box<ServerName>, yes_i_want_to_delete_local_media: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if server_name == self.services.globals.server_name() { if server_name == self.services.globals.server_name() && !yes_i_want_to_delete_local_media {
return Ok(RoomMessageEventContent::text_plain("This command only works for remote media.")); return Ok(RoomMessageEventContent::text_plain(
"This command only works for remote media by default.",
));
} }
let Ok(all_mxcs) = self.services.media.get_all_mxcs().await else { let Ok(all_mxcs) = self
.services
.media
.get_all_mxcs()
.await
.inspect_err(|e| error!("Failed to get MXC URIs from our database: {e}"))
else {
return Ok(RoomMessageEventContent::text_plain("Failed to get MXC URIs from our database")); return Ok(RoomMessageEventContent::text_plain("Failed to get MXC URIs from our database"));
}; };
let mut deleted_count: usize = 0; let mut deleted_count: usize = 0;
for mxc in all_mxcs { for mxc in all_mxcs {
let mxc_server_name = match mxc.server_name() { let Ok(mxc_server_name) = mxc.server_name().inspect_err(|e| {
Ok(server_name) => server_name, debug_warn!("Failed to parse MXC {mxc} server name from database, ignoring error and skipping: {e}");
Err(e) => { }) else {
if force { continue;
warn!("Failed to parse MXC {mxc} server name from database, ignoring error and skipping: {e}");
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to parse MXC {mxc} server name from database: {e}",
)));
},
}; };
if mxc_server_name != server_name || self.services.globals.server_is_ours(mxc_server_name) { if mxc_server_name != server_name
|| (self.services.globals.server_is_ours(mxc_server_name) && !yes_i_want_to_delete_local_media)
{
trace!("skipping MXC URI {mxc}"); trace!("skipping MXC URI {mxc}");
continue; continue;
} }
@@ -249,12 +286,8 @@ pub(super) async fn delete_all_from_server(
deleted_count = deleted_count.saturating_add(1); deleted_count = deleted_count.saturating_add(1);
}, },
Err(e) => { Err(e) => {
if force { debug_warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
warn!("Failed to delete {mxc}, ignoring error and skipping: {e}"); continue;
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!("Failed to delete MXC {mxc}: {e}")));
}, },
} }
} }
+27 -20
View File
@@ -10,7 +10,7 @@ use crate::admin_command_dispatch;
#[derive(Debug, 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 or event ID (not redacted)
Delete { Delete {
/// The MXC URL to delete /// The MXC URL to delete
#[arg(long)] #[arg(long)]
@@ -23,37 +23,44 @@ pub(super) enum MediaCommand {
}, },
/// - Deletes a codeblock list of MXC URLs from our database and on the /// - Deletes a codeblock list of MXC URLs from our database and on the
/// filesystem /// filesystem. This will always ignore errors.
DeleteList, DeleteList,
/// - Deletes all remote media in the last X amount of time using filesystem /// - Deletes all remote media in the last/after "X" time using filesystem
/// metadata first created at date. /// metadata first created at date, or fallback to last modified date.
/// This will always ignore errors by default.
///
/// Synapse
DeletePastRemoteMedia { DeletePastRemoteMedia {
/// - The duration (at or after), e.g. "5m" to delete all media in the /// - The duration (at or after/before), e.g. "5m" to delete all media
/// past 5 minutes /// in the past or up to 5 minutes
duration: String, duration: String,
/// Continues deleting remote media if an undeletable object is found #[arg(long, short)]
#[arg(short, long)] before: bool,
force: bool,
#[arg(long, short)]
after: bool,
/// Long argument to delete local media
#[arg(long)]
yes_i_want_to_delete_local_media: bool,
}, },
/// - Deletes all the local media from a local user on our server /// - Deletes all the local media from a local user on our server. This will
/// always ignore errors by default.
DeleteAllFromUser { DeleteAllFromUser {
username: String, username: String,
/// Continues deleting media if an undeletable object is found
#[arg(short, long)]
force: bool,
}, },
/// - Deletes all remote media from the specified remote server /// - Deletes all remote media from the specified remote server. This will
/// always ignore errors by default.
DeleteAllFromServer { DeleteAllFromServer {
server_name: Box<ServerName>, server_name: Box<ServerName>,
/// Continues deleting media if an undeletable object is found /// Long argument to delete local media
#[arg(short, long)] #[arg(long)]
force: bool, yes_i_want_to_delete_local_media: bool,
}, },
GetFileInfo { GetFileInfo {
@@ -82,10 +89,10 @@ pub(super) enum MediaCommand {
#[arg(short, long, default_value("10000"))] #[arg(short, long, default_value("10000"))]
timeout: u32, timeout: u32,
#[arg(short, long)] #[arg(short, long, default_value("800"))]
width: u32, width: u32,
#[arg(short, long)] #[arg(short, long, default_value("800"))]
height: u32, height: u32,
}, },
} }
+6 -1
View File
@@ -2,6 +2,7 @@ mod account_data;
mod appservice; mod appservice;
mod globals; mod globals;
mod presence; mod presence;
mod pusher;
mod resolver; mod resolver;
mod room_alias; mod room_alias;
mod room_state_cache; mod room_state_cache;
@@ -13,7 +14,7 @@ use conduit::Result;
use self::{ use self::{
account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand, account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
presence::PresenceCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand, presence::PresenceCommand, pusher::PusherCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand, room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand,
}; };
use crate::admin_command_dispatch; use crate::admin_command_dispatch;
@@ -57,4 +58,8 @@ pub(super) enum QueryCommand {
/// - resolver service /// - resolver service
#[command(subcommand)] #[command(subcommand)]
Resolver(ResolverCommand), Resolver(ResolverCommand),
/// - pusher service
#[command(subcommand)]
Pusher(PusherCommand),
} }
+32
View File
@@ -0,0 +1,32 @@
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, UserId};
use crate::Command;
#[derive(Debug, Subcommand)]
pub(crate) enum PusherCommand {
/// - Returns all the pushers for the user.
GetPushers {
/// Full user ID
user_id: Box<UserId>,
},
}
pub(super) async fn process(subcommand: PusherCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand {
PusherCommand::GetPushers {
user_id,
} => {
let timer = tokio::time::Instant::now();
let results = services.pusher.get_pushers(&user_id)?;
let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
)))
},
}
}
+14 -2
View File
@@ -10,6 +10,10 @@ pub(crate) enum RoomInfoCommand {
/// - List joined members in a room /// - List joined members in a room
ListJoinedMembers { ListJoinedMembers {
room_id: Box<RoomId>, room_id: Box<RoomId>,
/// Lists only our local users in the specified room
#[arg(long)]
local_only: bool,
}, },
/// - Displays room topic /// - Displays room topic
@@ -22,7 +26,7 @@ pub(crate) enum RoomInfoCommand {
} }
#[admin_command] #[admin_command]
async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { async fn list_joined_members(&self, room_id: Box<RoomId>, local_only: bool) -> Result<RoomMessageEventContent> {
let room_name = self let room_name = self
.services .services
.rooms .rooms
@@ -37,7 +41,15 @@ async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageE
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(Result::ok); .filter_map(|member| {
if local_only {
member
.ok()
.filter(|user| self.services.globals.user_is_local(user))
} else {
member.ok()
}
});
let member_info = members let member_info = members
.into_iter() .into_iter()
+97 -19
View File
@@ -1,12 +1,16 @@
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, leave_room, update_avatar_url, update_displayname}; use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room};
use conduit::{error, info, utils, warn, PduBuilder, Result}; use conduit::{error, info, utils, warn, PduBuilder, Result};
use ruma::{ use ruma::{
events::{ events::{
room::{message::RoomMessageEventContent, redaction::RoomRedactionEventContent}, room::{
message::RoomMessageEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
redaction::RoomRedactionEventContent,
},
tag::{TagEvent, TagEventContent, TagInfo}, tag::{TagEvent, TagEventContent, TagInfo},
RoomAccountDataEventType, TimelineEventType, RoomAccountDataEventType, StateEventType, TimelineEventType,
}, },
EventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId, EventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId,
}; };
@@ -111,6 +115,7 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
Some("Automatically joining this room upon registration".to_owned()), Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), self.services.globals.server_name().to_owned()], &[room_id_server_name.to_owned(), self.services.globals.server_name().to_owned()],
None, None,
&None,
) )
.await .await
{ {
@@ -128,6 +133,22 @@ pub(super) async fn create_user(&self, username: String, password: Option<String
// we dont add a device since we're not the user, just the creator // we dont add a device since we're not the user, just the creator
// if this account creation is from the CLI / --execute, invite the first user
// to admin room
if let Some(admin_room) = self.services.admin.get_admin_room()? {
if self
.services
.rooms
.state_cache
.room_joined_count(&admin_room)?
== Some(1)
{
self.services.admin.make_user_admin(&user_id).await?;
warn!("Granting {user_id} admin privileges as the first user");
}
}
// Inhibit login does not work for guests // Inhibit login does not work for guests
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
"Created user with user_id: {user_id} and password: `{password}`" "Created user with user_id: {user_id} and password: `{password}`"
@@ -163,9 +184,8 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?; full_user_deactivate(self.services, &user_id, all_joined_rooms).await?;
leave_all_rooms(self.services, &user_id).await;
} }
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
@@ -271,9 +291,7 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) ->
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?; full_user_deactivate(self.services, &user_id, all_joined_rooms).await?;
update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(self.services, &user_id).await;
} }
}, },
Err(e) => { Err(e) => {
@@ -343,7 +361,7 @@ pub(super) async fn force_join_room(
self.services.globals.user_is_local(&user_id), self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user" "Parsed user_id must be a local user"
); );
join_room_by_id_helper(self.services, &user_id, &room_id, None, &[], None).await?; join_room_by_id_helper(self.services, &user_id, &room_id, None, &[], None, &None).await?;
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been joined to {room_id}.", "{user_id} has been joined to {room_id}.",
@@ -369,23 +387,83 @@ pub(super) async fn force_leave_room(
} }
#[admin_command] #[admin_command]
pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> { pub(super) async fn force_demote(
&self, user_id: String, room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &user_id)?; let user_id = parse_local_user_id(self.services, &user_id)?;
let displayname = self let room_id = self.services.rooms.alias.resolve(&room_id).await?;
.services
.users
.displayname(&user_id)?
.unwrap_or_else(|| user_id.to_string());
assert!( assert!(
self.services.globals.user_is_local(&user_id), self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user" "Parsed user_id must be a local user"
); );
self.services
.admin let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
.make_user_admin(&user_id, displayname)
let room_power_levels = self
.services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomPowerLevels, "")?
.as_ref()
.and_then(|event| serde_json::from_str(event.content.get()).ok()?)
.and_then(|content: RoomPowerLevelsEventContent| content.into());
let user_can_demote_self = room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone()).user_can_change_user_power_level(&user_id, &user_id)
}) || self
.services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")?
.as_ref()
.is_some_and(|event| event.sender == user_id);
if !user_can_demote_self {
return Ok(RoomMessageEventContent::notice_markdown(
"User is not allowed to modify their own power levels in the room.",
));
}
let mut power_levels_content = room_power_levels.unwrap_or_default();
power_levels_content.users.remove(&user_id);
let event_id = self
.services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&power_levels_content).expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
&user_id,
&room_id,
&state_lock,
)
.await?; .await?;
Ok(RoomMessageEventContent::notice_markdown(format!(
"User {user_id} demoted themselves to the room default power level in {room_id} - {event_id}"
)))
}
#[admin_command]
pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &user_id)?;
assert!(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
self.services.admin.make_user_admin(&user_id).await?;
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been granted admin privileges.", "{user_id} has been granted admin privileges.",
))) )))
+7
View File
@@ -79,6 +79,13 @@ pub(super) enum UserCommand {
room_id: OwnedRoomOrAliasId, room_id: OwnedRoomOrAliasId,
}, },
/// - Forces the specified user to drop their power levels to the room
/// default, if their permissions allow and the auth check permits
ForceDemote {
user_id: String,
room_id: OwnedRoomOrAliasId,
},
/// - Grant server-admin privileges to a user. /// - Grant server-admin privileges to a user.
MakeUserAdmin { MakeUserAdmin {
user_id: String, user_id: String,
+154 -54
View File
@@ -2,7 +2,7 @@ use std::fmt::Write;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::{debug_info, error, info, utils, warn, Error, Result}; use conduit::{debug_info, error, info, utils, warn, Error, PduBuilder, Result};
use register::RegistrationKind; use register::RegistrationKind;
use ruma::{ use ruma::{
api::client::{ api::client::{
@@ -15,9 +15,17 @@ use ruma::{
error::ErrorKind, error::ErrorKind,
uiaa::{AuthFlow, AuthType, UiaaInfo}, uiaa::{AuthFlow, AuthType, UiaaInfo},
}, },
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType}, events::{
room::{
message::RoomMessageEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
GlobalAccountDataEventType, StateEventType, TimelineEventType,
},
push, OwnedRoomId, UserId, push, OwnedRoomId, UserId,
}; };
use serde_json::value::to_raw_value;
use service::Services;
use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH}; use super::{join_room_by_id_helper, DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::Ruma; use crate::Ruma;
@@ -296,50 +304,61 @@ pub(crate) async fn register_route(
debug_info!(%user_id, %device_id, "User account was created"); debug_info!(%user_id, %device_id, "User account was created");
let device_display_name = body.initial_device_display_name.clone().unwrap_or_default();
// log in conduit admin channel if a non-guest user registered // 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."); if !device_display_name.is_empty() {
services info!("New user \"{user_id}\" registered on this server with device display name: {device_display_name}");
.admin
.send_message(RoomMessageEventContent::notice_plain(format!( if services.globals.config.admin_room_notices {
"New user \"{user_id}\" registered on this server from IP {client}." services
))) .admin
.await; .send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client} and device display name \
\"{device_display_name}\""
)))
.await;
}
} else {
info!("New user \"{user_id}\" registered on this server.");
if services.globals.config.admin_room_notices {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client}"
)))
.await;
}
}
} }
// log in conduit admin channel if a guest registered // 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."); info!("New guest user \"{user_id}\" registered on this server.");
if let Some(device_display_name) = &body.initial_device_display_name { if !device_display_name.is_empty() {
if body if services.globals.config.admin_room_notices {
.initial_device_display_name
.as_ref()
.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 \
server from IP {client}." this server from IP {client}"
)))
.await;
} else {
services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
{client}.",
))) )))
.await; .await;
} }
} else { } else {
services #[allow(clippy::collapsible_else_if)]
.admin if services.globals.config.admin_room_notices {
.send_message(RoomMessageEventContent::notice_plain(format!( services
"Guest user \"{user_id}\" with no device display name registered on this server from IP {client}.", .admin
))) .send_message(RoomMessageEventContent::notice_plain(format!(
.await; "Guest user \"{user_id}\" with no device display name registered on this server from IP \
{client}",
)))
.await;
}
} }
} }
@@ -348,10 +367,7 @@ pub(crate) async fn register_route(
if !is_guest { if !is_guest {
if let Some(admin_room) = services.admin.get_admin_room()? { if let Some(admin_room) = services.admin.get_admin_room()? {
if services.rooms.state_cache.room_joined_count(&admin_room)? == Some(1) { if services.rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
services services.admin.make_user_admin(&user_id).await?;
.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");
} }
@@ -380,6 +396,7 @@ pub(crate) async fn register_route(
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,
&body.appservice_info,
) )
.await .await
{ {
@@ -476,12 +493,15 @@ pub(crate) async fn change_password_route(
} }
info!("User {sender_user} changed their password."); info!("User {sender_user} changed their password.");
services
.admin if services.globals.config.admin_room_notices {
.send_message(RoomMessageEventContent::notice_plain(format!( services
"User {sender_user} changed their password." .admin
))) .send_message(RoomMessageEventContent::notice_plain(format!(
.await; "User {sender_user} changed their password."
)))
.await;
}
Ok(change_password::v3::Response {}) Ok(change_password::v3::Response {})
} }
@@ -556,9 +576,6 @@ pub(crate) async fn deactivate_route(
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
} }
// Remove devices and mark account as deactivated
services.users.deactivate_account(sender_user)?;
// Remove profile pictures and display name // Remove profile pictures and display name
let all_joined_rooms: Vec<OwnedRoomId> = services let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms .rooms
@@ -566,19 +583,19 @@ pub(crate) async fn deactivate_route(
.rooms_joined(sender_user) .rooms_joined(sender_user)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
super::update_displayname(&services, sender_user.clone(), None, all_joined_rooms.clone()).await?;
super::update_avatar_url(&services, sender_user.clone(), None, None, all_joined_rooms).await?;
// Make the user leave all rooms before deactivation full_user_deactivate(&services, sender_user, all_joined_rooms).await?;
super::leave_all_rooms(&services, sender_user).await;
info!("User {sender_user} deactivated their account."); info!("User {sender_user} deactivated their account.");
services
.admin if services.globals.config.admin_room_notices {
.send_message(RoomMessageEventContent::notice_plain(format!( services
"User {sender_user} deactivated their account." .admin
))) .send_message(RoomMessageEventContent::notice_plain(format!(
.await; "User {sender_user} deactivated their account."
)))
.await;
}
Ok(deactivate::v3::Response { Ok(deactivate::v3::Response {
id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport, id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
@@ -648,3 +665,86 @@ pub(crate) async fn check_registration_token_validity(
valid: reg_token == body.token, valid: reg_token == body.token,
}) })
} }
/// Runs through all the deactivation steps:
///
/// - Mark as deactivated
/// - Removing display name
/// - Removing avatar URL and blurhash
/// - Removing all profile data
/// - Leaving all rooms (and forgets all of them)
pub async fn full_user_deactivate(
services: &Services, user_id: &UserId, all_joined_rooms: Vec<OwnedRoomId>,
) -> Result<()> {
services.users.deactivate_account(user_id)?;
super::update_displayname(services, user_id, None, all_joined_rooms.clone()).await?;
super::update_avatar_url(services, user_id, None, None, all_joined_rooms.clone()).await?;
let all_profile_keys = services
.users
.all_profile_keys(user_id)
.filter_map(Result::ok);
for (profile_key, _profile_value) in all_profile_keys {
if let Err(e) = services.users.set_profile_key(user_id, &profile_key, None) {
warn!("Failed removing {user_id} profile key {profile_key}: {e}");
}
}
for room_id in all_joined_rooms {
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
let room_power_levels = services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomPowerLevels, "")?
.as_ref()
.and_then(|event| serde_json::from_str(event.content.get()).ok()?)
.and_then(|content: RoomPowerLevelsEventContent| content.into());
let user_can_demote_self = room_power_levels
.as_ref()
.is_some_and(|power_levels_content| {
RoomPowerLevels::from(power_levels_content.clone()).user_can_change_user_power_level(user_id, user_id)
}) || services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")?
.as_ref()
.is_some_and(|event| event.sender == user_id);
if user_can_demote_self {
let mut power_levels_content = room_power_levels.unwrap_or_default();
power_levels_content.users.remove(user_id);
// ignore errors so deactivation doesn't fail
if let Err(e) = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: TimelineEventType::RoomPowerLevels,
content: to_raw_value(&power_levels_content).expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(String::new()),
redacts: None,
timestamp: None,
},
user_id,
&room_id,
&state_lock,
)
.await
{
warn!(%room_id, %user_id, "Failed to demote user's own power level: {e}");
} else {
info!("Demoted {user_id} in {room_id} as part of account deactivation");
}
}
}
super::leave_all_rooms(services, user_id).await;
Ok(())
}
+6
View File
@@ -7,6 +7,7 @@ use ruma::{
}, },
RoomVersionId, RoomVersionId,
}; };
use serde_json::json;
use crate::{Result, Ruma}; use crate::{Result, Ruma};
@@ -42,6 +43,11 @@ pub(crate) async fn get_capabilities_route(
enabled: false, enabled: false,
}; };
// MSC4133 capability
capabilities
.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))
.expect("this is valid JSON we created");
Ok(get_capabilities::v3::Response { Ok(get_capabilities::v3::Response {
capabilities, capabilities,
}) })
+27 -14
View File
@@ -1,6 +1,6 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::{err, info, warn, Error, Result}; use conduit::{err, info, warn, Err, Error, Result};
use ruma::{ use ruma::{
api::{ api::{
client::{ client::{
@@ -124,6 +124,10 @@ pub(crate) async fn set_room_visibility_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found")); return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
} }
if services.users.is_deactivated(sender_user).unwrap_or(false) && body.appservice_info.is_none() {
return Err!(Request(Forbidden("Guests cannot publish to room directories")));
}
if !user_can_publish_room(&services, sender_user, &body.room_id)? { if !user_can_publish_room(&services, sender_user, &body.room_id)? {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
@@ -133,20 +137,26 @@ pub(crate) async fn set_room_visibility_route(
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)?
&& body.appservice_info.is_none()
{
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",
body.room_id body.room_id
); );
services
.admin if services.globals.config.admin_room_notices {
.send_text(&format!( services
"Non-admin user {sender_user} tried to publish {0} to the room directory while \ .admin
\"lockdown_public_room_directory\" is enabled", .send_text(&format!(
body.room_id "Non-admin user {sender_user} tried to publish {0} to the room directory while \
)) \"lockdown_public_room_directory\" is enabled",
.await; body.room_id
))
.await;
}
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
@@ -155,10 +165,13 @@ pub(crate) async fn set_room_visibility_route(
} }
services.rooms.directory.set_public(&body.room_id)?; services.rooms.directory.set_public(&body.room_id)?;
services
.admin if services.globals.config.admin_room_notices {
.send_text(&format!("{sender_user} made {} public to the room directory", body.room_id)) services
.await; .admin
.send_text(&format!("{sender_user} made {} public to the room directory", body.room_id))
.await;
}
info!("{sender_user} made {0} public to the room directory", body.room_id); info!("{sender_user} made {0} public to the room directory", body.room_id);
}, },
room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id)?, room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id)?,
+28 -34
View File
@@ -40,13 +40,10 @@ use ruma::{
OwnedUserId, RoomId, RoomVersionId, ServerName, UserId, OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
}; };
use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; use serde_json::value::{to_raw_value, RawValue as RawJsonValue};
use service::{rooms::state::RoomMutexGuard, Services}; use service::{appservice::RegistrationInfo, rooms::state::RoomMutexGuard, Services};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use crate::{ use crate::{client::full_user_deactivate, Ruma};
client::{update_avatar_url, update_displayname},
Ruma,
};
/// Checks if the room is banned in any way possible and the sender user is not /// Checks if the room is banned in any way possible and the sender user is not
/// an admin. /// an admin.
@@ -74,16 +71,15 @@ async fn banned_room_check(
if services.globals.config.auto_deactivate_banned_room_attempts { if services.globals.config.auto_deactivate_banned_room_attempts {
warn!("Automatically deactivating user {user_id} due to attempted banned room join"); warn!("Automatically deactivating user {user_id} due to attempted banned room join");
services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
{client_ip}"
)))
.await;
if let Err(e) = services.users.deactivate_account(user_id) { if services.globals.config.admin_room_notices {
warn!(%user_id, %e, "Failed to deactivate account"); services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
{client_ip}"
)))
.await;
} }
let all_joined_rooms: Vec<OwnedRoomId> = services let all_joined_rooms: Vec<OwnedRoomId> = services
@@ -93,9 +89,7 @@ async fn banned_room_check(
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(services, user_id.into(), None, all_joined_rooms.clone()).await?; full_user_deactivate(services, user_id, all_joined_rooms).await?;
update_avatar_url(services, user_id.into(), None, None, all_joined_rooms).await?;
leave_all_rooms(services, user_id).await;
} }
return Err(Error::BadRequest( return Err(Error::BadRequest(
@@ -117,16 +111,15 @@ async fn banned_room_check(
if services.globals.config.auto_deactivate_banned_room_attempts { if services.globals.config.auto_deactivate_banned_room_attempts {
warn!("Automatically deactivating user {user_id} due to attempted banned room join"); warn!("Automatically deactivating user {user_id} due to attempted banned room join");
services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
{client_ip}"
)))
.await;
if let Err(e) = services.users.deactivate_account(user_id) { if services.globals.config.admin_room_notices {
warn!(%user_id, %e, "Failed to deactivate account"); services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
{client_ip}"
)))
.await;
} }
let all_joined_rooms: Vec<OwnedRoomId> = services let all_joined_rooms: Vec<OwnedRoomId> = services
@@ -136,9 +129,7 @@ async fn banned_room_check(
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(services, user_id.into(), None, all_joined_rooms.clone()).await?; full_user_deactivate(services, user_id, all_joined_rooms).await?;
update_avatar_url(services, user_id.into(), None, None, all_joined_rooms).await?;
leave_all_rooms(services, user_id).await;
} }
return Err(Error::BadRequest( return Err(Error::BadRequest(
@@ -209,6 +200,7 @@ pub(crate) async fn join_room_by_id_route(
body.reason.clone(), body.reason.clone(),
&servers, &servers,
body.third_party_signed.as_ref(), body.third_party_signed.as_ref(),
&body.appservice_info,
) )
.await .await
} }
@@ -228,13 +220,14 @@ pub(crate) async fn join_room_by_id_or_alias_route(
body: Ruma<join_room_by_id_or_alias::v3::Request>, body: Ruma<join_room_by_id_or_alias::v3::Request>,
) -> Result<join_room_by_id_or_alias::v3::Response> { ) -> Result<join_room_by_id_or_alias::v3::Response> {
let sender_user = body.sender_user.as_deref().expect("user is authenticated"); let sender_user = body.sender_user.as_deref().expect("user is authenticated");
let appservice_info = &body.appservice_info;
let body = body.body; let body = body.body;
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) { let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias) {
Ok(room_id) => { Ok(room_id) => {
banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client).await?; banned_room_check(&services, sender_user, Some(&room_id), room_id.server_name(), client).await?;
let mut servers = body.server_name.clone(); let mut servers = body.via.clone();
servers.extend( servers.extend(
services services
.rooms .rooms
@@ -267,13 +260,13 @@ pub(crate) async fn join_room_by_id_or_alias_route(
let response = services let response = services
.rooms .rooms
.alias .alias
.resolve_alias(&room_alias, Some(&body.server_name.clone())) .resolve_alias(&room_alias, Some(&body.via.clone()))
.await?; .await?;
let (room_id, mut pre_servers) = response; let (room_id, mut pre_servers) = response;
banned_room_check(&services, sender_user, Some(&room_id), Some(room_alias.server_name()), client).await?; banned_room_check(&services, sender_user, Some(&room_id), Some(room_alias.server_name()), client).await?;
let mut servers = body.server_name; let mut servers = body.via;
if let Some(pre_servers) = &mut pre_servers { if let Some(pre_servers) = &mut pre_servers {
servers.append(pre_servers); servers.append(pre_servers);
} }
@@ -310,6 +303,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
body.reason.clone(), body.reason.clone(),
&servers, &servers,
body.third_party_signed.as_ref(), body.third_party_signed.as_ref(),
appservice_info,
) )
.await?; .await?;
@@ -660,11 +654,11 @@ pub(crate) async fn joined_members_route(
pub async fn join_room_by_id_helper( pub async fn join_room_by_id_helper(
services: &Services, sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName], services: &Services, sender_user: &UserId, room_id: &RoomId, reason: Option<String>, servers: &[OwnedServerName],
third_party_signed: Option<&ThirdPartySigned>, third_party_signed: Option<&ThirdPartySigned>, appservice_info: &Option<RegistrationInfo>,
) -> Result<join_room_by_id::v3::Response> { ) -> Result<join_room_by_id::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await; let state_lock = services.rooms.state.mutex.lock(room_id).await;
let user_is_guest = services.users.is_deactivated(sender_user).unwrap_or(false); let user_is_guest = services.users.is_deactivated(sender_user).unwrap_or(false) && appservice_info.is_none();
if matches!(services.rooms.state_accessor.guest_can_join(room_id), Ok(false)) && user_is_guest { if matches!(services.rooms.state_accessor.guest_can_join(room_id), Ok(false)) && user_is_guest {
return Err!(Request(Forbidden("Guests are not allowed to join this room"))); return Err!(Request(Forbidden("Guests are not allowed to join this room")));
+1
View File
@@ -37,6 +37,7 @@ pub(super) mod unversioned;
pub(super) mod user_directory; pub(super) mod user_directory;
pub(super) mod voip; pub(super) mod voip;
pub use account::full_user_deactivate;
pub(super) use account::*; pub(super) use account::*;
pub(super) use alias::*; pub(super) use alias::*;
pub(super) use appservice::*; pub(super) use appservice::*;
+47 -19
View File
@@ -1,5 +1,5 @@
use axum::extract::State; use axum::extract::State;
use conduit::{pdu::PduBuilder, warn, Error, Result}; use conduit::{pdu::PduBuilder, warn, Err, Error, Result};
use ruma::{ use ruma::{
api::{ api::{
client::{ client::{
@@ -10,7 +10,7 @@ use ruma::{
}, },
events::{room::member::RoomMemberEventContent, StateEventType, TimelineEventType}, events::{room::member::RoomMemberEventContent, StateEventType, TimelineEventType},
presence::PresenceState, presence::PresenceState,
OwnedMxcUri, OwnedRoomId, OwnedUserId, OwnedMxcUri, OwnedRoomId, UserId,
}; };
use serde_json::value::to_raw_value; use serde_json::value::to_raw_value;
use service::Services; use service::Services;
@@ -26,20 +26,25 @@ pub(crate) async fn set_displayname_route(
State(services): State<crate::State>, body: Ruma<set_display_name::v3::Request>, 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");
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
let all_joined_rooms: Vec<OwnedRoomId> = services let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(sender_user) .rooms_joined(&body.user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(&services, sender_user.clone(), body.displayname.clone(), all_joined_rooms).await?; update_displayname(&services, &body.user_id, body.displayname.clone(), all_joined_rooms).await?;
if services.globals.allow_local_presence() { if services.globals.allow_local_presence() {
// Presence update // Presence update
services services
.presence .presence
.ping_presence(sender_user, &PresenceState::Online)?; .ping_presence(&body.user_id, &PresenceState::Online)?;
} }
Ok(set_display_name::v3::Response {}) Ok(set_display_name::v3::Response {})
@@ -110,16 +115,21 @@ pub(crate) async fn set_avatar_url_route(
State(services): State<crate::State>, body: Ruma<set_avatar_url::v3::Request>, 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");
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
let all_joined_rooms: Vec<OwnedRoomId> = services let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(sender_user) .rooms_joined(&body.user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_avatar_url( update_avatar_url(
&services, &services,
sender_user.clone(), &body.user_id,
body.avatar_url.clone(), body.avatar_url.clone(),
body.blurhash.clone(), body.blurhash.clone(),
all_joined_rooms, all_joined_rooms,
@@ -130,7 +140,7 @@ pub(crate) async fn set_avatar_url_route(
// Presence update // Presence update
services services
.presence .presence
.ping_presence(sender_user, &PresenceState::Online)?; .ping_presence(&body.user_id, &PresenceState::Online)?;
} }
Ok(set_avatar_url::v3::Response {}) Ok(set_avatar_url::v3::Response {})
@@ -196,7 +206,7 @@ pub(crate) async fn get_avatar_url_route(
/// # `GET /_matrix/client/v3/profile/{userId}` /// # `GET /_matrix/client/v3/profile/{userId}`
/// ///
/// Returns the displayname, avatar_url and blurhash of the user. /// Returns the displayname, avatar_url, blurhash, and tz of the user.
/// ///
/// - If user is on another server and we do not have a local copy already, /// - If user is on another server and we do not have a local copy already,
/// fetch profile over federation. /// fetch profile over federation.
@@ -232,11 +242,23 @@ pub(crate) async fn get_profile_route(
.users .users
.set_blurhash(&body.user_id, response.blurhash.clone()) .set_blurhash(&body.user_id, response.blurhash.clone())
.await?; .await?;
services
.users
.set_timezone(&body.user_id, response.tz.clone())
.await?;
for (profile_key, profile_key_value) in &response.custom_profile_fields {
services
.users
.set_profile_key(&body.user_id, profile_key, Some(profile_key_value.clone()))?;
}
return Ok(get_profile::v3::Response { return Ok(get_profile::v3::Response {
displayname: response.displayname, displayname: response.displayname,
avatar_url: response.avatar_url, avatar_url: response.avatar_url,
blurhash: response.blurhash, blurhash: response.blurhash,
tz: response.tz,
custom_profile_fields: response.custom_profile_fields,
}); });
} }
} }
@@ -251,13 +273,19 @@ pub(crate) async fn get_profile_route(
avatar_url: services.users.avatar_url(&body.user_id)?, 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)?,
tz: services.users.timezone(&body.user_id)?,
custom_profile_fields: services
.users
.all_profile_keys(&body.user_id)
.filter_map(Result::ok)
.collect(),
}) })
} }
pub async fn update_displayname( pub async fn update_displayname(
services: &Services, user_id: OwnedUserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>, services: &Services, user_id: &UserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>,
) -> Result<()> { ) -> Result<()> {
let current_display_name = services.users.displayname(&user_id).unwrap_or_default(); let current_display_name = services.users.displayname(user_id).unwrap_or_default();
if displayname == current_display_name { if displayname == current_display_name {
return Ok(()); return Ok(());
@@ -265,7 +293,7 @@ pub async fn update_displayname(
services services
.users .users
.set_displayname(&user_id, displayname.clone()) .set_displayname(user_id, displayname.clone())
.await?; .await?;
// Send a new join membership event into all joined rooms // Send a new join membership event into all joined rooms
@@ -309,11 +337,11 @@ pub async fn update_displayname(
} }
pub async fn update_avatar_url( pub async fn update_avatar_url(
services: &Services, user_id: OwnedUserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>, services: &Services, user_id: &UserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>,
all_joined_rooms: Vec<OwnedRoomId>, all_joined_rooms: Vec<OwnedRoomId>,
) -> Result<()> { ) -> Result<()> {
let current_avatar_url = services.users.avatar_url(&user_id).unwrap_or_default(); let current_avatar_url = services.users.avatar_url(user_id).unwrap_or_default();
let current_blurhash = services.users.blurhash(&user_id).unwrap_or_default(); let current_blurhash = services.users.blurhash(user_id).unwrap_or_default();
if current_avatar_url == avatar_url && current_blurhash == blurhash { if current_avatar_url == avatar_url && current_blurhash == blurhash {
return Ok(()); return Ok(());
@@ -321,11 +349,11 @@ pub async fn update_avatar_url(
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?;
// Send a new join membership event into all joined rooms // Send a new join membership event into all joined rooms
@@ -370,14 +398,14 @@ pub async fn update_avatar_url(
} }
pub async fn update_all_rooms( pub async fn update_all_rooms(
services: &Services, all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: OwnedUserId, services: &Services, all_joined_rooms: Vec<(PduBuilder, &OwnedRoomId)>, user_id: &UserId,
) { ) {
for (pdu_builder, room_id) in all_joined_rooms { for (pdu_builder, room_id) in all_joined_rooms {
let state_lock = services.rooms.state.mutex.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)
.await .await
{ {
warn!(%user_id, %room_id, %e, "Failed to update/send new profile join membership update in room"); warn!(%user_id, %room_id, %e, "Failed to update/send new profile join membership update in room");
+45 -3
View File
@@ -1,7 +1,7 @@
use std::{cmp::max, collections::BTreeMap}; use std::{cmp::max, collections::BTreeMap};
use axum::extract::State; use axum::extract::State;
use conduit::{debug_info, debug_warn, err}; use conduit::{debug_info, debug_warn, err, Err};
use ruma::{ use ruma::{
api::client::{ api::client::{
error::ErrorKind, error::ErrorKind,
@@ -64,6 +64,7 @@ const TRANSFERABLE_STATE_EVENTS: &[StateEventType; 9] = &[
/// - Send events listed in initial state /// - Send events listed in initial state
/// - Send events implied by `name` and `topic` /// - Send events implied by `name` and `topic`
/// - Send invite events /// - Send invite events
#[allow(clippy::large_stack_frames)]
pub(crate) async fn create_room_route( pub(crate) async fn create_room_route(
State(services): State<crate::State>, body: Ruma<create_room::v3::Request>, State(services): State<crate::State>, body: Ruma<create_room::v3::Request>,
) -> Result<create_room::v3::Response> { ) -> Result<create_room::v3::Response> {
@@ -92,6 +93,31 @@ pub(crate) async fn create_room_route(
)); ));
} }
if body.visibility == room::Visibility::Public
&& services.globals.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user)?
&& body.appservice_info.is_none()
{
info!(
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
\"lockdown_public_room_directory\" is enabled",
&room_id
);
if services.globals.config.admin_room_notices {
services
.admin
.send_text(&format!(
"Non-admin user {sender_user} tried to publish {0} to the room directory while \
\"lockdown_public_room_directory\" is enabled",
&room_id
))
.await;
}
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
}
let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?; let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?;
let state_lock = services.rooms.state.mutex.lock(&room_id).await; let state_lock = services.rooms.state.mutex.lock(&room_id).await;
@@ -119,6 +145,7 @@ pub(crate) async fn create_room_route(
None => services.globals.default_room_version(), None => services.globals.default_room_version(),
}; };
#[allow(clippy::single_match_else)]
let content = match &body.creation_content { let content = match &body.creation_content {
Some(content) => { Some(content) => {
use RoomVersionId::*; use RoomVersionId::*;
@@ -230,8 +257,7 @@ pub(crate) async fn create_room_route(
_ => RoomPreset::PrivateChat, // Room visibility should not be custom _ => RoomPreset::PrivateChat, // Room visibility should not be custom
}); });
let mut users = BTreeMap::new(); let mut users = BTreeMap::from_iter([(sender_user.clone(), int!(100))]);
users.insert(sender_user.clone(), int!(100));
if preset == RoomPreset::TrustedPrivateChat { if preset == RoomPreset::TrustedPrivateChat {
for invite_ in &body.invite { for invite_ in &body.invite {
@@ -450,6 +476,14 @@ pub(crate) async fn create_room_route(
if body.visibility == room::Visibility::Public { if body.visibility == room::Visibility::Public {
services.rooms.directory.set_public(&room_id)?; services.rooms.directory.set_public(&room_id)?;
if services.globals.config.admin_room_notices {
services
.admin
.send_text(&format!("{sender_user} made {} public to the room directory", &room_id))
.await;
}
info!("{sender_user} made {0} public to the room directory", &room_id);
} }
info!("{sender_user} created a room with room ID {room_id}"); info!("{sender_user} created a room with room ID {room_id}");
@@ -821,10 +855,18 @@ fn default_power_levels_content(
power_levels_content["events"]["m.room.history_visibility"] = power_levels_content["events"]["m.room.history_visibility"] =
serde_json::to_value(100).expect("100 is valid Value"); serde_json::to_value(100).expect("100 is valid Value");
// always allow users to respond (not post new) to polls. this is primarily
// useful in read-only announcement rooms that post a public poll.
power_levels_content["events"]["org.matrix.msc3381.poll.response"] =
serde_json::to_value(0).expect("0 is valid Value");
power_levels_content["events"]["m.poll.response"] = serde_json::to_value(0).expect("0 is valid Value");
// synapse does this too. clients do not expose these permissions. it prevents // synapse does this too. clients do not expose these permissions. it prevents
// default users from calling public rooms, for obvious reasons. // default users from calling public rooms, for obvious reasons.
if *visibility == room::Visibility::Public { if *visibility == room::Visibility::Public {
power_levels_content["events"]["m.call.invite"] = serde_json::to_value(50).expect("50 is valid Value"); power_levels_content["events"]["m.call.invite"] = serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["m.call"] = serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["m.call.member"] = serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["org.matrix.msc3401.call"] = power_levels_content["events"]["org.matrix.msc3401.call"] =
serde_json::to_value(50).expect("50 is valid Value"); serde_json::to_value(50).expect("50 is valid Value");
power_levels_content["events"]["org.matrix.msc3401.call.member"] = power_levels_content["events"]["org.matrix.msc3401.call.member"] =
+15 -24
View File
@@ -25,12 +25,12 @@ use ruma::{
}, },
uiaa::UiaaResponse, uiaa::UiaaResponse,
}, },
directory::RoomTypeFilter,
events::{ events::{
presence::PresenceEvent, presence::PresenceEvent,
room::member::{MembershipState, RoomMemberEventContent}, room::member::{MembershipState, RoomMemberEventContent},
AnyRawAccountDataEvent, StateEventType, TimelineEventType, AnyRawAccountDataEvent, StateEventType, TimelineEventType,
}, },
room::RoomType,
serde::Raw, serde::Raw,
state_res::Event, state_res::Event,
uint, DeviceId, EventId, MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId, uint, DeviceId, EventId, MilliSecondsSinceUnixEpoch, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId,
@@ -1760,32 +1760,23 @@ pub(crate) async fn sync_events_v4_route(
} }
fn filter_rooms( fn filter_rooms(
rooms: &[OwnedRoomId], State(services): State<crate::State>, filter: &[Option<RoomType>], negate: bool, rooms: &[OwnedRoomId], State(services): State<crate::State>, filter: &[RoomTypeFilter], negate: bool,
) -> Vec<OwnedRoomId> { ) -> Vec<OwnedRoomId> {
return rooms return rooms
.iter() .iter()
.filter(|r| { .filter(|r| match services.rooms.state_accessor.get_room_type(r) {
match services.rooms.state_accessor.get_room_type(r) { Err(e) => {
Err(e) => { warn!("Requested room type for {}, but could not retrieve with error {}", r, e);
warn!("Requested room type for {}, but could not retrieve with error {}", r, e); false
false },
}, Ok(result) => {
Ok(None) => { let result = RoomTypeFilter::from(result);
// For rooms which do not have a room type, use 'null' to include them if negate {
if negate { !filter.contains(&result)
!filter.contains(&None) } else {
} else { filter.is_empty() || filter.contains(&result)
filter.contains(&None) }
} },
},
Ok(Some(room_type)) => {
if negate {
!filter.contains(&Some(room_type))
} else {
filter.is_empty() || filter.contains(&Some(room_type))
}
},
}
}) })
.cloned() .cloned()
.collect(); .collect();
+341 -2
View File
@@ -1,12 +1,27 @@
use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduit::warn; use conduit::{warn, Err};
use ruma::{ use ruma::{
api::client::{error::ErrorKind, membership::mutual_rooms, room::get_summary}, api::{
client::{
error::ErrorKind,
membership::mutual_rooms,
profile::{
delete_profile_key, delete_timezone_key, get_profile_key, get_timezone_key, set_profile_key,
set_timezone_key,
},
room::get_summary,
},
federation,
},
events::room::member::MembershipState, events::room::member::MembershipState,
presence::PresenceState,
OwnedRoomId, OwnedRoomId,
}; };
use super::{update_avatar_url, update_displayname};
use crate::{Error, Result, Ruma, RumaResponse}; use crate::{Error, Result, Ruma, RumaResponse};
/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms` /// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
@@ -161,3 +176,327 @@ pub(crate) async fn get_room_summary(
.unwrap_or_else(|_e| None), .unwrap_or_else(|_e| None),
}) })
} }
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Deletes the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
///
/// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn delete_timezone_key_route(
State(services): State<crate::State>, body: Ruma<delete_timezone_key::unstable::Request>,
) -> Result<delete_timezone_key::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
services.users.set_timezone(&body.user_id, None).await?;
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
}
Ok(delete_timezone_key::unstable::Response {})
}
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Updates the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
///
/// - Also makes sure other users receive the update using presence EDUs
pub(crate) async fn set_timezone_key_route(
State(services): State<crate::State>, body: Ruma<set_timezone_key::unstable::Request>,
) -> Result<set_timezone_key::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
services
.users
.set_timezone(&body.user_id, body.tz.clone())
.await?;
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
}
Ok(set_timezone_key::unstable::Response {})
}
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
///
/// Updates the profile key-value field of a user, as per MSC4133.
///
/// This also handles the avatar_url and displayname being updated.
pub(crate) async fn set_profile_key_route(
State(services): State<crate::State>, body: Ruma<set_profile_key::unstable::Request>,
) -> Result<set_profile_key::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
if body.kv_pair.is_empty() {
return Err!(Request(BadJson(
"The key-value pair JSON body is empty. Use DELETE to delete a key"
)));
}
if body.kv_pair.len() > 1 {
// TODO: support PATCH or "recursively" adding keys in some sort
return Err!(Request(BadJson("This endpoint can only take one key-value pair at a time")));
}
let Some(profile_key_value) = body.kv_pair.get(&body.key) else {
return Err!(Request(BadJson(
"The key does not match the URL field key, or JSON body is empty (use DELETE)"
)));
};
if body
.kv_pair
.keys()
.any(|key| key.starts_with("u.") && !profile_key_value.is_string())
{
return Err!(Request(BadJson("u.* profile key fields must be strings")));
}
if body.kv_pair.keys().any(|key| key.len() > 128) {
return Err!(Request(BadJson("Key names cannot be longer than 128 bytes")));
}
if body.key == "displayname" {
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
update_displayname(&services, &body.user_id, Some(profile_key_value.to_string()), all_joined_rooms).await?;
} else if body.key == "avatar_url" {
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
update_avatar_url(&services, &body.user_id, Some(mxc), None, all_joined_rooms).await?;
} else {
services
.users
.set_profile_key(&body.user_id, &body.key, Some(profile_key_value.clone()))?;
}
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
}
Ok(set_profile_key::unstable::Response {})
}
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/{user_id}/{field}`
///
/// Deletes the profile key-value field of a user, as per MSC4133.
///
/// This also handles the avatar_url and displayname being updated.
pub(crate) async fn delete_profile_key_route(
State(services): State<crate::State>, body: Ruma<delete_profile_key::unstable::Request>,
) -> Result<delete_profile_key::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if *sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
if body.kv_pair.len() > 1 {
// TODO: support PATCH or "recursively" adding keys in some sort
return Err!(Request(BadJson("This endpoint can only take one key-value pair at a time")));
}
if body.key == "displayname" {
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
update_displayname(&services, &body.user_id, None, all_joined_rooms).await?;
} else if body.key == "avatar_url" {
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
update_avatar_url(&services, &body.user_id, None, None, all_joined_rooms).await?;
} else {
services
.users
.set_profile_key(&body.user_id, &body.key, None)?;
}
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
}
Ok(delete_profile_key::unstable::Response {})
}
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
///
/// Returns the `timezone` of the user as per MSC4133 and MSC4175.
///
/// - If user is on another server and we do not have a local copy already fetch
/// `timezone` over federation
pub(crate) async fn get_timezone_key_route(
State(services): State<crate::State>, body: Ruma<get_timezone_key::unstable::Request>,
) -> Result<get_timezone_key::unstable::Response> {
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
field: None, // we want the full user's profile to update locally as well
},
)
.await
{
if !services.users.exists(&body.user_id)? {
services.users.create(&body.user_id, None)?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.await?;
services
.users
.set_timezone(&body.user_id, response.tz.clone())
.await?;
return Ok(get_timezone_key::unstable::Response {
tz: response.tz,
});
}
}
if !services.users.exists(&body.user_id)? {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_timezone_key::unstable::Response {
tz: services.users.timezone(&body.user_id)?,
})
}
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/{userId}/{field}}`
///
/// Gets the profile key-value field of a user, as per MSC4133.
///
/// - If user is on another server and we do not have a local copy already fetch
/// `timezone` over federation
pub(crate) async fn get_profile_key_route(
State(services): State<crate::State>, body: Ruma<get_profile_key::unstable::Request>,
) -> Result<get_profile_key::unstable::Response> {
let mut profile_key_value: BTreeMap<String, serde_json::Value> = BTreeMap::new();
if !services.globals.user_is_local(&body.user_id) {
// Create and update our local copy of the user
if let Ok(response) = services
.sending
.send_federation_request(
body.user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: body.user_id.clone(),
field: None, // we want the full user's profile to update locally as well
},
)
.await
{
if !services.users.exists(&body.user_id)? {
services.users.create(&body.user_id, None)?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.await?;
services
.users
.set_timezone(&body.user_id, response.tz.clone())
.await?;
if let Some(value) = response.custom_profile_fields.get(&body.key) {
profile_key_value.insert(body.key.clone(), value.clone());
services
.users
.set_profile_key(&body.user_id, &body.key, Some(value.clone()))?;
} else {
return Err!(Request(NotFound("The requested profile key does not exist.")));
}
return Ok(get_profile_key::unstable::Response {
value: profile_key_value,
});
}
}
if !services.users.exists(&body.user_id)? {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
if let Some(value) = services.users.profile_key(&body.user_id, &body.key)? {
profile_key_value.insert(body.key.clone(), value);
} else {
return Err!(Request(NotFound("The requested profile key does not exist.")));
}
Ok(get_profile_key::unstable::Response {
value: profile_key_value,
})
}
+2
View File
@@ -55,6 +55,8 @@ pub(crate) async fn get_supported_versions_route(
("org.matrix.msc3575".to_owned(), true), /* sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1588877046) */ ("org.matrix.msc3575".to_owned(), true), /* sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/3575/files#r1588877046) */
("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */ ("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */
("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */ ("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */
("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
]), ]),
}; };
+1 -1
View File
@@ -24,7 +24,7 @@ pub(crate) async fn turn_server_route(
return Err!(Request(NotFound("Not Found"))); return Err!(Request(NotFound("Not Found")));
} }
let turn_secret = services.globals.turn_secret().clone(); let turn_secret = services.globals.turn_secret.clone();
let (username, password) = if !turn_secret.is_empty() { let (username, password) = if !turn_secret.is_empty() {
let expiry = SecondsSinceUnixEpoch::from_system_time( let expiry = SecondsSinceUnixEpoch::from_system_time(
+6
View File
@@ -22,6 +22,12 @@ use crate::{client, server};
pub fn build(router: Router<State>, server: &Server) -> Router<State> { pub fn build(router: Router<State>, server: &Server) -> Router<State> {
let config = &server.config; let config = &server.config;
let mut router = router let mut router = router
.ruma_route(client::get_timezone_key_route)
.ruma_route(client::get_profile_key_route)
.ruma_route(client::set_profile_key_route)
.ruma_route(client::delete_profile_key_route)
.ruma_route(client::set_timezone_key_route)
.ruma_route(client::delete_timezone_key_route)
.ruma_route(client::appservice_ping) .ruma_route(client::appservice_ping)
.ruma_route(client::get_supported_versions_route) .ruma_route(client::get_supported_versions_route)
.ruma_route(client::get_register_available_route) .ruma_route(client::get_register_available_route)
+1
View File
@@ -236,6 +236,7 @@ async fn auth_server(
} }
} }
#[allow(clippy::or_fun_call)]
let signature_uri = CanonicalJsonValue::String( let signature_uri = CanonicalJsonValue::String(
request request
.parts .parts
+20 -2
View File
@@ -1,3 +1,5 @@
use std::collections::BTreeMap;
use axum::extract::State; use axum::extract::State;
use conduit::{Error, Result}; use conduit::{Error, Result};
use get_profile_information::v1::ProfileField; use get_profile_information::v1::ProfileField;
@@ -75,6 +77,8 @@ pub(crate) async fn get_profile_information_route(
let mut displayname = None; let mut displayname = None;
let mut avatar_url = None; let mut avatar_url = None;
let mut blurhash = None; let mut blurhash = None;
let mut tz = None;
let mut custom_profile_fields = BTreeMap::new();
match &body.field { match &body.field {
Some(ProfileField::DisplayName) => { Some(ProfileField::DisplayName) => {
@@ -84,12 +88,24 @@ pub(crate) async fn get_profile_information_route(
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)?;
}, },
// TODO: what to do with custom Some(custom_field) => {
Some(_) => {}, if let Some(value) = services
.users
.profile_key(&body.user_id, custom_field.as_str())?
{
custom_profile_fields.insert(custom_field.to_string(), value);
}
},
None => { None => {
displayname = services.users.displayname(&body.user_id)?; displayname = services.users.displayname(&body.user_id)?;
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)?;
tz = services.users.timezone(&body.user_id)?;
custom_profile_fields = services
.users
.all_profile_keys(&body.user_id)
.filter_map(Result::ok)
.collect();
}, },
} }
@@ -97,5 +113,7 @@ pub(crate) async fn get_profile_information_route(
displayname, displayname,
avatar_url, avatar_url,
blurhash, blurhash,
tz,
custom_profile_fields,
}) })
} }
-5
View File
@@ -52,10 +52,6 @@ zstd_compression = [
perf_measurements = [] perf_measurements = []
sentry_telemetry = [] sentry_telemetry = []
# these do nothing, these are purely for informing users to update their build scripts if they use one
rocksdb = []
sha256_media = []
[dependencies] [dependencies]
argon2.workspace = true argon2.workspace = true
arrayvec.workspace = true arrayvec.workspace = true
@@ -83,7 +79,6 @@ regex.workspace = true
reqwest.workspace = true reqwest.workspace = true
ring.workspace = true ring.workspace = true
ruma.workspace = true ruma.workspace = true
rustls.workspace = true
sanitize-filename.workspace = true sanitize-filename.workspace = true
serde_json.workspace = true serde_json.workspace = true
serde_regex.workspace = true serde_regex.workspace = true
-16
View File
@@ -9,22 +9,6 @@ pub fn check(config: &Config) -> Result<()> {
info!("Note: conduwuit was built without optimisations (i.e. debug build)"); info!("Note: conduwuit was built without optimisations (i.e. debug build)");
} }
// prevents catching this in `--all-features`
if cfg!(all(feature = "rocksdb", not(feature = "sha256_media"))) {
warn!(
"Note the rocksdb feature was deleted from conduwuit. SQLite support was removed and RocksDB is the only \
supported backend now. Please update your build script to remove this feature."
);
}
// prevents catching this in `--all-features`
if cfg!(all(feature = "sha256_media", not(feature = "rocksdb"))) {
warn!(
"Note the sha256_media feature was deleted from conduwuit, it is now fully integrated in a \
forwards-compatible way. Please update your build script to remove this feature."
);
}
warn_deprecated(config); warn_deprecated(config);
warn_unknown_key(config); warn_unknown_key(config);
+65 -38
View File
@@ -21,7 +21,7 @@ use url::Url;
pub use self::check::check; pub use self::check::check;
use self::proxy::ProxyConfig; use self::proxy::ProxyConfig;
use crate::{error::Error, Err, Result}; use crate::{error::Error, utils::sys, Err, Result};
pub mod check; pub mod check;
pub mod proxy; pub mod proxy;
@@ -184,6 +184,8 @@ pub struct Config {
pub query_trusted_key_servers_first: bool, pub query_trusted_key_servers_first: bool,
#[serde(default = "default_log")] #[serde(default = "default_log")]
pub log: String, pub log: String,
#[serde(default = "true_fn", alias = "log_colours")]
pub log_colors: bool,
#[serde(default = "default_openid_token_ttl")] #[serde(default = "default_openid_token_ttl")]
pub openid_token_ttl: u64, pub openid_token_ttl: u64,
#[serde(default)] #[serde(default)]
@@ -194,6 +196,7 @@ pub struct Config {
pub turn_uris: Vec<String>, pub turn_uris: Vec<String>,
#[serde(default)] #[serde(default)]
pub turn_secret: String, pub turn_secret: String,
pub turn_secret_file: Option<PathBuf>,
#[serde(default = "default_turn_ttl")] #[serde(default = "default_turn_ttl")]
pub turn_ttl: u64, pub turn_ttl: u64,
@@ -374,6 +377,13 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub test: BTreeSet<String>, pub test: BTreeSet<String>,
/// Controls whether admin room notices like account registrations, password
/// changes, account deactivations, room directory publications, etc will
/// be sent to the admin room. Update notices and normal admin command
/// responses will still be sent.
#[serde(default = "true_fn")]
pub admin_room_notices: bool,
#[serde(flatten)] #[serde(flatten)]
#[allow(clippy::zero_sized_map_values)] // this is a catchall, the map shouldn't be zero at runtime #[allow(clippy::zero_sized_map_values)] // this is a catchall, the map shouldn't be zero at runtime
catchall: BTreeMap<String, IgnoredAny>, catchall: BTreeMap<String, IgnoredAny>,
@@ -426,29 +436,26 @@ const DEPRECATED_KEYS: &[&str; 9] = &[
impl Config { impl Config {
/// Pre-initialize config /// Pre-initialize config
pub fn load(path: &Option<PathBuf>) -> Result<Figment> { pub fn load(paths: &Option<Vec<PathBuf>>) -> Result<Figment> {
let raw_config = if let Some(config_file_env) = Env::var("CONDUIT_CONFIG") { let raw_config = if let Some(config_file_env) = Env::var("CONDUIT_CONFIG") {
Figment::new() Figment::new().merge(Toml::file(config_file_env).nested())
.merge(Toml::file(config_file_env).nested())
.merge(Env::prefixed("CONDUIT_").global().split("__"))
.merge(Env::prefixed("CONDUWUIT_").global().split("__"))
} else if let Some(config_file_arg) = Env::var("CONDUWUIT_CONFIG") { } else if let Some(config_file_arg) = Env::var("CONDUWUIT_CONFIG") {
Figment::new() Figment::new().merge(Toml::file(config_file_arg).nested())
.merge(Toml::file(config_file_arg).nested()) } else if let Some(config_file_args) = paths {
.merge(Env::prefixed("CONDUIT_").global().split("__")) let mut figment = Figment::new();
.merge(Env::prefixed("CONDUWUIT_").global().split("__"))
} else if let Some(config_file_arg) = path { for config in config_file_args {
Figment::new() figment = figment.merge(Toml::file(config).nested());
.merge(Toml::file(config_file_arg).nested()) }
.merge(Env::prefixed("CONDUIT_").global().split("__"))
.merge(Env::prefixed("CONDUWUIT_").global().split("__")) figment
} else { } else {
Figment::new() Figment::new()
.merge(Env::prefixed("CONDUIT_").global().split("__"))
.merge(Env::prefixed("CONDUWUIT_").global().split("__"))
}; };
Ok(raw_config) Ok(raw_config
.merge(Env::prefixed("CONDUIT_").global().split("__"))
.merge(Env::prefixed("CONDUWUIT_").global().split("__")))
} }
/// Finalize config /// Finalize config
@@ -466,7 +473,11 @@ impl Config {
#[must_use] #[must_use]
pub fn get_bind_addrs(&self) -> Vec<SocketAddr> { pub fn get_bind_addrs(&self) -> Vec<SocketAddr> {
let mut addrs = Vec::new(); let mut addrs = Vec::with_capacity(
self.get_bind_hosts()
.len()
.saturating_add(self.get_bind_ports().len()),
);
for host in &self.get_bind_hosts() { for host in &self.get_bind_hosts() {
for port in &self.get_bind_ports() { for port in &self.get_bind_ports() {
addrs.push(SocketAddr::new(*host, *port)); addrs.push(SocketAddr::new(*host, *port));
@@ -684,15 +695,20 @@ impl fmt::Display for Config {
} }
}); });
line("TURN secret", { line("TURN secret", {
if self.turn_secret.is_empty() { if self.turn_secret.is_empty() && self.turn_secret_file.is_none() {
"not set" "not set"
} else { } else {
"set" "set"
} }
}); });
line("TURN secret file path", {
self.turn_secret_file
.as_ref()
.map_or("", |path| path.to_str().unwrap_or_default())
});
line("Turn TTL", &self.turn_ttl.to_string()); line("Turn TTL", &self.turn_ttl.to_string());
line("Turn URIs", { line("Turn URIs", {
let mut lst = vec![]; let mut lst = Vec::with_capacity(self.turn_uris.len());
for item in self.turn_uris.iter().cloned().enumerate() { for item in self.turn_uris.iter().cloned().enumerate() {
let (_, uri): (usize, String) = item; let (_, uri): (usize, String) = item;
lst.push(uri); lst.push(uri);
@@ -700,7 +716,7 @@ impl fmt::Display for Config {
&lst.join(", ") &lst.join(", ")
}); });
line("Auto Join Rooms", { line("Auto Join Rooms", {
let mut lst = vec![]; let mut lst = Vec::with_capacity(self.auto_join_rooms.len());
for room in &self.auto_join_rooms { for room in &self.auto_join_rooms {
lst.push(room); lst.push(room);
} }
@@ -752,28 +768,28 @@ impl fmt::Display for Config {
line("Allow legacy (unauthenticated) media", &self.allow_legacy_media.to_string()); line("Allow legacy (unauthenticated) media", &self.allow_legacy_media.to_string());
line("Freeze legacy (unauthenticated) media", &self.freeze_legacy_media.to_string()); line("Freeze legacy (unauthenticated) media", &self.freeze_legacy_media.to_string());
line("Prevent Media Downloads From", { line("Prevent Media Downloads From", {
let mut lst = vec![]; let mut lst = Vec::with_capacity(self.prevent_media_downloads_from.len());
for domain in &self.prevent_media_downloads_from { for domain in &self.prevent_media_downloads_from {
lst.push(domain.host()); lst.push(domain.host());
} }
&lst.join(", ") &lst.join(", ")
}); });
line("Forbidden Remote Server Names (\"Global\" ACLs)", { line("Forbidden Remote Server Names (\"Global\" ACLs)", {
let mut lst = vec![]; let mut lst = Vec::with_capacity(self.forbidden_remote_server_names.len());
for domain in &self.forbidden_remote_server_names { for domain in &self.forbidden_remote_server_names {
lst.push(domain.host()); lst.push(domain.host());
} }
&lst.join(", ") &lst.join(", ")
}); });
line("Forbidden Remote Room Directory Server Names", { line("Forbidden Remote Room Directory Server Names", {
let mut lst = vec![]; let mut lst = Vec::with_capacity(self.forbidden_remote_room_directory_server_names.len());
for domain in &self.forbidden_remote_room_directory_server_names { for domain in &self.forbidden_remote_room_directory_server_names {
lst.push(domain.host()); lst.push(domain.host());
} }
&lst.join(", ") &lst.join(", ")
}); });
line("Outbound Request IP Range Denylist", { line("Outbound Request IP Range (CIDR) Denylist", {
let mut lst = vec![]; let mut lst = Vec::with_capacity(self.ip_range_denylist.len());
for item in self.ip_range_denylist.iter().cloned().enumerate() { for item in self.ip_range_denylist.iter().cloned().enumerate() {
let (_, ip): (usize, String) = item; let (_, ip): (usize, String) = item;
lst.push(ip); lst.push(ip);
@@ -862,6 +878,7 @@ impl fmt::Display for Config {
.map_or("", |url| url.as_str()), .map_or("", |url| url.as_str()),
); );
line("Enable the tokio-console", &self.tokio_console.to_string()); line("Enable the tokio-console", &self.tokio_console.to_string());
line("Admin room notices", &self.admin_room_notices.to_string());
Ok(()) Ok(())
} }
@@ -887,29 +904,29 @@ fn default_database_backups_to_keep() -> i16 { 1 }
fn default_database_backend() -> String { "rocksdb".to_owned() } fn default_database_backend() -> String { "rocksdb".to_owned() }
fn default_db_cache_capacity_mb() -> f64 { 256.0 } fn default_db_cache_capacity_mb() -> f64 { 128.0 + parallelism_scaled_f64(64.0) }
fn default_pdu_cache_capacity() -> u32 { 150_000 } fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
fn default_cache_capacity_modifier() -> f64 { 1.0 } fn default_cache_capacity_modifier() -> f64 { 1.0 }
fn default_auth_chain_cache_capacity() -> u32 { 100_000 } fn default_auth_chain_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
fn default_shorteventid_cache_capacity() -> u32 { 500_000 } fn default_shorteventid_cache_capacity() -> u32 { parallelism_scaled_u32(50_000).saturating_add(100_000) }
fn default_eventidshort_cache_capacity() -> u32 { 100_000 } fn default_eventidshort_cache_capacity() -> u32 { parallelism_scaled_u32(25_000).saturating_add(100_000) }
fn default_shortstatekey_cache_capacity() -> u32 { 100_000 } fn default_shortstatekey_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
fn default_statekeyshort_cache_capacity() -> u32 { 100_000 } fn default_statekeyshort_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
fn default_server_visibility_cache_capacity() -> u32 { 100 } fn default_server_visibility_cache_capacity() -> u32 { parallelism_scaled_u32(500) }
fn default_user_visibility_cache_capacity() -> u32 { 100 } fn default_user_visibility_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
fn default_stateinfo_cache_capacity() -> u32 { 100 } fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
fn default_roomid_spacehierarchy_cache_capacity() -> u32 { 100 } fn default_roomid_spacehierarchy_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
fn default_dns_cache_entries() -> u32 { 32768 } fn default_dns_cache_entries() -> u32 { 32768 }
@@ -1087,3 +1104,13 @@ fn default_admin_log_capture() -> String {
} }
fn default_admin_room_tag() -> String { "m.server_notice".to_owned() } fn default_admin_room_tag() -> String { "m.server_notice".to_owned() }
#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
fn parallelism_scaled_u32(val: u32) -> u32 {
let val = val.try_into().expect("failed to cast u32 to usize");
parallelism_scaled(val).try_into().unwrap_or(u32::MAX)
}
fn parallelism_scaled(val: usize) -> usize { val.saturating_mul(sys::available_parallelism()) }
+1 -1
View File
@@ -61,7 +61,7 @@ pub fn set_panic_trap() {
#[inline(always)] #[inline(always)]
#[allow(deprecated_in_future)] #[allow(deprecated_in_future)]
fn panic_handler(info: &panic::PanicInfo<'_>, next: &dyn Fn(&panic::PanicInfo<'_>)) { fn panic_handler(info: &panic::PanicHookInfo<'_>, next: &dyn Fn(&panic::PanicHookInfo<'_>)) {
trap(); trap();
next(info); next(info);
} }
+1
View File
@@ -94,6 +94,7 @@ pub const MAPS: &[&str] = &[
"userid_presenceid", "userid_presenceid",
"userid_selfsigningkeyid", "userid_selfsigningkeyid",
"userid_usersigningkeyid", "userid_usersigningkeyid",
"useridprofilekey_value",
"openidtoken_expiresatuserid", "openidtoken_expiresatuserid",
"userroomid_highlightcount", "userroomid_highlightcount",
"userroomid_invitestate", "userroomid_invitestate",
+4
View File
@@ -42,6 +42,7 @@ default = [
"gzip_compression", "gzip_compression",
"io_uring", "io_uring",
"jemalloc", "jemalloc",
"jemalloc_stats",
"release_max_log_level", "release_max_log_level",
"sentry_telemetry", "sentry_telemetry",
"systemd", "systemd",
@@ -65,6 +66,9 @@ console = [
# "conduit-router/dev_release_log_level", # "conduit-router/dev_release_log_level",
# "conduit-service/dev_release_log_level", # "conduit-service/dev_release_log_level",
#] #]
direct_tls = [
"conduit-router/direct_tls"
]
element_hacks = [ element_hacks = [
"conduit-api/element_hacks", "conduit-api/element_hacks",
"conduit-service/element_hacks", "conduit-service/element_hacks",
+1 -1
View File
@@ -14,7 +14,7 @@ use conduit::{
pub(crate) struct Args { pub(crate) struct Args {
#[arg(short, long)] #[arg(short, long)]
/// Path to the config TOML file (optional) /// Path to the config TOML file (optional)
pub(crate) config: Option<PathBuf>, pub(crate) config: Option<Vec<PathBuf>>,
/// Override a configuration variable using TOML 'key=value' syntax /// Override a configuration variable using TOML 'key=value' syntax
#[arg(long, short('O'))] #[arg(long, short('O'))]
+1 -1
View File
@@ -18,7 +18,7 @@ pub(crate) fn init(config: &Config) -> Result<(LogLevelReloadHandles, TracingFla
let reload_handles = LogLevelReloadHandles::default(); let reload_handles = LogLevelReloadHandles::default();
let console_filter = EnvFilter::try_new(&config.log).map_err(|e| err!(Config("log", "{e}.")))?; let console_filter = EnvFilter::try_new(&config.log).map_err(|e| err!(Config("log", "{e}.")))?;
let console_layer = tracing_subscriber::fmt::Layer::new(); let console_layer = tracing_subscriber::fmt::Layer::new().with_ansi(config.log_colors);
let (console_reload_filter, console_reload_handle) = reload::Layer::new(console_filter.clone()); let (console_reload_filter, console_reload_handle) = reload::Layer::new(console_filter.clone());
reload_handles.add("console", Box::new(console_reload_handle)); reload_handles.add("console", Box::new(console_reload_handle));
+8
View File
@@ -42,9 +42,16 @@ systemd = [
"dep:sd-notify", "dep:sd-notify",
] ]
direct_tls = [
"axum-server/tls-rustls",
"dep:rustls",
"dep:axum-server-dual-protocol",
]
[dependencies] [dependencies]
axum-client-ip.workspace = true axum-client-ip.workspace = true
axum-server-dual-protocol.workspace = true axum-server-dual-protocol.workspace = true
axum-server-dual-protocol.optional = true
axum-server.workspace = true axum-server.workspace = true
axum.workspace = true axum.workspace = true
conduit-admin.workspace = true conduit-admin.workspace = true
@@ -63,6 +70,7 @@ hyper.workspace = true
hyper-util.workspace = true hyper-util.workspace = true
ruma.workspace = true ruma.workspace = true
rustls.workspace = true rustls.workspace = true
rustls.optional = true
sentry.optional = true sentry.optional = true
sentry-tower.optional = true sentry-tower.optional = true
sentry-tower.workspace = true sentry-tower.workspace = true
+9 -1
View File
@@ -1,4 +1,5 @@
mod plain; mod plain;
#[cfg(feature = "direct_tls")]
mod tls; mod tls;
mod unix; mod unix;
@@ -23,7 +24,14 @@ pub(super) async fn serve(
if cfg!(unix) && config.unix_socket_path.is_some() { if cfg!(unix) && config.unix_socket_path.is_some() {
unix::serve(server, app, shutdown).await unix::serve(server, app, shutdown).await
} else if config.tls.is_some() { } else if config.tls.is_some() {
tls::serve(server, app, handle, addrs).await #[cfg(feature = "direct_tls")]
return tls::serve(server, app, handle, addrs).await;
#[cfg(not(feature = "direct_tls"))]
return conduit::Err!(Config(
"tls",
"conduwuit was not built with direct TLS support (\"direct_tls\")"
));
} else { } else {
plain::serve(server, app, handle, addrs).await plain::serve(server, app, handle, addrs).await
} }
+3 -1
View File
@@ -20,7 +20,9 @@ pub(super) async fn serve(
// we use ring for ruma and hashing state, but aws-lc-rs is the new default. // we use ring for ruma and hashing state, but aws-lc-rs is the new default.
// without this, TLS mode will panic. // without this, TLS mode will panic.
_ = rustls::crypto::aws_lc_rs::default_provider().install_default(); rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.expect("failed to initialise aws-lc-rs rustls crypto provider");
debug!("Using direct TLS. Certificate path {certs} and certificate private key path {key}",); debug!("Using direct TLS. Certificate path {certs} and certificate private key path {key}",);
info!( info!(
+1 -2
View File
@@ -101,8 +101,7 @@ pub async fn create_admin_room(services: &Services) -> Result<()> {
.await?; .await?;
// 3. Power levels // 3. Power levels
let mut users = BTreeMap::new(); let users = BTreeMap::from_iter([(server_user.clone(), 100.into())]);
users.insert(server_user.clone(), 100.into());
services services
.rooms .rooms
+3 -5
View File
@@ -21,7 +21,7 @@ impl super::Service {
/// Invite the user to the conduit admin room. /// Invite the user to the conduit admin room.
/// ///
/// In conduit, this is equivalent to granting admin privileges. /// In conduit, this is equivalent to granting admin privileges.
pub async fn make_user_admin(&self, user_id: &UserId, displayname: String) -> Result<()> { pub async fn make_user_admin(&self, user_id: &UserId) -> Result<()> {
let Some(room_id) = self.get_admin_room()? else { let Some(room_id) = self.get_admin_room()? else {
return Ok(()); return Ok(());
}; };
@@ -65,7 +65,7 @@ impl super::Service {
event_type: TimelineEventType::RoomMember, event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent { content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join, membership: MembershipState::Join,
displayname: Some(displayname), displayname: None,
avatar_url: None, avatar_url: None,
is_direct: None, is_direct: None,
third_party_invite: None, third_party_invite: None,
@@ -86,9 +86,7 @@ impl super::Service {
.await?; .await?;
// Set power level // Set power level
let mut users = BTreeMap::new(); let users = BTreeMap::from_iter([(server_user.clone(), 100.into()), (user_id.to_owned(), 100.into())]);
users.insert(server_user.clone(), 100.into());
users.insert(user_id.to_owned(), 100.into());
self.services self.services
.timeline .timeline
+14 -3
View File
@@ -24,6 +24,14 @@ use crate::{media, Services};
/// equal or lesser version. These are expected to be backward-compatible. /// equal or lesser version. These are expected to be backward-compatible.
pub(crate) const DATABASE_VERSION: u64 = 13; pub(crate) const DATABASE_VERSION: u64 = 13;
/// Conduit's database version.
///
/// Conduit bumped the database version to 16, but did not introduce any
/// breaking changes. Their database migrations are extremely fragile and risky,
/// and also do not really apply to us, so just to retain Conduit -> conduwuit
/// compatibility we'll check for both versions.
pub(crate) const CONDUIT_DATABASE_VERSION: u64 = 16;
pub(crate) async fn migrations(services: &Services) -> Result<()> { pub(crate) async fn migrations(services: &Services) -> Result<()> {
// Matrix resource ownership is based on the server name; changing it // Matrix resource ownership is based on the server name; changing it
// requires recreating the database from scratch. // requires recreating the database from scratch.
@@ -54,6 +62,7 @@ async fn fresh(services: &Services) -> Result<()> {
.db .db
.bump_database_version(DATABASE_VERSION)?; .bump_database_version(DATABASE_VERSION)?;
db["global"].insert(b"feat_sha256_media", &[])?;
db["global"].insert(b"fix_bad_double_separator_in_state_cache", &[])?; db["global"].insert(b"fix_bad_double_separator_in_state_cache", &[])?;
db["global"].insert(b"retroactively_fix_bad_data_from_roomuserid_joined", &[])?; db["global"].insert(b"retroactively_fix_bad_data_from_roomuserid_joined", &[])?;
@@ -147,9 +156,11 @@ async fn migrate(services: &Services) -> Result<()> {
retroactively_fix_bad_data_from_roomuserid_joined(services).await?; retroactively_fix_bad_data_from_roomuserid_joined(services).await?;
} }
assert_eq!( let version_match = services.globals.db.database_version().unwrap() == DATABASE_VERSION
services.globals.db.database_version().unwrap(), || services.globals.db.database_version().unwrap() == CONDUIT_DATABASE_VERSION;
DATABASE_VERSION,
assert!(
version_match,
"Failed asserting local database version {} is equal to known latest conduwuit database version {}", "Failed asserting local database version {} is equal to known latest conduwuit database version {}",
services.globals.db.database_version().unwrap(), services.globals.db.database_version().unwrap(),
DATABASE_VERSION, DATABASE_VERSION,
+22 -8
View File
@@ -40,6 +40,7 @@ pub struct Service {
pub stateres_mutex: Arc<Mutex<()>>, pub stateres_mutex: Arc<Mutex<()>>,
pub server_user: OwnedUserId, pub server_user: OwnedUserId,
pub admin_alias: OwnedRoomAliasId, pub admin_alias: OwnedRoomAliasId,
pub turn_secret: String,
} }
type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries type RateLimitState = (Instant, u32); // Time if last failed try, number of failed tries
@@ -84,6 +85,17 @@ impl crate::Service for Service {
.collect::<Result<_, String>>() .collect::<Result<_, String>>()
.map_err(|e| err!(Config("ip_range_denylist", e)))?; .map_err(|e| err!(Config("ip_range_denylist", e)))?;
let turn_secret = config
.turn_secret_file
.as_ref()
.map_or(config.turn_secret.clone(), |path| {
std::fs::read_to_string(path).unwrap_or_else(|e| {
error!("Failed to read the TURN secret file: {e}");
config.turn_secret.clone()
})
});
let mut s = Self { let mut s = Self {
db, db,
config: config.clone(), config: config.clone(),
@@ -99,6 +111,7 @@ impl crate::Service for Service {
.expect("#admins:server_name is valid alias name"), .expect("#admins:server_name is valid alias name"),
server_user: UserId::parse_with_server_name(String::from("conduit"), &config.server_name) server_user: UserId::parse_with_server_name(String::from("conduit"), &config.server_name)
.expect("@conduit:server_name is valid"), .expect("@conduit:server_name is valid"),
turn_secret,
}; };
if !s if !s
@@ -207,8 +220,6 @@ impl Service {
pub fn turn_username(&self) -> &String { &self.config.turn_username } pub fn turn_username(&self) -> &String { &self.config.turn_username }
pub fn turn_secret(&self) -> &String { &self.config.turn_secret }
pub fn allow_profile_lookup_federation_requests(&self) -> bool { pub fn allow_profile_lookup_federation_requests(&self) -> bool {
self.config.allow_profile_lookup_federation_requests self.config.allow_profile_lookup_federation_requests
} }
@@ -264,12 +275,15 @@ impl Service {
pub fn block_non_admin_invites(&self) -> bool { self.config.block_non_admin_invites } pub fn block_non_admin_invites(&self) -> bool { self.config.block_non_admin_invites }
pub fn supported_room_versions(&self) -> Vec<RoomVersionId> { pub fn supported_room_versions(&self) -> Vec<RoomVersionId> {
let mut room_versions: Vec<RoomVersionId> = Vec::with_capacity(self.stable_room_versions.len()); if self.config.allow_unstable_room_versions {
room_versions.extend(self.stable_room_versions.clone()); self.stable_room_versions
if self.allow_unstable_room_versions() { .clone()
room_versions.extend(self.unstable_room_versions.clone()); .into_iter()
}; .chain(self.unstable_room_versions.clone())
room_versions .collect()
} else {
self.stable_room_versions.clone()
}
} }
/// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found /// This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
+61 -67
View File
@@ -10,7 +10,7 @@ use std::{path::PathBuf, sync::Arc, time::SystemTime};
use async_trait::async_trait; use async_trait::async_trait;
use base64::{engine::general_purpose, Engine as _}; use base64::{engine::general_purpose, Engine as _};
use conduit::{ use conduit::{
debug, debug_error, debug_info, err, error, trace, debug, debug_error, debug_info, debug_warn, err, error, trace,
utils::{self, MutexMap}, utils::{self, MutexMap},
warn, Err, Result, Server, warn, Err, Result, Server,
}; };
@@ -99,45 +99,46 @@ impl Service {
pub async fn delete(&self, mxc: &Mxc<'_>) -> Result<()> { pub async fn delete(&self, mxc: &Mxc<'_>) -> Result<()> {
if let Ok(keys) = self.db.search_mxc_metadata_prefix(mxc) { if let Ok(keys) = self.db.search_mxc_metadata_prefix(mxc) {
for key in keys { for key in keys {
trace!(?mxc, ?key, "Deleting from filesystem"); trace!(?mxc, "MXC Key: {key:?}");
debug_info!(?mxc, "Deleting from filesystem");
if let Err(e) = self.remove_media_file(&key).await { if let Err(e) = self.remove_media_file(&key).await {
error!(?mxc, ?key, "Failed to remove media file: {e}"); debug_error!(?mxc, "Failed to remove media file: {e}");
} }
trace!(?mxc, ?key, "Deleting from database"); debug_info!(?mxc, "Deleting from database");
if let Err(e) = self.db.delete_file_mxc(mxc) { _ = self.db.delete_file_mxc(mxc);
error!(?mxc, ?key, "Failed to remove media from database: {e}");
}
} }
Ok(()) Ok(())
} else { } else {
Err!(Database(error!( Err!(Database(error!("Failed to find any media keys for MXC {mxc} in our database.")))
"Failed to find any media keys for MXC {mxc:?} in our database."
)))
} }
} }
/// Deletes all media by the specified user /// Deletes all media by the specified user
/// ///
/// currently, this is only practical for local users /// currently, this is only practical for local users
pub async fn delete_from_user(&self, user: &UserId, force: bool) -> Result<usize> { pub async fn delete_from_user(&self, user: &UserId) -> Result<usize> {
let mxcs = self.db.get_all_user_mxcs(user); let mxcs = self.db.get_all_user_mxcs(user);
let mut deletion_count: usize = 0; let mut deletion_count: usize = 0;
for mxc in mxcs { for mxc in mxcs {
let mxc: Mxc<'_> = mxc.as_str().try_into()?; let Ok(mxc) = mxc.as_str().try_into().inspect_err(|e| {
debug_info!("Deleting MXC {mxc} by user {user} from database and filesystem"); debug_error!(?mxc, "Failed to parse MXC URI from database: {e}");
if force { }) else {
_ = self continue;
.delete(&mxc) };
.await
.inspect_err(|e| warn!("Failed to delete {mxc} from user {user}, ignoring error: {e}"));
} else {
self.delete(&mxc).await?;
}
deletion_count = deletion_count.saturating_add(1); debug_info!(%deletion_count, "Deleting MXC {mxc} by user {user} from database and filesystem");
match self.delete(&mxc).await {
Ok(()) => {
deletion_count = deletion_count.saturating_add(1);
},
Err(e) => {
debug_error!(%deletion_count, "Failed to delete {mxc} from user {user}, ignoring error: {e}");
},
}
} }
Ok(deletion_count) Ok(deletion_count)
@@ -176,9 +177,6 @@ impl Service {
for key in all_keys { for key in all_keys {
trace!("Full MXC key from database: {key:?}"); trace!("Full MXC key from database: {key:?}");
// we need to get the MXC URL from the first part of the key (the first 0xff /
// 255 push). this is all necessary because of conduit using magic keys for
// media
let mut parts = key.split(|&b| b == 0xFF); let mut parts = key.split(|&b| b == 0xFF);
let mxc = parts let mxc = parts
.next() .next()
@@ -189,31 +187,33 @@ impl Service {
.transpose()?; .transpose()?;
let Some(mxc_s) = mxc else { let Some(mxc_s) = mxc else {
return Err!(Database("Parsed MXC URL unicode bytes from database but still is None")); debug_warn!(?mxc, "Parsed MXC URL unicode bytes from database but is still invalid");
continue;
}; };
trace!("Parsed MXC key to URL: {mxc_s}"); trace!("Parsed MXC key to URL: {mxc_s}");
let mxc = OwnedMxcUri::from(mxc_s); let mxc = OwnedMxcUri::from(mxc_s);
mxcs.push(mxc); if mxc.is_valid() {
mxcs.push(mxc);
} else {
debug_warn!("{mxc:?} from database was found to not be valid");
}
} }
Ok(mxcs) Ok(mxcs)
} }
/// Deletes all remote only media files in the given at or after /// Deletes all remote only media files in the given at or after
/// time/duration. Returns a u32 with the amount of media files deleted. /// time/duration. Returns a usize with the amount of media files deleted.
pub async fn delete_all_remote_media_at_after_time(&self, time: SystemTime, force: bool) -> Result<usize> { pub async fn delete_all_remote_media_at_after_time(
&self, time: SystemTime, before: bool, after: bool, yes_i_want_to_delete_local_media: bool,
) -> Result<usize> {
let all_keys = self.db.get_all_media_keys(); let all_keys = self.db.get_all_media_keys();
let mut remote_mxcs = Vec::with_capacity(all_keys.len()); let mut remote_mxcs = Vec::with_capacity(all_keys.len());
for key in all_keys { for key in all_keys {
trace!("Full MXC key from database: {key:?}"); trace!("Full MXC key from database: {key:?}");
// we need to get the MXC URL from the first part of the key (the first 0xff /
// 255 push). this is all necessary because of conduit using magic keys for
// media
let mut parts = key.split(|&b| b == 0xFF); let mut parts = key.split(|&b| b == 0xFF);
let mxc = parts let mxc = parts
.next() .next()
@@ -224,35 +224,30 @@ impl Service {
.transpose()?; .transpose()?;
let Some(mxc_s) = mxc else { let Some(mxc_s) = mxc else {
return Err!(Database("Parsed MXC URL unicode bytes from database but still is None")); debug_warn!(?mxc, "Parsed MXC URL unicode bytes from database but is still invalid");
continue;
}; };
trace!("Parsed MXC key to URL: {mxc_s}"); trace!("Parsed MXC key to URL: {mxc_s}");
let mxc = OwnedMxcUri::from(mxc_s); let mxc = OwnedMxcUri::from(mxc_s);
if mxc.server_name() == Ok(self.services.globals.server_name()) { if (mxc.server_name() == Ok(self.services.globals.server_name()) && !yes_i_want_to_delete_local_media)
debug!("Ignoring local media MXC: {mxc}"); || !mxc.is_valid()
// ignore our own MXC URLs as this would be local media. {
debug!("Ignoring local or broken media MXC: {mxc}");
continue; continue;
} }
let path = self.get_media_file(&key); let path = self.get_media_file(&key);
debug!("MXC path: {path:?}");
let file_metadata = match fs::metadata(path.clone()).await { let file_metadata = match fs::metadata(path.clone()).await {
Ok(file_metadata) => file_metadata, Ok(file_metadata) => file_metadata,
Err(e) => { Err(e) => {
if force { error!("Failed to obtain file metadata for MXC {mxc} at file path \"{path:?}\", skipping: {e}");
error!("Failed to obtain file metadata for MXC {mxc} at file path \"{path:?}\", skipping: {e}"); continue;
continue;
}
return Err!(Database(
"Failed to obtain file metadata for MXC {mxc} at file path \"{path:?}\": {e}"
));
}, },
}; };
debug!("File metadata: {file_metadata:?}"); trace!(%mxc, ?path, "File metadata: {file_metadata:?}");
let file_created_at = match file_metadata.created() { let file_created_at = match file_metadata.created() {
Ok(value) => value, Ok(value) => value,
@@ -261,33 +256,36 @@ impl Service {
file_metadata.modified()? file_metadata.modified()?
}, },
Err(err) => { Err(err) => {
if force { error!("Could not delete MXC {mxc} at path {path:?}: {err:?}. Skipping...");
error!("Could not delete MXC {mxc} at path {path:?}: {err:?}. Skipping..."); continue;
continue;
}
return Err(err.into());
}, },
}; };
debug!("File created at: {file_created_at:?}"); debug!("File created at: {file_created_at:?}");
if file_created_at <= time {
debug!("File is within user duration, pushing to list of file paths and keys to delete."); if file_created_at >= time && before {
debug!("File is within (before) user duration, pushing to list of file paths and keys to delete.");
remote_mxcs.push(mxc.to_string());
} else if file_created_at <= time && after {
debug!("File is not within (after) user duration, pushing to list of file paths and keys to delete.");
remote_mxcs.push(mxc.to_string()); remote_mxcs.push(mxc.to_string());
} }
} }
debug!(
"Finished going through all our media in database for eligible keys to delete, checking if these are empty"
);
if remote_mxcs.is_empty() { if remote_mxcs.is_empty() {
return Err!(Database("Did not found any eligible MXCs to delete.")); return Err!(Database("Did not found any eligible MXCs to delete."));
} }
debug_info!("Deleting media now in the past {time:?}."); debug_info!("Deleting media now in the past {time:?}");
let mut deletion_count: usize = 0; let mut deletion_count: usize = 0;
for mxc in remote_mxcs { for mxc in remote_mxcs {
let mxc: Mxc<'_> = mxc.as_str().try_into()?; let Ok(mxc) = mxc.as_str().try_into() else {
debug_warn!("Invalid MXC in database, skipping");
continue;
};
debug_info!("Deleting MXC {mxc} from database and filesystem"); debug_info!("Deleting MXC {mxc} from database and filesystem");
match self.delete(&mxc).await { match self.delete(&mxc).await {
@@ -295,12 +293,8 @@ impl Service {
deletion_count = deletion_count.saturating_add(1); deletion_count = deletion_count.saturating_add(1);
}, },
Err(e) => { Err(e) => {
if force { warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
warn!("Failed to delete {mxc}, ignoring error and skipping: {e}"); continue;
continue;
}
return Err!(Database(warn!("Failed to delete MXC {mxc}: {e}")));
}, },
} }
} }
+1
View File
@@ -63,6 +63,7 @@ impl Data {
.iter() .iter()
.enumerate() .enumerate()
{ {
#[allow(clippy::single_match_else)]
match short { match short {
Some(short) => ret.push( Some(short) => ret.push(
utils::u64_from_bytes(short).map_err(|_| Error::bad_database("Invalid shorteventid in db."))?, utils::u64_from_bytes(short).map_err(|_| Error::bad_database("Invalid shorteventid in db."))?,
+5 -1
View File
@@ -479,6 +479,8 @@ impl Service {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
))) )))
.into(), .into(),
ephemeral: Vec::new(),
to_device: Vec::new(),
}, },
) )
.await .await
@@ -636,7 +638,9 @@ impl Service {
.pdus .pdus
.iter() .iter()
.filter(|(_, res)| res.is_err()) .filter(|(_, res)| res.is_err())
.for_each(|(pdu_id, res)| warn!("error for {pdu_id} from remote: {res:?}")); .for_each(
|(pdu_id, res)| warn!(%transaction_id, %server, "error sending PDU {pdu_id} to remote server: {res:?}"),
);
}) })
.map(|_| dest.clone()) .map(|_| dest.clone())
.map_err(|e| (dest.clone(), e)) .map_err(|e| (dest.clone(), e))
+8 -2
View File
@@ -1,13 +1,13 @@
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use async_trait::async_trait; use async_trait::async_trait;
use conduit::{err, info, utils, warn, Error, Result}; use conduit::{debug, err, info, utils, warn, Error, Result};
use database::Map; use database::Map;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use serde::Deserialize; use serde::Deserialize;
use tokio::{sync::Notify, time::interval}; use tokio::{sync::Notify, time::interval};
use crate::{admin, client, Dep}; use crate::{admin, client, globals, Dep};
pub struct Service { pub struct Service {
services: Services, services: Services,
@@ -19,6 +19,7 @@ pub struct Service {
struct Services { struct Services {
admin: Dep<admin::Service>, admin: Dep<admin::Service>,
client: Dep<client::Service>, client: Dep<client::Service>,
globals: Dep<globals::Service>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -42,6 +43,7 @@ impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> { fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self { Ok(Arc::new(Self {
services: Services { services: Services {
globals: args.depend::<globals::Service>("globals"),
admin: args.depend::<admin::Service>("admin"), admin: args.depend::<admin::Service>("admin"),
client: args.depend::<client::Service>("client"), client: args.depend::<client::Service>("client"),
}, },
@@ -52,6 +54,10 @@ impl crate::Service for Service {
} }
async fn worker(self: Arc<Self>) -> Result<()> { async fn worker(self: Arc<Self>) -> Result<()> {
if !self.services.globals.allow_check_for_updates() {
debug!("Disabling update check");
return Ok(());
}
let mut i = interval(self.interval); let mut i = interval(self.interval);
loop { loop {
tokio::select! { tokio::select! {
+107
View File
@@ -32,6 +32,7 @@ pub struct Data {
userid_password: Arc<Map>, userid_password: Arc<Map>,
userid_selfsigningkeyid: Arc<Map>, userid_selfsigningkeyid: Arc<Map>,
userid_usersigningkeyid: Arc<Map>, userid_usersigningkeyid: Arc<Map>,
useridprofilekey_value: Arc<Map>,
services: Services, services: Services,
} }
@@ -64,6 +65,7 @@ impl Data {
userid_password: db["userid_password"].clone(), userid_password: db["userid_password"].clone(),
userid_selfsigningkeyid: db["userid_selfsigningkeyid"].clone(), userid_selfsigningkeyid: db["userid_selfsigningkeyid"].clone(),
userid_usersigningkeyid: db["userid_usersigningkeyid"].clone(), userid_usersigningkeyid: db["userid_usersigningkeyid"].clone(),
useridprofilekey_value: db["useridprofilekey_value"].clone(),
services: Services { services: Services {
server: args.server.clone(), server: args.server.clone(),
globals: args.depend::<globals::Service>("globals"), globals: args.depend::<globals::Service>("globals"),
@@ -231,6 +233,111 @@ impl Data {
.transpose() .transpose()
} }
/// Gets a specific user profile key
pub(super) fn profile_key(&self, user_id: &UserId, profile_key: &str) -> Result<Option<serde_json::Value>> {
let mut key = user_id.as_bytes().to_vec();
key.push(0xFF);
key.extend_from_slice(profile_key.as_bytes());
self.useridprofilekey_value
.get(&key)?
.map_or(Ok(None), |bytes| Ok(Some(serde_json::from_slice(&bytes).unwrap())))
}
/// Gets all the user's profile keys and values in an iterator
pub(super) fn all_profile_keys<'a>(
&'a self, user_id: &UserId,
) -> Box<dyn Iterator<Item = Result<(String, serde_json::Value)>> + 'a + Send> {
let prefix = user_id.as_bytes().to_vec();
Box::new(
self.useridprofilekey_value
.scan_prefix(prefix)
.map(|(key, value)| {
let profile_key_name = utils::string_from_bytes(
key.rsplit(|&b| b == 0xFF)
.next()
.ok_or_else(|| err!(Database("Profile key in db is invalid")))?,
)
.map_err(|e| err!(Database("Profile key in db is invalid. {e}")))?;
let profile_key_value = serde_json::from_slice(&value)
.map_err(|e| err!(Database("Profile key in db is invalid. {e}")))?;
Ok((profile_key_name, profile_key_value))
}),
)
}
/// Sets a new profile key value, removes the key if value is None
pub(super) fn set_profile_key(
&self, user_id: &UserId, profile_key: &str, profile_key_value: Option<serde_json::Value>,
) -> Result<()> {
let mut key = user_id.as_bytes().to_vec();
key.push(0xFF);
key.extend_from_slice(profile_key.as_bytes());
// TODO: insert to the stable MSC4175 key when it's stable
if let Some(value) = profile_key_value {
let value = serde_json::to_vec(&value).unwrap();
self.useridprofilekey_value.insert(&key, &value)
} else {
self.useridprofilekey_value.remove(&key)
}
}
/// Get the timezone of a user.
pub(super) fn timezone(&self, user_id: &UserId) -> Result<Option<String>> {
// first check the unstable prefix
let mut key = user_id.as_bytes().to_vec();
key.push(0xFF);
key.extend_from_slice(b"us.cloke.msc4175.tz");
let value = self
.useridprofilekey_value
.get(&key)?
.map(|bytes| utils::string_from_bytes(&bytes).map_err(|e| err!(Database("Timezone in db is invalid. {e}"))))
.transpose()
.unwrap();
// TODO: transparently migrate unstable key usage to the stable key once MSC4133
// and MSC4175 are stable, likely a remove/insert in this block
if value.is_none() || value.as_ref().is_some_and(String::is_empty) {
// check the stable prefix
let mut key = user_id.as_bytes().to_vec();
key.push(0xFF);
key.extend_from_slice(b"m.tz");
return self
.useridprofilekey_value
.get(&key)?
.map(|bytes| {
utils::string_from_bytes(&bytes).map_err(|e| err!(Database("Timezone in db is invalid. {e}")))
})
.transpose();
}
Ok(value)
}
/// Sets a new timezone or removes it if timezone is None.
pub(super) fn set_timezone(&self, user_id: &UserId, timezone: Option<String>) -> Result<()> {
let mut key = user_id.as_bytes().to_vec();
key.push(0xFF);
key.extend_from_slice(b"us.cloke.msc4175.tz");
// TODO: insert to the stable MSC4175 key when it's stable
if let Some(timezone) = timezone {
self.useridprofilekey_value
.insert(&key, timezone.as_bytes())?;
} else {
self.useridprofilekey_value.remove(&key)?;
}
Ok(())
}
/// Sets a new avatar_url or removes it if avatar_url is None. /// Sets a new avatar_url or removes it if avatar_url is None.
pub(super) fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> { pub(super) fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
if let Some(blurhash) = blurhash { if let Some(blurhash) = blurhash {
+27
View File
@@ -327,6 +327,33 @@ impl Service {
/// Get the blurhash of a user. /// Get the blurhash of a user.
pub fn blurhash(&self, user_id: &UserId) -> Result<Option<String>> { self.db.blurhash(user_id) } pub fn blurhash(&self, user_id: &UserId) -> Result<Option<String>> { self.db.blurhash(user_id) }
pub fn timezone(&self, user_id: &UserId) -> Result<Option<String>> { self.db.timezone(user_id) }
/// Gets a specific user profile key
pub fn profile_key(&self, user_id: &UserId, profile_key: &str) -> Result<Option<serde_json::Value>> {
self.db.profile_key(user_id, profile_key)
}
/// Gets all the user's profile keys and values in an iterator
pub fn all_profile_keys<'a>(
&'a self, user_id: &UserId,
) -> Box<dyn Iterator<Item = Result<(String, serde_json::Value)>> + 'a + Send> {
self.db.all_profile_keys(user_id)
}
/// Sets a new profile key value, removes the key if value is None
pub fn set_profile_key(
&self, user_id: &UserId, profile_key: &str, profile_key_value: Option<serde_json::Value>,
) -> Result<()> {
self.db
.set_profile_key(user_id, profile_key, profile_key_value)
}
/// Sets a new tz or removes it if tz is None.
pub async fn set_timezone(&self, user_id: &UserId, tz: Option<String>) -> Result<()> {
self.db.set_timezone(user_id, tz)
}
/// Sets a new blurhash or removes it if blurhash is None. /// Sets a new blurhash or removes it if blurhash is None.
pub async fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> { pub async fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
self.db.set_blurhash(user_id, blurhash) self.db.set_blurhash(user_id, blurhash)
@@ -64,6 +64,7 @@
{"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Invited_user_can_reject_invite_over_federation_several_times"} {"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Invited_user_can_reject_invite_over_federation_several_times"}
{"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Invited_user_has_'is_direct'_flag_in_prev_content_after_joining"} {"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Invited_user_has_'is_direct'_flag_in_prev_content_after_joining"}
{"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Remote_invited_user_can_see_room_metadata"} {"Action":"pass","Test":"TestFederationRoomsInvite/Parallel/Remote_invited_user_can_see_room_metadata"}
{"Action":"pass","Test":"TestFederationThumbnail"}
{"Action":"fail","Test":"TestGetMissingEventsGapFilling"} {"Action":"fail","Test":"TestGetMissingEventsGapFilling"}
{"Action":"fail","Test":"TestInboundCanReturnMissingEvents"} {"Action":"fail","Test":"TestInboundCanReturnMissingEvents"}
{"Action":"fail","Test":"TestInboundCanReturnMissingEvents/Inbound_federation_can_return_missing_events_for_invited_visibility"} {"Action":"fail","Test":"TestInboundCanReturnMissingEvents/Inbound_federation_can_return_missing_events_for_invited_visibility"}