Compare commits

..

1 Commits

Author SHA1 Message Date
Jade Ellis 4a7ad1350b WIP 2025-06-14 20:14:09 +01:00
233 changed files with 9015 additions and 12475 deletions
-2
View File
@@ -1,2 +0,0 @@
[alias]
xtask = "run --package xtask --"
+1 -1
View File
@@ -23,6 +23,6 @@ indent_size = 2
indent_style = tab indent_style = tab
max_line_length = 98 max_line_length = 98
[*.yml] [{.forgejo/**/*.yml,.github/**/*.yml}]
indent_size = 2 indent_size = 2
indent_style = space indent_style = space
-27
View File
@@ -1,27 +0,0 @@
name: prefligit
description: |
Runs prefligit, pre-commit reimplemented in Rust.
inputs:
extra_args:
description: options to pass to pre-commit run
required: false
default: '--all-files'
runs:
using: composite
steps:
- name: Install uv
uses: https://github.com/astral-sh/setup-uv@v6
with:
enable-cache: true
ignore-nothing-to-cache: true
- name: Install Prefligit
shell: bash
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prefligit/releases/download/v0.0.10/prefligit-installer.sh | sh
- uses: actions/cache@v3
with:
path: ~/.cache/prefligit
key: prefligit-0|${{ hashFiles('.pre-commit-config.yaml') }}
- run: prefligit run --show-diff-on-failure --color=always -v ${{ inputs.extra_args }}
shell: bash
-55
View File
@@ -1,55 +0,0 @@
version: 1
x-source: &source forgejo.ellis.link/continuwuation/continuwuity
x-tags:
releases: &tags-releases
tags:
allow:
- "latest"
- "v[0-9]+\\.[0-9]+\\.[0-9]+(-[a-z0-9\\.]+)?"
- "v[0-9]+\\.[0-9]+"
- "v[0-9]+"
main: &tags-main
tags:
allow:
- "latest"
- "v[0-9]+\\.[0-9]+\\.[0-9]+(-[a-z0-9\\.]+)?"
- "v[0-9]+\\.[0-9]+"
- "v[0-9]+"
- "main"
commits: &tags-commits
tags:
allow:
- "latest"
- "v[0-9]+\\.[0-9]+\\.[0-9]+(-[a-z0-9\\.]+)?"
- "v[0-9]+\\.[0-9]+"
- "v[0-9]+"
- "main"
- "sha-[a-f0-9]+"
all: &tags-all
tags:
allow:
- ".*"
# Registry credentials
creds:
- registry: forgejo.ellis.link
user: "{{env \"BUILTIN_REGISTRY_USER\"}}"
pass: "{{env \"BUILTIN_REGISTRY_PASSWORD\"}}"
- registry: registry.gitlab.com
user: "{{env \"GITLAB_USERNAME\"}}"
pass: "{{env \"GITLAB_TOKEN\"}}"
# Global defaults
defaults:
parallel: 3
interval: 2h
digestTags: true
# Sync configuration - each registry gets different image sets
sync:
- source: *source
target: registry.gitlab.com/continuwuity/continuwuity
type: repository
<<: *tags-main
-1
View File
@@ -17,7 +17,6 @@ jobs:
docs: docs:
name: Build and Deploy Documentation name: Build and Deploy Documentation
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: secrets.CLOUDFLARE_API_TOKEN != ''
steps: steps:
- name: Sync repository - name: Sync repository
+19 -16
View File
@@ -11,16 +11,16 @@ concurrency:
jobs: jobs:
build-and-deploy: build-and-deploy:
name: 🏗️ Build and Deploy name: Build and Deploy Element Web
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 📦 Setup Node.js - name: Setup Node.js
uses: https://github.com/actions/setup-node@v4 uses: https://code.forgejo.org/actions/setup-node@v4
with: with:
node-version: "22" node-version: "20"
- name: 🔨 Clone, setup, and build Element Web - name: Clone, setup, and build Element Web
run: | run: |
echo "Cloning Element Web..." echo "Cloning Element Web..."
git clone https://github.com/maunium/element-web git clone https://github.com/maunium/element-web
@@ -64,7 +64,7 @@ jobs:
echo "Checking for build output..." echo "Checking for build output..."
ls -la webapp/ ls -la webapp/
- name: ⚙️ Create config.json - name: Create config.json
run: | run: |
cat <<EOF > ./element-web/webapp/config.json cat <<EOF > ./element-web/webapp/config.json
{ {
@@ -100,25 +100,28 @@ jobs:
echo "Created ./element-web/webapp/config.json" echo "Created ./element-web/webapp/config.json"
cat ./element-web/webapp/config.json cat ./element-web/webapp/config.json
- name: 📤 Upload Artifact - name: Upload Artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3 uses: https://code.forgejo.org/actions/upload-artifact@v3
with: with:
name: element-web name: element-web
path: ./element-web/webapp/ path: ./element-web/webapp/
retention-days: 14 retention-days: 14
- name: 🛠️ Install Wrangler - name: Install Wrangler
run: npm install --save-dev wrangler@latest run: npm install --save-dev wrangler@latest
- name: 🚀 Deploy to Cloudflare Pages - name: Deploy to Cloudflare Pages (Production)
if: vars.CLOUDFLARE_PROJECT_NAME != '' if: github.ref == 'refs/heads/main' && vars.CLOUDFLARE_PROJECT_NAME != ''
id: deploy
uses: https://github.com/cloudflare/wrangler-action@v3 uses: https://github.com/cloudflare/wrangler-action@v3
with: with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: >- command: pages deploy ./element-web/webapp --branch="main" --commit-dirty=true --project-name="${{ vars.CLOUDFLARE_PROJECT_NAME }}-element"
pages deploy ./element-web/webapp
--branch="${{ github.ref == 'refs/heads/main' && 'main' || github.head_ref || github.ref_name }}" - name: Deploy to Cloudflare Pages (Preview)
--commit-dirty=true if: github.ref != 'refs/heads/main' && vars.CLOUDFLARE_PROJECT_NAME != ''
--project-name="${{ vars.CLOUDFLARE_PROJECT_NAME }}-element" uses: https://github.com/cloudflare/wrangler-action@v3
with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: pages deploy ./element-web/webapp --branch="${{ github.head_ref || github.ref_name }}" --commit-dirty=true --project-name="${{ vars.CLOUDFLARE_PROJECT_NAME }}-element"
-47
View File
@@ -1,47 +0,0 @@
name: Mirror Container Images
on:
schedule:
# Run every 2 hours
- cron: "0 */2 * * *"
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (check only, no actual mirroring)'
required: false
default: false
type: boolean
concurrency:
group: "mirror-images"
cancel-in-progress: true
jobs:
mirror-images:
runs-on: ubuntu-latest
env:
BUILTIN_REGISTRY_USER: ${{ vars.BUILTIN_REGISTRY_USER }}
BUILTIN_REGISTRY_PASSWORD: ${{ secrets.BUILTIN_REGISTRY_PASSWORD }}
GITLAB_USERNAME: ${{ vars.GITLAB_USERNAME }}
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install regctl
uses: https://forgejo.ellis.link/continuwuation/regclient-actions/regctl-installer@main
with:
binary: regsync
- name: Check what images need mirroring
run: |
echo "Checking images that need mirroring..."
regsync check -c .forgejo/regsync/regsync.yml -v info
- name: Mirror images
if: ${{ !inputs.dry_run }}
run: |
echo "Starting image mirroring..."
regsync once -c .forgejo/regsync/regsync.yml -v info
-22
View File
@@ -1,22 +0,0 @@
name: Checks / Prefligit
on:
push:
pull_request:
permissions:
contents: read
jobs:
prefligit:
runs-on: ubuntu-latest
env:
FROM_REF: ${{ github.event.pull_request.base.sha || (!github.event.forced && ( github.event.before != '0000000000000000000000000000000000000000' && github.event.before || github.sha )) || format('{0}~', github.sha) }}
TO_REF: ${{ github.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- uses: ./.forgejo/actions/prefligit
with:
extra_args: --all-files --hook-stage manual
+8 -31
View File
@@ -49,7 +49,6 @@ jobs:
const platforms = ['linux/amd64', 'linux/arm64'] const platforms = ['linux/amd64', 'linux/arm64']
core.setOutput('build_matrix', JSON.stringify({ core.setOutput('build_matrix', JSON.stringify({
platform: platforms, platform: platforms,
target_cpu: ['base'],
include: platforms.map(platform => { return { include: platforms.map(platform => { return {
platform, platform,
slug: platform.replace('/', '-') slug: platform.replace('/', '-')
@@ -67,8 +66,6 @@ jobs:
strategy: strategy:
matrix: matrix:
{ {
"target_cpu": ["base"],
"profile": ["release"],
"include": "include":
[ [
{ "platform": "linux/amd64", "slug": "linux-amd64" }, { "platform": "linux/amd64", "slug": "linux-amd64" },
@@ -76,7 +73,6 @@ jobs:
], ],
"platform": ["linux/amd64", "linux/arm64"], "platform": ["linux/amd64", "linux/arm64"],
} }
steps: steps:
- name: Echo strategy - name: Echo strategy
run: echo '${{ toJSON(fromJSON(needs.define-variables.outputs.build_matrix)) }}' run: echo '${{ toJSON(fromJSON(needs.define-variables.outputs.build_matrix)) }}'
@@ -144,8 +140,8 @@ jobs:
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: | path: |
cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }} cargo-target-${{ matrix.slug }}
key: cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}} key: cargo-target-${{ matrix.slug }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}}
- name: Cache apt cache - name: Cache apt cache
id: cache-apt id: cache-apt
uses: actions/cache@v3 uses: actions/cache@v3
@@ -167,9 +163,9 @@ jobs:
{ {
".cargo/registry": "/usr/local/cargo/registry", ".cargo/registry": "/usr/local/cargo/registry",
".cargo/git/db": "/usr/local/cargo/git/db", ".cargo/git/db": "/usr/local/cargo/git/db",
"cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}": { "cargo-target-${{ matrix.slug }}": {
"target": "/app/target", "target": "/app/target",
"id": "cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}" "id": "cargo-target-${{ matrix.platform }}"
}, },
"var-cache-apt-${{ matrix.slug }}": "/var/cache/apt", "var-cache-apt-${{ matrix.slug }}": "/var/cache/apt",
"var-lib-apt-${{ matrix.slug }}": "/var/lib/apt" "var-lib-apt-${{ matrix.slug }}": "/var/lib/apt"
@@ -204,31 +200,13 @@ jobs:
digest="${{ steps.build.outputs.digest }}" digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}" touch "/tmp/digests/${digest#sha256:}"
- name: Extract binary from container (image)
id: extract-binary-image
run: |
mkdir -p /tmp/binaries
digest="${{ steps.build.outputs.digest }}"
echo "container_id=$(docker create --platform ${{ matrix.platform }} ${{ needs.define-variables.outputs.images_list }}@$digest)" >> $GITHUB_OUTPUT
- name: Extract binary from container (copy)
run: docker cp ${{ steps.extract-binary-image.outputs.container_id }}:/sbin/conduwuit /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
- name: Extract binary from container (cleanup)
run: docker rm ${{ steps.extract-binary-image.outputs.container_id }}
- name: Upload binary artifact
uses: forgejo/upload-artifact@v4
with:
name: conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
path: /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
if-no-files-found: error
- name: Upload digest - name: Upload digest
uses: forgejo/upload-artifact@v4 uses: forgejo/upload-artifact@v4
with: with:
name: digests-${{ matrix.slug }} name: digests-${{ matrix.slug }}
path: /tmp/digests/* path: /tmp/digests/*
if-no-files-found: error if-no-files-found: error
retention-days: 5 retention-days: 1
merge: merge:
runs-on: dind runs-on: dind
@@ -256,13 +234,12 @@ jobs:
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
tags: | tags: |
type=semver,pattern={{version}},prefix=v type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }},prefix=v type=semver,pattern=v{{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.0.') }}
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }},prefix=v type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }} type=ref,event=branch,prefix=${{ format('refs/heads/{0}', github.event.repository.default_branch) != github.ref && 'branch-' || '' }}
type=ref,event=pr type=ref,event=pr
type=sha,format=long type=sha,format=long
type=raw,value=latest,enable=${{ !startsWith(github.ref, 'refs/tags/v') }}
images: ${{needs.define-variables.outputs.images}} images: ${{needs.define-variables.outputs.images}}
# default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509 # default labels & annotations: https://github.com/docker/metadata-action/blob/master/src/meta.ts#L509
env: env:
+1 -1
View File
@@ -1,4 +1,4 @@
name: Checks / Rust name: Rust Checks
on: on:
push: push:
-2
View File
@@ -5,5 +5,3 @@ f419c64aca300a338096b4e0db4c73ace54f23d0
# use chain_width 60 # use chain_width 60
162948313c212193965dece50b816ef0903172ba 162948313c212193965dece50b816ef0903172ba
5998a0d883d31b866f7c8c46433a8857eae51a89 5998a0d883d31b866f7c8c46433a8857eae51a89
# trailing whitespace and newlines
46c193e74b2ce86c48ce802333a0aabce37fd6e9
+1 -1
View File
@@ -84,4 +84,4 @@ Cargo.lock text
*.zst binary *.zst binary
# Text files where line endings should be preserved # Text files where line endings should be preserved
*.patch -text *.patch -text
-5
View File
@@ -1,5 +0,0 @@
github: [JadedBlueEyes]
# Doesn't support an array, so we can only list nex
ko_fi: nexy7574
custom:
- https://ko-fi.com/JadedBlueEyes
-47
View File
@@ -1,47 +0,0 @@
default_install_hook_types:
- pre-commit
- commit-msg
default_stages:
- pre-commit
- manual
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-byte-order-marker
- id: check-case-conflict
- id: check-symlinks
- id: destroyed-symlinks
- id: check-yaml
- id: check-json
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: mixed-line-ending
- id: check-merge-conflict
- id: check-added-large-files
- repo: https://github.com/crate-ci/typos
rev: v1.26.0
hooks:
- id: typos
- id: typos
name: commit-msg-typos
stages: [commit-msg]
- repo: https://github.com/crate-ci/committed
rev: v1.1.7
hooks:
- id: committed
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo +nightly fmt --
language: system
types: [rust]
pass_filenames: false
stages:
- pre-commit
+54 -112
View File
@@ -1,6 +1,6 @@
# Contributing guide # Contributing guide
This page is about contributing to Continuwuity. The This page is for about contributing to Continuwuity. 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
@@ -10,7 +10,7 @@ and comment on it.
### Linting and Formatting ### Linting and Formatting
It is mandatory all your changes satisfy the lints (clippy, rustc, rustdoc, etc) It is mandatory all your changes satisfy the lints (clippy, rustc, rustdoc, etc)
and your code is formatted via the **nightly** rustfmt (`cargo +nightly fmt`). A lot of the and your code is formatted via the **nightly** `cargo fmt`. A lot of the
`rustfmt.toml` features depend on nightly toolchain. It would be ideal if they `rustfmt.toml` features depend on nightly toolchain. It would be ideal if they
weren't nightly-exclusive features, but they currently still are. CI's rustfmt weren't nightly-exclusive features, but they currently still are. CI's rustfmt
uses nightly. uses nightly.
@@ -21,91 +21,67 @@ comment saying why. Do not write inefficient code for the sake of satisfying
lints. If a lint is wrong and provides a more inefficient solution or lints. If a lint is wrong and provides a more inefficient solution or
suggestion, allow the lint and mention that in a comment. suggestion, allow the lint and mention that in a comment.
### Pre-commit Checks ### Running CI tests locally
Continuwuity uses pre-commit hooks to enforce various coding standards and catch common issues before they're committed. These checks include: continuwuity's CI for tests, linting, formatting, audit, etc use
[`engage`][engage]. engage can be installed from nixpkgs or `cargo install
engage`. continuwuity's Nix flake devshell has the nixpkgs engage with `direnv`.
Use `engage --help` for more usage details.
- Code formatting and linting To test, format, lint, etc that CI would do, install engage, allow the `.envrc`
- Typo detection (both in code and commit messages) file using `direnv allow`, and run `engage`.
- Checking for large files
- Ensuring proper line endings and no trailing whitespace
- Validating YAML, JSON, and TOML files
- Checking for merge conflicts
You can run these checks locally by installing [prefligit](https://github.com/j178/prefligit): All of the tasks are defined at the [engage.toml][engage.toml] file. You can
view all of them neatly by running `engage list`
If you would like to run only a specific engage task group, use `just`:
```bash - `engage just <group>`
# Install prefligit using cargo-binstall - Example: `engage just lints`
cargo binstall prefligit
# Install git hooks to run checks automatically If you would like to run a specific engage task in a specific group, use `just
prefligit install <GROUP> [TASK]`: `engage just lints cargo-fmt`
# Run all checks The following binaries are used in [`engage.toml`][engage.toml]:
prefligit --all-files
```
Alternatively, you can use [pre-commit](https://pre-commit.com/): - [`engage`][engage]
```bash - `nix`
# Install pre-commit - [`direnv`][direnv]
pip install pre-commit - `rustc`
- `cargo`
# Install the hooks - `cargo-fmt`
pre-commit install - `rustdoc`
- `cargo-clippy`
# Run all checks manually - [`cargo-audit`][cargo-audit]
pre-commit run --all-files - [`cargo-deb`][cargo-deb]
``` - [`lychee`][lychee]
- [`markdownlint-cli`][markdownlint-cli]
These same checks are run in CI via the prefligit-checks workflow to ensure consistency. - `dpkg`
### Running tests locally
Tests, compilation, and linting can be run with standard Cargo commands:
```bash
# Run tests
cargo test
# Check compilation
cargo check --workspace
# Run lints
cargo clippy --workspace
# Auto-fix: cargo clippy --workspace --fix --allow-staged;
# Format code (must use nightly)
cargo +nightly fmt
```
### Matrix tests ### Matrix tests
Continuwuity uses [Complement][complement] for Matrix protocol compliance testing. Complement tests are run manually by developers, and documentation on how to run these tests locally is currently being developed. CI runs [Complement][complement], but currently does not fail if results from
the checked-in results differ with the new results. If your changes are done to
fix Matrix tests, note that in your pull request. If more Complement tests start
failing from your changes, please review the logs (they are uploaded as
artifacts) and determine if they're intended or not.
If your changes are done to fix Matrix tests, please note that in your pull request. If more Complement tests start failing from your changes, please review the logs and determine if they're intended or not. If you'd like to run Complement locally using Nix, see the
[testing](development/testing.md) page.
[Sytest][sytest] is currently unsupported. [Sytest][sytest] support will come soon.
### Writing documentation ### Writing documentation
Continuwuity's website uses [`mdbook`][mdbook] and is deployed via CI using Cloudflare Pages Continuwuity's website uses [`mdbook`][mdbook] and deployed via CI using GitHub
in the [`documentation.yml`][documentation.yml] workflow file. All documentation is in the `docs/` Pages in the [`documentation.yml`][documentation.yml] workflow file with Nix's
directory at the top level. mdbook in the devshell. All documentation is in the `docs/` directory at the top
level. The compiled mdbook website is also uploaded as an artifact.
To build the documentation locally: To build the documentation using Nix, run: `bin/nix-build-and-cache just .#book`
1. Install mdbook if you don't have it already: The output of the mdbook generation is in `result/`. mdbooks can be opened in
```bash your browser from the individual HTML files without any web server needed.
cargo install mdbook # or cargo binstall, or another method
```
2. Build the documentation:
```bash
mdbook build
```
The output of the mdbook generation is in `public/`. You can open the HTML files directly in your browser without needing a web server.
### Inclusivity and Diversity ### Inclusivity and Diversity
@@ -133,40 +109,6 @@ Rust's default style and standards with regards to [function names, variable
names, comments](https://rust-lang.github.io/api-guidelines/naming.html), etc names, comments](https://rust-lang.github.io/api-guidelines/naming.html), etc
applies here. applies here.
### Commit Messages
Continuwuity follows the [Conventional Commits](https://www.conventionalcommits.org/) specification for commit messages. This provides a standardized format that makes the commit history more readable and enables automated tools to generate changelogs.
The basic structure is:
```
<type>[(optional scope)]: <description>
[optional body]
[optional footer(s)]
```
The allowed types for commits are:
- `fix`: Bug fixes
- `feat`: New features
- `docs`: Documentation changes
- `style`: Changes that don't affect the meaning of the code (formatting, etc.)
- `refactor`: Code changes that neither fix bugs nor add features
- `perf`: Performance improvements
- `test`: Adding or fixing tests
- `build`: Changes to the build system or dependencies
- `ci`: Changes to CI configuration
- `chore`: Other changes that don't modify source or test files
Examples:
```
feat: add user authentication
fix(database): resolve connection pooling issue
docs: update installation instructions
```
The project uses the `committed` hook to validate commit messages in pre-commit. This ensures all commits follow the conventional format.
### Creating pull requests ### Creating pull requests
Please try to keep contributions to the Forgejo Instance. While the mirrors of continuwuity Please try to keep contributions to the Forgejo Instance. While the mirrors of continuwuity
@@ -176,13 +118,6 @@ This prevents us from having to ping once in a while to double check the status
of it, especially when the CI completed successfully and everything so it of it, especially when the CI completed successfully and everything so it
*looks* done. *looks* done.
Before submitting a pull request, please ensure:
1. Your code passes all CI checks (formatting, linting, typo detection, etc.)
2. Your commit messages follow the conventional commits format
3. Tests are added for new functionality
4. Documentation is updated if needed
Direct all PRs/MRs to the `main` branch. Direct all PRs/MRs to the `main` branch.
@@ -190,13 +125,20 @@ 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, and continuwuity's Code of Conduct. in line with the Contributor's Covenant, and continuwuity's Code of Conduct.
Contribution by users who violate either of these code of conducts may not have Contribution by users who violate either of these code of conducts will not have
their contributions accepted. This includes users who have been banned from their contributions accepted. This includes users who have been banned from
continuwuity Matrix rooms for Code of Conduct violations. continuwuityMatrix rooms for Code of Conduct violations.
[issues]: https://forgejo.ellis.link/continuwuation/continuwuity/issues [issues]: https://forgejo.ellis.link/continuwuation/continuwuity/issues
[continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org [continuwuity-matrix]: https://matrix.to/#/#continuwuity:continuwuity.org
[complement]: https://github.com/matrix-org/complement/ [complement]: https://github.com/matrix-org/complement/
[engage.toml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/engage.toml
[engage]: https://charles.page.computer.surgery/engage/
[sytest]: https://github.com/matrix-org/sytest/ [sytest]: https://github.com/matrix-org/sytest/
[cargo-deb]: https://github.com/kornelski/cargo-deb
[lychee]: https://github.com/lycheeverse/lychee
[markdownlint-cli]: https://github.com/igorshubovych/markdownlint-cli
[cargo-audit]: https://github.com/RustSec/rustsec/tree/main/cargo-audit
[direnv]: https://direnv.net/
[mdbook]: https://rust-lang.github.io/mdBook/ [mdbook]: https://rust-lang.github.io/mdBook/
[documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml [documentation.yml]: https://forgejo.ellis.link/continuwuation/continuwuity/src/branch/main/.forgejo/workflows/documentation.yml
Generated
+257 -552
View File
File diff suppressed because it is too large Load Diff
+12 -19
View File
@@ -2,7 +2,7 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["src/*", "xtask/*"] members = ["src/*"]
default-members = ["src/*"] default-members = ["src/*"]
[workspace.package] [workspace.package]
@@ -21,7 +21,7 @@ license = "Apache-2.0"
readme = "README.md" readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity" repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
rust-version = "1.86.0" rust-version = "1.86.0"
version = "0.5.0-rc.6" version = "0.5.0-rc.5"
[workspace.metadata.crane] [workspace.metadata.crane]
name = "conduwuit" name = "conduwuit"
@@ -213,8 +213,6 @@ default-features = false
version = "0.3.19" version = "0.3.19"
default-features = false default-features = false
features = ["env-filter", "std", "tracing", "tracing-log", "ansi", "fmt"] features = ["env-filter", "std", "tracing", "tracing-log", "ansi", "fmt"]
[workspace.dependencies.tracing-journald]
version = "0.3.1"
[workspace.dependencies.tracing-core] [workspace.dependencies.tracing-core]
version = "0.1.33" version = "0.1.33"
default-features = false default-features = false
@@ -352,7 +350,7 @@ version = "0.1.2"
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma" git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes" #branch = "conduwuit-changes"
rev = "a4b948b40417a65ab0282ae47cc50035dd455e02" rev = "d6870a7fb7f6cccff63f7fd0ff6c581bad80e983"
features = [ features = [
"compat", "compat",
"rand", "rand",
@@ -383,7 +381,7 @@ features = [
"unstable-msc4121", "unstable-msc4121",
"unstable-msc4125", "unstable-msc4125",
"unstable-msc4186", "unstable-msc4186",
"unstable-msc4203", # sending to-device events to appservices "unstable-msc4203", # sending to-device events to appservices
"unstable-msc4210", # remove legacy mentions "unstable-msc4210", # remove legacy mentions
"unstable-extensible-events", "unstable-extensible-events",
"unstable-pdu", "unstable-pdu",
@@ -558,11 +556,11 @@ rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
git = "https://forgejo.ellis.link/continuwuation/tracing" git = "https://forgejo.ellis.link/continuwuation/tracing"
rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa" rev = "1e64095a8051a1adf0d1faa307f9f030889ec2aa"
# adds a tab completion callback: https://forgejo.ellis.link/continuwuation/rustyline-async/src/branch/main/.patchy/0002-add-tab-completion-callback.patch # adds a tab completion callback: https://forgejo.ellis.link/continuwuation/rustyline-async/commit/de26100b0db03e419a3d8e1dd26895d170d1fe50
# adds event for CTRL+\: https://forgejo.ellis.link/continuwuation/rustyline-async/src/branch/main/.patchy/0001-add-event-for-ctrl.patch # adds event for CTRL+\: https://forgejo.ellis.link/continuwuation/rustyline-async/commit/67d8c49aeac03a5ef4e818f663eaa94dd7bf339b
[patch.crates-io.rustyline-async] [patch.crates-io.rustyline-async]
git = "https://forgejo.ellis.link/continuwuation/rustyline-async" git = "https://forgejo.ellis.link/continuwuation/rustyline-async"
rev = "e9f01cf8c6605483cb80b3b0309b400940493d7f" rev = "deaeb0694e2083f53d363b648da06e10fc13900c"
# adds LIFO queue scheduling; this should be updated with PR progress. # adds LIFO queue scheduling; this should be updated with PR progress.
[patch.crates-io.event-listener] [patch.crates-io.event-listener]
@@ -582,11 +580,12 @@ rev = "9c8e51510c35077df888ee72a36b4b05637147da"
git = "https://forgejo.ellis.link/continuwuation/hyper-util" git = "https://forgejo.ellis.link/continuwuation/hyper-util"
rev = "e4ae7628fe4fcdacef9788c4c8415317a4489941" rev = "e4ae7628fe4fcdacef9788c4c8415317a4489941"
# Allows no-aaaa option in resolv.conf # allows no-aaaa option in resolv.conf
# Use 1-indexed line numbers when displaying parse error messages # bumps rust edition and toolchain to 1.86.0 and 2024
# use sat_add on line number errors
[patch.crates-io.resolv-conf] [patch.crates-io.resolv-conf]
git = "https://forgejo.ellis.link/continuwuation/resolv-conf" git = "https://forgejo.ellis.link/continuwuation/resolv-conf"
rev = "56251316cc4127bcbf36e68ce5e2093f4d33e227" rev = "200e958941d522a70c5877e3d846f55b5586c68d"
# #
# Our crates # Our crates
@@ -638,11 +637,6 @@ package = "conduwuit_build_metadata"
path = "src/build_metadata" path = "src/build_metadata"
default-features = false default-features = false
[workspace.dependencies.conduwuit]
package = "conduwuit"
path = "src/main"
############################################################################### ###############################################################################
# #
# Release profiles # Release profiles
@@ -768,8 +762,7 @@ inherits = "dev"
# '-Clink-arg=-Wl,-z,nodlopen', # '-Clink-arg=-Wl,-z,nodlopen',
# '-Clink-arg=-Wl,-z,nodelete', # '-Clink-arg=-Wl,-z,nodelete',
#] #]
[profile.dev.package.xtask-generate-commands]
inherits = "dev"
[profile.dev.package.conduwuit] [profile.dev.package.conduwuit]
inherits = "dev" inherits = "dev"
#rustflags = [ #rustflags = [
+5 -9
View File
@@ -4,10 +4,6 @@
## A community-driven [Matrix](https://matrix.org/) homeserver in Rust ## A community-driven [Matrix](https://matrix.org/) homeserver in Rust
[![Chat on Matrix](https://img.shields.io/matrix/continuwuity%3Acontinuwuity.org?server_fqdn=matrix.continuwuity.org&fetchMode=summary&logo=matrix)](https://matrix.to/#/#continuwuity:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org) [![Join the space](https://img.shields.io/matrix/space%3Acontinuwuity.org?server_fqdn=matrix.continuwuity.org&fetchMode=summary&logo=matrix&label=space)](https://matrix.to/#/#space:continuwuity.org?via=continuwuity.org&via=ellis.link&via=explodie.org&via=matrix.org)
<!-- ANCHOR_END: catchphrase --> <!-- ANCHOR_END: catchphrase -->
[continuwuity] is a Matrix homeserver written in Rust. [continuwuity] is a Matrix homeserver written in Rust.
@@ -15,13 +11,11 @@ It's a community continuation of the [conduwuit](https://github.com/girlbossceo/
<!-- ANCHOR: body --> <!-- ANCHOR: body -->
[![forgejo.ellis.link](https://img.shields.io/badge/Ellis%20Git-main+packages-green?style=flat&logo=forgejo&labelColor=fff)](https://forgejo.ellis.link/continuwuation/continuwuity) [![Stars](https://forgejo.ellis.link/continuwuation/continuwuity/badges/stars.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/stars) [![Issues](https://forgejo.ellis.link/continuwuation/continuwuity/badges/issues/open.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/issues?state=open) [![Pull Requests](https://forgejo.ellis.link/continuwuation/continuwuity/badges/pulls/open.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/pulls?state=open) [![forgejo.ellis.link](https://img.shields.io/badge/Ellis%20Git-main+packages-green?style=flat&logo=forgejo&labelColor=fff)](https://forgejo.ellis.link/continuwuation/continuwuity) ![](https://forgejo.ellis.link/continuwuation/continuwuity/badges/stars.svg?style=flat) [![](https://forgejo.ellis.link/continuwuation/continuwuity/badges/issues/open.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/issues?state=open) [![](https://forgejo.ellis.link/continuwuation/continuwuity/badges/pulls/open.svg?style=flat)](https://forgejo.ellis.link/continuwuation/continuwuity/pulls?state=open)
[![GitHub](https://img.shields.io/badge/GitHub-mirror-blue?style=flat&logo=github&labelColor=fff&logoColor=24292f)](https://github.com/continuwuity/continuwuity) [![Stars](https://img.shields.io/github/stars/continuwuity/continuwuity?style=flat)](https://github.com/continuwuity/continuwuity/stargazers) [![GitHub](https://img.shields.io/badge/GitHub-mirror-blue?style=flat&logo=github&labelColor=fff&logoColor=24292f)](https://github.com/continuwuity/continuwuity) ![](https://img.shields.io/github/stars/continuwuity/continuwuity?style=flat)
[![GitLab](https://img.shields.io/badge/GitLab-mirror-blue?style=flat&logo=gitlab&labelColor=fff)](https://gitlab.com/continuwuity/continuwuity) [![Stars](https://img.shields.io/gitlab/stars/continuwuity/continuwuity?style=flat)](https://gitlab.com/continuwuity/continuwuity/-/starrers) [![Codeberg](https://img.shields.io/badge/Codeberg-mirror-2185D0?style=flat&logo=codeberg&labelColor=fff)](https://codeberg.org/nexy7574/continuwuity) ![](https://codeberg.org/nexy7574/continuwuity/badges/stars.svg?style=flat)
[![Codeberg](https://img.shields.io/badge/Codeberg-mirror-2185D0?style=flat&logo=codeberg&labelColor=fff)](https://codeberg.org/continuwuity/continuwuity) [![Stars](https://codeberg.org/continuwuity/continuwuity/badges/stars.svg?style=flat)](https://codeberg.org/continuwuity/continuwuity/stars)
### Why does this exist? ### Why does this exist?
@@ -65,6 +59,8 @@ There are currently no open registration Continuwuity instances available.
We're working our way through all of the issues in the [Forgejo project](https://forgejo.ellis.link/continuwuation/continuwuity/issues). We're working our way through all of the issues in the [Forgejo project](https://forgejo.ellis.link/continuwuation/continuwuity/issues).
- [Replacing old conduwuit links with working continuwuity links](https://forgejo.ellis.link/continuwuation/continuwuity/issues/742)
- [Getting CI and docs deployment working on the new Forgejo project](https://forgejo.ellis.link/continuwuation/continuwuity/issues/740)
- [Packaging & availability in more places](https://forgejo.ellis.link/continuwuation/continuwuity/issues/747) - [Packaging & availability in more places](https://forgejo.ellis.link/continuwuation/continuwuity/issues/747)
- [Appservices bugs & features](https://forgejo.ellis.link/continuwuation/continuwuity/issues?q=&type=all&state=open&labels=178&milestone=0&assignee=0&poster=0) - [Appservices bugs & features](https://forgejo.ellis.link/continuwuation/continuwuity/issues?q=&type=all&state=open&labels=178&milestone=0&assignee=0&poster=0)
- [Improving compatibility and spec compliance](https://forgejo.ellis.link/continuwuation/continuwuity/issues?labels=119) - [Improving compatibility and spec compliance](https://forgejo.ellis.link/continuwuation/continuwuity/issues?labels=119)
+1 -7
View File
@@ -6,7 +6,6 @@ After=network-online.target
Documentation=https://continuwuity.org/ Documentation=https://continuwuity.org/
RequiresMountsFor=/var/lib/private/conduwuit RequiresMountsFor=/var/lib/private/conduwuit
Alias=matrix-conduwuit.service Alias=matrix-conduwuit.service
[Service] [Service]
DynamicUser=yes DynamicUser=yes
Type=notify-reload Type=notify-reload
@@ -17,10 +16,6 @@ DeviceAllow=char-tty
StandardInput=tty-force StandardInput=tty-force
StandardOutput=tty StandardOutput=tty
StandardError=journal+console StandardError=journal+console
Environment="CONTINUWUITY_LOG_TO_JOURNALD=1"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
TTYReset=yes TTYReset=yes
# uncomment to allow buffer to be cleared every restart # uncomment to allow buffer to be cleared every restart
TTYVTDisallocate=no TTYVTDisallocate=no
@@ -64,8 +59,7 @@ StateDirectory=conduwuit
RuntimeDirectory=conduwuit RuntimeDirectory=conduwuit
RuntimeDirectoryMode=0750 RuntimeDirectoryMode=0750
Environment=CONTINUWUITY_CONFIG=${CREDENTIALS_DIRECTORY}/config.toml Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
LoadCredential=config.toml:/etc/conduwuit/conduwuit.toml
BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit
BindPaths=/var/lib/private/conduwuit:/var/lib/private/matrix-conduit BindPaths=/var/lib/private/conduwuit:/var/lib/private/matrix-conduit
-3
View File
@@ -1,3 +0,0 @@
style = "conventional"
subject_length = 72
allowed_types = ["ci", "build", "fix", "feat", "chore", "docs", "style", "refactor", "perf", "test"]
+9 -45
View File
@@ -119,6 +119,15 @@
# #
#allow_announcements_check = true #allow_announcements_check = true
# If enabled, continuwuity will send anonymous analytics data periodically
# to help improve development. This includes basic server metadata like
# version, commit hash, and federation status. All requests are signed
# with the server's federation signing key. Data is sent on startup (with
# up to 5 minutes jitter) and every 12 hours thereafter (with up to 30
# minutes jitter) to distribute load.
#
#allow_analytics = true
# Set this to any float value to multiply continuwuity's in-memory LRU # Set this to any float value to multiply continuwuity's in-memory LRU
# caches with such as "auth_chain_cache_capacity". # caches with such as "auth_chain_cache_capacity".
# #
@@ -398,22 +407,6 @@
# #
#allow_registration = false #allow_registration = false
# If registration is enabled, and this setting is true, new users
# registered after the first admin user will be automatically suspended
# and will require an admin to run `!admin users unsuspend <user_id>`.
#
# Suspended users are still able to read messages, make profile updates,
# leave rooms, and deactivate their account, however cannot send messages,
# invites, or create/join or otherwise modify rooms.
# They are effectively read-only.
#
# If you want to use this to screen people who register on your server,
# you should add a room to `auto_join_rooms` that is public, and contains
# information that new users can read (since they won't be able to DM
# anyone, or send a message, and may be confused).
#
#suspend_on_register = false
# Enabling this setting opens registration to anyone without restrictions. # Enabling this setting opens registration to anyone without restrictions.
# This makes your server vulnerable to abuse # This makes your server vulnerable to abuse
# #
@@ -676,21 +669,6 @@
# #
#log_thread_ids = false #log_thread_ids = false
# Enable journald logging on Unix platforms
#
# When enabled, log output will be sent to the systemd journal
# This is only supported on Unix platforms
#
#log_to_journald = false
# The syslog identifier to use with journald logging
#
# Only used when journald logging is enabled
#
# Defaults to the binary name
#
#journald_identifier =
# OpenID token expiration/TTL in seconds. # OpenID token expiration/TTL in seconds.
# #
# These are the OpenID tokens that are primarily used for Matrix account # These are the OpenID tokens that are primarily used for Matrix account
@@ -1084,13 +1062,6 @@
# #
#presence_timeout_remote_users = true #presence_timeout_remote_users = true
# Allow local read receipts.
#
# Disabling this will effectively also disable outgoing federated read
# receipts.
#
#allow_local_read_receipts = true
# Allow receiving incoming read receipts from remote servers. # Allow receiving incoming read receipts from remote servers.
# #
#allow_incoming_read_receipts = true #allow_incoming_read_receipts = true
@@ -1099,13 +1070,6 @@
# #
#allow_outgoing_read_receipts = true #allow_outgoing_read_receipts = true
# Allow local typing updates.
#
# Disabling this will effectively also disable outgoing federated typing
# updates.
#
#allow_local_typing = true
# Allow outgoing typing updates to federation. # Allow outgoing typing updates to federation.
# #
#allow_outgoing_typing = true #allow_outgoing_typing = true
-3
View File
@@ -14,9 +14,6 @@ Type=notify
Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml" Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=1"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
ExecStart=/usr/sbin/conduwuit ExecStart=/usr/sbin/conduwuit
ReadWritePaths=/var/lib/conduwuit /etc/conduwuit ReadWritePaths=/var/lib/conduwuit /etc/conduwuit
+8 -18
View File
@@ -1,16 +1,15 @@
ARG RUST_VERSION=1 ARG RUST_VERSION=1
ARG DEBIAN_VERSION=bookworm
FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx FROM --platform=$BUILDPLATFORM docker.io/tonistiigi/xx AS xx
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-${DEBIAN_VERSION} AS base FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-bookworm AS base
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-${DEBIAN_VERSION} AS toolchain FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-slim-bookworm AS toolchain
# Prevent deletion of apt cache # Prevent deletion of apt cache
RUN rm -f /etc/apt/apt.conf.d/docker-clean RUN rm -f /etc/apt/apt.conf.d/docker-clean
# Match Rustc version as close as possible # Match Rustc version as close as possible
# rustc -vV # rustc -vV
ARG LLVM_VERSION=20 ARG LLVM_VERSION=19
# ENV RUSTUP_TOOLCHAIN=${RUST_VERSION} # ENV RUSTUP_TOOLCHAIN=${RUST_VERSION}
# Install repo tools # Install repo tools
@@ -20,18 +19,10 @@ ARG LLVM_VERSION=20
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y \ apt-get update && apt-get install -y \
pkg-config make jq \ clang-${LLVM_VERSION} lld-${LLVM_VERSION} pkg-config make jq \
curl git software-properties-common \ curl git \
file file
# LLVM packages
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
curl https://apt.llvm.org/llvm.sh > llvm.sh && \
chmod +x llvm.sh && \
./llvm.sh ${LLVM_VERSION} && \
rm llvm.sh
# Create symlinks for LLVM tools # Create symlinks for LLVM tools
RUN <<EOF RUN <<EOF
set -o xtrace set -o xtrace
@@ -48,7 +39,7 @@ EOF
# Developer tool versions # Developer tool versions
# renovate: datasource=github-releases depName=cargo-bins/cargo-binstall # renovate: datasource=github-releases depName=cargo-bins/cargo-binstall
ENV BINSTALL_VERSION=1.13.0 ENV BINSTALL_VERSION=1.12.3
# renovate: datasource=github-releases depName=psastras/sbom-rs # renovate: datasource=github-releases depName=psastras/sbom-rs
ENV CARGO_SBOM_VERSION=0.9.1 ENV CARGO_SBOM_VERSION=0.9.1
# renovate: datasource=crate depName=lddtree # renovate: datasource=crate depName=lddtree
@@ -149,12 +140,11 @@ ENV GIT_REMOTE_COMMIT_URL=$GIT_REMOTE_COMMIT_URL
ENV CONDUWUIT_VERSION_EXTRA=$CONDUWUIT_VERSION_EXTRA ENV CONDUWUIT_VERSION_EXTRA=$CONDUWUIT_VERSION_EXTRA
ENV CONTINUWUITY_VERSION_EXTRA=$CONTINUWUITY_VERSION_EXTRA ENV CONTINUWUITY_VERSION_EXTRA=$CONTINUWUITY_VERSION_EXTRA
ARG RUST_PROFILE=release
# Build the binary # Build the binary
RUN --mount=type=cache,target=/usr/local/cargo/registry \ RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git/db \ --mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/app/target,id=cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \ --mount=type=cache,target=/app/target,id=cargo-target-${TARGETPLATFORM} \
bash <<'EOF' bash <<'EOF'
set -o allexport set -o allexport
set -o xtrace set -o xtrace
@@ -163,7 +153,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
jq -r ".target_directory")) jq -r ".target_directory"))
mkdir /out/sbin mkdir /out/sbin
PACKAGE=conduwuit PACKAGE=conduwuit
xx-cargo build --locked --profile ${RUST_PROFILE} \ xx-cargo build --locked --release \
-p $PACKAGE; -p $PACKAGE;
BINARIES=($(cargo metadata --no-deps --format-version 1 | \ BINARIES=($(cargo metadata --no-deps --format-version 1 | \
jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name")) jq -r ".packages[] | select(.name == \"$PACKAGE\") | .targets[] | select( .kind | map(. == \"bin\") | any ) | .name"))
-1
View File
@@ -15,7 +15,6 @@
- [Appservices](appservices.md) - [Appservices](appservices.md)
- [Maintenance](maintenance.md) - [Maintenance](maintenance.md)
- [Troubleshooting](troubleshooting.md) - [Troubleshooting](troubleshooting.md)
- [Admin Command Reference](admin_reference.md)
- [Development](development.md) - [Development](development.md)
- [Contributing](contributing.md) - [Contributing](contributing.md)
- [Testing](development/testing.md) - [Testing](development/testing.md)
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -1,5 +1,3 @@
# Continuwuity for Arch Linux # Continuwuity for Arch Linux
Continuwuity is available on the `archlinuxcn` repository and AUR, with the same package name `continuwuity`, which includes latest taggged version. The development version is available on AUR as `continuwuity-git` Continuwuity does not have any Arch Linux packages at this time.
Simply install the `continuwuity` package. Configure the service in `/etc/conduwuit/conduwuit.toml`, then enable/start the continuwuity.service.
@@ -34,3 +34,4 @@ services:
# - "traefik.http.routers.to-element-web.tls.certresolver=letsencrypt" # - "traefik.http.routers.to-element-web.tls.certresolver=letsencrypt"
# vim: ts=2:sw=2:expandtab # vim: ts=2:sw=2:expandtab
+1 -1
View File
@@ -26,7 +26,7 @@ services:
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- db:/var/lib/continuwuity - db:/var/lib/continuwuity
- /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's. - /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's.
#- ./continuwuity.toml:/etc/continuwuity.toml #- ./continuwuity.toml:/etc/continuwuity.toml
environment: environment:
CONTINUWUITY_SERVER_NAME: example.com # EDIT THIS CONTINUWUITY_SERVER_NAME: example.com # EDIT THIS
@@ -8,7 +8,7 @@ services:
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- db:/var/lib/continuwuity - db:/var/lib/continuwuity
- /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's. - /etc/resolv.conf:/etc/resolv.conf:ro # Use the host's DNS resolver rather than Docker's.
#- ./continuwuity.toml:/etc/continuwuity.toml #- ./continuwuity.toml:/etc/continuwuity.toml
networks: networks:
- proxy - proxy
+1 -1
View File
@@ -29,7 +29,7 @@ appropriately to use Continuwuity instead of Conduit.
Due to the lack of a Continuwuity NixOS module, when using the `services.matrix-conduit` module Due to the lack of a Continuwuity NixOS module, when using the `services.matrix-conduit` module
a workaround like the one below is necessary to use UNIX sockets. This is because the UNIX a workaround like the one below is necessary to use UNIX sockets. This is because the UNIX
socket option does not exist in Conduit, and the module forcibly sets the `address` and socket option does not exist in Conduit, and the module forcibly sets the `address` and
`port` config options. `port` config options.
```nix ```nix
+30 -39
View File
@@ -68,22 +68,31 @@ do this if Rust supported workspace-level features to begin with.
## List of forked dependencies ## List of forked dependencies
During Continuwuity (and prior projects) development, we have had to fork some dependencies to support our use-cases. During Continuwuity development, we have had to fork
These forks exist for various reasons including features that upstream projects won't accept, some dependencies to support our use-cases in some areas. This ranges from
faster-paced development, Continuwuity-specific usecases, or lack of time to upstream changes. things said upstream project won't accept for any reason, faster-paced
development (unresponsive or slow upstream), Continuwuity-specific usecases, or
lack of time to upstream some things.
All forked dependencies are maintained under the [continuwuation organization on Forgejo](https://forgejo.ellis.link/continuwuation): - [ruma/ruma][1]: <https://github.com/girlbossceo/ruwuma> - various performance
improvements, more features, faster-paced development, better client/server interop
- [ruwuma][continuwuation-ruwuma] - Fork of [ruma/ruma][ruma] with various performance improvements, more features and better client/server interop hacks upstream won't accept, etc
- [rocksdb][continuwuation-rocksdb] - Fork of [facebook/rocksdb][rocksdb] via [`@zaidoon1`][8] with liburing build fixes and GCC debug build fixes - [facebook/rocksdb][2]: <https://github.com/girlbossceo/rocksdb> - liburing
- [jemallocator][continuwuation-jemallocator] - Fork of [tikv/jemallocator][jemallocator] fixing musl builds, suspicious code, build fixes and GCC debug build fix
and adding support for redzones in Valgrind - [tikv/jemallocator][3]: <https://github.com/girlbossceo/jemallocator> - musl
- [rustyline-async][continuwuation-rustyline-async] - Fork of [zyansheep/rustyline-async][rustyline-async] with tab completion callback builds seem to be broken on upstream, fixes some broken/suspicious code in
and `CTRL+\` signal quit event for Continuwuity console CLI places, additional safety measures, and support redzones for Valgrind
- [rust-rocksdb][continuwuation-rust-rocksdb] - Fork of [rust-rocksdb/rust-rocksdb][rust-rocksdb] fixing musl build issues, - [zyansheep/rustyline-async][4]:
removing unnecessary `gtest` include, and using our RocksDB and jemallocator forks <https://github.com/girlbossceo/rustyline-async> - tab completion callback and
- [tracing][continuwuation-tracing] - Fork of [tokio-rs/tracing][tracing] implementing `Clone` for `EnvFilter` to `CTRL+\` signal quit event for Continuwuity console CLI
support dynamically changing tracing environments - [rust-rocksdb/rust-rocksdb][5]:
<https://github.com/girlbossceo/rust-rocksdb-zaidoon1> - [`@zaidoon1`][8]'s fork
has quicker updates, more up to date dependencies, etc. Our fork fixes musl build
issues, removes unnecessary `gtest` include, and uses our RocksDB and jemallocator
forks.
- [tokio-rs/tracing][6]: <https://github.com/girlbossceo/tracing> - Implements
`Clone` for `EnvFilter` to support dynamically changing tracing envfilter's
alongside other logging/metrics things
## Debugging with `tokio-console` ## Debugging with `tokio-console`
@@ -104,30 +113,12 @@ You will also need to enable the `tokio_console` config option in Continuwuity w
starting it. This was due to tokio-console causing gradual memory leak/usage starting it. This was due to tokio-console causing gradual memory leak/usage
if left enabled. if left enabled.
## Building Docker Images [1]: https://github.com/ruma/ruma/
[2]: https://github.com/facebook/rocksdb/
To build a Docker image for Continuwuity, use the standard Docker build command: [3]: https://github.com/tikv/jemallocator/
[4]: https://github.com/zyansheep/rustyline-async/
```bash [5]: https://github.com/rust-rocksdb/rust-rocksdb/
docker build -f docker/Dockerfile . [6]: https://github.com/tokio-rs/tracing/
```
The image can be cross-compiled for different architectures.
[continuwuation-ruwuma]: https://forgejo.ellis.link/continuwuation/ruwuma
[continuwuation-rocksdb]: https://forgejo.ellis.link/continuwuation/rocksdb
[continuwuation-jemallocator]: https://forgejo.ellis.link/continuwuation/jemallocator
[continuwuation-rustyline-async]: https://forgejo.ellis.link/continuwuation/rustyline-async
[continuwuation-rust-rocksdb]: https://forgejo.ellis.link/continuwuation/rust-rocksdb
[continuwuation-tracing]: https://forgejo.ellis.link/continuwuation/tracing
[ruma]: https://github.com/ruma/ruma/
[rocksdb]: https://github.com/facebook/rocksdb/
[jemallocator]: https://github.com/tikv/jemallocator/
[rustyline-async]: https://github.com/zyansheep/rustyline-async/
[rust-rocksdb]: https://github.com/rust-rocksdb/rust-rocksdb/
[tracing]: https://github.com/tokio-rs/tracing/
[7]: https://docs.rs/tokio-console/latest/tokio_console/ [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 [9]: https://github.com/rust-lang/cargo/issues/12162
-21
View File
@@ -1,21 +0,0 @@
# Command-Line Help for `continuwuity`
This document contains the help content for the `continuwuity` command-line program.
**Command Overview:**
* [`continuwuity`↴](#continuwuity)
## `continuwuity`
a very cool Matrix chat homeserver written in Rust
**Usage:** `continuwuity [OPTIONS]`
###### **Options:**
* `-c`, `--config <CONFIG>` — Path to the config TOML file (optional)
* `-O`, `--option <OPTION>` — Override a configuration variable using TOML 'key=value' syntax
* `--read-only` — Run in a stricter read-only --maintenance mode
* `--maintenance` — Run in maintenance mode while refusing connections
* `--execute <EXECUTE>` — Execute console command automatically after startup
+1 -1
View File
@@ -3,4 +3,4 @@
Content-Type: application/json Content-Type: application/json
/.well-known/continuwuity/* /.well-known/continuwuity/*
Access-Control-Allow-Origin: * Access-Control-Allow-Origin: *
Content-Type: application/json Content-Type: application/json
+1 -5
View File
@@ -4,10 +4,6 @@
{ {
"id": 1, "id": 1,
"message": "Welcome to Continuwuity! Important announcements about the project will appear here." "message": "Welcome to Continuwuity! Important announcements about the project will appear here."
},
{
"id": 2,
"message": "🎉 Continuwuity v0.5.0-rc.6 is now available! This release includes improved knock-restricted room handling, automatic support contact configuration, and a new HTML landing page. Check [the release notes for full details](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.0-rc.6) and upgrade instructions."
} }
] ]
} }
+1 -1
View File
@@ -32,4 +32,4 @@
"required": [ "required": [
"announcements" "announcements"
] ]
} }
+1 -1
View File
@@ -21,4 +21,4 @@
} }
], ],
"support_page": "https://continuwuity.org/introduction#contact" "support_page": "https://continuwuity.org/introduction#contact"
} }
Generated
+103 -50
View File
@@ -10,11 +10,11 @@
"nixpkgs-stable": "nixpkgs-stable" "nixpkgs-stable": "nixpkgs-stable"
}, },
"locked": { "locked": {
"lastModified": 1751403276, "lastModified": 1738524606,
"narHash": "sha256-V0EPQNsQko1a8OqIWc2lLviLnMpR1m08Ej00z5RVTfs=", "narHash": "sha256-hPYEJ4juK3ph7kbjbvv7PlU1D9pAkkhl+pwx8fZY53U=",
"owner": "zhaofengli", "owner": "zhaofengli",
"repo": "attic", "repo": "attic",
"rev": "896ad88fa57ad5dbcd267c0ac51f1b71ccfcb4dd", "rev": "ff8a897d1f4408ebbf4d45fa9049c06b3e1e3f4e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -32,11 +32,11 @@
"nixpkgs": "nixpkgs_4" "nixpkgs": "nixpkgs_4"
}, },
"locked": { "locked": {
"lastModified": 1748883665, "lastModified": 1737621947,
"narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=", "narHash": "sha256-8HFvG7fvIFbgtaYAY2628Tb89fA55nPm2jSiNs0/Cws=",
"owner": "cachix", "owner": "cachix",
"repo": "cachix", "repo": "cachix",
"rev": "f707778d902af4d62d8dd92c269f8e70de09acbe", "rev": "f65a3cd5e339c223471e64c051434616e18cc4f5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -63,11 +63,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1744206633, "lastModified": 1728672398,
"narHash": "sha256-pb5aYkE8FOoa4n123slgHiOf1UbNSnKe5pEZC+xXD5g=", "narHash": "sha256-KxuGSoVUFnQLB2ZcYODW7AVPAh9JqRlD5BrfsC/Q4qs=",
"owner": "cachix", "owner": "cachix",
"repo": "cachix", "repo": "cachix",
"rev": "8a60090640b96f9df95d1ab99e5763a586be1404", "rev": "aac51f698309fd0f381149214b7eee213c66ef0a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -77,6 +77,23 @@
"type": "github" "type": "github"
} }
}, },
"complement": {
"flake": false,
"locked": {
"lastModified": 1741891349,
"narHash": "sha256-YvrzOWcX7DH1drp5SGa+E/fc7wN3hqFtPbqPjZpOu1Q=",
"owner": "girlbossceo",
"repo": "complement",
"rev": "e587b3df569cba411aeac7c20b6366d03c143745",
"type": "github"
},
"original": {
"owner": "girlbossceo",
"ref": "main",
"repo": "complement",
"type": "github"
}
},
"crane": { "crane": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -100,11 +117,11 @@
}, },
"crane_2": { "crane_2": {
"locked": { "locked": {
"lastModified": 1750266157, "lastModified": 1739936662,
"narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=", "narHash": "sha256-x4syUjNUuRblR07nDPeLDP7DpphaBVbUaSoeZkFbGSk=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "e37c943371b73ed87faf33f7583860f81f1d5a48", "rev": "19de14aaeb869287647d9461cbd389187d8ecdb7",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -132,11 +149,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1748273445, "lastModified": 1733323168,
"narHash": "sha256-5V0dzpNgQM0CHDsMzh+ludYeu1S+Y+IMjbaskSSdFh0=", "narHash": "sha256-d5DwB4MZvlaQpN6OQ4SLYxb5jA4UH5EtV5t5WOtjLPU=",
"owner": "cachix", "owner": "cachix",
"repo": "devenv", "repo": "devenv",
"rev": "668a50d8b7bdb19a0131f53c9f6c25c9071e1ffb", "rev": "efa9010b8b1cfd5dd3c7ed1e172a470c3b84a064",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -153,11 +170,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1751525020, "lastModified": 1740724364,
"narHash": "sha256-oDO6lCYS5Bf4jUITChj9XV7k3TP38DE0Ckz5n5ORCME=", "narHash": "sha256-D1jLIueJx1dPrP09ZZwTrPf4cubV+TsFMYbpYYTVj6A=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "a1a5f92f47787e7df9f30e5e5ac13e679215aa1e", "rev": "edf7d9e431cda8782e729253835f178a356d3aab",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -186,11 +203,11 @@
"flake-compat_2": { "flake-compat_2": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1747046372, "lastModified": 1733328505,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -202,11 +219,11 @@
"flake-compat_3": { "flake-compat_3": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1747046372, "lastModified": 1733328505,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -289,14 +306,15 @@
"nixpkgs": [ "nixpkgs": [
"cachix", "cachix",
"nixpkgs" "nixpkgs"
] ],
"nixpkgs-stable": "nixpkgs-stable_2"
}, },
"locked": { "locked": {
"lastModified": 1747372754, "lastModified": 1733318908,
"narHash": "sha256-2Y53NGIX2vxfie1rOW0Qb86vjRZ7ngizoo+bnXU9D9k=", "narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46", "rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -343,6 +361,23 @@
"type": "github" "type": "github"
} }
}, },
"liburing": {
"flake": false,
"locked": {
"lastModified": 1740613216,
"narHash": "sha256-NpPOBqNND3Qe9IwqYs0mJLGTmIx7e6FgUEBAnJ+1ZLA=",
"owner": "axboe",
"repo": "liburing",
"rev": "e1003e496e66f9b0ae06674869795edf772d5500",
"type": "github"
},
"original": {
"owner": "axboe",
"ref": "master",
"repo": "liburing",
"type": "github"
}
},
"nix": { "nix": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
@@ -366,11 +401,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1745930071, "lastModified": 1727438425,
"narHash": "sha256-bYyjarS3qSNqxfgc89IoVz8cAFDkF9yPE63EJr+h50s=", "narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=",
"owner": "domenkozar", "owner": "domenkozar",
"repo": "nix", "repo": "nix",
"rev": "b455edf3505f1bf0172b39a735caef94687d0d9c", "rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -449,13 +484,29 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": { "nixpkgs-stable_2": {
"locked": { "locked": {
"lastModified": 1733212471, "lastModified": 1730741070,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=", "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776", "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1730531603,
"narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7ffd9ae656aec493492b44d0ddfb28e79a1ea25d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -483,11 +534,11 @@
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1748190013, "lastModified": 1733212471,
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=", "narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291", "rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -499,11 +550,11 @@
}, },
"nixpkgs_5": { "nixpkgs_5": {
"locked": { "locked": {
"lastModified": 1751498133, "lastModified": 1740547748,
"narHash": "sha256-QWJ+NQbMU+NcU2xiyo7SNox1fAuwksGlQhpzBl76g1I=", "narHash": "sha256-Ly2fBL1LscV+KyCqPRufUBuiw+zmWrlJzpWOWbahplg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d55716bb59b91ae9d1ced4b1ccdea7a442ecbfdb", "rev": "3a05eebede89661660945da1f151959900903b6a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -518,26 +569,28 @@
"locked": { "locked": {
"lastModified": 1741308171, "lastModified": 1741308171,
"narHash": "sha256-YdBvdQ75UJg5ffwNjxizpviCVwVDJnBkM8ZtGIduMgY=", "narHash": "sha256-YdBvdQ75UJg5ffwNjxizpviCVwVDJnBkM8ZtGIduMgY=",
"ref": "v9.11.1", "owner": "girlbossceo",
"repo": "rocksdb",
"rev": "3ce04794bcfbbb0d2e6f81ae35fc4acf688b6986", "rev": "3ce04794bcfbbb0d2e6f81ae35fc4acf688b6986",
"revCount": 13177, "type": "github"
"type": "git",
"url": "https://forgejo.ellis.link/continuwuation/rocksdb"
}, },
"original": { "original": {
"owner": "girlbossceo",
"ref": "v9.11.1", "ref": "v9.11.1",
"type": "git", "repo": "rocksdb",
"url": "https://forgejo.ellis.link/continuwuation/rocksdb" "type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"attic": "attic", "attic": "attic",
"cachix": "cachix", "cachix": "cachix",
"complement": "complement",
"crane": "crane_2", "crane": "crane_2",
"fenix": "fenix", "fenix": "fenix",
"flake-compat": "flake-compat_3", "flake-compat": "flake-compat_3",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"liburing": "liburing",
"nix-filter": "nix-filter", "nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_5", "nixpkgs": "nixpkgs_5",
"rocksdb": "rocksdb" "rocksdb": "rocksdb"
@@ -546,11 +599,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1751433876, "lastModified": 1740691488,
"narHash": "sha256-IsdwOcvLLDDlkFNwhdD5BZy20okIQL01+UQ7Kxbqh8s=", "narHash": "sha256-Fs6vBrByuiOf2WO77qeMDMTXcTGzrIMqLBv+lNeywwM=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "11d45c881389dae90b0da5a94cde52c79d0fc7ef", "rev": "fe3eda77d3a7ce212388bda7b6cec8bffcc077e5",
"type": "github" "type": "github"
}, },
"original": { "original": {
+528 -295
View File
@@ -2,344 +2,577 @@
inputs = { inputs = {
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";
crane = { complement = { url = "github:girlbossceo/complement?ref=main"; flake = false; };
url = "github:ipetkov/crane?ref=master"; crane = { url = "github:ipetkov/crane?ref=master"; };
}; fenix = { url = "github:nix-community/fenix?ref=main"; inputs.nixpkgs.follows = "nixpkgs"; };
fenix = { flake-compat = { url = "github:edolstra/flake-compat?ref=master"; flake = false; };
url = "github:nix-community/fenix?ref=main";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-compat = {
url = "github:edolstra/flake-compat?ref=master";
flake = false;
};
flake-utils.url = "github:numtide/flake-utils?ref=main"; 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=nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs?ref=nixpkgs-unstable";
rocksdb = { rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.11.1"; flake = false; };
url = "git+https://forgejo.ellis.link/continuwuation/rocksdb?ref=v9.11.1"; liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
flake = false;
};
}; };
outputs = outputs = inputs:
inputs: inputs.flake-utils.lib.eachDefaultSystem (system:
inputs.flake-utils.lib.eachDefaultSystem ( let
system: pkgsHost = import inputs.nixpkgs{
let inherit system;
pkgsHost = import inputs.nixpkgs { };
pkgsHostStatic = pkgsHost.pkgsStatic;
# The Rust toolchain to use
toolchain = inputs.fenix.packages.${system}.fromToolchainFile {
file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml`
sha256 = "sha256-X/4ZBHO3iW0fOenQ3foEvscgAPJYl2abspaBThDOukI=";
};
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
inherit pkgs;
book = self.callPackage ./nix/pkgs/book {};
complement = self.callPackage ./nix/pkgs/complement {};
craneLib = ((inputs.crane.mkLib pkgs).overrideToolchain (_: toolchain));
inherit inputs;
main = self.callPackage ./nix/pkgs/main {};
oci-image = self.callPackage ./nix/pkgs/oci-image {};
tini = pkgs.tini.overrideAttrs {
# newer clang/gcc is unhappy with tini-static: <https://3.dog/~strawberry/pb/c8y4>
patches = [ (pkgs.fetchpatch {
url = "https://patch-diff.githubusercontent.com/raw/krallin/tini/pull/224.patch";
hash = "sha256-4bTfAhRyIT71VALhHY13hUgbjLEUyvgkIJMt3w9ag3k=";
})
];
};
liburing = pkgs.liburing.overrideAttrs {
# Tests weren't building
outputs = [ "out" "dev" "man" ];
buildFlags = [ "library" ];
src = inputs.liburing;
};
rocksdb = (pkgs.rocksdb.override {
liburing = self.liburing;
}).overrideAttrs (old: {
src = inputs.rocksdb;
version = pkgs.lib.removePrefix
"v"
(builtins.fromJSON (builtins.readFile ./flake.lock))
.nodes.rocksdb.original.ref;
# we have this already at https://github.com/girlbossceo/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
# unsetting this so i don't have to revert it and make this nix exclusive
patches = [];
cmakeFlags = pkgs.lib.subtractLists
[
# no real reason to have snappy or zlib, no one uses this
"-DWITH_SNAPPY=1"
"-DZLIB=1"
"-DWITH_ZLIB=1"
# we dont need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=1"
# we dont need to build rocksdb tests
"-DWITH_TESTS=1"
# we use rust-rocksdb via C interface and dont need C++ RTTI
"-DUSE_RTTI=1"
# this doesn't exist in RocksDB, and USE_SSE is deprecated for
# PORTABLE=$(march)
"-DFORCE_SSE42=1"
# PORTABLE will get set in main/default.nix
"-DPORTABLE=1"
]
old.cmakeFlags
++ [
# no real reason to have snappy, no one uses this
"-DWITH_SNAPPY=0"
"-DZLIB=0"
"-DWITH_ZLIB=0"
# we dont need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=0"
# we dont need trace tools
"-DWITH_TRACE_TOOLS=0"
# we dont need to build rocksdb tests
"-DWITH_TESTS=0"
# we use rust-rocksdb via C interface and dont need C++ RTTI
"-DUSE_RTTI=0"
];
# outputs has "tools" which we dont need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
preInstall = "";
});
});
scopeHost = mkScope pkgsHost;
scopeHostStatic = mkScope pkgsHostStatic;
scopeCrossLinux = mkScope pkgsHost.pkgsLinux.pkgsStatic;
mkCrossScope = crossSystem:
let pkgsCrossStatic = (import inputs.nixpkgs {
inherit system; inherit system;
crossSystem = {
config = crossSystem;
};
}).pkgsStatic;
in
mkScope pkgsCrossStatic;
mkDevShell = scope: scope.pkgs.mkShell {
env = scope.main.env // {
# Rust Analyzer needs to be able to find the path to default crate
# sources, and it can read this environment variable to do so. The
# `rust-src` component is required in order for this to work.
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
# Convenient way to access a pinned version of Complement's source
# code.
COMPLEMENT_SRC = inputs.complement.outPath;
# Needed for Complement: <https://github.com/golang/go/issues/52690>
CGO_CFLAGS = "-Wl,--no-gc-sections";
CGO_LDFLAGS = "-Wl,--no-gc-sections";
}; };
# The Rust toolchain to use # Development tools
toolchain = inputs.fenix.packages.${system}.fromToolchainFile { packages = [
file = ./rust-toolchain.toml; # Always use nightly rustfmt because most of its options are unstable
#
# This needs to come before `toolchain` in this list, otherwise
# `$PATH` will have stable rustfmt instead.
inputs.fenix.packages.${system}.latest.rustfmt
# See also `rust-toolchain.toml` toolchain
sha256 = "sha256-KUm16pHj+cRedf8vxs/Hd2YWxpOrWZ7UOrwhILdSJBU="; ]
}; ++ (with pkgsHost.pkgs; [
# Required by hardened-malloc.rs dep
binutils
mkScope = cargo-audit
pkgs: cargo-auditable
pkgs.lib.makeScope pkgs.newScope (self: {
inherit pkgs inputs;
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (_: toolchain);
main = self.callPackage ./nix/pkgs/main { };
liburing = pkgs.liburing.overrideAttrs {
# Tests weren't building
outputs = [
"out"
"dev"
"man"
];
buildFlags = [ "library" ];
};
rocksdb =
(pkgs.rocksdb_9_10.override {
# Override the liburing input for the build with our own so
# we have it built with the library flag
inherit (self) liburing;
}).overrideAttrs
(old: {
src = inputs.rocksdb;
version = "v9.11.1";
cmakeFlags =
pkgs.lib.subtractLists [
# No real reason to have snappy or zlib, no one uses this
"-DWITH_SNAPPY=1"
"-DZLIB=1"
"-DWITH_ZLIB=1"
# We don't need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=1"
# We don't need to build rocksdb tests
"-DWITH_TESTS=1"
# We use rust-rocksdb via C interface and don't need C++ RTTI
"-DUSE_RTTI=1"
# This doesn't exist in RocksDB, and USE_SSE is deprecated for
# PORTABLE=$(march)
"-DFORCE_SSE42=1"
# PORTABLE will get set in main/default.nix
"-DPORTABLE=1"
] old.cmakeFlags
++ [
# No real reason to have snappy, no one uses this
"-DWITH_SNAPPY=0"
"-DZLIB=0"
"-DWITH_ZLIB=0"
# We don't need to use ldb or sst_dump (core_tools)
"-DWITH_CORE_TOOLS=0"
# We don't need trace tools
"-DWITH_TRACE_TOOLS=0"
# We don't need to build rocksdb tests
"-DWITH_TESTS=0"
# We use rust-rocksdb via C interface and don't need C++ RTTI
"-DUSE_RTTI=0"
];
# outputs has "tools" which we don't need or use # Needed for producing Debian packages
outputs = [ "out" ]; cargo-deb
# preInstall hooks has stuff for messing with ldb/sst_dump which we don't need or use # Needed for CI to check validity of produced Debian packages (dpkg-deb)
preInstall = ""; dpkg
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155 engage
# Unsetting this so we don't have to revert it and make this nix exclusive
patches = [ ];
postPatch = '' # Needed for Complement
# Fix gcc-13 build failures due to missing <cstdint> and go
# <system_error> includes, fixed upstream since 8.x
sed -e '1i #include <cstdint>' -i db/compaction/compaction_iteration_stats.h
sed -e '1i #include <cstdint>' -i table/block_based/data_block_hash_index.h
sed -e '1i #include <cstdint>' -i util/string_util.h
sed -e '1i #include <cstdint>' -i include/rocksdb/utilities/checkpoint.h
'';
});
});
scopeHost = mkScope pkgsHost; # Needed for our script for Complement
mkCrossScope = jq
crossSystem: gotestfmt
let
pkgsCrossStatic =
(import inputs.nixpkgs {
inherit system;
crossSystem = {
config = crossSystem;
};
}).pkgsStatic;
in
mkScope pkgsCrossStatic;
in # Needed for finding broken markdown links
{ lychee
packages =
{ # Needed for linting markdown files
default = scopeHost.main.override { markdownlint-cli
disable_features = [
# Don't include experimental features # Useful for editing the book locally
mdbook
# used for rust caching in CI to speed it up
sccache
]
# liburing is Linux-exclusive
++ lib.optional stdenv.hostPlatform.isLinux liburing
++ lib.optional stdenv.hostPlatform.isLinux numactl)
++ scope.main.buildInputs
++ scope.main.propagatedBuildInputs
++ scope.main.nativeBuildInputs;
};
in
{
packages = {
default = scopeHost.main.override {
disable_features = [
# dont include experimental features
"experimental" "experimental"
# jemalloc profiling/stats features are expensive and shouldn't # jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds. # be expected on non-debug builds.
"jemalloc_prof" "jemalloc_prof"
"jemalloc_stats" "jemalloc_stats"
# This is non-functional on nix for some reason # this is non-functional on nix for some reason
"hardened_malloc" "hardened_malloc"
# conduwuit_mods is a development-only hot reload feature # conduwuit_mods is a development-only hot reload feature
"conduwuit_mods" "conduwuit_mods"
]; ];
}; };
default-debug = scopeHost.main.override { default-debug = scopeHost.main.override {
profile = "dev"; profile = "dev";
# Debug build users expect full logs # debug build users expect full logs
disable_release_max_log_level = true; disable_release_max_log_level = true;
disable_features = [ disable_features = [
# Don't include experimental features # dont include experimental features
"experimental"
# This is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
# Just a test profile used for things like CI and complement
default-test = scopeHost.main.override {
profile = "test";
disable_release_max_log_level = true;
disable_features = [
# Don't include experimental features
"experimental" "experimental"
# this is non-functional on nix for some reason # this is non-functional on nix for some reason
"hardened_malloc" "hardened_malloc"
# conduwuit_mods is a development-only hot reload feature # conduwuit_mods is a development-only hot reload feature
"conduwuit_mods" "conduwuit_mods"
]; ];
}; };
all-features = scopeHost.main.override { # just a test profile used for things like CI and complement
all_features = true; default-test = scopeHost.main.override {
disable_features = [ profile = "test";
# Don't include experimental features disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
all-features = scopeHost.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental" "experimental"
# jemalloc profiling/stats features are expensive and shouldn't # jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds. # be expected on non-debug builds.
"jemalloc_prof" "jemalloc_prof"
"jemalloc_stats" "jemalloc_stats"
# This is non-functional on nix for some reason # this is non-functional on nix for some reason
"hardened_malloc" "hardened_malloc"
# conduwuit_mods is a development-only hot reload feature # conduwuit_mods is a development-only hot reload feature
"conduwuit_mods" "conduwuit_mods"
]; ];
}; };
all-features-debug = scopeHost.main.override { all-features-debug = scopeHost.main.override {
profile = "dev"; profile = "dev";
all_features = true; all_features = true;
# Debug build users expect full logs # debug build users expect full logs
disable_release_max_log_level = true; disable_release_max_log_level = true;
disable_features = [ disable_features = [
# Don't include experimental features # dont include experimental features
"experimental" "experimental"
# This is non-functional on nix for some reason # this is non-functional on nix for some reason
"hardened_malloc" "hardened_malloc"
# conduwuit_mods is a development-only hot reload feature # conduwuit_mods is a development-only hot reload feature
"conduwuit_mods" "conduwuit_mods"
]; ];
}; };
hmalloc = scopeHost.main.override { features = [ "hardened_malloc" ]; }; hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
}
// builtins.listToAttrs (
builtins.concatLists (
builtins.map
(
crossSystem:
let
binaryName = "static-${crossSystem}";
scopeCrossStatic = mkCrossScope crossSystem;
in
[
# An output for a statically-linked binary
{
name = binaryName;
value = scopeCrossStatic.main;
}
# An output for a statically-linked binary with x86_64 haswell oci-image = scopeHost.oci-image;
# target optimisations oci-image-all-features = scopeHost.oci-image.override {
{ main = scopeHost.main.override {
name = "${binaryName}-x86_64-haswell-optimised"; all_features = true;
value = scopeCrossStatic.main.override { disable_features = [
x86_64_haswell_target_optimised = # dont include experimental features
if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false; "experimental"
}; # jemalloc profiling/stats features are expensive and shouldn't
} # be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
};
oci-image-all-features-debug = scopeHost.oci-image.override {
main = scopeHost.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
};
oci-image-hmalloc = scopeHost.oci-image.override {
main = scopeHost.main.override {
features = ["hardened_malloc"];
};
};
# An output for a statically-linked unstripped debug ("dev") binary book = scopeHost.book;
{
name = "${binaryName}-debug";
value = scopeCrossStatic.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
};
}
# An output for a statically-linked unstripped debug binary with the complement = scopeHost.complement;
# "test" profile (for CI usage only) static-complement = scopeHostStatic.complement;
{ # macOS containers don't exist, so the complement images must be forced to linux
name = "${binaryName}-test"; linux-complement = (mkCrossScope "${pkgsHost.hostPlatform.qemuArch}-linux-musl").complement;
value = scopeCrossStatic.main.override {
profile = "test";
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with `--all-features`
{
name = "${binaryName}-all-features";
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with `--all-features` and with x86_64 haswell
# target optimisations
{
name = "${binaryName}-all-features-x86_64-haswell-optimised";
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
x86_64_haswell_target_optimised =
if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false;
};
}
# An output for a statically-linked unstripped debug ("dev") binary with `--all-features`
{
name = "${binaryName}-all-features-debug";
value = scopeCrossStatic.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with hardened_malloc
{
name = "${binaryName}-hmalloc";
value = scopeCrossStatic.main.override {
features = [ "hardened_malloc" ];
};
}
]
)
[
#"x86_64-apple-darwin"
#"aarch64-apple-darwin"
"x86_64-linux-gnu"
"x86_64-linux-musl"
"aarch64-linux-musl"
]
)
);
} }
); //
builtins.listToAttrs
(builtins.concatLists
(builtins.map
(crossSystem:
let
binaryName = "static-${crossSystem}";
scopeCrossStatic = mkCrossScope crossSystem;
in
[
# An output for a statically-linked binary
{
name = binaryName;
value = scopeCrossStatic.main;
}
# An output for a statically-linked binary with x86_64 haswell
# target optimisations
{
name = "${binaryName}-x86_64-haswell-optimised";
value = scopeCrossStatic.main.override {
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
}
# An output for a statically-linked unstripped debug ("dev") binary
{
name = "${binaryName}-debug";
value = scopeCrossStatic.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
};
}
# An output for a statically-linked unstripped debug binary with the
# "test" profile (for CI usage only)
{
name = "${binaryName}-test";
value = scopeCrossStatic.main.override {
profile = "test";
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with `--all-features`
{
name = "${binaryName}-all-features";
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with `--all-features` and with x86_64 haswell
# target optimisations
{
name = "${binaryName}-all-features-x86_64-haswell-optimised";
value = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
}
# An output for a statically-linked unstripped debug ("dev") binary with `--all-features`
{
name = "${binaryName}-all-features-debug";
value = scopeCrossStatic.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}
# An output for a statically-linked binary with hardened_malloc
{
name = "${binaryName}-hmalloc";
value = scopeCrossStatic.main.override {
features = ["hardened_malloc"];
};
}
# An output for an OCI image based on that binary
{
name = "oci-image-${crossSystem}";
value = scopeCrossStatic.oci-image;
}
# An output for an OCI image based on that binary with x86_64 haswell
# target optimisations
{
name = "oci-image-${crossSystem}-x86_64-haswell-optimised";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
};
}
# An output for an OCI image based on that unstripped debug ("dev") binary
{
name = "oci-image-${crossSystem}-debug";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
profile = "dev";
# debug build users expect full logs
disable_release_max_log_level = true;
};
};
}
# An output for an OCI image based on that binary with `--all-features`
{
name = "oci-image-${crossSystem}-all-features";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
};
}
# An output for an OCI image based on that binary with `--all-features` and with x86_64 haswell
# target optimisations
{
name = "oci-image-${crossSystem}-all-features-x86_64-haswell-optimised";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
x86_64_haswell_target_optimised = (if (crossSystem == "x86_64-linux-gnu" || crossSystem == "x86_64-linux-musl") then true else false);
};
};
}
# An output for an OCI image based on that unstripped debug ("dev") binary with `--all-features`
{
name = "oci-image-${crossSystem}-all-features-debug";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
profile = "dev";
all_features = true;
# debug build users expect full logs
disable_release_max_log_level = true;
disable_features = [
# dont include experimental features
"experimental"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
};
}
# An output for an OCI image based on that binary with hardened_malloc
{
name = "oci-image-${crossSystem}-hmalloc";
value = scopeCrossStatic.oci-image.override {
main = scopeCrossStatic.main.override {
features = ["hardened_malloc"];
};
};
}
# An output for a complement OCI image for the specified platform
{
name = "complement-${crossSystem}";
value = scopeCrossStatic.complement;
}
]
)
[
#"x86_64-apple-darwin"
#"aarch64-apple-darwin"
"x86_64-linux-gnu"
"x86_64-linux-musl"
"aarch64-linux-musl"
]
)
);
devShells.default = mkDevShell scopeHostStatic;
devShells.all-features = mkDevShell
(scopeHostStatic.overrideScope (final: prev: {
main = prev.main.override {
all_features = true;
disable_features = [
# dont include experimental features
"experimental"
# jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds.
"jemalloc_prof"
"jemalloc_stats"
# this is non-functional on nix for some reason
"hardened_malloc"
# conduwuit_mods is a development-only hot reload feature
"conduwuit_mods"
];
};
}));
devShells.no-features = mkDevShell
(scopeHostStatic.overrideScope (final: prev: {
main = prev.main.override { default_features = false; };
}));
devShells.dynamic = mkDevShell scopeHost;
});
} }
+36
View File
@@ -0,0 +1,36 @@
{ inputs
# Dependencies
, main
, mdbook
, stdenv
}:
stdenv.mkDerivation {
inherit (main) pname version;
src = inputs.nix-filter {
root = inputs.self;
include = [
"book.toml"
"conduwuit-example.toml"
"CODE_OF_CONDUCT.md"
"CONTRIBUTING.md"
"README.md"
"development.md"
"debian/conduwuit.service"
"debian/README.md"
"arch/conduwuit.service"
"docs"
"theme"
];
};
nativeBuildInputs = [
mdbook
];
buildPhase = ''
mdbook build -d $out
'';
}
+21
View File
@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDfzCCAmegAwIBAgIUcrZdSPmCh33Evys/U6mTPpShqdcwDQYJKoZIhvcNAQEL
BQAwPzELMAkGA1UEBhMCNjkxCzAJBgNVBAgMAjQyMRUwEwYDVQQKDAx3b29mZXJz
IGluYy4xDDAKBgNVBAMMA2hzMTAgFw0yNTAzMTMxMjU4NTFaGA8yMDUyMDcyODEy
NTg1MVowPzELMAkGA1UEBhMCNjkxCzAJBgNVBAgMAjQyMRUwEwYDVQQKDAx3b29m
ZXJzIGluYy4xDDAKBgNVBAMMA2hzMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANL+h2ZmK/FqN5uLJPtIy6Feqcyb6EX7MQBEtxuJ56bTAbjHuCLZLpYt
/wOWJ91drHqZ7Xd5iTisGdMu8YS803HSnHkzngf4VXKhVrdzW2YDrpZRxmOhtp88
awOHmP7mqlJyBbCOQw8aDVrT0KmEIWzA7g+nFRQ5Ff85MaP+sQrHGKZbo61q8HBp
L0XuaqNckruUKtxnEqrm5xx5sYyYKg7rrSFE5JMFoWKB1FNWJxyWT42BhGtnJZsK
K5c+NDSOU4TatxoN6mpNSBpCz/a11PiQHMEfqRk6JA4g3911dqPTfZBevUdBh8gl
8maIzqeZGhvyeKTmull1Y0781yyuj98CAwEAAaNxMG8wCQYDVR0TBAIwADALBgNV
HQ8EBAMCBPAwNgYDVR0RBC8wLYIRKi5kb2NrZXIuaW50ZXJuYWyCA2hzMYIDaHMy
ggNoczOCA2hzNIcEfwAAATAdBgNVHQ4EFgQUr4VYrmW1d+vjBTJewvy7fJYhLDYw
DQYJKoZIhvcNAQELBQADggEBADkYqkjNYxjWX8hUUAmFHNdCwzT1CpYe/5qzLiyJ
irDSdMlC5g6QqMUSrpu7nZxo1lRe1dXGroFVfWpoDxyCjSQhplQZgtYqtyLfOIx+
HQ7cPE/tUU/KsTGc0aL61cETB6u8fj+rQKUGdfbSlm0Rpu4v0gC8RnDj06X/hZ7e
VkWU+dOBzxlqHuLlwFFtVDgCyyTatIROx5V+GpMHrVqBPO7HcHhwqZ30k2kMM8J3
y1CWaliQM85jqtSZV+yUHKQV8EksSowCFJuguf+Ahz0i0/koaI3i8m4MRN/1j13d
jbTaX5a11Ynm3A27jioZdtMRty6AJ88oCp18jxVzqTxNNO4=
-----END CERTIFICATE-----
+50
View File
@@ -0,0 +1,50 @@
[global]
address = "0.0.0.0"
allow_device_name_federation = true
allow_guest_registration = true
allow_public_room_directory_over_federation = true
allow_public_room_directory_without_auth = true
allow_registration = true
database_path = "/database"
log = "trace,h2=debug,hyper=debug"
port = [8008, 8448]
trusted_servers = []
only_query_trusted_key_servers = false
query_trusted_key_servers_first = false
query_trusted_key_servers_first_on_join = false
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
ip_range_denylist = []
url_preview_domain_contains_allowlist = ["*"]
url_preview_domain_explicit_denylist = ["*"]
media_compat_file_link = false
media_startup_check = true
prune_missing_media = true
log_colors = true
admin_room_notices = false
allow_check_for_updates = false
intentionally_unknown_config_option_for_testing = true
rocksdb_log_level = "info"
rocksdb_max_log_files = 1
rocksdb_recovery_mode = 0
rocksdb_paranoid_file_checks = true
log_guest_registrations = false
allow_legacy_media = true
startup_netburst = true
startup_netburst_keep = -1
allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure = true
# valgrind makes things so slow
dns_timeout = 60
dns_attempts = 20
request_conn_timeout = 60
request_timeout = 120
well_known_conn_timeout = 60
well_known_timeout = 60
federation_idle_timeout = 300
sender_timeout = 300
sender_idle_timeout = 300
sender_retry_backoff_limit = 300
[global.tls]
dual_protocol = true
+89
View File
@@ -0,0 +1,89 @@
# Dependencies
{ bashInteractive
, buildEnv
, coreutils
, dockerTools
, lib
, main
, stdenv
, tini
, writeShellScriptBin
}:
let
main' = main.override {
profile = "test";
all_features = true;
disable_release_max_log_level = true;
disable_features = [
# console/CLI stuff isn't used or relevant for complement
"console"
"tokio_console"
# sentry telemetry isn't useful for complement, disabled by default anyways
"sentry_telemetry"
"perf_measurements"
# this is non-functional on nix for some reason
"hardened_malloc"
# dont include experimental features
"experimental"
# compression isn't needed for complement
"brotli_compression"
"gzip_compression"
"zstd_compression"
# complement doesn't need hot reloading
"conduwuit_mods"
# complement doesn't have URL preview media tests
"url_preview"
];
};
start = writeShellScriptBin "start" ''
set -euxo pipefail
${lib.getExe' coreutils "env"} \
CONDUWUIT_SERVER_NAME="$SERVER_NAME" \
${lib.getExe main'}
'';
in
dockerTools.buildImage {
name = "complement-conduwuit";
tag = "main";
copyToRoot = buildEnv {
name = "root";
pathsToLink = [
"/bin"
];
paths = [
bashInteractive
coreutils
main'
start
];
};
config = {
Cmd = [
"${lib.getExe start}"
];
Entrypoint = if !stdenv.hostPlatform.isDarwin
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
# are handled as expected
then [ "${lib.getExe' tini "tini"}" "--" ]
else [];
Env = [
"CONTINUWUITY_TLS__KEY=${./private_key.key}"
"CONTINUWUITY_TLS__CERTS=${./certificate.crt}"
"CONTINUWUITY_CONFIG=${./config.toml}"
"RUST_BACKTRACE=full"
];
ExposedPorts = {
"8008/tcp" = {};
"8448/tcp" = {};
};
};
}
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDS/odmZivxajeb
iyT7SMuhXqnMm+hF+zEARLcbieem0wG4x7gi2S6WLf8DlifdXax6me13eYk4rBnT
LvGEvNNx0px5M54H+FVyoVa3c1tmA66WUcZjobafPGsDh5j+5qpScgWwjkMPGg1a
09CphCFswO4PpxUUORX/OTGj/rEKxximW6OtavBwaS9F7mqjXJK7lCrcZxKq5ucc
ebGMmCoO660hROSTBaFigdRTVicclk+NgYRrZyWbCiuXPjQ0jlOE2rcaDepqTUga
Qs/2tdT4kBzBH6kZOiQOIN/ddXaj032QXr1HQYfIJfJmiM6nmRob8nik5rpZdWNO
/Ncsro/fAgMBAAECggEAITCCkfv+a5I+vwvrPE/eIDso0JOxvNhfg+BLQVy3AMnu
WmeoMmshZeREWgcTrEGg8QQnk4Sdrjl8MnkO6sddJ2luza3t7OkGX+q7Hk5aETkB
DIo+f8ufU3sIhlydF3OnVSK0fGpUaBq8AQ6Soyeyrk3G5NVufmjgae5QPbDBnqUb
piOGyfcwagL4JtCbZsMk8AT7vQSynLm6zaWsVzWNd71jummLqtVV063K95J9PqVN
D8meEcP3WR5kQrvf+mgy9RVgWLRtVWN8OLZfJ9yrnl4Efj62elrldUj4jaCFezGQ
8f0W+d8jjt038qhmEdymw2MWQ+X/b0R79lJar1Up8QKBgQD1DtHxauhl+JUoI3y+
3eboqXl7YPJt1/GTnChb4b6D1Z1hvLsOKUa7hjGEfruYGbsWXBCRMICdfzp+iWcq
/lEOp7/YU9OaW4lQMoG4sXMoBWd9uLgg0E+aH6VDJOBvxsfafqM4ufmtspzwEm90
FU1cq6oImomFnPChSq4X+3+YpwKBgQDcalaK9llCcscWA8HAP8WVVNTjCOqiDp9q
td61E9IO/FIB/gW5y+JkaFRrA2CN1zY3s3K92uveLTNYTArecWlDcPNNFDuaYu2M
Roz4bC104HGh+zztJ0iPVzELL81Lgg6wHhLONN+eVi4gTftJxzJFXybyb+xVT25A
91ynKXB+CQKBgQC+Ub43MoI+/6pHvBfb3FbDByvz6D0flgBmVXb6tP3TQYmzKHJV
8zSd2wCGGC71V7Z3DRVIzVR1/SOetnPLbivhp+JUzfWfAcxI3pDksdvvjxLrDxTh
VycbWcxtsywjY0w/ou581eLVRcygnpC0pP6qJCAwAmUfwd0YRvmiYo6cLQKBgHIW
UIlJDdaJFmdctnLOD3VGHZMOUHRlYTqYvJe5lKbRD5mcZFZRI/OY1Ok3LEj+tj+K
kL+YizHK76KqaY3N4hBYbHbfHCLDRfWvptQHGlg+vFJ9eoG+LZ6UIPyLV5XX0cZz
KoS1dXG9Zc6uznzXsDucDsq6B/f4TzctUjXsCyARAoGAOKb4HtuNyYAW0jUlujR7
IMHwUesOGlhSXqFtP9aTvk6qJgvV0+3CKcWEb4y02g+uYftP8BLNbJbIt9qOqLYh
tOVyzCoamAi8araAhjA0w4dXvqDCDK7k/gZFkojmKQtRijoxTHnWcDc3vAjYCgaM
9MVtdgSkuh2gwkD/mMoAJXM=
-----END PRIVATE KEY-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIChDCCAWwCAQAwPzELMAkGA1UEBhMCNjkxCzAJBgNVBAgMAjQyMRUwEwYDVQQK
DAx3b29mZXJzIGluYy4xDDAKBgNVBAMMA2hzMTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANL+h2ZmK/FqN5uLJPtIy6Feqcyb6EX7MQBEtxuJ56bTAbjH
uCLZLpYt/wOWJ91drHqZ7Xd5iTisGdMu8YS803HSnHkzngf4VXKhVrdzW2YDrpZR
xmOhtp88awOHmP7mqlJyBbCOQw8aDVrT0KmEIWzA7g+nFRQ5Ff85MaP+sQrHGKZb
o61q8HBpL0XuaqNckruUKtxnEqrm5xx5sYyYKg7rrSFE5JMFoWKB1FNWJxyWT42B
hGtnJZsKK5c+NDSOU4TatxoN6mpNSBpCz/a11PiQHMEfqRk6JA4g3911dqPTfZBe
vUdBh8gl8maIzqeZGhvyeKTmull1Y0781yyuj98CAwEAAaAAMA0GCSqGSIb3DQEB
CwUAA4IBAQDR/gjfxN0IID1MidyhZB4qpdWn3m6qZnEQqoTyHHdWalbfNXcALC79
ffS+Smx40N5hEPvqy6euR89N5YuYvt8Hs+j7aWNBn7Wus5Favixcm2JcfCTJn2R3
r8FefuSs2xGkoyGsPFFcXE13SP/9zrZiwvOgSIuTdz/Pbh6GtEx7aV4DqHJsrXnb
XuPxpQleoBqKvQgSlmaEBsJg13TQB+Fl2foBVUtqAFDQiv+RIuircf0yesMCKJaK
MPH4Oo+r3pR8lI8ewfJPreRhCoV+XrGYMubaakz003TJ1xlOW8M+N9a6eFyMVh76
U1nY/KP8Ua6Lgaj9PRz7JCRzNoshZID/
-----END CERTIFICATE REQUEST-----
+12
View File
@@ -0,0 +1,12 @@
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.docker.internal
DNS.2 = hs1
DNS.3 = hs2
DNS.4 = hs3
DNS.5 = hs4
IP.1 = 127.0.0.1
+29 -25
View File
@@ -4,47 +4,51 @@
, stdenv , stdenv
}: }:
lib.optionalAttrs stdenv.hostPlatform.isStatic lib.optionalAttrs stdenv.hostPlatform.isStatic {
{ ROCKSDB_STATIC = "";
ROCKSDB_STATIC = ""; }
}
// //
{ {
CARGO_BUILD_RUSTFLAGS = CARGO_BUILD_RUSTFLAGS =
lib.concatStringsSep lib.concatStringsSep
" " " "
(lib.optionals ([]
stdenv.hostPlatform.isStatic # This disables PIE for static builds, which isn't great in terms
[ "-C" "relocation-model=static" ] # of security. Unfortunately, my hand is forced because nixpkgs'
++ lib.optionals # `libstdc++.a` is built without `-fPIE`, which precludes us from
(stdenv.buildPlatform.config != stdenv.hostPlatform.config) # leaving PIE enabled.
[ ++ lib.optionals
"-l" stdenv.hostPlatform.isStatic
"c" [ "-C" "relocation-model=static" ]
++ lib.optionals
(stdenv.buildPlatform.config != stdenv.hostPlatform.config)
[
"-l"
"c"
"-l" "-l"
"stdc++" "stdc++"
"-L" "-L"
"${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib" "${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib"
] ]
); );
} }
# What follows is stolen from [here][0]. Its purpose is to properly # What follows is stolen from [here][0]. Its purpose is to properly
# configure compilers and linkers for various stages of the build, and # configure compilers and linkers for various stages of the build, and
# 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/nixpkgs-unstable/pkgs/build-support/rust/lib/default.nix#L48-L68 # [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;
in in
lib.optionalAttrs lib.optionalAttrs
(stdenv.targetPlatform.rust.rustcTarget (stdenv.targetPlatform.rust.rustcTarget
!= stdenv.hostPlatform.rust.rustcTarget) != stdenv.hostPlatform.rust.rustcTarget)
( (
let let
inherit (stdenv.targetPlatform.rust) cargoEnvVarTarget; inherit (stdenv.targetPlatform.rust) cargoEnvVarTarget;
+135 -138
View File
@@ -12,146 +12,144 @@
, rust-jemalloc-sys , rust-jemalloc-sys
, stdenv , stdenv
# Options (keep sorted) # Options (keep sorted)
, all_features ? false , all_features ? false
, default_features ? true , default_features ? true
# default list of disabled features # default list of disabled features
, disable_features ? [ , disable_features ? [
# dont include experimental features # dont include experimental features
"experimental" "experimental"
# jemalloc profiling/stats features are expensive and shouldn't # jemalloc profiling/stats features are expensive and shouldn't
# be expected on non-debug builds. # be expected on non-debug builds.
"jemalloc_prof" "jemalloc_prof"
"jemalloc_stats" "jemalloc_stats"
# this is non-functional on nix for some reason # this is non-functional on nix for some reason
"hardened_malloc" "hardened_malloc"
# conduwuit_mods is a development-only hot reload feature # conduwuit_mods is a development-only hot reload feature
"conduwuit_mods" "conduwuit_mods"
] ]
, disable_release_max_log_level ? false , disable_release_max_log_level ? false
, features ? [ ] , features ? []
, profile ? "release" , profile ? "release"
# rocksdb compiled with -march=haswell and target-cpu=haswell rustflag # rocksdb compiled with -march=haswell and target-cpu=haswell rustflag
# haswell is pretty much any x86 cpu made in the last 12 years, and # haswell is pretty much any x86 cpu made in the last 12 years, and
# supports modern CPU extensions that rocksdb can make use of. # supports modern CPU extensions that rocksdb can make use of.
# disable if trying to make a portable x86_64 build for very old hardware # disable if trying to make a portable x86_64 build for very old hardware
, x86_64_haswell_target_optimised ? false , x86_64_haswell_target_optimised ? false
}: }:
let let
# We perform default-feature unification in nix, because some of the dependencies # We perform default-feature unification in nix, because some of the dependencies
# on the nix side depend on feature values. # on the nix side depend on feature values.
crateFeatures = path: crateFeatures = path:
let manifest = lib.importTOML "${path}/Cargo.toml"; in let manifest = lib.importTOML "${path}/Cargo.toml"; in
lib.remove "default" (lib.attrNames manifest.features); lib.remove "default" (lib.attrNames manifest.features);
crateDefaultFeatures = path: crateDefaultFeatures = path:
(lib.importTOML "${path}/Cargo.toml").features.default; (lib.importTOML "${path}/Cargo.toml").features.default;
allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main"; allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main";
allFeatures = crateFeatures "${inputs.self}/src/main"; allFeatures = crateFeatures "${inputs.self}/src/main";
features' = lib.unique features' = lib.unique
(features ++ (features ++
lib.optionals default_features allDefaultFeatures ++ lib.optionals default_features allDefaultFeatures ++
lib.optionals all_features allFeatures); lib.optionals all_features allFeatures);
disable_features' = disable_features ++ lib.optionals disable_release_max_log_level [ "release_max_log_level" ]; disable_features' = disable_features ++ lib.optionals disable_release_max_log_level ["release_max_log_level"];
features'' = lib.subtractLists disable_features' features'; features'' = lib.subtractLists disable_features' features';
featureEnabled = feature: builtins.elem feature features''; featureEnabled = feature : builtins.elem feature features'';
enableLiburing = featureEnabled "io_uring" && !stdenv.hostPlatform.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
# own. In order for this to work, we need to set flags on the build that match # own. In order for this to work, we need to set flags on the build that match
# whatever flags tikv-jemalloc-sys was going to use. These are dependent on # whatever flags tikv-jemalloc-sys was going to use. These are dependent on
# which features we enable in tikv-jemalloc-sys. # which features we enable in tikv-jemalloc-sys.
rust-jemalloc-sys' = (rust-jemalloc-sys.override { rust-jemalloc-sys' = (rust-jemalloc-sys.override {
# tikv-jemalloc-sys/unprefixed_malloc_on_supported_platforms feature # tikv-jemalloc-sys/unprefixed_malloc_on_supported_platforms feature
unprefixed = true; unprefixed = true;
}).overrideAttrs (old: { }).overrideAttrs (old: {
configureFlags = old.configureFlags ++ configureFlags = old.configureFlags ++
# we dont need docs # we dont need docs
[ "--disable-doc" ] ++ [ "--disable-doc" ] ++
# we dont need cxx/C++ integration # we dont need cxx/C++ integration
[ "--disable-cxx" ] ++ [ "--disable-cxx" ] ++
# tikv-jemalloc-sys/profiling feature # tikv-jemalloc-sys/profiling feature
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof" ++ lib.optional (featureEnabled "jemalloc_prof") "--enable-prof" ++
# tikv-jemalloc-sys/stats feature # tikv-jemalloc-sys/stats feature
(if (featureEnabled "jemalloc_stats") then [ "--enable-stats" ] else [ "--disable-stats" ]); (if (featureEnabled "jemalloc_stats") then [ "--enable-stats" ] else [ "--disable-stats" ]);
});
buildDepsOnlyEnv =
let
rocksdb' = (rocksdb.override {
jemalloc = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
# rocksdb fails to build with prefixed jemalloc, which is required on
# darwin due to [1]. In this case, fall back to building rocksdb with
# libc malloc. This should not cause conflicts, because all of the
# jemalloc symbols are prefixed.
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
enableJemalloc = featureEnabled "jemalloc" && !stdenv.hostPlatform.isDarwin;
# for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely
enableLiburing = enableLiburing;
}).overrideAttrs (old: {
enableLiburing = enableLiburing;
cmakeFlags = (if x86_64_haswell_target_optimised then (lib.subtractLists [
# dont make a portable build if x86_64_haswell_target_optimised is enabled
"-DPORTABLE=1"
] old.cmakeFlags
++ [ "-DPORTABLE=haswell" ]) else ([ "-DPORTABLE=1" ])
)
++ old.cmakeFlags;
# outputs has "tools" which we dont need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
preInstall = "";
});
in
{
# https://crane.dev/faq/rebuilds-bindgen.html
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
CARGO_PROFILE = profile;
ROCKSDB_INCLUDE_DIR = "${rocksdb'}/include";
ROCKSDB_LIB_DIR = "${rocksdb'}/lib";
}
//
(import ./cross-compilation-env.nix {
# Keep sorted
inherit
lib
pkgsBuildHost
rust
stdenv;
}); });
buildDepsOnlyEnv = buildPackageEnv = {
let GIT_COMMIT_HASH = inputs.self.rev or inputs.self.dirtyRev or "";
rocksdb' = (rocksdb.override { GIT_COMMIT_HASH_SHORT = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
jemalloc = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys'; } // buildDepsOnlyEnv // {
# rocksdb fails to build with prefixed jemalloc, which is required on # Only needed in static stdenv because these are transitive dependencies of rocksdb
# darwin due to [1]. In this case, fall back to building rocksdb with CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
# libc malloc. This should not cause conflicts, because all of the + lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
# jemalloc symbols are prefixed.
#
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
enableJemalloc = featureEnabled "jemalloc" && !stdenv.hostPlatform.isDarwin;
# for some reason enableLiburing in nixpkgs rocksdb is default true
# which breaks Darwin entirely
inherit enableLiburing;
}).overrideAttrs (old: {
inherit enableLiburing;
cmakeFlags = (if x86_64_haswell_target_optimised then
(lib.subtractLists [
# dont make a portable build if x86_64_haswell_target_optimised is enabled
"-DPORTABLE=1"
]
old.cmakeFlags
++ [ "-DPORTABLE=haswell" ]) else [ "-DPORTABLE=1" ]
)
++ old.cmakeFlags;
# outputs has "tools" which we dont need or use
outputs = [ "out" ];
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
preInstall = "";
});
in
{
# https://crane.dev/faq/rebuilds-bindgen.html
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
CARGO_PROFILE = profile;
ROCKSDB_INCLUDE_DIR = "${rocksdb'}/include";
ROCKSDB_LIB_DIR = "${rocksdb'}/lib";
}
//
(import ./cross-compilation-env.nix {
# Keep sorted
inherit
lib
pkgsBuildHost
rust
stdenv;
});
buildPackageEnv = {
GIT_COMMIT_HASH = inputs.self.rev or inputs.self.dirtyRev or "";
GIT_COMMIT_HASH_SHORT = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
} // buildDepsOnlyEnv // {
# Only needed in static stdenv because these are transitive dependencies of rocksdb
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
" -L${lib.getLib liburing}/lib -luring" " -L${lib.getLib liburing}/lib -luring"
+ lib.optionalString x86_64_haswell_target_optimised + lib.optionalString x86_64_haswell_target_optimised
" -Ctarget-cpu=haswell"; " -Ctarget-cpu=haswell";
}; };
commonAttrs = { commonAttrs = {
inherit inherit
(craneLib.crateNameFromCargoToml { (craneLib.crateNameFromCargoToml {
cargoToml = "${inputs.self}/Cargo.toml"; cargoToml = "${inputs.self}/Cargo.toml";
}) })
pname pname
version; version;
src = let filter = inputs.nix-filter.lib; in filter { src = let filter = inputs.nix-filter.lib; in filter {
root = inputs.self; root = inputs.self;
@@ -162,7 +160,6 @@ let
"Cargo.lock" "Cargo.lock"
"Cargo.toml" "Cargo.toml"
"src" "src"
"xtask"
]; ];
}; };
@@ -170,22 +167,22 @@ let
cargoExtraArgs = "--no-default-features --locked " cargoExtraArgs = "--no-default-features --locked "
+ lib.optionalString + lib.optionalString
(features'' != [ ]) (features'' != [])
"--features " + (builtins.concatStringsSep "," features''); "--features " + (builtins.concatStringsSep "," features'');
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 # needed to build Rust applications on macOS
++ lib.optionals stdenv.hostPlatform.isDarwin [ ++ 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 # 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
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612 # https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
pkgsBuildHost.darwin.apple_sdk.frameworks.Security 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
@@ -198,11 +195,11 @@ let
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious # differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
# rebuilds of bindgen and its depedents. # rebuilds of bindgen and its depedents.
jq jq
]; ];
}; };
in in
craneLib.buildPackage (commonAttrs // { craneLib.buildPackage ( commonAttrs // {
cargoArtifacts = craneLib.buildDepsOnly (commonAttrs // { cargoArtifacts = craneLib.buildDepsOnly (commonAttrs // {
env = buildDepsOnlyEnv; env = buildDepsOnlyEnv;
}); });
@@ -211,8 +208,8 @@ craneLib.buildPackage (commonAttrs // {
cargoExtraArgs = "--no-default-features --locked " cargoExtraArgs = "--no-default-features --locked "
+ lib.optionalString + lib.optionalString
(features'' != [ ]) (features'' != [])
"--features " + (builtins.concatStringsSep "," features''); "--features " + (builtins.concatStringsSep "," features'');
env = buildPackageEnv; env = buildPackageEnv;
+46
View File
@@ -0,0 +1,46 @@
{ inputs
# Dependencies
, dockerTools
, lib
, main
, stdenv
, tini
}:
dockerTools.buildLayeredImage {
name = main.pname;
tag = "main";
created = "@${toString inputs.self.lastModified}";
contents = [
dockerTools.caCertificates
main
];
config = {
Entrypoint = if !stdenv.hostPlatform.isDarwin
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
# are handled as expected
then [ "${lib.getExe' tini "tini"}" "--" ]
else [];
Cmd = [
"${lib.getExe main}"
];
Env = [
"RUST_BACKTRACE=full"
];
Labels = {
"org.opencontainers.image.authors" = "June Clementine Strawberry <june@girlboss.ceo> and Jason Volk
<jason@zemos.net>";
"org.opencontainers.image.created" ="@${toString inputs.self.lastModified}";
"org.opencontainers.image.description" = "a very cool Matrix chat homeserver written in Rust";
"org.opencontainers.image.documentation" = "https://continuwuity.org/";
"org.opencontainers.image.licenses" = "Apache-2.0";
"org.opencontainers.image.revision" = inputs.self.rev or inputs.self.dirtyRev or "";
"org.opencontainers.image.source" = "https://forgejo.ellis.link/continuwuation/continuwuity";
"org.opencontainers.image.title" = main.pname;
"org.opencontainers.image.url" = "https://continuwuity.org/";
"org.opencontainers.image.vendor" = "continuwuation";
"org.opencontainers.image.version" = main.version;
};
};
}
+9 -1
View File
@@ -9,7 +9,7 @@
# If you're having trouble making the relevant changes, bug a maintainer. # If you're having trouble making the relevant changes, bug a maintainer.
[toolchain] [toolchain]
channel = "1.87.0" channel = "1.86.0"
profile = "minimal" profile = "minimal"
components = [ components = [
# For rust-analyzer # For rust-analyzer
@@ -19,3 +19,11 @@ components = [
"rustfmt", "rustfmt",
"clippy", "clippy",
] ]
targets = [
#"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-musl",
"aarch64-unknown-linux-gnu",
#"aarch64-apple-darwin",
]
+2 -2
View File
@@ -9,8 +9,8 @@ use crate::{
}; };
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(name = conduwuit_core::name(), version = conduwuit_core::version())] #[command(name = "conduwuit", version = conduwuit::version())]
pub enum AdminCommand { pub(super) enum AdminCommand {
#[command(subcommand)] #[command(subcommand)]
/// - Commands for managing appservices /// - Commands for managing appservices
Appservices(AppserviceCommand), Appservices(AppserviceCommand),
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::admin_command_dispatch;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
#[admin_command_dispatch] #[admin_command_dispatch]
pub enum AppserviceCommand { pub(super) enum AppserviceCommand {
/// - Register an appservice using its registration YAML /// - Register an appservice using its registration YAML
/// ///
/// This command needs a YAML generated by an appservice (such as a bridge), /// This command needs a YAML generated by an appservice (such as a bridge),
+1 -1
View File
@@ -7,6 +7,6 @@ use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum CheckCommand { pub(super) enum CheckCommand {
CheckAllUsers, CheckAllUsers,
} }
+1 -8
View File
@@ -7,14 +7,13 @@ use futures::{
io::{AsyncWriteExt, BufWriter}, io::{AsyncWriteExt, BufWriter},
lock::Mutex, lock::Mutex,
}; };
use ruma::{EventId, UserId}; use ruma::EventId;
pub(crate) struct Context<'a> { pub(crate) struct Context<'a> {
pub(crate) services: &'a Services, pub(crate) services: &'a Services,
pub(crate) body: &'a [&'a str], pub(crate) body: &'a [&'a str],
pub(crate) timer: SystemTime, pub(crate) timer: SystemTime,
pub(crate) reply_id: Option<&'a EventId>, pub(crate) reply_id: Option<&'a EventId>,
pub(crate) sender: Option<&'a UserId>,
pub(crate) output: Mutex<BufWriter<Vec<u8>>>, pub(crate) output: Mutex<BufWriter<Vec<u8>>>,
} }
@@ -37,10 +36,4 @@ impl Context<'_> {
output.write_all(s.as_bytes()).map_err(Into::into).await output.write_all(s.as_bytes()).map_err(Into::into).await
}) })
} }
/// Get the sender as a string, or service user ID if not available
pub(crate) fn sender_or_service_user(&self) -> &UserId {
self.sender
.unwrap_or_else(|| self.services.globals.server_user.as_ref())
}
} }
+19 -33
View File
@@ -7,10 +7,7 @@ use std::{
use conduwuit::{ use conduwuit::{
Err, Result, debug_error, err, info, Err, Result, debug_error, err, info,
matrix::{ matrix::pdu::{PduEvent, PduId, RawPduId},
Event,
pdu::{PduEvent, PduId, RawPduId},
},
trace, utils, trace, utils,
utils::{ utils::{
stream::{IterStream, ReadyExt}, stream::{IterStream, ReadyExt},
@@ -22,7 +19,7 @@ use futures::{FutureExt, StreamExt, TryStreamExt};
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw, api::federation::event::get_room_state,
}; };
use service::rooms::{ use service::rooms::{
short::{ShortEventId, ShortRoomId}, short::{ShortEventId, ShortRoomId},
@@ -242,11 +239,10 @@ pub(super) async fn get_remote_pdu(
}) })
.await .await
{ {
| Err(e) => { | Err(e) =>
return Err!( return Err!(
"Remote server did not have PDU or failed sending request to remote server: {e}" "Remote server did not have PDU or failed sending request to remote server: {e}"
); ),
},
| Ok(response) => { | Ok(response) => {
let json: CanonicalJsonObject = let json: CanonicalJsonObject =
serde_json::from_str(response.pdu.get()).map_err(|e| { serde_json::from_str(response.pdu.get()).map_err(|e| {
@@ -299,12 +295,12 @@ pub(super) async fn get_remote_pdu(
#[admin_command] #[admin_command]
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result { pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.rooms.alias.resolve(&room).await?; let room_id = self.services.rooms.alias.resolve(&room).await?;
let room_state: Vec<Raw<AnyStateEvent>> = self let room_state: Vec<_> = self
.services .services
.rooms .rooms
.state_accessor .state_accessor
.room_state_full_pdus(&room_id) .room_state_full_pdus(&room_id)
.map_ok(Event::into_format) .map_ok(PduEvent::into_state_event)
.try_collect() .try_collect()
.await?; .await?;
@@ -388,9 +384,8 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
.reload .reload
.reload(&old_filter_layer, Some(handles)) .reload(&old_filter_layer, Some(handles))
{ {
| Err(e) => { | Err(e) =>
return Err!("Failed to modify and reload the global tracing log level: {e}"); return Err!("Failed to modify and reload the global tracing log level: {e}"),
},
| Ok(()) => { | Ok(()) => {
let value = &self.services.server.config.log; let value = &self.services.server.config.log;
let out = format!("Successfully changed log level back to config value {value}"); let out = format!("Successfully changed log level back to config value {value}");
@@ -412,12 +407,9 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
.reload .reload
.reload(&new_filter_layer, Some(handles)) .reload(&new_filter_layer, Some(handles))
{ {
| Ok(()) => { | Ok(()) => return self.write_str("Successfully changed log level").await,
return self.write_str("Successfully changed log level").await; | Err(e) =>
}, return Err!("Failed to modify and reload the global tracing log level: {e}"),
| Err(e) => {
return Err!("Failed to modify and reload the global tracing log level: {e}");
},
} }
} }
@@ -537,7 +529,6 @@ pub(super) async fn force_set_room_state_from_server(
&self, &self,
room_id: OwnedRoomId, room_id: OwnedRoomId,
server_name: OwnedServerName, server_name: OwnedServerName,
at_event: Option<OwnedEventId>,
) -> Result { ) -> Result {
if !self if !self
.services .services
@@ -549,18 +540,13 @@ pub(super) async fn force_set_room_state_from_server(
return Err!("We are not participating in the room / we don't know about the room ID."); return Err!("We are not participating in the room / we don't know about the room ID.");
} }
let at_event_id = match at_event { let first_pdu = self
| Some(event_id) => event_id, .services
| None => self .rooms
.services .timeline
.rooms .latest_pdu_in_room(&room_id)
.timeline .await
.latest_pdu_in_room(&room_id) .map_err(|_| err!(Database("Failed to find the latest PDU in database")))?;
.await
.map_err(|_| err!(Database("Failed to find the latest PDU in database")))?
.event_id()
.to_owned(),
};
let room_version = self.services.rooms.state.get_room_version(&room_id).await?; let room_version = self.services.rooms.state.get_room_version(&room_id).await?;
@@ -571,7 +557,7 @@ pub(super) async fn force_set_room_state_from_server(
.sending .sending
.send_federation_request(&server_name, get_room_state::v1::Request { .send_federation_request(&server_name, get_room_state::v1::Request {
room_id: room_id.clone(), room_id: room_id.clone(),
event_id: at_event_id, event_id: first_pdu.event_id.clone(),
}) })
.await?; .await?;
+4 -7
View File
@@ -11,7 +11,7 @@ use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum DebugCommand { pub(super) enum DebugCommand {
/// - Echo input of admin command /// - Echo input of admin command
Echo { Echo {
message: Vec<String>, message: Vec<String>,
@@ -32,13 +32,13 @@ pub enum DebugCommand {
/// the command. /// the command.
ParsePdu, ParsePdu,
/// - Retrieve and print a PDU by EventID from the Continuwuity database /// - Retrieve and print a PDU by EventID from the conduwuit database
GetPdu { GetPdu {
/// An event ID (a $ followed by the base64 reference hash) /// An event ID (a $ followed by the base64 reference hash)
event_id: OwnedEventId, event_id: OwnedEventId,
}, },
/// - Retrieve and print a PDU by PduId from the Continuwuity database /// - Retrieve and print a PDU by PduId from the conduwuit database
GetShortPdu { GetShortPdu {
/// Shortroomid integer /// Shortroomid integer
shortroomid: ShortRoomId, shortroomid: ShortRoomId,
@@ -177,12 +177,9 @@ pub enum DebugCommand {
room_id: OwnedRoomId, room_id: OwnedRoomId,
/// The server we will use to query the room state for /// The server we will use to query the room state for
server_name: OwnedServerName, server_name: OwnedServerName,
/// The event ID of the latest known PDU in the room. Will be found
/// automatically if not provided.
event_id: Option<OwnedEventId>,
}, },
/// - Runs a server name through Continuwuity's true destination resolution /// - Runs a server name through conduwuit's true destination resolution
/// process /// process
/// ///
/// Useful for debugging well-known issues /// Useful for debugging well-known issues
+1 -1
View File
@@ -4,7 +4,7 @@ use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
pub enum TesterCommand { pub(crate) enum TesterCommand {
Panic, Panic,
Failure, Failure,
Tester, Tester,
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum FederationCommand { pub(super) enum FederationCommand {
/// - List all rooms we are currently handling an incoming pdu from /// - List all rooms we are currently handling an incoming pdu from
IncomingFederation, IncomingFederation,
+3 -3
View File
@@ -9,7 +9,7 @@ use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub 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 or event ID (not redacted) /// via a single MXC URL or event ID (not redacted)
Delete { Delete {
@@ -90,10 +90,10 @@ pub enum MediaCommand {
#[arg(short, long, default_value("10000"))] #[arg(short, long, default_value("10000"))]
timeout: u32, timeout: u32,
#[arg(long, default_value("800"))] #[arg(short, long, default_value("800"))]
width: u32, width: u32,
#[arg(long, default_value("800"))] #[arg(short, long, default_value("800"))]
height: u32, height: u32,
}, },
} }
-2
View File
@@ -33,8 +33,6 @@ conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {} conduwuit::mod_dtor! {}
conduwuit::rustc_flags_capture! {} conduwuit::rustc_flags_capture! {}
pub use crate::admin::AdminCommand;
/// Install the admin command processor /// Install the admin command processor
pub async fn init(admin_service: &service::admin::Service) { pub async fn init(admin_service: &service::admin::Service) {
_ = admin_service _ = admin_service
+2 -2
View File
@@ -63,7 +63,6 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
body: &body, body: &body,
timer: SystemTime::now(), timer: SystemTime::now(),
reply_id: input.reply_id.as_deref(), reply_id: input.reply_id.as_deref(),
sender: input.sender.as_deref(),
output: BufWriter::new(Vec::new()).into(), output: BufWriter::new(Vec::new()).into(),
}; };
@@ -94,7 +93,8 @@ async fn process_command(services: Arc<Services>, input: &CommandInput) -> Proce
#[allow(clippy::result_large_err)] #[allow(clippy::result_large_err)]
fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult { fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult {
let link = "Please submit a [bug report](https://forgejo.ellis.link/continuwuation/continuwuity/issues/new). 🥺"; let link =
"Please submit a [bug report](https://forgejo.ellis.link/continuwuation/continuwuity/issues/new). 🥺";
let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}"); let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}");
let content = RoomMessageEventContent::notice_markdown(msg); let content = RoomMessageEventContent::notice_markdown(msg);
error!("Panic while processing command: {error:?}"); error!("Panic while processing command: {error:?}");
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/account_data.rs /// All the getters and iterators from src/database/key_value/account_data.rs
pub enum AccountDataCommand { pub(crate) enum AccountDataCommand {
/// - Returns all changes to the account data that happened after `since`. /// - Returns all changes to the account data that happened after `since`.
ChangesSince { ChangesSince {
/// Full user ID /// Full user ID
+1 -1
View File
@@ -6,7 +6,7 @@ use crate::Context;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/appservice.rs /// All the getters and iterators from src/database/key_value/appservice.rs
pub enum AppserviceCommand { pub(crate) enum AppserviceCommand {
/// - Gets the appservice registration info/details from the ID as a string /// - Gets the appservice registration info/details from the ID as a string
GetRegistration { GetRegistration {
/// Appservice registration ID /// Appservice registration ID
+1 -1
View File
@@ -6,7 +6,7 @@ use crate::Context;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/globals.rs /// All the getters and iterators from src/database/key_value/globals.rs
pub enum GlobalsCommand { pub(crate) enum GlobalsCommand {
DatabaseVersion, DatabaseVersion,
CurrentCount, CurrentCount,
+1 -1
View File
@@ -27,7 +27,7 @@ use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// Query tables from database /// Query tables from database
pub enum QueryCommand { pub(super) enum QueryCommand {
/// - account_data.rs iterators and getters /// - account_data.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
AccountData(AccountDataCommand), AccountData(AccountDataCommand),
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::Context;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/presence.rs /// All the getters and iterators from src/database/key_value/presence.rs
pub enum PresenceCommand { pub(crate) enum PresenceCommand {
/// - Returns the latest presence event for the given user. /// - Returns the latest presence event for the given user.
GetPresence { GetPresence {
/// Full user ID /// Full user ID
+1 -1
View File
@@ -5,7 +5,7 @@ use ruma::OwnedUserId;
use crate::Context; use crate::Context;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum PusherCommand { pub(crate) enum PusherCommand {
/// - Returns all the pushers for the user. /// - Returns all the pushers for the user.
GetPushers { GetPushers {
/// Full user ID /// Full user ID
+1 -1
View File
@@ -19,7 +19,7 @@ use crate::{admin_command, admin_command_dispatch};
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
/// Query tables from database /// Query tables from database
pub enum RawCommand { pub(crate) enum RawCommand {
/// - List database maps /// - List database maps
RawMaps, RawMaps,
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// Resolver service and caches /// Resolver service and caches
pub enum ResolverCommand { pub(crate) enum ResolverCommand {
/// Query the destinations cache /// Query the destinations cache
DestinationsCache { DestinationsCache {
server_name: Option<OwnedServerName>, server_name: Option<OwnedServerName>,
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::Context;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/rooms/alias.rs /// All the getters and iterators from src/database/key_value/rooms/alias.rs
pub enum RoomAliasCommand { pub(crate) enum RoomAliasCommand {
ResolveLocalAlias { ResolveLocalAlias {
/// Full room alias /// Full room alias
alias: OwnedRoomAliasId, alias: OwnedRoomAliasId,
+1 -1
View File
@@ -6,7 +6,7 @@ use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
use crate::Context; use crate::Context;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum RoomStateCacheCommand { pub(crate) enum RoomStateCacheCommand {
ServerInRoom { ServerInRoom {
server: OwnedServerName, server: OwnedServerName,
room_id: OwnedRoomId, room_id: OwnedRoomId,
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// Query tables from database /// Query tables from database
pub enum RoomTimelineCommand { pub(crate) enum RoomTimelineCommand {
Pdus { Pdus {
room_id: OwnedRoomOrAliasId, room_id: OwnedRoomOrAliasId,
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::Context;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/sending.rs /// All the getters and iterators from src/database/key_value/sending.rs
pub enum SendingCommand { pub(crate) enum SendingCommand {
/// - Queries database for all `servercurrentevent_data` /// - Queries database for all `servercurrentevent_data`
ActiveRequests, ActiveRequests,
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// Query tables from database /// Query tables from database
pub enum ShortCommand { pub(crate) enum ShortCommand {
ShortEventId { ShortEventId {
event_id: OwnedEventId, event_id: OwnedEventId,
}, },
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/users.rs /// All the getters and iterators from src/database/key_value/users.rs
pub enum UsersCommand { pub(crate) enum UsersCommand {
CountUsers, CountUsers,
IterUsers, IterUsers,
+1 -1
View File
@@ -8,7 +8,7 @@ use ruma::{OwnedRoomAliasId, OwnedRoomId};
use crate::Context; use crate::Context;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum RoomAliasCommand { pub(crate) enum RoomAliasCommand {
/// - Make an alias point to a room. /// - Make an alias point to a room.
Set { Set {
#[arg(short, long)] #[arg(short, long)]
+1 -1
View File
@@ -6,7 +6,7 @@ use ruma::OwnedRoomId;
use crate::{Context, PAGE_SIZE, get_room_info}; use crate::{Context, PAGE_SIZE, get_room_info};
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum RoomDirectoryCommand { pub(crate) enum RoomDirectoryCommand {
/// - Publish a room to the room directory /// - Publish a room to the room directory
Publish { Publish {
/// The room id of the room to publish /// The room id of the room to publish
+1 -1
View File
@@ -7,7 +7,7 @@ use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum RoomInfoCommand { pub(crate) enum RoomInfoCommand {
/// - List joined members in a room /// - List joined members in a room
ListJoinedMembers { ListJoinedMembers {
room_id: OwnedRoomId, room_id: OwnedRoomId,
+1 -1
View File
@@ -16,7 +16,7 @@ use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum RoomCommand { pub(super) enum RoomCommand {
/// - List all rooms the server knows about /// - List all rooms the server knows about
#[clap(alias = "list")] #[clap(alias = "list")]
ListRooms { ListRooms {
+48 -28
View File
@@ -1,18 +1,18 @@
use api::client::leave_room; use api::client::leave_room;
use clap::Subcommand; use clap::Subcommand;
use conduwuit::{ use conduwuit::{
Err, Result, debug, info, Err, Result, debug,
utils::{IterStream, ReadyExt}, utils::{IterStream, ReadyExt},
warn, warn,
}; };
use futures::{FutureExt, StreamExt}; use futures::StreamExt;
use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId}; use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId};
use crate::{admin_command, admin_command_dispatch, get_room_info}; use crate::{admin_command, admin_command_dispatch, get_room_info};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum RoomModerationCommand { pub(crate) enum RoomModerationCommand {
/// - Bans a room from local users joining and evicts all our local users /// - Bans a room from local users joining and evicts all our local users
/// (including server /// (including server
/// admins) /// admins)
@@ -70,6 +70,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
}; };
debug!("Room specified is a room ID, banning room ID"); debug!("Room specified is a room ID, banning room ID");
self.services.rooms.metadata.ban_room(room_id, true);
room_id.to_owned() room_id.to_owned()
} else if room.is_room_alias_id() { } else if room.is_room_alias_id() {
@@ -89,25 +90,47 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
locally, if not using get_alias_helper to fetch room ID remotely" locally, if not using get_alias_helper to fetch room ID remotely"
); );
match self let room_id = match self
.services .services
.rooms .rooms
.alias .alias
.resolve_alias(room_alias, None) .resolve_local_alias(room_alias)
.await .await
{ {
| Ok((room_id, servers)) => { | Ok(room_id) => room_id,
| _ => {
debug!( debug!(
?room_id, "We don't have this room alias to a room ID locally, attempting to fetch \
?servers, room ID over federation"
"Got federation response fetching room ID for room {room}"
); );
room_id
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
{
| Ok((room_id, servers)) => {
debug!(
?room_id,
?servers,
"Got federation response fetching room ID for {room_id}"
);
room_id
},
| Err(e) => {
return Err!(
"Failed to resolve room alias {room_alias} to a room ID: {e}"
);
},
}
}, },
| Err(e) => { };
return Err!("Failed to resolve room alias {room} to a room ID: {e}");
}, self.services.rooms.metadata.ban_room(&room_id, true);
}
room_id
} else { } else {
return Err!( return Err!(
"Room specified is not a room ID or room alias. Please note that this requires a \ "Room specified is not a room ID or room alias. Please note that this requires a \
@@ -116,7 +139,7 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
); );
}; };
info!("Making all users leave the room {room_id} and forgetting it"); debug!("Making all users leave the room {room_id} and forgetting it");
let mut users = self let mut users = self
.services .services
.rooms .rooms
@@ -127,15 +150,12 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
.boxed(); .boxed();
while let Some(ref user_id) = users.next().await { while let Some(ref user_id) = users.next().await {
info!( debug!(
"Attempting leave for user {user_id} in room {room_id} (ignoring all errors, \ "Attempting leave for user {user_id} in room {room_id} (ignoring all errors, \
evicting admins too)", evicting admins too)",
); );
if let Err(e) = leave_room(self.services, user_id, &room_id, None) if let Err(e) = leave_room(self.services, user_id, &room_id, None).await {
.boxed()
.await
{
warn!("Failed to leave room: {e}"); warn!("Failed to leave room: {e}");
} }
@@ -157,9 +177,10 @@ async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
}) })
.await; .await;
self.services.rooms.directory.set_not_public(&room_id); // remove from the room directory // unpublish from room directory
self.services.rooms.metadata.ban_room(&room_id, true); // prevent further joins self.services.rooms.directory.set_not_public(&room_id);
self.services.rooms.metadata.disable_room(&room_id, true); // disable federation
self.services.rooms.metadata.disable_room(&room_id, true);
self.write_str( self.write_str(
"Room banned, removed all our local users, and disabled incoming federation with room.", "Room banned, removed all our local users, and disabled incoming federation with room.",
@@ -281,6 +302,8 @@ async fn ban_list_of_rooms(&self) -> Result {
} }
for room_id in room_ids { for room_id in room_ids {
self.services.rooms.metadata.ban_room(&room_id, true);
debug!("Banned {room_id} successfully"); debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1); room_ban_count = room_ban_count.saturating_add(1);
@@ -300,10 +323,7 @@ async fn ban_list_of_rooms(&self) -> Result {
evicting admins too)", evicting admins too)",
); );
if let Err(e) = leave_room(self.services, user_id, &room_id, None) if let Err(e) = leave_room(self.services, user_id, &room_id, None).await {
.boxed()
.await
{
warn!("Failed to leave room: {e}"); warn!("Failed to leave room: {e}");
} }
@@ -326,9 +346,9 @@ async fn ban_list_of_rooms(&self) -> Result {
}) })
.await; .await;
self.services.rooms.metadata.ban_room(&room_id, true);
// unpublish from room directory, ignore errors // unpublish from room directory, ignore errors
self.services.rooms.directory.set_not_public(&room_id); self.services.rooms.directory.set_not_public(&room_id);
self.services.rooms.metadata.disable_room(&room_id, true); self.services.rooms.metadata.disable_room(&room_id, true);
} }
+10
View File
@@ -145,6 +145,16 @@ pub(super) async fn restart(&self, force: bool) -> Result {
self.write_str("Restarting server...").await self.write_str("Restarting server...").await
} }
#[admin_command]
pub(super) async fn upload_analytics(&self) -> Result {
match self.services.analytics.force_upload().await {
| Ok(()) => self.write_str("Analytics uploaded successfully.").await,
| Err(e) =>
self.write_str(&format!("Failed to upload analytics: {e}"))
.await,
}
}
#[admin_command] #[admin_command]
pub(super) async fn shutdown(&self) -> Result { pub(super) async fn shutdown(&self) -> Result {
warn!("shutdown command"); warn!("shutdown command");
+4 -1
View File
@@ -9,7 +9,7 @@ use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum ServerCommand { pub(super) enum ServerCommand {
/// - Time elapsed since startup /// - Time elapsed since startup
Uptime, Uptime,
@@ -64,4 +64,7 @@ pub enum ServerCommand {
/// - Shutdown the server /// - Shutdown the server
Shutdown, Shutdown,
/// - Upload analytics
UploadAnalytics,
} }
+17 -60
View File
@@ -1,16 +1,14 @@
use std::{collections::BTreeMap, fmt::Write as _}; use std::{collections::BTreeMap, fmt::Write as _};
use api::client::{ use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room};
full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url,
update_displayname,
};
use conduwuit::{ use conduwuit::{
Err, Result, debug, debug_warn, error, info, is_equal_to, Err, Result, debug, debug_warn, error, info, is_equal_to,
matrix::{Event, pdu::PduBuilder}, matrix::pdu::PduBuilder,
utils::{self, ReadyExt}, utils::{self, ReadyExt},
warn, warn,
}; };
use futures::{FutureExt, StreamExt}; use conduwuit_api::client::{leave_all_rooms, update_avatar_url, update_displayname};
use futures::StreamExt;
use ruma::{ use ruma::{
OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId, OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, UserId,
events::{ events::{
@@ -226,47 +224,6 @@ pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) ->
.await .await
} }
#[admin_command]
pub(super) async fn suspend(&self, user_id: String) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
if user_id == self.services.globals.server_user {
return Err!("Not allowed to suspend the server service account.",);
}
if !self.services.users.exists(&user_id).await {
return Err!("User {user_id} does not exist.");
}
if self.services.users.is_admin(&user_id).await {
return Err!("Admin users cannot be suspended.");
}
// TODO: Record the actual user that sent the suspension where possible
self.services
.users
.suspend_account(&user_id, self.sender_or_service_user())
.await;
self.write_str(&format!("User {user_id} has been suspended."))
.await
}
#[admin_command]
pub(super) async fn unsuspend(&self, user_id: String) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
if user_id == self.services.globals.server_user {
return Err!("Not allowed to unsuspend the server service account.",);
}
if !self.services.users.exists(&user_id).await {
return Err!("User {user_id} does not exist.");
}
self.services.users.unsuspend_account(&user_id).await;
self.write_str(&format!("User {user_id} has been unsuspended."))
.await
}
#[admin_command] #[admin_command]
pub(super) async fn reset_password(&self, username: String, password: Option<String>) -> Result { pub(super) async fn reset_password(&self, username: String, password: Option<String>) -> Result {
let user_id = parse_local_user_id(self.services, &username)?; let user_id = parse_local_user_id(self.services, &username)?;
@@ -286,9 +243,8 @@ pub(super) async fn reset_password(&self, username: String, password: Option<Str
.set_password(&user_id, Some(new_password.as_str())) .set_password(&user_id, Some(new_password.as_str()))
{ {
| Err(e) => return Err!("Couldn't reset the password for user {user_id}: {e}"), | Err(e) => return Err!("Couldn't reset the password for user {user_id}: {e}"),
| Ok(()) => { | Ok(()) =>
write!(self, "Successfully reset the password for user {user_id}: `{new_password}`") write!(self, "Successfully reset the password for user {user_id}: `{new_password}`"),
},
} }
.await .await
} }
@@ -699,9 +655,7 @@ pub(super) async fn force_leave_room(
return Err!("{user_id} is not joined in the room"); return Err!("{user_id} is not joined in the room");
} }
leave_room(self.services, &user_id, &room_id, None) leave_room(self.services, &user_id, &room_id, None).await?;
.boxed()
.await?;
self.write_str(&format!("{user_id} has left {room_id}.",)) self.write_str(&format!("{user_id} has left {room_id}.",))
.await .await
@@ -738,7 +692,7 @@ pub(super) async fn force_demote(&self, user_id: String, room_id: OwnedRoomOrAli
.state_accessor .state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "") .room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await .await
.is_ok_and(|event| event.sender() == user_id); .is_ok_and(|event| event.sender == user_id);
if !user_can_demote_self { if !user_can_demote_self {
return Err!("User is not allowed to modify their own power levels in the room.",); return Err!("User is not allowed to modify their own power levels in the room.",);
@@ -889,7 +843,10 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
return Err!("Event is already redacted."); return Err!("Event is already redacted.");
} }
if !self.services.globals.user_is_local(event.sender()) { let room_id = event.room_id;
let sender_user = event.sender;
if !self.services.globals.user_is_local(&sender_user) {
return Err!("This command only works on local users."); return Err!("This command only works on local users.");
} }
@@ -899,21 +856,21 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
); );
let redaction_event_id = { let redaction_event_id = {
let state_lock = self.services.rooms.state.mutex.lock(event.room_id()).await; let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
self.services self.services
.rooms .rooms
.timeline .timeline
.build_and_append_pdu( .build_and_append_pdu(
PduBuilder { PduBuilder {
redacts: Some(event.event_id().to_owned()), redacts: Some(event.event_id.clone()),
..PduBuilder::timeline(&RoomRedactionEventContent { ..PduBuilder::timeline(&RoomRedactionEventContent {
redacts: Some(event.event_id().to_owned()), redacts: Some(event.event_id.clone()),
reason: Some(reason), reason: Some(reason),
}) })
}, },
event.sender(), &sender_user,
event.room_id(), &room_id,
&state_lock, &state_lock,
) )
.await? .await?
+1 -23
View File
@@ -8,7 +8,7 @@ use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub enum UserCommand { pub(super) enum UserCommand {
/// - Create a new user /// - Create a new user
#[clap(alias = "create")] #[clap(alias = "create")]
CreateUser { CreateUser {
@@ -59,28 +59,6 @@ pub enum UserCommand {
force: bool, force: bool,
}, },
/// - Suspend a user
///
/// Suspended users are able to log in, sync, and read messages, but are not
/// able to send events nor redact them, cannot change their profile, and
/// are unable to join, invite to, or knock on rooms.
///
/// Suspended users can still leave rooms and deactivate their account.
/// Suspending them effectively makes them read-only.
Suspend {
/// Username of the user to suspend
user_id: String,
},
/// - Unsuspend a user
///
/// Reverses the effects of the `suspend` command, allowing the user to send
/// messages, change their profile, create room invites, etc.
Unsuspend {
/// Username of the user to unsuspend
user_id: String,
},
/// - List local users in the database /// - List local users in the database
#[clap(alias = "list")] #[clap(alias = "list")]
ListUsers, ListUsers,
+67 -78
View File
@@ -3,9 +3,10 @@ use std::fmt::Write;
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Error, Event, Result, debug_info, err, error, info, is_equal_to, Err, Error, Result, debug_info, err, error, info, is_equal_to,
matrix::pdu::PduBuilder, matrix::pdu::PduBuilder,
utils::{self, ReadyExt, stream::BroadbandExt}, utils,
utils::{ReadyExt, stream::BroadbandExt},
warn, warn,
}; };
use conduwuit_service::Services; use conduwuit_service::Services;
@@ -150,32 +151,16 @@ pub(crate) async fn register_route(
if !services.config.allow_registration && body.appservice_info.is_none() { if !services.config.allow_registration && body.appservice_info.is_none() {
match (body.username.as_ref(), body.initial_device_display_name.as_ref()) { match (body.username.as_ref(), body.initial_device_display_name.as_ref()) {
| (Some(username), Some(device_display_name)) => { | (Some(username), Some(device_display_name)) => {
info!( info!(%is_guest, user = %username, device_name = %device_display_name, "Rejecting registration attempt as registration is disabled");
%is_guest,
user = %username,
device_name = %device_display_name,
"Rejecting registration attempt as registration is disabled"
);
}, },
| (Some(username), _) => { | (Some(username), _) => {
info!( info!(%is_guest, user = %username, "Rejecting registration attempt as registration is disabled");
%is_guest,
user = %username,
"Rejecting registration attempt as registration is disabled"
);
}, },
| (_, Some(device_display_name)) => { | (_, Some(device_display_name)) => {
info!( info!(%is_guest, device_name = %device_display_name, "Rejecting registration attempt as registration is disabled");
%is_guest,
device_name = %device_display_name,
"Rejecting registration attempt as registration is disabled"
);
}, },
| (None, _) => { | (None, _) => {
info!( info!(%is_guest, "Rejecting registration attempt as registration is disabled");
%is_guest,
"Rejecting registration attempt as registration is disabled"
);
}, },
} }
@@ -366,7 +351,8 @@ pub(crate) async fn register_route(
if !services.globals.new_user_displayname_suffix().is_empty() if !services.globals.new_user_displayname_suffix().is_empty()
&& body.appservice_info.is_none() && body.appservice_info.is_none()
{ {
write!(displayname, " {}", services.server.config.new_user_displayname_suffix)?; write!(displayname, " {}", services.server.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer");
} }
services services
@@ -384,7 +370,8 @@ pub(crate) async fn register_route(
content: ruma::events::push_rules::PushRulesEventContent { content: ruma::events::push_rules::PushRulesEventContent {
global: push::Ruleset::server_default(&user_id), global: push::Ruleset::server_default(&user_id),
}, },
})?, })
.expect("to json always works"),
) )
.await?; .await?;
@@ -429,21 +416,32 @@ pub(crate) async fn register_route(
// log in conduit admin channel if a non-guest user registered // log in conduit admin channel if a non-guest user registered
if body.appservice_info.is_none() && !is_guest { if body.appservice_info.is_none() && !is_guest {
if !device_display_name.is_empty() { if !device_display_name.is_empty() {
let notice = format!( info!(
"New user \"{user_id}\" registered on this server from IP {client} and device \ "New user \"{user_id}\" registered on this server with device display name: \
display name \"{device_display_name}\"" \"{device_display_name}\""
); );
info!("{notice}");
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services.admin.notice(&notice).await; services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client} and \
device display name \"{device_display_name}\""
)))
.await
.ok();
} }
} else { } else {
let notice = format!("New user \"{user_id}\" registered on this server."); info!("New user \"{user_id}\" registered on this server.");
info!("{notice}");
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services.admin.notice(&notice).await; services
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client}"
)))
.await
.ok();
} }
} }
} }
@@ -456,22 +454,24 @@ pub(crate) async fn register_route(
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.notice(&format!( .send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with device display name \ "Guest user \"{user_id}\" with device display name \
\"{device_display_name}\" registered on this server from IP {client}" \"{device_display_name}\" registered on this server from IP {client}"
)) )))
.await; .await
.ok();
} }
} else { } else {
#[allow(clippy::collapsible_else_if)] #[allow(clippy::collapsible_else_if)]
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.notice(&format!( .send_message(RoomMessageEventContent::notice_plain(format!(
"Guest user \"{user_id}\" with no device display name registered on \ "Guest user \"{user_id}\" with no device display name registered on \
this server from IP {client}", this server from IP {client}",
)) )))
.await; .await
.ok();
} }
} }
} }
@@ -490,25 +490,6 @@ pub(crate) async fn register_route(
{ {
services.admin.make_user_admin(&user_id).await?; services.admin.make_user_admin(&user_id).await?;
warn!("Granting {user_id} admin privileges as the first user"); warn!("Granting {user_id} admin privileges as the first user");
} else if services.config.suspend_on_register {
// This is not an admin, suspend them.
// Note that we can still do auto joins for suspended users
services
.users
.suspend_account(&user_id, &services.globals.server_user)
.await;
// And send an @room notice to the admin room, to prompt admins to review the
// new user and ideally unsuspend them if deemed appropriate.
if services.server.config.admin_room_notices {
services
.admin
.send_loud_message(RoomMessageEventContent::text_plain(format!(
"User {user_id} has been suspended as they are not the first user \
on this server. Please review and unsuspend them if appropriate."
)))
.await
.ok();
}
} }
} }
} }
@@ -603,6 +584,7 @@ pub(crate) async fn change_password_route(
.sender_user .sender_user
.as_ref() .as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?; .ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let sender_device = body.sender_device();
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }], flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
@@ -616,7 +598,7 @@ pub(crate) async fn change_password_route(
| Some(auth) => { | Some(auth) => {
let (worked, uiaainfo) = services let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo) .try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?; .await?;
if !worked { if !worked {
@@ -630,7 +612,7 @@ pub(crate) async fn change_password_route(
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services services
.uiaa .uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
}, },
@@ -649,7 +631,7 @@ pub(crate) async fn change_password_route(
services services
.users .users
.all_device_ids(sender_user) .all_device_ids(sender_user)
.ready_filter(|id| *id != body.sender_device()) .ready_filter(|id| *id != sender_device)
.for_each(|id| services.users.remove_device(sender_user, id)) .for_each(|id| services.users.remove_device(sender_user, id))
.await; .await;
@@ -658,17 +640,17 @@ pub(crate) async fn change_password_route(
.pusher .pusher
.get_pushkeys(sender_user) .get_pushkeys(sender_user)
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.broad_filter_map(async |pushkey| { .broad_filter_map(|pushkey| async move {
services services
.pusher .pusher
.get_pusher_device(&pushkey) .get_pusher_device(&pushkey)
.await .await
.ok() .ok()
.filter(|pusher_device| pusher_device != body.sender_device()) .filter(|pusher_device| pusher_device != sender_device)
.is_some() .is_some()
.then_some(pushkey) .then_some(pushkey)
}) })
.for_each(async |pushkey| { .for_each(|pushkey| async move {
services.pusher.delete_pusher(sender_user, &pushkey).await; services.pusher.delete_pusher(sender_user, &pushkey).await;
}) })
.await; .await;
@@ -679,8 +661,11 @@ pub(crate) async fn change_password_route(
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.notice(&format!("User {sender_user} changed their password.")) .send_message(RoomMessageEventContent::notice_plain(format!(
.await; "User {sender_user} changed their password."
)))
.await
.ok();
} }
Ok(change_password::v3::Response {}) Ok(change_password::v3::Response {})
@@ -695,10 +680,13 @@ pub(crate) async fn whoami_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<whoami::v3::Request>, body: Ruma<whoami::v3::Request>,
) -> Result<whoami::v3::Response> { ) -> Result<whoami::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device_id = body.sender_device.clone();
Ok(whoami::v3::Response { Ok(whoami::v3::Response {
user_id: body.sender_user().to_owned(), user_id: sender_user.clone(),
device_id: body.sender_device.clone(), device_id,
is_guest: services.users.is_deactivated(body.sender_user()).await? is_guest: services.users.is_deactivated(sender_user).await?
&& body.appservice_info.is_none(), && body.appservice_info.is_none(),
}) })
} }
@@ -726,6 +714,7 @@ pub(crate) async fn deactivate_route(
.sender_user .sender_user
.as_ref() .as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?; .ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let sender_device = body.sender_device();
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }], flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
@@ -739,7 +728,7 @@ pub(crate) async fn deactivate_route(
| Some(auth) => { | Some(auth) => {
let (worked, uiaainfo) = services let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo) .try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?; .await?;
if !worked { if !worked {
@@ -752,7 +741,7 @@ pub(crate) async fn deactivate_route(
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services services
.uiaa .uiaa
.create(sender_user, body.sender_device(), &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, json);
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
}, },
@@ -774,17 +763,18 @@ pub(crate) async fn deactivate_route(
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await; super::update_displayname(&services, sender_user, None, &all_joined_rooms).await;
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await; super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await;
full_user_deactivate(&services, sender_user, &all_joined_rooms) full_user_deactivate(&services, sender_user, &all_joined_rooms).await?;
.boxed()
.await?;
info!("User {sender_user} deactivated their account."); info!("User {sender_user} deactivated their account.");
if services.server.config.admin_room_notices { if services.server.config.admin_room_notices {
services services
.admin .admin
.notice(&format!("User {sender_user} deactivated their account.")) .send_message(RoomMessageEventContent::notice_plain(format!(
.await; "User {sender_user} deactivated their account."
)))
.await
.ok();
} }
Ok(deactivate::v3::Response { Ok(deactivate::v3::Response {
@@ -861,7 +851,6 @@ pub async fn full_user_deactivate(
all_joined_rooms: &[OwnedRoomId], all_joined_rooms: &[OwnedRoomId],
) -> Result<()> { ) -> Result<()> {
services.users.deactivate_account(user_id).await.ok(); services.users.deactivate_account(user_id).await.ok();
super::update_displayname(services, user_id, None, all_joined_rooms).await; super::update_displayname(services, user_id, None, all_joined_rooms).await;
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await; super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await;
@@ -898,7 +887,7 @@ pub async fn full_user_deactivate(
.state_accessor .state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "") .room_state_get(room_id, &StateEventType::RoomCreate, "")
.await .await
.is_ok_and(|event| event.sender() == user_id); .is_ok_and(|event| event.sender == user_id);
if user_can_demote_self { if user_can_demote_self {
let mut power_levels_content = room_power_levels.unwrap_or_default(); let mut power_levels_content = room_power_levels.unwrap_or_default();
@@ -926,7 +915,7 @@ pub async fn full_user_deactivate(
} }
} }
super::leave_all_rooms(services, user_id).boxed().await; super::leave_all_rooms(services, user_id).await;
Ok(()) Ok(())
} }
+2 -8
View File
@@ -17,10 +17,7 @@ pub(crate) async fn create_alias_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<create_alias::v3::Request>, body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> { ) -> Result<create_alias::v3::Response> {
let sender_user = body.sender_user(); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
services services
.rooms .rooms
@@ -65,10 +62,7 @@ pub(crate) async fn delete_alias_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<delete_alias::v3::Request>, body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> { ) -> Result<delete_alias::v3::Response> {
let sender_user = body.sender_user(); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
services services
.rooms .rooms
+91 -43
View File
@@ -2,10 +2,8 @@ use std::cmp::Ordering;
use axum::extract::State; use axum::extract::State;
use conduwuit::{Err, Result, err}; use conduwuit::{Err, Result, err};
use conduwuit_service::Services;
use futures::{FutureExt, future::try_join};
use ruma::{ use ruma::{
UInt, UserId, UInt,
api::client::backup::{ api::client::backup::{
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session,
create_backup_version, delete_backup_keys, delete_backup_keys_for_room, create_backup_version, delete_backup_keys, delete_backup_keys_for_room,
@@ -60,9 +58,21 @@ pub(crate) async fn get_latest_backup_info_route(
.await .await
.map_err(|_| err!(Request(NotFound("Key backup does not exist."))))?; .map_err(|_| err!(Request(NotFound("Key backup does not exist."))))?;
let (count, etag) = get_count_etag(&services, body.sender_user(), &version).await?; Ok(get_latest_backup_info::v3::Response {
algorithm,
Ok(get_latest_backup_info::v3::Response { algorithm, count, etag, version }) count: (UInt::try_from(
services
.key_backups
.count_keys(body.sender_user(), &version)
.await,
)
.expect("user backup keys count should not be that high")),
etag: services
.key_backups
.get_etag(body.sender_user(), &version)
.await,
version,
})
} }
/// # `GET /_matrix/client/v3/room_keys/version/{version}` /// # `GET /_matrix/client/v3/room_keys/version/{version}`
@@ -80,12 +90,17 @@ pub(crate) async fn get_backup_info_route(
err!(Request(NotFound("Key backup does not exist at version {:?}", body.version))) err!(Request(NotFound("Key backup does not exist at version {:?}", body.version)))
})?; })?;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?;
Ok(get_backup_info::v3::Response { Ok(get_backup_info::v3::Response {
algorithm, algorithm,
count, count: services
etag, .key_backups
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
version: body.version.clone(), version: body.version.clone(),
}) })
} }
@@ -140,9 +155,17 @@ pub(crate) async fn add_backup_keys_route(
} }
} }
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; Ok(add_backup_keys::v3::Response {
count: services
Ok(add_backup_keys::v3::Response { count, etag }) .key_backups
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}` /// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}`
@@ -175,9 +198,17 @@ pub(crate) async fn add_backup_keys_for_room_route(
.await?; .await?;
} }
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; Ok(add_backup_keys_for_room::v3::Response {
count: services
Ok(add_backup_keys_for_room::v3::Response { count, etag }) .key_backups
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` /// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
@@ -275,9 +306,17 @@ pub(crate) async fn add_backup_keys_for_session_route(
.await?; .await?;
} }
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; Ok(add_backup_keys_for_session::v3::Response {
count: services
Ok(add_backup_keys_for_session::v3::Response { count, etag }) .key_backups
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `GET /_matrix/client/r0/room_keys/keys` /// # `GET /_matrix/client/r0/room_keys/keys`
@@ -340,9 +379,17 @@ pub(crate) async fn delete_backup_keys_route(
.delete_all_keys(body.sender_user(), &body.version) .delete_all_keys(body.sender_user(), &body.version)
.await; .await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; Ok(delete_backup_keys::v3::Response {
count: services
Ok(delete_backup_keys::v3::Response { count, etag }) .key_backups
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}` /// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}`
@@ -357,9 +404,17 @@ pub(crate) async fn delete_backup_keys_for_room_route(
.delete_room_keys(body.sender_user(), &body.version, &body.room_id) .delete_room_keys(body.sender_user(), &body.version, &body.room_id)
.await; .await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; Ok(delete_backup_keys_for_room::v3::Response {
count: services
Ok(delete_backup_keys_for_room::v3::Response { count, etag }) .key_backups
.count_keys(body.sender_user(), &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(body.sender_user(), &body.version)
.await,
})
} }
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` /// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
@@ -374,22 +429,15 @@ pub(crate) async fn delete_backup_keys_for_session_route(
.delete_room_key(body.sender_user(), &body.version, &body.room_id, &body.session_id) .delete_room_key(body.sender_user(), &body.version, &body.room_id, &body.session_id)
.await; .await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; Ok(delete_backup_keys_for_session::v3::Response {
count: services
Ok(delete_backup_keys_for_session::v3::Response { count, etag }) .key_backups
} .count_keys(body.sender_user(), &body.version)
.await
async fn get_count_etag( .try_into()?,
services: &Services, etag: services
sender_user: &UserId, .key_backups
version: &str, .get_etag(body.sender_user(), &body.version)
) -> Result<(UInt, String)> { .await,
let count = services })
.key_backups
.count_keys(sender_user, version)
.map(TryInto::try_into);
let etag = services.key_backups.get_etag(sender_user, version).map(Ok);
Ok(try_join(count, etag).await?)
} }
+10 -6
View File
@@ -26,8 +26,8 @@ pub(crate) async fn get_capabilities_route(
let mut capabilities = Capabilities::default(); let mut capabilities = Capabilities::default();
capabilities.room_versions = RoomVersionsCapability { capabilities.room_versions = RoomVersionsCapability {
available,
default: services.server.config.default_room_version.clone(), default: services.server.config.default_room_version.clone(),
available,
}; };
// we do not implement 3PID stuff // we do not implement 3PID stuff
@@ -38,12 +38,16 @@ pub(crate) async fn get_capabilities_route(
}; };
// MSC4133 capability // MSC4133 capability
capabilities.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))?; capabilities
.set("uk.tcpip.msc4133.profile_fields", json!({"enabled": true}))
.expect("this is valid JSON we created");
capabilities.set( capabilities
"org.matrix.msc4267.forget_forced_upon_leave", .set(
json!({"enabled": services.config.forget_forced_upon_leave}), "org.matrix.msc4267.forget_forced_upon_leave",
)?; json!({"enabled": services.config.forget_forced_upon_leave}),
)
.expect("valid JSON we created");
Ok(get_capabilities::v3::Response { capabilities }) Ok(get_capabilities::v3::Response { capabilities })
} }
+8 -6
View File
@@ -1,6 +1,8 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{ use conduwuit::{
Err, Event, Result, at, debug_warn, err, ref_at, Err, Result, at, debug_warn, err,
matrix::pdu::PduEvent,
ref_at,
utils::{ utils::{
IterStream, IterStream,
future::TryExtExt, future::TryExtExt,
@@ -109,7 +111,7 @@ pub(crate) async fn get_context_route(
let lazy_loading_context = lazy_loading::Context { let lazy_loading_context = lazy_loading::Context {
user_id: sender_user, user_id: sender_user,
device_id: Some(sender_device), device_id: sender_device,
room_id, room_id,
token: Some(base_count.into_unsigned()), token: Some(base_count.into_unsigned()),
options: Some(&filter.lazy_load_options), options: Some(&filter.lazy_load_options),
@@ -177,12 +179,12 @@ pub(crate) async fn get_context_route(
.broad_filter_map(|event_id: &OwnedEventId| { .broad_filter_map(|event_id: &OwnedEventId| {
services.rooms.timeline.get_pdu(event_id.as_ref()).ok() services.rooms.timeline.get_pdu(event_id.as_ref()).ok()
}) })
.map(Event::into_format) .map(PduEvent::into_state_event)
.collect() .collect()
.await; .await;
Ok(get_context::v3::Response { Ok(get_context::v3::Response {
event: base_event.map(at!(1)).map(Event::into_format), event: base_event.map(at!(1)).map(PduEvent::into_room_event),
start: events_before start: events_before
.last() .last()
@@ -201,13 +203,13 @@ pub(crate) async fn get_context_route(
events_before: events_before events_before: events_before
.into_iter() .into_iter()
.map(at!(1)) .map(at!(1))
.map(Event::into_format) .map(PduEvent::into_room_event)
.collect(), .collect(),
events_after: events_after events_after: events_after
.into_iter() .into_iter()
.map(at!(1)) .map(at!(1))
.map(Event::into_format) .map(PduEvent::into_room_event)
.collect(), .collect(),
state, state,
+6 -2
View File
@@ -21,9 +21,11 @@ pub(crate) async fn get_devices_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_devices::v3::Request>, body: Ruma<get_devices::v3::Request>,
) -> Result<get_devices::v3::Response> { ) -> Result<get_devices::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let devices: Vec<device::Device> = services let devices: Vec<device::Device> = services
.users .users
.all_devices_metadata(body.sender_user()) .all_devices_metadata(sender_user)
.collect() .collect()
.await; .await;
@@ -37,9 +39,11 @@ pub(crate) async fn get_device_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_device::v3::Request>, body: Ruma<get_device::v3::Request>,
) -> Result<get_device::v3::Response> { ) -> Result<get_device::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device = services let device = services
.users .users
.get_device_metadata(body.sender_user(), &body.body.device_id) .get_device_metadata(sender_user, &body.body.device_id)
.await .await
.map_err(|_| err!(Request(NotFound("Device not found."))))?; .map_err(|_| err!(Request(NotFound("Device not found."))))?;
+3 -6
View File
@@ -1,7 +1,7 @@
use axum::extract::State; use axum::extract::State;
use axum_client_ip::InsecureClientIp; use axum_client_ip::InsecureClientIp;
use conduwuit::{ use conduwuit::{
Err, Event, Result, err, info, Err, Result, err, info,
utils::{ utils::{
TryFutureExtExt, TryFutureExtExt,
math::Expected, math::Expected,
@@ -128,9 +128,6 @@ pub(crate) async fn set_room_visibility_route(
// Return 404 if the room doesn't exist // Return 404 if the room doesn't exist
return Err!(Request(NotFound("Room not found"))); return Err!(Request(NotFound("Room not found")));
} }
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if services if services
.users .users
@@ -352,7 +349,7 @@ async fn user_can_publish_room(
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "") .room_state_get(room_id, &StateEventType::RoomPowerLevels, "")
.await .await
{ {
| Ok(event) => serde_json::from_str(event.content().get()) | Ok(event) => serde_json::from_str(event.content.get())
.map_err(|_| err!(Database("Invalid event content for m.room.power_levels"))) .map_err(|_| err!(Database("Invalid event content for m.room.power_levels")))
.map(|content: RoomPowerLevelsEventContent| { .map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content) RoomPowerLevels::from(content)
@@ -365,7 +362,7 @@ async fn user_can_publish_room(
.room_state_get(room_id, &StateEventType::RoomCreate, "") .room_state_get(room_id, &StateEventType::RoomCreate, "")
.await .await
{ {
| Ok(event) => Ok(event.sender() == user_id), | Ok(event) => Ok(event.sender == user_id),
| _ => Err!(Request(Forbidden("User is not allowed to publish this room"))), | _ => Err!(Request(Forbidden("User is not allowed to publish this room"))),
} }
}, },
+6 -4
View File
@@ -13,9 +13,11 @@ pub(crate) async fn get_filter_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_filter::v3::Request>, body: Ruma<get_filter::v3::Request>,
) -> Result<get_filter::v3::Response> { ) -> Result<get_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services services
.users .users
.get_filter(body.sender_user(), &body.filter_id) .get_filter(sender_user, &body.filter_id)
.await .await
.map(get_filter::v3::Response::new) .map(get_filter::v3::Response::new)
.map_err(|_| err!(Request(NotFound("Filter not found.")))) .map_err(|_| err!(Request(NotFound("Filter not found."))))
@@ -28,9 +30,9 @@ pub(crate) async fn create_filter_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<create_filter::v3::Request>, body: Ruma<create_filter::v3::Request>,
) -> Result<create_filter::v3::Response> { ) -> Result<create_filter::v3::Response> {
let filter_id = services let sender_user = body.sender_user.as_ref().expect("user is authenticated");
.users
.create_filter(body.sender_user(), &body.filter); let filter_id = services.users.create_filter(sender_user, &body.filter);
Ok(create_filter::v3::Response::new(filter_id)) Ok(create_filter::v3::Response::new(filter_id))
} }
+6 -5
View File
@@ -126,7 +126,7 @@ pub(crate) async fn get_keys_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_keys::v3::Request>, body: Ruma<get_keys::v3::Request>,
) -> Result<get_keys::v3::Response> { ) -> Result<get_keys::v3::Response> {
let sender_user = body.sender_user(); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
get_keys_helper( get_keys_helper(
&services, &services,
@@ -157,7 +157,8 @@ pub(crate) async fn upload_signing_keys_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<upload_signing_keys::v3::Request>, body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::v3::Response> { ) -> Result<upload_signing_keys::v3::Response> {
let (sender_user, sender_device) = body.sender(); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
// UIAA // UIAA
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
@@ -202,12 +203,12 @@ pub(crate) async fn upload_signing_keys_route(
} }
// Success! // Success!
}, },
| _ => match body.json_body.as_ref() { | _ => match body.json_body {
| Some(json) => { | Some(json) => {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services services
.uiaa .uiaa
.create(sender_user, sender_device, &uiaainfo, json); .create(sender_user, sender_device, &uiaainfo, &json);
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
}, },
@@ -372,7 +373,7 @@ pub(crate) async fn get_key_changes_route(
State(services): State<crate::State>, State(services): State<crate::State>,
body: Ruma<get_key_changes::v3::Request>, body: Ruma<get_key_changes::v3::Request>,
) -> Result<get_key_changes::v3::Response> { ) -> Result<get_key_changes::v3::Response> {
let sender_user = body.sender_user(); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut device_list_updates = HashSet::new(); let mut device_list_updates = HashSet::new();
+5 -8
View File
@@ -51,10 +51,7 @@ pub(crate) async fn create_content_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<create_content::v3::Request>, body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> { ) -> Result<create_content::v3::Response> {
let user = body.sender_user(); let user = body.sender_user.as_ref().expect("user is authenticated");
if services.users.is_suspended(user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let filename = body.filename.as_deref(); let filename = body.filename.as_deref();
let content_type = body.content_type.as_deref(); let content_type = body.content_type.as_deref();
@@ -97,7 +94,7 @@ pub(crate) async fn get_content_thumbnail_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v1::Request>, body: Ruma<get_content_thumbnail::v1::Request>,
) -> Result<get_content_thumbnail::v1::Response> { ) -> Result<get_content_thumbnail::v1::Response> {
let user = body.sender_user(); let user = body.sender_user.as_ref().expect("user is authenticated");
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?; let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let mxc = Mxc { let mxc = Mxc {
@@ -134,7 +131,7 @@ pub(crate) async fn get_content_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content::v1::Request>, body: Ruma<get_content::v1::Request>,
) -> Result<get_content::v1::Response> { ) -> Result<get_content::v1::Response> {
let user = body.sender_user(); let user = body.sender_user.as_ref().expect("user is authenticated");
let mxc = Mxc { let mxc = Mxc {
server_name: &body.server_name, server_name: &body.server_name,
@@ -170,7 +167,7 @@ pub(crate) async fn get_content_as_filename_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_as_filename::v1::Request>, body: Ruma<get_content_as_filename::v1::Request>,
) -> Result<get_content_as_filename::v1::Response> { ) -> Result<get_content_as_filename::v1::Response> {
let user = body.sender_user(); let user = body.sender_user.as_ref().expect("user is authenticated");
let mxc = Mxc { let mxc = Mxc {
server_name: &body.server_name, server_name: &body.server_name,
@@ -206,7 +203,7 @@ pub(crate) async fn get_media_preview_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_media_preview::v1::Request>, body: Ruma<get_media_preview::v1::Request>,
) -> Result<get_media_preview::v1::Response> { ) -> Result<get_media_preview::v1::Response> {
let sender_user = body.sender_user(); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let url = &body.url; let url = &body.url;
let url = Url::parse(&body.url).map_err(|e| { let url = Url::parse(&body.url).map_err(|e| {
+1 -1
View File
@@ -55,7 +55,7 @@ pub(crate) async fn get_media_preview_legacy_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_media_preview::v3::Request>, body: Ruma<get_media_preview::v3::Request>,
) -> Result<get_media_preview::v3::Response> { ) -> Result<get_media_preview::v3::Response> {
let sender_user = body.sender_user(); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let url = &body.url; let url = &body.url;
let url = Url::parse(&body.url).map_err(|e| { let url = Url::parse(&body.url).map_err(|e| {
File diff suppressed because it is too large Load Diff
-60
View File
@@ -1,60 +0,0 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use ruma::{
api::client::membership::ban_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/ban`
///
/// Tries to send a ban event into the room.
pub(crate) async fn ban_user_route(
State(services): State<crate::State>,
body: Ruma<ban_user::v3::Request>,
) -> Result<ban_user::v3::Response> {
let sender_user = body.sender_user();
if sender_user == body.user_id {
return Err!(Request(Forbidden("You cannot ban yourself.")));
}
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let current_member_content = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
.unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Ban));
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Ban,
reason: body.reason.clone(),
displayname: None, // display name may be offensive
avatar_url: None, // avatar may be offensive
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
redact_events: body.redact_events,
..current_member_content
}),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
drop(state_lock);
Ok(ban_user::v3::Response::new())
}
-52
View File
@@ -1,52 +0,0 @@
use axum::extract::State;
use conduwuit::{Err, Result, is_matching, result::NotFound, utils::FutureBoolExt};
use futures::pin_mut;
use ruma::{api::client::membership::forget_room, events::room::member::MembershipState};
use crate::Ruma;
/// # `POST /_matrix/client/v3/rooms/{roomId}/forget`
///
/// Forgets about a room.
///
/// - If the sender user currently left the room: Stops sender user from
/// receiving information about the room
///
/// Note: Other devices of the user have no way of knowing the room was
/// forgotten, so this has to be called from every device
pub(crate) async fn forget_room_route(
State(services): State<crate::State>,
body: Ruma<forget_room::v3::Request>,
) -> Result<forget_room::v3::Response> {
let user_id = body.sender_user();
let room_id = &body.room_id;
let joined = services.rooms.state_cache.is_joined(user_id, room_id);
let knocked = services.rooms.state_cache.is_knocked(user_id, room_id);
let invited = services.rooms.state_cache.is_invited(user_id, room_id);
pin_mut!(joined, knocked, invited);
if joined.or(knocked).or(invited).await {
return Err!(Request(Unknown("You must leave the room before forgetting it")));
}
let membership = services
.rooms
.state_accessor
.get_member(room_id, user_id)
.await;
if membership.is_not_found() {
return Err!(Request(Unknown("No membership event was found, room was never joined")));
}
let non_membership = membership
.map(|member| member.membership)
.is_ok_and(is_matching!(MembershipState::Leave | MembershipState::Ban));
if non_membership || services.rooms.state_cache.is_left(user_id, room_id).await {
services.rooms.state_cache.forget(room_id, user_id);
}
Ok(forget_room::v3::Response::new())
}
-238
View File
@@ -1,238 +0,0 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug_error, err, info,
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
};
use futures::{FutureExt, join};
use ruma::{
OwnedServerName, RoomId, UserId,
api::{client::membership::invite_user, federation::membership::create_invite},
events::room::member::{MembershipState, RoomMemberEventContent},
};
use service::Services;
use super::banned_room_check;
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
///
/// Tries to send an invite event into the room.
#[tracing::instrument(skip_all, fields(%client), name = "invite")]
pub(crate) async fn invite_user_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<invite_user::v3::Request>,
) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
debug_error!(
"User {sender_user} is not an admin and attempted to send an invite to room {}",
&body.room_id
);
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
banned_room_check(
&services,
sender_user,
Some(&body.room_id),
body.room_id.server_name(),
client,
)
.await?;
match &body.recipient {
| invite_user::v3::InvitationRecipient::UserId { user_id } => {
let sender_ignored_recipient = services.users.user_is_ignored(sender_user, user_id);
let recipient_ignored_by_sender =
services.users.user_is_ignored(user_id, sender_user);
let (sender_ignored_recipient, recipient_ignored_by_sender) =
join!(sender_ignored_recipient, recipient_ignored_by_sender);
if sender_ignored_recipient {
return Ok(invite_user::v3::Response {});
}
if let Ok(target_user_membership) = services
.rooms
.state_accessor
.get_member(&body.room_id, user_id)
.await
{
if target_user_membership.membership == MembershipState::Ban {
return Err!(Request(Forbidden("User is banned from this room.")));
}
}
if recipient_ignored_by_sender {
// silently drop the invite to the recipient if they've been ignored by the
// sender, pretend it worked
return Ok(invite_user::v3::Response {});
}
invite_helper(
&services,
sender_user,
user_id,
&body.room_id,
body.reason.clone(),
false,
)
.boxed()
.await?;
Ok(invite_user::v3::Response {})
},
| _ => {
Err!(Request(NotFound("User not found.")))
},
}
}
pub(crate) async fn invite_helper(
services: &Services,
sender_user: &UserId,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
is_direct: bool,
) -> Result {
if !services.users.is_admin(sender_user).await && services.config.block_non_admin_invites {
info!(
"User {sender_user} is not an admin and attempted to send an invite to room \
{room_id}"
);
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
}
if !services.globals.user_is_local(user_id) {
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
avatar_url: services.users.avatar_url(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
let (pdu, pdu_json) = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await?;
let invite_room_state = services.rooms.state.summary_stripped(&pdu).await;
drop(state_lock);
(pdu, pdu_json, invite_room_state)
};
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
let response = services
.sending
.send_federation_request(user_id.server_name(), create_invite::v2::Request {
room_id: room_id.to_owned(),
event_id: (*pdu.event_id).to_owned(),
room_version: room_version_id.clone(),
event: services
.sending
.convert_to_outgoing_federation_event(pdu_json.clone())
.await,
invite_room_state,
via: services
.rooms
.state_cache
.servers_route_via(room_id)
.await
.ok(),
})
.await?;
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
let (event_id, value) = gen_event_id_canonical_json(&response.event, &room_version_id)
.map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
if pdu.event_id != event_id {
return Err!(Request(BadJson(warn!(
%pdu.event_id, %event_id,
"Server {} sent event with wrong event ID",
user_id.server_name()
))));
}
let origin: OwnedServerName = serde_json::from_value(serde_json::to_value(
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event missing origin field."))))?,
)?)
.map_err(|e| {
err!(Request(BadJson(warn!("Origin field in event is not a valid server name: {e}"))))
})?;
let pdu_id = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value, true)
.boxed()
.await?
.ok_or_else(|| {
err!(Request(InvalidParam("Could not accept incoming PDU as timeline event.")))
})?;
return services.sending.send_pdu_room(room_id, &pdu_id).await;
}
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
return Err!(Request(Forbidden(
"You must be joined in the room you are trying to invite from."
)));
}
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = RoomMemberEventContent {
displayname: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
blurhash: services.users.blurhash(user_id).await.ok(),
is_direct: Some(is_direct),
reason,
..RoomMemberEventContent::new(MembershipState::Invite)
};
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(user_id.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await?;
drop(state_lock);
Ok(())
}
-989
View File
@@ -1,989 +0,0 @@
use std::{borrow::Borrow, collections::HashMap, iter::once, sync::Arc};
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Result, debug, debug_info, debug_warn, err, error, info,
matrix::{
StateKey,
event::{gen_event_id, gen_event_id_canonical_json},
pdu::{PduBuilder, PduEvent},
state_res,
},
result::FlatOk,
trace,
utils::{
self, shuffle,
stream::{IterStream, ReadyExt},
},
warn,
};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
RoomVersionId, UserId,
api::{
client::{
error::ErrorKind,
membership::{ThirdPartySigned, join_room_by_id, join_room_by_id_or_alias},
},
federation::{self},
},
canonical_json::to_canonical_value,
events::{
StateEventType,
room::{
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
},
},
};
use service::{
Services,
appservice::RegistrationInfo,
rooms::{
state::RoomMutexGuard,
state_compressor::{CompressedState, HashSetCompressStateEvent},
},
};
use super::banned_room_check;
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/join`
///
/// Tries to join the sender user into a room.
///
/// - If the server knowns about this room: creates the join event and does auth
/// rules locally
/// - If the server does not know about the room: asks other servers over
/// federation
#[tracing::instrument(skip_all, fields(%client), name = "join")]
pub(crate) async fn join_room_by_id_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<join_room_by_id::v3::Request>,
) -> Result<join_room_by_id::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
banned_room_check(
&services,
sender_user,
Some(&body.room_id),
body.room_id.server_name(),
client,
)
.await?;
// There is no body.server_name for /roomId/join
let mut servers: Vec<_> = services
.rooms
.state_cache
.servers_invite_via(&body.room_id)
.map(ToOwned::to_owned)
.collect()
.await;
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &body.room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Some(server) = body.room_id.server_name() {
servers.push(server.into());
}
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
join_room_by_id_helper(
&services,
sender_user,
&body.room_id,
body.reason.clone(),
&servers,
body.third_party_signed.as_ref(),
&body.appservice_info,
)
.boxed()
.await
}
/// # `POST /_matrix/client/r0/join/{roomIdOrAlias}`
///
/// Tries to join the sender user into a room.
///
/// - If the server knowns about this room: creates the join event and does auth
/// rules locally
/// - If the server does not know about the room: use the server name query
/// param if specified. if not specified, asks other servers over federation
/// via room alias server name and room ID server name
#[tracing::instrument(skip_all, fields(%client), name = "join")]
pub(crate) async fn join_room_by_id_or_alias_route(
State(services): State<crate::State>,
InsecureClientIp(client): InsecureClientIp,
body: Ruma<join_room_by_id_or_alias::v3::Request>,
) -> Result<join_room_by_id_or_alias::v3::Response> {
let sender_user = body.sender_user();
let appservice_info = &body.appservice_info;
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let (servers, room_id) = match OwnedRoomId::try_from(body.room_id_or_alias.clone()) {
| Ok(room_id) => {
banned_room_check(
&services,
sender_user,
Some(&room_id),
room_id.server_name(),
client,
)
.boxed()
.await?;
let mut servers = body.via.clone();
servers.extend(
services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| event.get_field("sender").ok().flatten())
.filter_map(|sender: &str| UserId::parse(sender).ok())
.map(|user| user.server_name().to_owned()),
);
if let Some(server) = room_id.server_name() {
servers.push(server.to_owned());
}
servers.sort_unstable();
servers.dedup();
shuffle(&mut servers);
(servers, room_id)
},
| Err(room_alias) => {
let (room_id, mut servers) = services
.rooms
.alias
.resolve_alias(&room_alias, Some(body.via.clone()))
.await?;
banned_room_check(
&services,
sender_user,
Some(&room_id),
Some(room_alias.server_name()),
client,
)
.await?;
let addl_via_servers = services
.rooms
.state_cache
.servers_invite_via(&room_id)
.map(ToOwned::to_owned);
let addl_state_servers = services
.rooms
.state_cache
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default();
let mut addl_servers: Vec<_> = addl_state_servers
.iter()
.map(|event| event.get_field("sender"))
.filter_map(FlatOk::flat_ok)
.map(|user: &UserId| user.server_name().to_owned())
.stream()
.chain(addl_via_servers)
.collect()
.await;
addl_servers.sort_unstable();
addl_servers.dedup();
shuffle(&mut addl_servers);
servers.append(&mut addl_servers);
(servers, room_id)
},
};
let join_room_response = join_room_by_id_helper(
&services,
sender_user,
&room_id,
body.reason.clone(),
&servers,
body.third_party_signed.as_ref(),
appservice_info,
)
.boxed()
.await?;
Ok(join_room_by_id_or_alias::v3::Response { room_id: join_room_response.room_id })
}
pub async fn join_room_by_id_helper(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
third_party_signed: Option<&ThirdPartySigned>,
appservice_info: &Option<RegistrationInfo>,
) -> Result<join_room_by_id::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let user_is_guest = services
.users
.is_deactivated(sender_user)
.await
.unwrap_or(false)
&& appservice_info.is_none();
if user_is_guest && !services.rooms.state_accessor.guest_can_join(room_id).await {
return Err!(Request(Forbidden("Guests are not allowed to join this room")));
}
if services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already joined in {room_id}");
return Ok(join_room_by_id::v3::Response { room_id: room_id.into() });
}
let server_in_room = services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)
.await;
// Only check our known membership if we're already in the room.
// See: https://forgejo.ellis.link/continuwuation/continuwuity/issues/855
let membership = if server_in_room {
services
.rooms
.state_accessor
.get_member(room_id, sender_user)
.await
} else {
debug!("Ignoring local state for join {room_id}, we aren't in the room yet.");
Ok(RoomMemberEventContent::new(MembershipState::Leave))
};
if let Ok(m) = membership {
if m.membership == MembershipState::Ban {
debug_warn!("{sender_user} is banned from {room_id} but attempted to join");
// TODO: return reason
return Err!(Request(Forbidden("You are banned from the room.")));
}
}
let local_join = server_in_room
|| servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]));
if local_join {
join_room_by_id_helper_local(
services,
sender_user,
room_id,
reason,
servers,
third_party_signed,
state_lock,
)
.boxed()
.await?;
} else {
// Ask a remote server if we are not participating in this room
join_room_by_id_helper_remote(
services,
sender_user,
room_id,
reason,
servers,
third_party_signed,
state_lock,
)
.boxed()
.await?;
}
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_remote")]
async fn join_room_by_id_helper_remote(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>,
state_lock: RoomMutexGuard,
) -> Result {
info!("Joining {room_id} over federation.");
let (make_join_response, remote_server) =
make_join_request(services, sender_user, room_id, servers).await?;
info!("make_join finished");
let Some(room_version_id) = make_join_response.room_version else {
return Err!(BadServerResponse("Remote room version is not supported by conduwuit"));
};
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut join_event_stub: CanonicalJsonObject =
serde_json::from_str(make_join_response.event.get()).map_err(|e| {
err!(BadServerResponse(warn!(
"Invalid make_join event json received from server: {e:?}"
)))
})?;
let join_authorized_via_users_server = {
use RoomVersionId::*;
if !matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6 | V7) {
join_event_stub
.get("content")
.map(|s| {
s.as_object()?
.get("join_authorised_via_users_server")?
.as_str()
})
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok())
} else {
None
}
};
join_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
join_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
join_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server: join_authorized_via_users_server.clone(),
..RoomMemberEventContent::new(MembershipState::Join)
})
.expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
join_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let mut join_event = join_event_stub;
info!("Asking {remote_server} for send_join in room {room_id}");
let send_join_request = federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
};
let send_join_response = match services
.sending
.send_synapse_request(&remote_server, send_join_request)
.await
{
| Ok(response) => response,
| Err(e) => {
error!("send_join failed: {e}");
return Err(e);
},
};
info!("send_join finished");
if join_authorized_via_users_server.is_some() {
if let Some(signed_raw) = &send_join_response.room_state.event {
debug_info!(
"There is a signed event with join_authorized_via_users_server. This room is \
probably using restricted joins. Adding signature to our event"
);
let (signed_event_id, signed_value) =
gen_event_id_canonical_json(signed_raw, &room_version_id).map_err(|e| {
err!(Request(BadJson(warn!(
"Could not convert event to canonical JSON: {e}"
))))
})?;
if signed_event_id != event_id {
return Err!(Request(BadJson(warn!(
%signed_event_id, %event_id,
"Server {remote_server} sent event with wrong event ID"
))));
}
match signed_value["signatures"]
.as_object()
.ok_or_else(|| {
err!(BadServerResponse(warn!(
"Server {remote_server} sent invalid signatures type"
)))
})
.and_then(|e| {
e.get(remote_server.as_str()).ok_or_else(|| {
err!(BadServerResponse(warn!(
"Server {remote_server} did not send its signature for a restricted \
room"
)))
})
}) {
| Ok(signature) => {
join_event
.get_mut("signatures")
.expect("we created a valid pdu")
.as_object_mut()
.expect("we created a valid pdu")
.insert(remote_server.to_string(), signature.clone());
},
| Err(e) => {
warn!(
"Server {remote_server} sent invalid signature in send_join signatures \
for event {signed_value:?}: {e:?}",
);
},
}
}
}
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
info!("Parsing join event");
let parsed_join_pdu = PduEvent::from_id_val(&event_id, join_event.clone())
.map_err(|e| err!(BadServerResponse("Invalid join event PDU: {e:?}")))?;
info!("Acquiring server signing keys for response events");
let resp_events = &send_join_response.room_state;
let resp_state = &resp_events.state;
let resp_auth = &resp_events.auth_chain;
services
.server_keys
.acquire_events_pubkeys(resp_auth.iter().chain(resp_state.iter()))
.await;
info!("Going through send_join response room_state");
let cork = services.db.cork_and_flush();
let state = send_join_response
.room_state
.state
.iter()
.stream()
.then(|pdu| {
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
})
.ready_filter_map(Result::ok)
.fold(HashMap::new(), |mut state, (event_id, value)| async move {
let pdu = match PduEvent::from_id_val(&event_id, value.clone()) {
| Ok(pdu) => pdu,
| Err(e) => {
debug_warn!("Invalid PDU in send_join response: {e:?}: {value:#?}");
return state;
},
};
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
if let Some(state_key) = &pdu.state_key {
let shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
.await;
state.insert(shortstatekey, pdu.event_id.clone());
}
state
})
.await;
drop(cork);
info!("Going through send_join response auth_chain");
let cork = services.db.cork_and_flush();
send_join_response
.room_state
.auth_chain
.iter()
.stream()
.then(|pdu| {
services
.server_keys
.validate_and_add_event_id_no_fetch(pdu, &room_version_id)
})
.ready_filter_map(Result::ok)
.ready_for_each(|(event_id, value)| {
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
})
.await;
drop(cork);
debug!("Running send_join auth check");
let fetch_state = &state;
let state_fetch = |k: StateEventType, s: StateKey| async move {
let shortstatekey = services.rooms.short.get_shortstatekey(&k, &s).await.ok()?;
let event_id = fetch_state.get(&shortstatekey)?;
services.rooms.timeline.get_pdu(event_id).await.ok()
};
let auth_check = state_res::event_auth::auth_check(
&state_res::RoomVersion::new(&room_version_id)?,
&parsed_join_pdu,
None, // TODO: third party invite
|k, s| state_fetch(k.clone(), s.into()),
)
.await
.map_err(|e| err!(Request(Forbidden(warn!("Auth check failed: {e:?}")))))?;
if !auth_check {
return Err!(Request(Forbidden("Auth check failed")));
}
info!("Compressing state from send_join");
let compressed: CompressedState = services
.rooms
.state_compressor
.compress_state_events(state.iter().map(|(ssk, eid)| (ssk, eid.borrow())))
.collect()
.await;
debug!("Saving compressed state");
let HashSetCompressStateEvent {
shortstatehash: statehash_before_join,
added,
removed,
} = services
.rooms
.state_compressor
.save_state(room_id, Arc::new(compressed))
.await?;
debug!("Forcing state for new room");
services
.rooms
.state
.force_state(room_id, statehash_before_join, added, removed, &state_lock)
.await?;
info!("Updating joined counts for new room");
services
.rooms
.state_cache
.update_joined_count(room_id)
.await;
// We append to state before appending the pdu, so we don't have a moment in
// time with the pdu without it's state. This is okay because append_pdu can't
// fail.
let statehash_after_join = services
.rooms
.state
.append_to_state(&parsed_join_pdu)
.await?;
info!("Appending new room join event");
services
.rooms
.timeline
.append_pdu(
&parsed_join_pdu,
join_event,
once(parsed_join_pdu.event_id.borrow()),
&state_lock,
)
.await?;
info!("Setting final room state for new room");
// We set the room state after inserting the pdu, so that we never have a moment
// in time where events in the current room state do not exist
services
.rooms
.state
.set_room_state(room_id, statehash_after_join, &state_lock);
Ok(())
}
#[tracing::instrument(skip_all, fields(%sender_user, %room_id), name = "join_local")]
async fn join_room_by_id_helper_local(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
reason: Option<String>,
servers: &[OwnedServerName],
_third_party_signed: Option<&ThirdPartySigned>,
state_lock: RoomMutexGuard,
) -> Result {
debug_info!("We can join locally");
let join_rules_event_content = services
.rooms
.state_accessor
.room_state_get_content::<RoomJoinRulesEventContent>(
room_id,
&StateEventType::RoomJoinRules,
"",
)
.await;
let restriction_rooms = match join_rules_event_content {
| Ok(RoomJoinRulesEventContent {
join_rule: JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted),
}) => restricted
.allow
.into_iter()
.filter_map(|a| match a {
| AllowRule::RoomMembership(r) => Some(r.room_id),
| _ => None,
})
.collect(),
| _ => Vec::new(),
};
let join_authorized_via_users_server: Option<OwnedUserId> = {
if restriction_rooms
.iter()
.stream()
.any(|restriction_room_id| {
services
.rooms
.state_cache
.is_joined(sender_user, restriction_room_id)
})
.await
{
services
.rooms
.state_cache
.local_users_in_room(room_id)
.filter(|user| {
services.rooms.state_accessor.user_can_invite(
room_id,
user,
sender_user,
&state_lock,
)
})
.boxed()
.next()
.await
.map(ToOwned::to_owned)
} else {
None
}
};
let content = RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
};
// Try normal join first
let Err(error) = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(sender_user.to_string(), &content),
sender_user,
room_id,
&state_lock,
)
.await
else {
return Ok(());
};
if restriction_rooms.is_empty()
&& (servers.is_empty()
|| servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
{
return Err(error);
}
warn!(
"We couldn't do the join locally, maybe federation can help to satisfy the restricted \
join requirements"
);
let Ok((make_join_response, remote_server)) =
make_join_request(services, sender_user, room_id, servers).await
else {
return Err(error);
};
let Some(room_version_id) = make_join_response.room_version else {
return Err!(BadServerResponse("Remote room version is not supported by conduwuit"));
};
if !services.server.supported_room_version(&room_version_id) {
return Err!(BadServerResponse(
"Remote room version {room_version_id} is not supported by conduwuit"
));
}
let mut join_event_stub: CanonicalJsonObject =
serde_json::from_str(make_join_response.event.get()).map_err(|e| {
err!(BadServerResponse("Invalid make_join event json received from server: {e:?}"))
})?;
let join_authorized_via_users_server = join_event_stub
.get("content")
.map(|s| {
s.as_object()?
.get("join_authorised_via_users_server")?
.as_str()
})
.and_then(|s| OwnedUserId::try_from(s.unwrap_or_default()).ok());
join_event_stub.insert(
"origin".to_owned(),
CanonicalJsonValue::String(services.globals.server_name().as_str().to_owned()),
);
join_event_stub.insert(
"origin_server_ts".to_owned(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
);
join_event_stub.insert(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server,
..RoomMemberEventContent::new(MembershipState::Join)
})
.expect("event is valid, we just created it"),
);
// We keep the "event_id" in the pdu only in v1 or
// v2 rooms
match room_version_id {
| RoomVersionId::V1 | RoomVersionId::V2 => {},
| _ => {
join_event_stub.remove("event_id");
},
}
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
services
.server_keys
.hash_and_sign_event(&mut join_event_stub, &room_version_id)?;
// Generate event id
let event_id = gen_event_id(&join_event_stub, &room_version_id)?;
// Add event_id back
join_event_stub
.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.clone().into()));
// It has enough fields to be called a proper event now
let join_event = join_event_stub;
let send_join_response = services
.sending
.send_synapse_request(
&remote_server,
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.clone(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone())
.await,
},
)
.await?;
if let Some(signed_raw) = send_join_response.room_state.event {
let (signed_event_id, signed_value) =
gen_event_id_canonical_json(&signed_raw, &room_version_id).map_err(|e| {
err!(Request(BadJson(warn!("Could not convert event to canonical JSON: {e}"))))
})?;
if signed_event_id != event_id {
return Err!(Request(BadJson(
warn!(%signed_event_id, %event_id, "Server {remote_server} sent event with wrong event ID")
)));
}
drop(state_lock);
services
.rooms
.event_handler
.handle_incoming_pdu(&remote_server, room_id, &signed_event_id, signed_value, true)
.boxed()
.await?;
} else {
return Err(error);
}
Ok(())
}
async fn make_join_request(
services: &Services,
sender_user: &UserId,
room_id: &RoomId,
servers: &[OwnedServerName],
) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> {
let mut make_join_response_and_server =
Err!(BadServerResponse("No server available to assist in joining."));
let mut make_join_counter: usize = 0;
let mut incompatible_room_version_count: usize = 0;
for remote_server in servers {
if services.globals.server_is_ours(remote_server) {
continue;
}
info!("Asking {remote_server} for make_join ({make_join_counter})");
let make_join_response = services
.sending
.send_federation_request(
remote_server,
federation::membership::prepare_join_event::v1::Request {
room_id: room_id.to_owned(),
user_id: sender_user.to_owned(),
ver: services.server.supported_room_versions().collect(),
},
)
.await;
trace!("make_join response: {:?}", make_join_response);
make_join_counter = make_join_counter.saturating_add(1);
if let Err(ref e) = make_join_response {
if matches!(
e.kind(),
ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion
) {
incompatible_room_version_count =
incompatible_room_version_count.saturating_add(1);
}
if incompatible_room_version_count > 15 {
info!(
"15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or \
M_UNSUPPORTED_ROOM_VERSION, assuming that conduwuit does not support the \
room version {room_id}: {e}"
);
make_join_response_and_server =
Err!(BadServerResponse("Room version is not supported by Conduwuit"));
return make_join_response_and_server;
}
if make_join_counter > 40 {
warn!(
"40 servers failed to provide valid make_join response, assuming no server \
can assist in joining."
);
make_join_response_and_server =
Err!(BadServerResponse("No server available to assist in joining."));
return make_join_response_and_server;
}
}
make_join_response_and_server = make_join_response.map(|r| (r, remote_server.clone()));
if make_join_response_and_server.is_ok() {
break;
}
}
make_join_response_and_server
}
-65
View File
@@ -1,65 +0,0 @@
use axum::extract::State;
use conduwuit::{Err, Result, matrix::pdu::PduBuilder};
use ruma::{
api::client::membership::kick_user,
events::room::member::{MembershipState, RoomMemberEventContent},
};
use crate::Ruma;
/// # `POST /_matrix/client/r0/rooms/{roomId}/kick`
///
/// Tries to send a kick event into the room.
pub(crate) async fn kick_user_route(
State(services): State<crate::State>,
body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> {
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let Ok(event) = services
.rooms
.state_accessor
.get_member(&body.room_id, &body.user_id)
.await
else {
// copy synapse's behaviour of returning 200 without any change to the state
// instead of erroring on left users
return Ok(kick_user::v3::Response::new());
};
if !matches!(
event.membership,
MembershipState::Invite | MembershipState::Knock | MembershipState::Join,
) {
return Err!(Request(Forbidden(
"Cannot kick a user who is not apart of the room (current membership: {})",
event.membership
)));
}
services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent {
membership: MembershipState::Leave,
reason: body.reason.clone(),
is_direct: None,
join_authorized_via_users_server: None,
third_party_invite: None,
..event
}),
sender_user,
&body.room_id,
&state_lock,
)
.await?;
drop(state_lock);
Ok(kick_user::v3::Response::new())
}

Some files were not shown because too many files have changed in this diff Show More