Compare commits

..
Author SHA1 Message Date
Ginger 3b66ff6b9e feat: Remove support for server-side blurhashing 2026-05-12 11:06:33 -04:00
443 changed files with 22248 additions and 31421 deletions
@@ -44,7 +44,7 @@ runs:
- name: Login to builtin registry - name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with: with:
registry: ${{ env.BUILTIN_REGISTRY }} registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }} username: ${{ inputs.registry_user }}
@@ -52,7 +52,7 @@ runs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
with: with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125) # Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }} driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
@@ -61,7 +61,7 @@ runs:
- name: Extract metadata (tags) for Docker - name: Extract metadata (tags) for Docker
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
id: meta id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with: with:
flavor: | flavor: |
latest=auto latest=auto
@@ -67,7 +67,7 @@ runs:
uses: ./.forgejo/actions/rust-toolchain uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
with: with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125) # Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }} driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
@@ -75,11 +75,11 @@ runs:
- name: Set up QEMU - name: Set up QEMU
if: ${{ env.BUILDKIT_ENDPOINT == '' }} if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4 uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
- name: Login to builtin registry - name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }} if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with: with:
registry: ${{ env.BUILTIN_REGISTRY }} registry: ${{ env.BUILTIN_REGISTRY }}
username: ${{ inputs.registry_user }} username: ${{ inputs.registry_user }}
@@ -87,7 +87,7 @@ runs:
- name: Extract metadata (labels, annotations) for Docker - name: Extract metadata (labels, annotations) for Docker
id: meta id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with: with:
images: ${{ inputs.images }} images: ${{ inputs.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
+1 -1
View File
@@ -33,7 +33,7 @@ runs:
echo "version=$(rustup --version)" >> $GITHUB_OUTPUT echo "version=$(rustup --version)" >> $GITHUB_OUTPUT
- name: Cache rustup toolchains - name: Cache rustup toolchains
if: steps.rustup-version.outputs.version == '' if: steps.rustup-version.outputs.version == ''
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
~/.rustup ~/.rustup
@@ -17,7 +17,7 @@ inputs:
llvm-version: llvm-version:
description: 'LLVM version to install' description: 'LLVM version to install'
required: false required: false
default: '21' default: '20'
outputs: outputs:
llvm-version: llvm-version:
@@ -57,7 +57,7 @@ runs:
- name: Check for LLVM cache - name: Check for LLVM cache
id: cache id: cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
/usr/bin/clang-* /usr/bin/clang-*
@@ -120,7 +120,7 @@ runs:
- name: Install additional packages - name: Install additional packages
if: inputs.extra-packages != '' if: inputs.extra-packages != ''
uses: https://github.com/awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # latest uses: https://github.com/awalsh128/cache-apt-pkgs-action@2c09a5e66da6c8016428a2172bd76e5e4f14bb17 # latest
with: with:
packages: ${{ inputs.extra-packages }} packages: ${{ inputs.extra-packages }}
version: 1.0 version: 1.0
+2 -2
View File
@@ -65,7 +65,7 @@ runs:
- name: Cache toolchain binaries - name: Cache toolchain binaries
id: toolchain-cache id: toolchain-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
.cargo/bin .cargo/bin
@@ -76,7 +76,7 @@ runs:
- name: Cache Cargo registry and git - name: Cache Cargo registry and git
id: registry-cache id: registry-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
.cargo/registry/index .cargo/registry/index
+5 -5
View File
@@ -31,7 +31,7 @@ runs:
- name: Restore binary cache - name: Restore binary cache
id: binary-cache id: binary-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
/usr/share/rust/.cargo/bin /usr/share/rust/.cargo/bin
@@ -71,13 +71,13 @@ runs:
- name: Install timelord-cli and git-warp-time - name: Install timelord-cli and git-warp-time
if: steps.check-binaries.outputs.need-install == 'true' if: steps.check-binaries.outputs.need-install == 'true'
uses: https://github.com/taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2 uses: https://github.com/taiki-e/install-action@b5fddbb5361bce8a06fb168c9d403a6cc552b084 # v2
with: with:
tool: git-warp-time,timelord-cli@3.0.1 tool: git-warp-time,timelord-cli@3.0.1
- name: Save binary cache - name: Save binary cache
if: steps.check-binaries.outputs.need-install == 'true' if: steps.check-binaries.outputs.need-install == 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
/usr/share/rust/.cargo/bin /usr/share/rust/.cargo/bin
@@ -87,7 +87,7 @@ runs:
- name: Restore timelord cache with fallbacks - name: Restore timelord cache with fallbacks
id: timelord-restore id: timelord-restore
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: ${{ env.TIMELORD_CACHE_PATH }} path: ${{ env.TIMELORD_CACHE_PATH }}
key: ${{ env.TIMELORD_KEY }} key: ${{ env.TIMELORD_KEY }}
@@ -114,7 +114,7 @@ runs:
timelord sync --source-dir ${{ env.TIMELORD_PATH }} --cache-dir ${{ env.TIMELORD_CACHE_PATH }} timelord sync --source-dir ${{ env.TIMELORD_PATH }} --cache-dir ${{ env.TIMELORD_CACHE_PATH }}
- name: Save updated timelord cache immediately - name: Save updated timelord cache immediately
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: ${{ env.TIMELORD_CACHE_PATH }} path: ${{ env.TIMELORD_CACHE_PATH }}
key: ${{ env.TIMELORD_KEY }} key: ${{ env.TIMELORD_KEY }}
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
const labelsToAdd = new Set(); const labelsToAdd = new Set();
for (const file of fileNames) { for (const file of fileNames) {
if (file.startsWith('docs/') || file.startsWith('theme/') || (file.endsWith('.md') && !file.startsWith('changelog.d/')) || file == 'rspress.config.ts') { if (file.startsWith('docs/') || file.startsWith('theme/') || file.endsWith('.md') || file == 'rspress.config.ts') {
labelsToAdd.add('Documentation'); labelsToAdd.add('Documentation');
} }
if (file.startsWith('.forgejo/')) { if (file.startsWith('.forgejo/')) {
+17 -6
View File
@@ -10,7 +10,7 @@ on:
- "v*.*.*" - "v*.*.*"
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: '30 0 * * 1' - cron: '30 0 * * *'
jobs: jobs:
build: build:
@@ -41,15 +41,26 @@ jobs:
# else # else
# echo "No workaround needed for llvm-project#153385" # echo "No workaround needed for llvm-project#153385"
# fi # fi
- name: Pick compatible clang version
id: clang-version
run: |
# both latest need to use clang-23, but oldstable and previous can just use clang
if [[ "${{ matrix.container }}" == "ubuntu-latest" ]]; then
echo "Using clang-23 package for ${{ matrix.container }}"
echo "version=clang-23" >> $GITHUB_OUTPUT
else
echo "Using default clang package for ${{ matrix.container }}"
echo "version=clang" >> $GITHUB_OUTPUT
fi
- name: Checkout repository with full history - name: Checkout repository with full history
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.ref_name }} ref: ${{ github.ref_name }}
- name: Cache Cargo registry - name: Cache Cargo registry
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
~/.cargo/registry ~/.cargo/registry
@@ -82,10 +93,10 @@ jobs:
# VERSION is the package version, COMPONENT is used in # VERSION is the package version, COMPONENT is used in
# apt's repository config like a git repo branch # apt's repository config like a git repo branch
VERSION=$BASE_VERSION VERSION=$BASE_VERSION
if [[ ${{ forge.ref_name }} =~ ^v+[0-9]+\.+[0-9]+\.+[0-9]+$ ]]; then if [[ ${{ forge.ref_name }} =~ ^v+[0-9]\.+[0-9]\.+[0-9]$ ]]; then
# Use the "stable" component for tagged semver releases # Use the "stable" component for tagged semver releases
COMPONENT="stable" COMPONENT="stable"
elif [[ ${{ forge.ref_name }} =~ ^v+[0-9]+\.+[0-9]+\.+[0-9]+ ]]; then elif [[ ${{ forge.ref_name }} =~ ^v+[0-9]\.+[0-9]\.+[0-9] ]]; then
# Use the "unstable" component for tagged semver pre-releases # Use the "unstable" component for tagged semver pre-releases
COMPONENT="unstable" COMPONENT="unstable"
else else
@@ -119,7 +130,7 @@ jobs:
run: | run: |
apt-get update -y apt-get update -y
# Build dependencies for rocksdb # Build dependencies for rocksdb
apt-get install -y liburing-dev clang apt-get install -y liburing-dev ${{ steps.clang-version.outputs.version }}
- name: Run cargo-deb - name: Run cargo-deb
id: cargo-deb id: cargo-deb
+394
View File
@@ -0,0 +1,394 @@
name: Build / Fedora RPM
concurrency:
group: "build-fedora-${{ github.ref }}"
cancel-in-progress: true
on:
push:
tags:
- "v*.*.*"
# paths:
# - 'pkg/fedora/**'
# - 'src/**'
# - 'Cargo.toml'
# - 'Cargo.lock'
# - '.forgejo/workflows/build-fedora.yml'
workflow_dispatch:
schedule:
- cron: '30 0 * * *'
jobs:
build:
runs-on: fedora-latest
steps:
- name: Detect Fedora version
id: fedora
run: |
VERSION=$(rpm -E %fedora)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Fedora version: $VERSION"
- name: Checkout repository with full history
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Cache DNF packages
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
/var/cache/dnf
/var/cache/yum
key: dnf-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('pkg/fedora/continuwuity.spec.rpkg') }}-v1
restore-keys: |
dnf-fedora${{ steps.fedora.outputs.version }}-
- name: Cache Cargo registry
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-fedora${{ steps.fedora.outputs.version }}-
- name: Cache Rust build dependencies
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/rpmbuild/BUILD/*/target/release/deps
~/rpmbuild/BUILD/*/target/release/build
~/rpmbuild/BUILD/*/target/release/.fingerprint
~/rpmbuild/BUILD/*/target/release/incremental
key: rust-deps-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
rust-deps-fedora${{ steps.fedora.outputs.version }}-
- name: Setup sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
- name: Configure sccache environment
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV
echo "SCCACHE_CACHE_SIZE=10G" >> $GITHUB_ENV
# Aggressive GC since cache restores don't increment counter
echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV
- name: Install base RPM tools
run: |
dnf install -y --setopt=keepcache=1 \
fedora-packager \
python3-pip \
rpm-sign \
rpkg \
wget
- name: Setup build environment and build SRPM
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global user.email "ci@continuwuity.org"
git config --global user.name "Continuwuity"
rpmdev-setuptree
cd "$GITHUB_WORKSPACE"
# Determine release suffix and version based on ref type and branch
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
# Tags get clean version numbers for stable releases
RELEASE_SUFFIX=""
TAG_NAME="${{ github.ref_name }}"
# Extract version from tag (remove v prefix if present)
TAG_VERSION=$(echo "$TAG_NAME" | sed 's/^v//' | tr '-' '~')
# Create spec file with tag version
sed -e "s/^Version:.*$/Version: $TAG_VERSION/" \
-e "s/^Release:.*$/Release: 1%{?dist}/" \
pkg/fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg
elif [ "${{ github.ref_name }}" = "main" ]; then
# Main branch gets .dev suffix
RELEASE_SUFFIX=".dev"
# Replace the Release line to include our suffix
sed "s/^Release:.*$/Release: 1${RELEASE_SUFFIX}%{?dist}/" \
pkg/fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg
else
# Other branches get sanitized branch name as suffix
SAFE_BRANCH=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/_/g' | cut -c1-20)
RELEASE_SUFFIX=".${SAFE_BRANCH}"
# Replace the Release line to include our suffix
sed "s/^Release:.*$/Release: 1${RELEASE_SUFFIX}%{?dist}/" \
pkg/fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg
fi
rpkg srpm --outdir "$HOME/rpmbuild/SRPMS"
ls -la $HOME/rpmbuild/SRPMS/
- name: Install build dependencies from SRPM
run: |
SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1)
if [ -z "$SRPM" ]; then
echo "Error: No SRPM file found"
exit 1
fi
echo "Installing build dependencies from: $(basename $SRPM)"
dnf builddep -y "$SRPM"
- name: Build RPM from SRPM
run: |
SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1)
if [ -z "$SRPM" ]; then
echo "Error: No SRPM file found"
exit 1
fi
echo "Building from SRPM: $SRPM"
rpmbuild --rebuild "$SRPM" \
--define "_topdir $HOME/rpmbuild" \
--define "_sourcedir $GITHUB_WORKSPACE" \
--nocheck # Skip %check section to avoid test dependencies
- name: Test RPM installation
run: |
# Find the main binary RPM (exclude debug and source RPMs)
RPM=$(find "$HOME/rpmbuild/RPMS" -name "continuwuity-*.rpm" \
! -name "*debuginfo*" \
! -name "*debugsource*" \
! -name "*.src.rpm" | head -1)
if [ -z "$RPM" ]; then
echo "Error: No binary RPM file found"
exit 1
fi
echo "Testing installation of: $RPM"
# Dry run first
rpm -qpi "$RPM"
echo ""
rpm -qpl "$RPM"
# Actually install it
dnf install -y "$RPM"
# Verify installation
rpm -qa | grep continuwuity
# Check that the binary exists
[ -f /usr/bin/conduwuit ] && echo "✅ Binary installed successfully"
[ -f /usr/lib/systemd/system/conduwuit.service ] && echo "✅ Systemd service installed"
[ -f /etc/conduwuit/conduwuit.toml ] && echo "✅ Config file installed"
- name: List built packages
run: |
echo "Binary RPMs:"
find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec ls -la {} \;
echo ""
echo "Source RPMs:"
find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec ls -la {} \;
- name: Collect artifacts
run: |
mkdir -p artifacts
find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \;
find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \;
cd artifacts
echo "Build Information:" > BUILD_INFO.txt
echo "==================" >> BUILD_INFO.txt
echo "Git commit: ${{ github.sha }}" >> BUILD_INFO.txt
echo "Git branch: ${{ github.ref_name }}" >> BUILD_INFO.txt
echo "Build date: $(date -u +%Y-%m-%d_%H:%M:%S_UTC)" >> BUILD_INFO.txt
echo "" >> BUILD_INFO.txt
echo "Package contents:" >> BUILD_INFO.txt
echo "-----------------" >> BUILD_INFO.txt
for rpm in *.rpm; do
echo "" >> BUILD_INFO.txt
echo "File: $rpm" >> BUILD_INFO.txt
rpm -qpi "$rpm" 2>/dev/null | grep -E "^(Name|Version|Release|Architecture|Size)" >> BUILD_INFO.txt
done
ls -la
- name: Upload binary RPM artifact
run: |
# Find the main binary RPM (exclude debug and source RPMs)
BIN_RPM=$(find artifacts -name "continuwuity-*.rpm" \
! -name "*debuginfo*" \
! -name "*debugsource*" \
! -name "*.src.rpm" \
-type f)
mkdir -p upload-bin
cp $BIN_RPM upload-bin/
- name: Upload binary RPM
uses: forgejo/upload-artifact@v4
with:
name: continuwuity
path: upload-bin/
- name: Upload debug RPM artifact
uses: forgejo/upload-artifact@v4
with:
name: continuwuity-debug
path: artifacts/*debuginfo*.rpm
- name: Publish to RPM Package Registry
if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }}
run: |
# Find the main binary RPM (exclude debug and source RPMs)
RPM=$(find artifacts -name "continuwuity-*.rpm" \
! -name "*debuginfo*" \
! -name "*debugsource*" \
! -name "*.src.rpm" \
-type f | head -1)
if [ -z "$RPM" ]; then
echo "No binary RPM found to publish"
exit 0
fi
RPM_BASENAME=$(basename "$RPM")
echo "Publishing: $RPM_BASENAME"
# Determine the group based on ref type and branch
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
# For tags, extract the tag name for version info
TAG_NAME="${{ github.ref_name }}"
if [[ "$TAG_NAME" == *"-"* ]]; then
GROUP="unstable"
else
GROUP="stable"
fi
elif [ "${{ github.ref_name }}" = "main" ]; then
GROUP="dev"
else
# Use sanitized branch name as group for feature branches
GROUP=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30)
fi
PACKAGE_INFO=$(rpm -qpi "$RPM" 2>/dev/null)
PACKAGE_NAME=$(echo "$PACKAGE_INFO" | grep "^Name" | awk '{print $3}')
PACKAGE_VERSION=$(echo "$PACKAGE_INFO" | grep "^Version" | awk '{print $3}')
PACKAGE_RELEASE=$(echo "$PACKAGE_INFO" | grep "^Release" | awk '{print $3}')
PACKAGE_ARCH=$(echo "$PACKAGE_INFO" | grep "^Architecture" | awk '{print $2}')
# Full version includes release
FULL_VERSION="${PACKAGE_VERSION}-${PACKAGE_RELEASE}"
# Forgejo's RPM registry cannot overwrite existing packages, so we must delete first
# 404 is OK if package doesn't exist yet
echo "Removing any existing package: $PACKAGE_NAME-$FULL_VERSION.$PACKAGE_ARCH"
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then
echo "ERROR: Failed to delete package (HTTP $HTTP_CODE)"
echo "$RESPONSE" | head -n -1
exit 1
fi
curl --fail-with-body \
-X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/x-rpm" \
-T "$RPM" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/upload?sign=true"
echo ""
echo "✅ Published binary RPM to: https://forgejo.ellis.link/continuwuation/-/packages/rpm/continuwuity/"
echo "Group: $GROUP"
# Upload debug RPMs to separate group
DEBUG_RPMS=$(find artifacts -name "*debuginfo*.rpm")
if [ -n "$DEBUG_RPMS" ]; then
echo ""
echo "Publishing debug RPMs to group: ${GROUP}-debug"
for DEBUG_RPM in $DEBUG_RPMS; do
echo "Publishing: $(basename "$DEBUG_RPM")"
DEBUG_INFO=$(rpm -qpi "$DEBUG_RPM" 2>/dev/null)
DEBUG_NAME=$(echo "$DEBUG_INFO" | grep "^Name" | awk '{print $3}')
DEBUG_VERSION=$(echo "$DEBUG_INFO" | grep "^Version" | awk '{print $3}')
DEBUG_RELEASE=$(echo "$DEBUG_INFO" | grep "^Release" | awk '{print $3}')
DEBUG_ARCH=$(echo "$DEBUG_INFO" | grep "^Architecture" | awk '{print $2}')
DEBUG_FULL_VERSION="${DEBUG_VERSION}-${DEBUG_RELEASE}"
# Must delete existing package first (Forgejo limitation)
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then
echo "ERROR: Failed to delete debug package (HTTP $HTTP_CODE)"
echo "$RESPONSE" | head -n -1
exit 1
fi
curl --fail-with-body \
-X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/x-rpm" \
-T "$DEBUG_RPM" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/upload?sign=true"
done
echo "✅ Published debug RPMs to group: ${GROUP}-debug"
fi
# Also upload the SRPM to separate group
SRPM=$(find artifacts -name "*.src.rpm" | head -1)
if [ -n "$SRPM" ]; then
echo ""
echo "Publishing source RPM: $(basename "$SRPM")"
echo "Publishing to group: ${GROUP}-src"
SRPM_INFO=$(rpm -qpi "$SRPM" 2>/dev/null)
SRPM_NAME=$(echo "$SRPM_INFO" | grep "^Name" | awk '{print $3}')
SRPM_VERSION=$(echo "$SRPM_INFO" | grep "^Version" | awk '{print $3}')
SRPM_RELEASE=$(echo "$SRPM_INFO" | grep "^Release" | awk '{print $3}')
SRPM_FULL_VERSION="${SRPM_VERSION}-${SRPM_RELEASE}"
# Must delete existing SRPM first (Forgejo limitation)
echo "Removing any existing SRPM: $SRPM_NAME-$SRPM_FULL_VERSION.src"
RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then
echo "ERROR: Failed to delete SRPM (HTTP $HTTP_CODE)"
echo "$RESPONSE" | head -n -1
exit 1
fi
curl --fail-with-body \
-X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/x-rpm" \
-T "$SRPM" \
"https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/upload?sign=true"
echo "✅ Published source RPM to group: ${GROUP}-src"
fi
-71
View File
@@ -1,71 +0,0 @@
name: Build / Static via Nix
concurrency:
group: "build-nix-${{ forge.ref }}"
cancel-in-progress: true
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
schedule:
- cron: '30 0 * * 3'
jobs:
build:
name: "Build ${{ matrix.filename }} Binary"
runs-on: ubuntu-latest
strategy:
matrix:
include:
- package: default-static-x86_64
filename: conduwuit-linux-static-amd64
- package: default-static-aarch64
filename: conduwuit-linux-static-arm64
- package: max-perf-static-aarch64
filename: conduwuit-linux-static-arm64-maxperf
- package: max-perf-haswell-static-x86_64
filename: conduwuit-haswell-linux-static-amd64-maxperf
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
- name: Install Lix
uses: https://github.com/samueldr/lix-gha-installer-action@a0fee77b2a98bb7c5c0ed7ae6d6ad4903dbdad0d
with:
extra_nix_config: experimental-features = nix-command flakes flake-self-attrs
- name: Build static binary
run: |
nix build .#${{ matrix.package }}
install -D result/bin/conduwuit /tmp/binaries/${{ matrix.filename }}
- name: Upload binary artifact
uses: forgejo/upload-artifact@v4
with:
name: ${{ matrix.filename }}
path: /tmp/binaries/${{ matrix.filename }}
release-binaries:
name: "Release Binaries"
runs-on: ubuntu-latest
needs:
- build
permissions:
contents: write
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Download binary artifacts
uses: forgejo/download-artifact@v4
with:
pattern: conduwuit*
path: binaries
merge-multiple: true
- name: Create Release and Upload
uses: https://github.com/softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
with:
draft: true
files: binaries/*
+11 -7
View File
@@ -14,19 +14,23 @@ jobs:
name: Check changelog is added name: Check changelog is added
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
persist-credentials: false
sparse-checkout: .
- name: Check for changelog entry - name: Check for changelog entry
id: check_files id: check_files
run: | run: |
AUTH=$(echo -n "x-access-token:${{ secrets.GITHUB_TOKEN }}" | base64 -w 0) git fetch origin ${GITHUB_BASE_REF}
git config --global http.${{ github.server_url }}/.extraheader "Authorization: basic $AUTH"
git clone "${{ github.event.repository.clone_url }}" repo.git --bare
git -C repo.git fetch origin pull/${{ github.event.pull_request.number }}/head
# Check for Added (A) or Modified (M) files in changelog.d # Check for Added (A) or Modified (M) files in changelog.d
CHANGELOG_CHANGES=$(git -C repo.git diff --name-status ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- changelog.d/) CHANGELOG_CHANGES=$(git diff --name-status origin/${GITHUB_BASE_REF}...HEAD -- changelog.d/)
SRC_CHANGES=$(git -C repo.git diff --name-status ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }} -- src/) SRC_CHANGES=$(git diff --name-status origin/${GITHUB_BASE_REF}...HEAD -- src/)
echo "Changes in changelog.d/:" echo "Changes in changelog.d/:"
echo "$CHANGELOG_CHANGES" echo "$CHANGELOG_CHANGES"
+5 -5
View File
@@ -21,7 +21,7 @@ jobs:
steps: steps:
- name: Sync repository - name: Sync repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
fetch-depth: 0 fetch-depth: 0
@@ -32,12 +32,12 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20' if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20'
uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 uses: https://github.com/actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with: with:
node-version: 22 node-version: 22
- name: Cache npm dependencies - name: Cache npm dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: ~/.npm path: ~/.npm
key: continuwuity-rspress-${{ steps.runner-env.outputs.slug }}-${{ steps.runner-env.outputs.arch }}-node-${{ steps.runner-env.outputs.node_version }}-${{ hashFiles('package-lock.json') }} key: continuwuity-rspress-${{ steps.runner-env.outputs.slug }}-${{ steps.runner-env.outputs.arch }}-node-${{ steps.runner-env.outputs.node_version }}-${{ hashFiles('package-lock.json') }}
@@ -56,7 +56,7 @@ jobs:
- name: Deploy to Cloudflare Pages (Production) - name: Deploy to Cloudflare Pages (Production)
if: github.ref == 'refs/heads/main' && vars.CLOUDFLARE_PROJECT_NAME != '' if: github.ref == 'refs/heads/main' && vars.CLOUDFLARE_PROJECT_NAME != ''
uses: https://github.com/cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4 uses: https://github.com/cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
with: with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
- name: Deploy to Cloudflare Pages (Preview) - name: Deploy to Cloudflare Pages (Preview)
if: github.ref != 'refs/heads/main' && vars.CLOUDFLARE_PROJECT_NAME != '' if: github.ref != 'refs/heads/main' && vars.CLOUDFLARE_PROJECT_NAME != ''
uses: https://github.com/cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4 uses: https://github.com/cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
with: with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
steps: steps:
- name: 📦 Setup Node.js - name: 📦 Setup Node.js
uses: https://github.com/actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 uses: https://github.com/actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with: with:
node-version: "22" node-version: "22"
@@ -121,7 +121,7 @@ jobs:
- name: 🚀 Deploy to Cloudflare Pages - name: 🚀 Deploy to Cloudflare Pages
if: vars.CLOUDFLARE_PROJECT_NAME != '' if: vars.CLOUDFLARE_PROJECT_NAME != ''
id: deploy id: deploy
uses: https://github.com/cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4 uses: https://github.com/cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
with: with:
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+2 -2
View File
@@ -41,7 +41,7 @@ jobs:
DOCKER_MIRROR_TOKEN: ${{ secrets.DOCKER_MIRROR_TOKEN }} DOCKER_MIRROR_TOKEN: ${{ secrets.DOCKER_MIRROR_TOKEN }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
@@ -55,7 +55,7 @@ jobs:
# repositories: continuwuity # repositories: continuwuity
- name: Install regsync - name: Install regsync
uses: https://github.com/regclient/actions/regsync-installer@39bf30ced4be9d147faaabbde2682e9725fb8bd1 # main uses: https://github.com/regclient/actions/regsync-installer@f3c6d87835906c175eb6ccfc18b348b69bb447e7 # main
- name: Check what images need mirroring - name: Check what images need mirroring
run: | run: |
+4 -4
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
@@ -48,12 +48,12 @@ jobs:
rust: ${{ steps.filter.outputs.rust }} rust: ${{ steps.filter.outputs.rust }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
- name: Check for file changes - name: Check for file changes
uses: https://github.com/dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4 uses: https://github.com/dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4
id: filter id: filter
with: with:
filters: | filters: |
@@ -70,7 +70,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
+7 -9
View File
@@ -46,7 +46,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
- name: Prepare Docker build environment - name: Prepare Docker build environment
@@ -62,7 +62,7 @@ jobs:
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push Docker image by digest - name: Build and push Docker image by digest
id: build id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
with: with:
context: . context: .
file: "docker/Dockerfile" file: "docker/Dockerfile"
@@ -74,7 +74,6 @@ jobs:
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }} CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
TARGET_CPU= TARGET_CPU=
RUST_PROFILE=release RUST_PROFILE=release
CARGO_FEATURES=default
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
labels: ${{ steps.prepare.outputs.metadata_labels }} labels: ${{ steps.prepare.outputs.metadata_labels }}
annotations: ${{ steps.prepare.outputs.metadata_annotations }} annotations: ${{ steps.prepare.outputs.metadata_annotations }}
@@ -101,7 +100,7 @@ jobs:
needs: build-release needs: build-release
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
- name: Create multi-platform manifest - name: Create multi-platform manifest
@@ -134,7 +133,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
- name: Prepare max-perf Docker build environment - name: Prepare max-perf Docker build environment
@@ -150,7 +149,7 @@ jobs:
registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }} registry_password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Build and push max-perf Docker image by digest - name: Build and push max-perf Docker image by digest
id: build id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7
with: with:
context: . context: .
file: "docker/Dockerfile" file: "docker/Dockerfile"
@@ -162,7 +161,6 @@ jobs:
CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }} CARGO_INCREMENTAL=${{ env.BUILDKIT_ENDPOINT != '' && '1' || '0' }}
TARGET_CPU=${{ matrix.target_cpu }} TARGET_CPU=${{ matrix.target_cpu }}
RUST_PROFILE=release-max-perf RUST_PROFILE=release-max-perf
CARGO_FEATURES=default,release_max_log_level
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
labels: ${{ steps.prepare.outputs.metadata_labels }} labels: ${{ steps.prepare.outputs.metadata_labels }}
annotations: ${{ steps.prepare.outputs.metadata_annotations }} annotations: ${{ steps.prepare.outputs.metadata_annotations }}
@@ -189,7 +187,7 @@ jobs:
needs: build-maxperf needs: build-maxperf
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: false persist-credentials: false
- name: Create max-perf manifest - name: Create max-perf manifest
@@ -218,7 +216,7 @@ jobs:
path: binaries path: binaries
merge-multiple: true merge-multiple: true
- name: Create Release and Upload - name: Create Release and Upload
uses: https://github.com/softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3 uses: https://github.com/softprops/action-gh-release@v2
with: with:
draft: true draft: true
files: binaries/* files: binaries/*
+7 -7
View File
@@ -43,11 +43,11 @@ jobs:
name: Renovate name: Renovate
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/renovatebot/renovate:43.260.2@sha256:60d830108d9ff6408d11a55d5f110ee0976bed78a6b82b08211e3ddb72f72952 image: ghcr.io/renovatebot/renovate:43.140.0@sha256:61303c28b10a491c559529fb6f41745850e4755a43a54c04c3ae6848d6eaf5cc
options: --tmpfs /tmp:exec options: --tmpfs /tmp:exec
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
show-progress: false show-progress: false
@@ -55,7 +55,7 @@ jobs:
run: /usr/local/renovate/node -e 'console.log(`node heap limit = ${require("v8").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)' run: /usr/local/renovate/node -e 'console.log(`node heap limit = ${require("v8").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'
- name: Restore renovate repo cache - name: Restore renovate repo cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
/tmp/renovate/cache/renovate/repository /tmp/renovate/cache/renovate/repository
@@ -64,7 +64,7 @@ jobs:
renovate-repo-cache- renovate-repo-cache-
- name: Restore renovate package cache - name: Restore renovate package cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
/tmp/renovate/cache/renovate/renovate-cache-sqlite /tmp/renovate/cache/renovate/renovate-cache-sqlite
@@ -73,7 +73,7 @@ jobs:
renovate-package-cache- renovate-package-cache-
- name: Restore renovate OSV cache - name: Restore renovate OSV cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
/tmp/osv /tmp/osv
@@ -117,7 +117,7 @@ jobs:
- name: Save renovate package cache - name: Save renovate package cache
if: always() if: always()
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
/tmp/renovate/cache/renovate/renovate-cache-sqlite /tmp/renovate/cache/renovate/renovate-cache-sqlite
@@ -125,7 +125,7 @@ jobs:
- name: Save renovate OSV cache - name: Save renovate OSV cache
if: always() if: always()
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with: with:
path: | path: |
/tmp/osv /tmp/osv
+3 -3
View File
@@ -14,7 +14,7 @@ jobs:
update-flake-hashes: update-flake-hashes:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
persist-credentials: true persist-credentials: true
token: ${{ secrets.FORGEJO_TOKEN }} token: ${{ secrets.FORGEJO_TOKEN }}
@@ -27,7 +27,7 @@ jobs:
- name: Get new toolchain hash - name: Get new toolchain hash
run: | run: |
# Set the current sha256 to an empty hash to make `nix build` calculate a new one # Set the current sha256 to an empty hash to make `nix build` calculate a new one
awk '/fromToolchainName *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/rust.nix > temp.nix awk '/fromToolchainFile *\{/{found=1; print; next} found && /sha256 =/{sub(/sha256 = .*/, "sha256 = lib.fakeSha256;"); found=0} 1' nix/rust.nix > temp.nix
mv temp.nix nix/rust.nix mv temp.nix nix/rust.nix
# Build continuwuity and filter for the new hash # Build continuwuity and filter for the new hash
@@ -39,7 +39,7 @@ jobs:
sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/rust.nix sed -i "s|lib.fakeSha256|\"$new_hash\"|" nix/rust.nix
echo "New hash:" echo "New hash:"
awk -F'"' '/fromToolchainName/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/rust.nix awk -F'"' '/fromToolchainFile/{found=1; next} found && /sha256 =/{print $2; found=0}' nix/rust.nix
echo "Expected new hash:" echo "Expected new hash:"
cat new_toolchain_hash.txt cat new_toolchain_hash.txt
+1 -1
View File
@@ -1,4 +1,4 @@
github: [JadedBlueEyes, timedoutuk, gingershaped] github: [JadedBlueEyes, nexy7574, gingershaped]
custom: custom:
- https://timedout.uk/donate.html - https://timedout.uk/donate.html
- https://jade.ellis.link/sponsors - https://jade.ellis.link/sponsors
+1 -13
View File
@@ -24,7 +24,7 @@ repos:
- id: check-added-large-files - id: check-added-large-files
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.48.0 rev: v1.46.1
hooks: hooks:
- id: typos - id: typos
- id: typos - id: typos
@@ -57,15 +57,3 @@ repos:
types: [rust] types: [rust]
stages: stages:
- pre-push - pre-push
- repo: local
hooks:
- id: docs-build
name: docs build
entry: npm run docs:build
language: system
pass_filenames: false
files: docs/
types: [mdx]
stages:
- pre-commit
-90
View File
@@ -1,93 +1,3 @@
# Continuwuity 26.6.2 (2026-07-12)
## Bugfixes
- Fixed the server returning 500 errors if `admin_console_automatic` is enabled and no TTY is available. Contributed by @s1lv3r. (#1975)
- Fixed `global.oauth.compatibility_mode` being required, despite being ignored, when the `[global.oauth.oidc]` config section is provided.
- Fixed an issue with a migration that could cause user accounts imported from an identity provider to be marked as deactivated when the server started. If you have accounts affected by this issue, use `!admin users reset-password --convert-to-local-account` to reactivate them.
# Continuwuity 26.6.1 (2026-07-12)
## Features
- Added enforcement for new federated invite checks and corrected a bunch of related spec compliance issues along the way. Contributed by @nex. (#1952)
## Bugfixes
- Fixed existing accounts failing to link when logging in with OIDC if `prompt_for_localpart` was `false`. (#1942)
- Authentication is no longer required on the `/_matrix/client/v3/account/3pid/email/requestToken` endpoint. (#1953)
- Fixed newly created rooms failing to sync properly in clients using legacy sync.
- Stopped appservice users from being erroneously marked as deactivated during a 26.6 database migration.
- Whitespace will now automatically be trimmed from the start and end of the `global.oauth.oidc.client_secret_file`.
# Continuwuity 26.6.0 (2026-07-10)
## Features
- Added support for linking an external identity provider with OIDC. Contributed by @ginger. (#765)
- Updated [MSC4284: Policy Servers](https://github.com/matrix-org/matrix-spec-proposals/pull/4284) implementation to support the newly stabilised proposal. Contributed by @nex. (#1487)
- Added config option for default room ACLs. Contributed by @eve. (#1691)
- Added support for fallback encryption keys. (#1710)
- Add `!admin users reject-all-invites` to clean invite spam (#1741)
- Implemented event rejection, which should resolve and prevent future netsplits of the kinds observed
within some Continuwuity rooms.
Also resolved several bugs related to both soft-failing events, and event backfilling, which should
improve state resolution stability.
The `!admin debug get-pdu` command was updated to disambiguate event acceptance status, and
`!admin debug show-auth-chain` was added to visually display event auth chains, which may assist
developers in debugging strangely complex events.
Contributed by @nex. (#1747)
- Added full support for [MSC4168: Update `m.space.*` state on room upgrade](https://github.com/matrix-org/matrix-spec-proposals/pull/4168). Contributed by @nex. (#1807)
- Improved the performance and reliability of fetching missing events, improving network partition recovery. Contributed
by @nex. (#1818)
- Added static builds using Nix, allowing for Continuwuity on musl. During this, we also introduced a `max-perf-haswell` package, separating it from `max-perf`, so you may want to swap to this if you are on NixOS. Contributed by @Henry-Hiles (QuadRadical). (#1853)
- Added support for MSC4380 invite blocking, which has become part of the Matrix specification in v1.18. Contributed by @nex. (#1875)
- Added `!admin debug get-state-at` command (#1877)
- Added a configuration option to allow choosing a client IP source that is not the TCP connecting IP. Contributed by @nex. (#1931)
- Added support for MSC4466, which allows clients to customize how changes to a user's global profile are propagated. Contributed by @ginger.
- Added support for Matrix 1.16's `state_after` feature, allowing clients which understand it to sync room state changes more reliably. Contributed by @ginger.
- Added support for authenticating clients using the new OAuth 2.0 login API. Contributed by @ginger.
- Appservice device management as outlined in MSC4190 (part of Matrix 1.17) is now fully supported. Contributed by @ginger.
- Users may now be forbidden from deactivating their own accounts with the new `allow_deactivation` config option. Contributed by @ginger.
## Bugfixes
- Adjusted legacy sync logic to allow the `roomsynctoken_shortstatehash` database column to be dropped, massively reducing database sizes, especially for old deployments. Contributed by @ginger. (#917)
- Fixed a bug that caused the server to drop events during processing if several events for the same room were sent in a singular transaction. Contributed by @nex. (#1711)
- fix `!admin query account-data account-data-get` not returning the content (#1742)
- Fixed an issue where Continuwuity would only advertise support for the unstable endpoint for Mutual Rooms (MSC2666), despite only supporting the stable endpoint. Contributed by @Henry-Hiles (QuadRadical) (#1752)
- Fixed admin commands being ignored when they had leading whitespace before admin commands. Contributed by @kitvonsnookerz. (#1804)
- Fixed several bugs in the `POST /_matrix/client/v3/rooms/{roomId}/upgrade` endpoint. Contributed by @nex. (#1807)
- Devices which set their presence as "offline" will no longer be considered for presence updates. Contributed by @timedout.
- Improved invite and join reliability in clients using legacy sync. Contributed by @ginger
- The invite recipient's membership event is now included in invite stripped state, which should fix flaky invite display in some clients. Contributed by @ginger
## Improved Documentation
- Add performance tuning documentation. Contributed by @stratself. (#1498)
- Explain accessing Continuwuity's server console when deployed via Docker. (#1671)
- Clarified in the config that `max_request_size` affects federated media as well. (#1706)
- Added example configuration using caddy-docker-proxy in the livekit setup section of the docs. Contributed by @Cease (#1762)
- Updated deployment docs to account for new RPM package availability across more distros. Contributed by @julian45. (#1912)
## Deprecations and Removals
- Removed support for LDAP. (#1701)
- Removed support for guest user registration, a little-used and deprecated approach to room previews.
- Removed the `/_conduwuit/` versions of the `local_user_count` and `version` routes. These routes are still accessible under the `/_continuwuity` prefix.
- Support for server-side blurhashing (part of MSC2448) has been removed.
- The deprecated `well_known.rtc_focus_server_urls` config option has been removed. MatrixRTC foci should be configured using the `matrix_rtc.foci` config option.
## Misc
- #1505, #1829, #1927, #1933, #1934
- Switched from Continuwuity's fork of Ruma back to upstream Ruma. Contributed by @ginger.
- The version of Debian that the Docker-based build process uses has been upgraded from Bookworm to Trixie, meaning that standalone binaries now have a minimum glibc of 2.41, and can no longer be used on distro versions from before 2025-01-30
# Continuwuity 0.5.8 (2026-04-24) # Continuwuity 0.5.8 (2026-04-24)
## Features ## Features
+5 -5
View File
@@ -26,7 +26,7 @@ ### Pre-commit Checks
```bash ```bash
# Install prek using cargo-binstall https://github.com/cargo-bins/cargo-binstall # Install prek using cargo-binstall
cargo binstall prek cargo binstall prek
# Install git hooks to run checks automatically # Install git hooks to run checks automatically
@@ -137,9 +137,9 @@ ### Commit Messages
Examples: Examples:
``` ```
feat: Add user authentication feat: add user authentication
fix(database): Resolve connection pooling issue fix(database): resolve connection pooling issue
docs: Update installation instructions 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. The project uses the `committed` hook to validate commit messages in pre-commit. This ensures all commits follow the conventional format.
@@ -155,7 +155,7 @@ ### Creating pull requests
Before submitting a pull request, please ensure: Before submitting a pull request, please ensure:
1. Your code passes all CI checks (formatting, linting, typo detection, etc.) 1. Your code passes all CI checks (formatting, linting, typo detection, etc.)
2. Your code follows the [code style guide](docs/development/code_style.mdx) 2. Your code follows the [code style guide](/development/code_style.md)
3. Your commit messages follow the conventional commits format 3. Your commit messages follow the conventional commits format
4. Tests are added for new functionality 4. Tests are added for new functionality
5. Documentation is updated if needed 5. Documentation is updated if needed
Generated
+763 -1208
View File
File diff suppressed because it is too large Load Diff
+17 -27
View File
@@ -12,7 +12,7 @@ license = "Apache-2.0"
# See also `rust-toolchain.toml` # See also `rust-toolchain.toml`
readme = "README.md" readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity" repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
version = "26.6.2" version = "0.5.9"
[workspace.metadata.crane] [workspace.metadata.crane]
name = "conduwuit" name = "conduwuit"
@@ -39,13 +39,13 @@ features = ["ffi", "std", "union"]
version = "1.1.0" version = "1.1.0"
[workspace.dependencies.ctor] [workspace.dependencies.ctor]
version = "1.0.6" version = "0.13.0"
[workspace.dependencies.dtor] [workspace.dependencies.dtor]
version = "1.0.0" version = "0.13.0"
[workspace.dependencies.cargo_toml] [workspace.dependencies.cargo_toml]
version = "1.0" version = "0.22"
default-features = false default-features = false
features = ["features"] features = ["features"]
@@ -124,7 +124,7 @@ default-features = false
features = ["util"] features = ["util"]
[workspace.dependencies.tower-http] [workspace.dependencies.tower-http]
version = "0.7.0" version = "0.6.8"
default-features = false default-features = false
features = [ features = [
"add-extension", "add-extension",
@@ -164,7 +164,7 @@ features = ["raw_value"]
# Used for appservice registration files # Used for appservice registration files
[workspace.dependencies.serde-saphyr] [workspace.dependencies.serde-saphyr]
version = "0.0.29" version = "0.0.25"
# Used to load forbidden room/user regex from config # Used to load forbidden room/user regex from config
[workspace.dependencies.serde_regex] [workspace.dependencies.serde_regex]
@@ -296,7 +296,7 @@ default-features = false
features = ["env", "toml"] features = ["env", "toml"]
[workspace.dependencies.hickory-resolver] [workspace.dependencies.hickory-resolver]
version = "0.26.0" version = "0.25.2"
default-features = false default-features = false
features = [ features = [
"serde", "serde",
@@ -316,7 +316,7 @@ default-features = false
# Used to make working with iterators easier, was already a transitive depdendency # Used to make working with iterators easier, was already a transitive depdendency
[workspace.dependencies.itertools] [workspace.dependencies.itertools]
version = "0.15.0" version = "0.14.0"
# to parse user-friendly time durations in admin commands # to parse user-friendly time durations in admin commands
#TODO: overlaps chrono? #TODO: overlaps chrono?
@@ -344,7 +344,7 @@ version = "1.1.1"
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
# version = "0.14.1" # version = "0.14.1"
git = "https://github.com/ruma/ruma.git" git = "https://github.com/ruma/ruma.git"
rev = "9b6a2e7323649af926e4b5363d87239ee4247f4a" rev = "9c9dccc93f054bbd28f23f630223fffa6289ecbc"
features = [ features = [
"appservice-api-c", "appservice-api-c",
"client-api", "client-api",
@@ -356,7 +356,6 @@ features = [
"ring-compat", "ring-compat",
"compat-upload-signatures", "compat-upload-signatures",
"compat-optional-txn-pdus", "compat-optional-txn-pdus",
"compat-get-3pids",
"unstable-msc2666", "unstable-msc2666",
"unstable-msc2867", "unstable-msc2867",
"unstable-msc2870", "unstable-msc2870",
@@ -374,18 +373,18 @@ features = [
"unstable-msc4195", "unstable-msc4195",
"unstable-msc4203", "unstable-msc4203",
"unstable-msc4310", "unstable-msc4310",
"unstable-msc4373",
"unstable-msc4380", "unstable-msc4380",
"unstable-msc4143", "unstable-msc4143",
"unstable-msc4293", "unstable-msc4293",
"unstable-msc4406", "unstable-msc4406",
"unstable-msc4439", "unstable-msc4439",
"unstable-msc4466",
"unstable-extensible-events", "unstable-extensible-events",
] ]
[workspace.dependencies.rust-rocksdb] [workspace.dependencies.rust-rocksdb]
git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1" git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
rev = "0a25ff92f7c09b55eec496b9c192c7d5136ab2b8" rev = "31fb8f772c7afcdc0061ab6a40cfa3a1be2fccd9"
default-features = false default-features = false
features = [ features = [
"multi-threaded-cf", "multi-threaded-cf",
@@ -403,25 +402,22 @@ default-features = false
version = "0.11.0" version = "0.11.0"
default-features = false default-features = false
[workspace.dependencies.openidconnect]
version = "4.0.1"
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring # optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
[workspace.dependencies.opentelemetry] [workspace.dependencies.opentelemetry]
version = "0.32.0" version = "0.31.0"
[workspace.dependencies.tracing-flame] [workspace.dependencies.tracing-flame]
version = "0.2.0" version = "0.2.0"
[workspace.dependencies.tracing-opentelemetry] [workspace.dependencies.tracing-opentelemetry]
version = "0.33.0" version = "0.32.0"
[workspace.dependencies.opentelemetry_sdk] [workspace.dependencies.opentelemetry_sdk]
version = "0.32.0" version = "0.31.0"
features = ["rt-tokio"] features = ["rt-tokio"]
[workspace.dependencies.opentelemetry-otlp] [workspace.dependencies.opentelemetry-otlp]
version = "0.32.0" version = "0.31.0"
features = ["http", "grpc-tonic", "trace", "logs", "metrics"] features = ["http", "grpc-tonic", "trace", "logs", "metrics"]
@@ -496,7 +492,7 @@ version = "0.4.9"
default-features = false default-features = false
[workspace.dependencies.termimad] [workspace.dependencies.termimad]
version = "0.35.0" version = "0.34.0"
default-features = false default-features = false
[workspace.dependencies.checked_ops] [workspace.dependencies.checked_ops]
@@ -538,7 +534,7 @@ version = "2.1.1"
features = ["std"] features = ["std"]
[workspace.dependencies.minicbor-serde] [workspace.dependencies.minicbor-serde]
version = "0.7.0" version = "0.6.0"
features = ["std"] features = ["std"]
[workspace.dependencies.maplit] [workspace.dependencies.maplit]
@@ -563,12 +559,6 @@ features = ["std"]
[workspace.dependencies.nonzero_ext] [workspace.dependencies.nonzero_ext]
version = "0.3.0" version = "0.3.0"
[workspace.dependencies.resolvematrix]
version = "1.2.0"
[workspace.dependencies.serde_urlencoded]
version = "0.7.1"
# #
# Patches # Patches
# #
-1
View File
@@ -23,7 +23,6 @@ ### Responsible Disclosure
1. **Contact members of the team directly** over E2EE private message. 1. **Contact members of the team directly** over E2EE private message.
- [@jade:ellis.link](https://matrix.to/#/@jade:ellis.link) - [@jade:ellis.link](https://matrix.to/#/@jade:ellis.link)
- [@nex:nexy7574.co.uk](https://matrix.to/#/@nex:nexy7574.co.uk) - [@nex:nexy7574.co.uk](https://matrix.to/#/@nex:nexy7574.co.uk)
- [@ginger:gingershaped.computer](https://matrix.to/#/@ginger:gingershaped.computer)
2. **Email the security team** at [security@continuwuity.org](mailto:security@continuwuity.org). This is not E2EE, so don't include sensitive details. 2. **Email the security team** at [security@continuwuity.org](mailto:security@continuwuity.org). This is not E2EE, so don't include sensitive details.
3. **Do not disclose the vulnerability publicly** until it has been addressed 3. **Do not disclose the vulnerability publicly** until it has been addressed
4. **Provide detailed information** about the vulnerability, including: 4. **Provide detailed information** about the vulnerability, including:
-1
View File
@@ -1 +0,0 @@
Appservices are now properly able to create devices for E2EE.
+1
View File
@@ -0,0 +1 @@
The invite recipient's membership event is now included in invite stripped state, which should fix flaky invite display in some clients. Contributed by @ginger
+1
View File
@@ -0,0 +1 @@
Switched from Continuwuity's fork of Ruma back to upstream Ruma. Contributed by @ginger.
-1
View File
@@ -1 +0,0 @@
Fixed newly created rooms failing to sync properly in clients using legacy sync.
+1
View File
@@ -0,0 +1 @@
Removed support for guest user registration, a little-used and deprecated approach to room previews.
+1
View File
@@ -0,0 +1 @@
The deprecated `well_known.rtc_focus_server_urls` config option has been removed. MatrixRTC foci should be configured using the `matrix_rtc.foci` config option.
-1
View File
@@ -1 +0,0 @@
Appservices may now specify both the unstable and stable `device_id` query parameters in a request. The stable parameter will take priority. Contributed by @ginger.
+1
View File
@@ -0,0 +1 @@
Support for server-side blurhashing (part of MSC2448) has been removed.
+1
View File
@@ -0,0 +1 @@
Add performance tuning documentation. Contributed by @stratself.
+1
View File
@@ -0,0 +1 @@
Explain accessing Continuwuity's server console when deployed via Docker.
+1
View File
@@ -0,0 +1 @@
Removed support for LDAP.
+1
View File
@@ -0,0 +1 @@
Clarified in the config that `max_request_size` affects federated media as well.
+1
View File
@@ -0,0 +1 @@
Added support for fallback encryption keys.
+1
View File
@@ -0,0 +1 @@
Fixed a bug that caused the server to drop events during processing if several events for the same room were sent in a singular transaction. Contributed by @nex.
+1
View File
@@ -0,0 +1 @@
Add `!admin users reject-all-invites` to clean invite spam
+1
View File
@@ -0,0 +1 @@
fix `!admin query account-data account-data-get` not returning the content
+1
View File
@@ -0,0 +1 @@
Fixed an issue where Continuwuity would only advertise support for the unstable endpoint for Mutual Rooms (MSC2666), despite only supporting the stable endpoint. Contributed by @Henry-Hiles (QuadRadical)
-1
View File
@@ -1 +0,0 @@
Fixed the deeplink redirect for deleting devices. Contributed by @koen
-1
View File
@@ -1 +0,0 @@
Fix status code for oauth registration. Contributed by @n00byking
-1
View File
@@ -1 +0,0 @@
Exempt m.room.create from auth_events check. Contributed by @eleboucher
+34 -205
View File
@@ -290,27 +290,6 @@
# #
#ip_lookup_strategy = 5 #ip_lookup_strategy = 5
# The source to use for discovering the real connecting client IP.
#
# Takes any of the following options:
#
# "cf_connecting_ip" - `Cf-Connecting-Ip` header
# "cloudfront_viewer_address" - `CloudFront-Viewer-Address` header
# "fly_client_ip" - `Fly-Client-IP` header
# "x_forwarded_for" - rightmost value of the `X-Forwarded-For` header
# "true_client_ip" - `True-Client-Ip` header
# "x_envoy_external_address" - `X-Envoy-External-Address` header
# "x_real_ip" - `X-Real-Ip` header
#
# Only set this if you are certain only your reverse proxy
# will send the expected header. There is no "is the connecting IP allowed
# to set this header" check; if the header selected is present, it is
# used.
#
# Defaults to the IP address actually making the connection.
#
#request_ip_source = false
# Max request size for file uploads in bytes. Defaults to 20MB. # Max request size for file uploads in bytes. Defaults to 20MB.
# Also limits incoming federated media. # Also limits incoming federated media.
# #
@@ -318,7 +297,7 @@
# This item is undocumented. Please contribute documentation for it. # This item is undocumented. Please contribute documentation for it.
# #
#max_fetch_prev_events = 1024 #max_fetch_prev_events = 192
# How many incoming federation transactions the server is willing to be # How many incoming federation transactions the server is willing to be
# processing at any given time before it becomes overloaded and starts # processing at any given time before it becomes overloaded and starts
@@ -393,18 +372,21 @@
# #
#federation_timeout = 60 #federation_timeout = 60
# Policy server request timeout (seconds). Generally policy # MSC4284 Policy server request timeout (seconds). Generally policy
# servers should respond near instantly, however may slow down under # servers should respond near instantly, however may slow down under
# load. If a policy server doesn't respond in a short amount of time, the # load. If a policy server doesn't respond in a short amount of time, the
# room it is configured in may become unusable if this limit is set too # room it is configured in may become unusable if this limit is set too
# high. 30 seconds is a good default, however lower values may be # high. 10 seconds is a good default, however dropping this to 3-5 seconds
# acceptable if temporary send failures are an okay trade-off. # can be acceptable.
# #
# Please be aware that policy requests are *NOT* currently re-tried, so if
# a spam check request fails, the event will be assumed to be not spam,
# which in some cases may result in spam being sent to or received from
# the room that would typically be prevented.
# #
# About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/ # About policy servers: https://matrix.org/blog/2025/04/introducing-policy-servers/
# (Stabilized in Matrix v1.18)
# #
#policy_server_request_timeout = 30 #policy_server_request_timeout = 10
# Federation client idle connection pool timeout (seconds). # Federation client idle connection pool timeout (seconds).
# #
@@ -542,15 +524,17 @@
# #
#recaptcha_private_site_key = #recaptcha_private_site_key =
# Controls whether users are allowed to deactivate their own accounts # Policy documents, such as terms and conditions or a privacy policy,
# through the account management panel or their Matrix clients. Server # which users must agree to when registering an account.
# admins can always deactivate users using the relevant admin commands.
# #
# Note that, in some jurisdictions, you may be legally required to honor # Example:
# users who request to deactivate their accounts if you set this option # ```ignore
# to `false`. # [global.registration_terms.privacy_policy]
# en = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
# es = { name = "Política de Privacidad", url = "https://homeserver.example/es/privacy_policy.html" }
# ```
# #
#allow_deactivation = true #registration_terms = {}
# Controls whether encrypted rooms and events are allowed. # Controls whether encrypted rooms and events are allowed.
# #
@@ -640,38 +624,6 @@
# #
#default_room_version = "12" #default_room_version = "12"
# A default allow value for the Access Control List when creating a room.
#
# If a list is provided, new rooms will be created with
# a m.room.server_acl event. Only servers which match one of the patterns
# in the list will be permitted to participate in the room.
#
# ACLs in existing rooms will not be updated automatically. This is not
# a substitute for moderation bots.
#
#default_room_acl_allow =
# A default deny value for the Access Control List when creating a room.
#
# If a list is provided, new rooms will be created with
# a m.room.server_acl event. Servers which match one of the patterns
# in the list will be NOT permitted to participate in the room.
#
# This config cannot be used if the default_room_acl_allow config is used.
#
# ACLs in existing rooms will not be updated automatically. This is not
# a substitute for moderation bots.
#
#default_room_acl_deny =
# The number of forward extremities to tolerate in a room before
# attempting to manually squash them with a "dummy event". Setting this
# above 20 will hinder its efficacy, and setting it below 5 will cause
# more dummy events to be sent than necessary (which increases federation
# traffic).
#
#dummy_event_threshold = 10
# Enable OpenTelemetry OTLP tracing export. This replaces the deprecated # Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
# Jaeger exporter. Traces will be sent via OTLP to a collector (such as # Jaeger exporter. Traces will be sent via OTLP to a collector (such as
# Jaeger) that supports the OpenTelemetry Protocol. # Jaeger) that supports the OpenTelemetry Protocol.
@@ -1455,11 +1407,6 @@
# #
#send_messages_from_ignored_users_to_client = false #send_messages_from_ignored_users_to_client = false
# Send "org.matrix.dummy_event" events to the client. This is a debugging
# option.
#
#send_dummy_events_to_clients = false
# Vector list of IPv4 and IPv6 CIDR ranges / subnets *in quotes* that you # Vector list of IPv4 and IPv6 CIDR ranges / subnets *in quotes* that you
# do not want continuwuity to send outbound requests to. Defaults to # do not want continuwuity to send outbound requests to. Defaults to
# RFC1918, unroutable, loopback, multicast, and testnet addresses for # RFC1918, unroutable, loopback, multicast, and testnet addresses for
@@ -1623,6 +1570,19 @@
# #
#block_non_admin_invites = false #block_non_admin_invites = false
# Enable or disable making requests to MSC4284 Policy Servers.
# It is recommended you keep this enabled unless you experience frequent
# connectivity issues, such as in a restricted networking environment.
#
#enable_msc4284_policy_servers = true
# Enable running locally generated events through configured MSC4284
# policy servers. You may wish to disable this if your server is
# single-user for a slight speed benefit in some rooms, but otherwise
# should leave it enabled.
#
#policy_server_check_own_events = true
# Allow admins to enter commands in rooms other than "#admins" (admin # Allow admins to enter commands in rooms other than "#admins" (admin
# room) by prefixing your message with "\!admin" or "\\!admin" followed up # room) by prefixing your message with "\!admin" or "\\!admin" followed up
# a normal continuwuity admin command. The reply will be publicly visible # a normal continuwuity admin command. The reply will be publicly visible
@@ -1889,11 +1849,6 @@
# #
#support_page = #support_page =
# The ed25519 public key for the policy server available at this server's
# name. Must be unpadded base64.
#
#policy_server_public_key =
# Role string for server support contacts, to be served as part of the # Role string for server support contacts, to be served as part of the
# MSC1929 server support endpoint at /.well-known/matrix/support. # MSC1929 server support endpoint at /.well-known/matrix/support.
# #
@@ -2004,10 +1959,8 @@
# #
#sender = #sender =
# Whether to allow public registration with an email address. # Whether to require that users provide an email address when they
# # register.
# Note that, if this option is enabled, anyone will be able to register an
# account with just an email address.
# #
# If either this option or `require_email_for_token_registration` are set, # If either this option or `require_email_for_token_registration` are set,
# users will not be allowed to remove their email address. # users will not be allowed to remove their email address.
@@ -2015,130 +1968,6 @@
#require_email_for_registration = false #require_email_for_registration = false
# Whether to require that users who register with a registration token # Whether to require that users who register with a registration token
# provide an email address. This option is independent of # provide an email address.
# `require_email_for_registration`.
# #
#require_email_for_token_registration = false #require_email_for_token_registration = false
#[global.registration_terms]
# The language code to provide to clients along with the policy documents.
#
#language = "en"
# Policy documents, such as terms and conditions or a privacy policy,
# which users must agree to when registering an account.
#
# Example:
# ```ignore
# [global.registration_terms.documents]
# privacy_policy = { name = "Privacy Policy", url = "https://homeserver.example/en/privacy_policy.html" }
# ```
#
#documents =
#[global.oauth]
# The compatibility mode to use for OAuth.
#
# - "disabled": OAuth will be unavailable. Users will only be able to log
# in using legacy authentication.
# - "hybrid": OAuth and legacy authentication will both be available. Some
# clients may only use one or the other.
# - "exclusive": Only OAuth will be available. Clients which require
# legacy authentication will be unable to log in.
#
#compatibility_mode = "hybrid"
#[global.oauth.oidc]
# Uncommenting this section will enable Continuwuity's support for
# authenticating users using an OpenID Connect-compatible identity provider.
# This is referred to as "delegated authentication".
#
# IMPORTANT NOTE: When delegated authentication is active, Continuwuity will behave as if
# the `global.oauth.compatibility_mode` setting is set to `exclusive`.
# Matrix clients which do not support OAuth login (also referred to as "next-gen auth") will NOT be able
# to log in while delegated authentication is active.
# The OIDC issuer URL. Continuwuity will use OpenID Connect Discovery to
# automatically fetch the identity provider's metadata from this URL.
# Generally you should set this to the base domain your identity provider
# runs on.
#
#discovery_url =
# The OAuth client ID for Continuwuity to use when communicating with the
# identity provider.
#
#client_id =
# The OAuth client secret for Continuwuity to use when communicating with
# the identity provider.
#
#client_secret =
# A path to a file which Continuwuity will read the client secret from.
# If this option is set, it will override `client_secret`.
#
# The server will fail to start if the file cannot be read.
#
#client_secret_file =
# Additional scopes Continuwuity should request from the IDP. This may be
# necessary to access certain claims. Continuwuity always requests the
# `openid` scope.
#
#additional_scopes = []
# Whether the user should be prompted to choose a localpart
# when signing in for the first time. If this is `false`, Continuwuity
# will attempt to use the value of the `preferred_username_claim`
# (see below) as the user's localpart. Authentication will
# fail if this claim is missing or is not a valid localpart.
#
#prompt_for_localpart = true
# The claim to use for the user's localpart, if `prompt_for_localpart` is
# false.
#
#preferred_username_claim = "preferred_username"
# The claim which will be used to set the user's email address,
# either on initial registration or on every login depending on
# the value of `profile_key_import_mode`. Continuwuity assumes that
# the IDP has taken care of verifying that the user controls the email
# address it provides.
#
# This option does nothing if SMTP is not configured.
#
# If this option is set, and `profile_key_import_mode` is `on_login`,
# users will not be able to change their email addresses themselves.
#
#email_claim = "email"
# Defines how claims returned from the IDP should be mapped to a user's
# profile data. The profile field named in each key will be set from the
# claim named in the corresponding value when the user first registers,
# and possibly on subsequent logins as well, depending on the value of
# `profile_key_import_mode` (see below).
#
# Per-room overrides to the user's display name or avatar will be
# preserved by the import process.
#
# SECURITY NOTE: If the `avatar_url` field is set, Continuwuity will
# perform a HTTP GET to the URL in the mapped claim and use the returned
# file as the user's profile picture. Make sure your users are not able
# to set the value of the mapped claim to an arbitrary URL.
#
#profile_key_map = { displayname = "name" }
# When profile keys should be imported from the IDP's claims.
#
# - "on_registration": Listed keys will be imported once, when the user
# logs in for the first time and their shadow account is created.
# - "on_login": Listed keys will be imported every time the user logs in.
# Additionally, users will not be able to manually edit any listed keys
# through their Matrix client.
#
#profile_key_import_mode = "on_registration"
+5 -7
View File
@@ -1,5 +1,5 @@
ARG RUST_VERSION=1 ARG RUST_VERSION=1
ARG DEBIAN_VERSION=trixie 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-${DEBIAN_VERSION} AS base
@@ -10,7 +10,7 @@ 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=22 ARG LLVM_VERSION=21
# ENV RUSTUP_TOOLCHAIN=${RUST_VERSION} # ENV RUSTUP_TOOLCHAIN=${RUST_VERSION}
# Install repo tools # Install repo tools
@@ -22,7 +22,7 @@ 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 \ pkg-config make jq \
wget curl git lsb-release gpg \ wget curl git software-properties-common \
file file
# golang cmake # golang cmake
@@ -50,7 +50,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.20.1 ENV BINSTALL_VERSION=1.19.1
# 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
@@ -171,7 +171,6 @@ 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=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \ --mount=type=cache,target=/app/target,id=continuwuity-cargo-target-${TARGET_CPU}-${TARGETPLATFORM}-${RUST_PROFILE} \
bash <<'EOF' bash <<'EOF'
set -euo pipefail
set -o allexport set -o allexport
set -o xtrace set -o xtrace
. /etc/environment . /etc/environment
@@ -192,7 +191,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
CARGO_LOG=debug xx-cargo build --locked --profile ${RUST_PROFILE} \ xx-cargo build --locked --profile ${RUST_PROFILE} \
--no-default-features --features ${CARGO_FEATURES} \ --no-default-features --features ${CARGO_FEATURES} \
-p $PACKAGE; -p $PACKAGE;
BINARIES=($(cargo metadata --no-deps --format-version 1 | \ BINARIES=($(cargo metadata --no-deps --format-version 1 | \
@@ -208,7 +207,6 @@ EOF
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 \
bash <<'EOF' bash <<'EOF'
set -euo pipefail
set -o xtrace set -o xtrace
mkdir /out/sbom mkdir /out/sbom
typeset -A PACKAGES typeset -A PACKAGES
+1 -1
View File
@@ -18,7 +18,7 @@ RUN --mount=type=cache,target=/etc/apk/cache apk add \
# 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.20.1 ENV BINSTALL_VERSION=1.19.1
# 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
+2 -2
View File
@@ -36,8 +36,8 @@
}, },
{ {
"type": "dir", "type": "dir",
"name": "guides", "name": "advanced",
"label": "Guides" "label": "Advanced"
}, },
"security", "security",
{ {
+2 -2
View File
@@ -1,8 +1,8 @@
[ [
{ {
"text": "Documentation", "text": "Guide",
"link": "/introduction", "link": "/introduction",
"activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting|guides)" "activeMatch": "^/(introduction|configuration|deploying|calls|appservices|maintenance|troubleshooting|advanced)"
}, },
{ {
"text": "Development", "text": "Development",
@@ -13,10 +13,6 @@
"type": "file", "type": "file",
"name": "performance", "name": "performance",
"label": "Performance tuning" "label": "Performance tuning"
},
{
"type": "file",
"name": "oidc",
"label": "Delegated authentication"
} }
] ]
@@ -124,11 +124,7 @@ ## Reverse proxying well-known files to Continuwuity
### Serving well-known files manually ### Serving well-known files manually
Instead of reverse proxying well-known URIs, you can serve these files directly as static JSON that match the ones above. This is useful if your base domain points to a different physical server, and reverse proxying isn't feasible. Instead of configuring `[global.well_known]` options and reverse proxying well-known URIs, you can serve these files directly as static JSON that match the ones above. This is useful if your base domain points to a different physical server, and reverse proxying isn't feasible.
:::warning
Even if you choose to serve the well-known files manually, if you are using delegation at all, you **must** still set the `global.well_known.client` configuration option to the domain you're delegating to. Continuwuity needs to know the domain it runs on for OAuth-compatible clients to work correctly.
:::
<details> <details>
@@ -156,17 +152,6 @@ ### Serving well-known files manually
</details> </details>
<details>
<summary>Accompanying Continuwuity configuration</summary>
```toml
[global.well_known]
client = "https://matrix.example.com/"
```
</details>
Remember to set the `Access-Control-Allow-Origin: *` header in your `/.well-known/matrix/client` path for web clients to work. Remember to set the `Access-Control-Allow-Origin: *` header in your `/.well-known/matrix/client` path for web clients to work.
## Troubleshooting ## Troubleshooting
+9 -13
View File
@@ -74,11 +74,13 @@ ## Unbound
- Increase `discard-timeout` to something like `4800` to wait longer for upstream resolvers, as recursion can take a long time to respond to some domains. Continuwuity default to `dns_timeout = 10` seconds, so dropping requests early would lead to unnecessary retries and/or failures. - Increase `discard-timeout` to something like `4800` to wait longer for upstream resolvers, as recursion can take a long time to respond to some domains. Continuwuity default to `dns_timeout = 10` seconds, so dropping requests early would lead to unnecessary retries and/or failures.
### Recursion versus forwarding ### Using a forwarder (optional)
Unbound by default employs **recursive resolution** and contacts many servers around the world. While this allows updated and authoritative answers and are generally viable for most users, sometimes these recursive queries can be too slow to fully resolve. As an alternative, you can consider **forwarding** your queries to public resolvers, and benefit from faster responses from their CDNs. Unbound by default employs **recursive resolution** and contacts many servers around the world. If this is not performant enough, consider forwarding your queries to public resolvers to benefit from their CDNs and get faster responses.
Do note that most popular upstreams (such as Google DNS or Quad9) employ IP ratelimiting, so a generous cache is still needed to avoid making too many queries. However, most popular upstreams (such as Google DNS or Quad9) employ IP ratelimiting, so a generous cache is still needed to avoid making too many queries.
DNS-over-TLS forwarders may also be used should you need on-the-wire encryption, but TLS overhead causes some speed penalties.
If you want to use forwarders, configure it as follows: If you want to use forwarders, configure it as follows:
@@ -97,8 +99,6 @@ # Use cloudflare public resolvers as an example
# forward-addr: 2606:4700:4700::1111@53 # forward-addr: 2606:4700:4700::1111@53
# alternatively, use DNS-over-TLS for forwarders. # alternatively, use DNS-over-TLS for forwarders.
# this will encrypt traffic between you and the forwarder,
# but takes more time due to TLS overhead.
# forward-zone: # forward-zone:
# name: "." # name: "."
# forward-tls-upstream: yes # forward-tls-upstream: yes
@@ -133,11 +133,9 @@ ### dnsmasq
[arch-linux-dnsmasq]: https://wiki.archlinux.org/title/Dnsmasq [arch-linux-dnsmasq]: https://wiki.archlinux.org/title/Dnsmasq
### Technitium DNS ### Technitium
[Technitium DNS Server][technitium] supports recursion as well as a myriad of forwarding protocols, allows saving cache to disk natively, and does work well with Continuwuity. Its out-of-the-box configs however ratelimits single-IP requests by a lot, and hence must be changed. [Technitium][technitium] supports recursion as well as a myriad of forwarding protocols, allows saving cache to disk natively, and does work well with Continuwuity. Its default configurations however ratelimits single-IP requests by a lot, and hence must be changed. You may consult this [community guide][technitium-continuwuity] for more details on setting up a dedicated Technitium for Continuwuity.
You may consult this [community guide][technitium-continuwuity] for more details on setting up and fine-tuning a dedicated Technitium instance for Continuwuity.
[technitium]: https://github.com/TechnitiumSoftware/DnsServer [technitium]: https://github.com/TechnitiumSoftware/DnsServer
[technitium-continuwuity]: https://muoi.me/~stratself/articles/technitium-continuwuity/ [technitium-continuwuity]: https://muoi.me/~stratself/articles/technitium-continuwuity/
@@ -152,13 +150,11 @@ ## Testing
## Further steps ## Further steps
It is recommended to set **`dns_cache_entries = 0`** inside Continuwuity to fully rely on the external resolver. While Continuwuity does have an internal cache, it can run into reliability issues if you're federating with many domains. - (Recommended) Set **`dns_cache_entries = 0`** inside Continuwuity and fully rely on the more performant external resolver.
Additionally, you can also make the following improvements:
- Consider employing **persistent cache to disk**, so your resolver can still run without hassle after a restart. Unbound, via [Cache DB module][unbound-cachedb], can use Redis as a storage backend for this feature. - Consider employing **persistent cache to disk**, so your resolver can still run without hassle after a restart. Unbound, via [Cache DB module][unbound-cachedb], can use Redis as a storage backend for this feature.
- Consider [enabling **Serve Stale**][unbound-serve-stale] functionality to serve expired data beyond DNS TTLs. Since most Matrix homeservers have static IPs, this should still allow federating with them when upstream resolvers have timed out. For dnsproxy, this corresponds to its [optimistic caching options][dnsproxy-usage]. - Consider [enabling **Serve Stale**][unbound-serve-stale] functionality to serve expired data beyond DNS TTLs. Since most Matrix homeservers have static IPs, this should help improve federation with them especially when upstream resolvers have timed out. For dnsproxy, this corresponds to its [optimistic caching options][dnsproxy-usage].
- If you still experience DNS performance issues, another step could be to **disable DNSSEC** (which is computationally expensive) at a cost of slightly decreased security. On Unbound this is done by commenting out `trust-anchors` config options and removing the `validator` module. - If you still experience DNS performance issues, another step could be to **disable DNSSEC** (which is computationally expensive) at a cost of slightly decreased security. On Unbound this is done by commenting out `trust-anchors` config options and removing the `validator` module.
+15 -88
View File
@@ -25,9 +25,9 @@ ### 2. Services
:::tip Generating the secrets :::tip Generating the secrets
LiveKit provides a utility to generate secure random keys LiveKit provides a utility to generate secure random keys
```bash ```bash
docker run --rm livekit/livekit-server:latest generate-keys ~$ docker run --rm livekit/livekit-server:latest generate-keys
# API Key: APIUxUnMnSkuFWV API Key: APIUxUnMnSkuFWV
# API Secret: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A API Secret: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
``` ```
::: :::
@@ -187,75 +187,6 @@ ### 4. Configure your Reverse Proxy
``` ```
</details> </details>
<details>
<summary>Example docker compose file with caddy-docker-proxy labels</summary>
```yaml
# This setup assumes all containers share the same bridge network
services:
lk-jwt-service:
image: ghcr.io/element-hq/lk-jwt-service:latest
container_name: lk-jwt-service
# lk-jwt-service environment config here..
labels:
caddy: livekit.example.com
caddy.@lk-jwt-service.path: "/sfu/get* /healthz* /get_token*"
caddy.reverse_proxy: "@lk-jwt-service {{upstreams 8081}}"
livekit:
image: livekit/livekit-server:latest
container_name: livekit
command: --config /etc/livekit.yaml
restart: unless-stopped
labels:
caddy: livekit.example.com
caddy.reverse_proxy: "{{upstreams 7880}}"
volumes:
- ./livekit.yaml:/etc/livekit.yaml:ro
ports:
- "127.0.0.1:7880:7880/tcp"
- "7881:7881/tcp"
- "50100-50200:50100-50200/udp"
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
ports:
- 80:80
- 443:443
environment:
- CADDY_INGRESS_NETWORKS=caddy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data:/data
restart: unless-stopped
labels:
# If you already configured `[global.well_known]` with Continuwuity,
# comment out the *_respond labels and add this line
# caddy.reverse_proxy: /.well-known/matrix/* homeserver:8008
caddy.1_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
caddy.2_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://livekit.example.com"}]}
# If you are having problems with continuwuity serving headers uncomment
# the header section below.
# caddy: example.com
# caddy.0_header: "*"
# caddy.0_header.Access-Control-Allow-Origin: "*"
# caddy.0_header.Access-Control-Allow-Methods: "GET, POST, OPTIONS"
# caddy.0_header.Access-Control-Allow-Headers: "Authorization"
# caddy.0_header.Content-Type: "application/json"
homeserver:
image: forgejo.ellis.link/continuwuation/continuwuity:latest
restart: unless-stopped
# add additional environment, volume, and network config here...
labels:
caddy: matrix.example.com
caddy.reverse_proxy: "{{upstreams 8008}}"
```
</details>
### 6. Start Everything ### 6. Start Everything
@@ -331,14 +262,14 @@ # livekit.yaml
## Testing ## Testing
To test that LiveKit is successfully integrated with Continuwuity, you will need to replicate its [Token Exchange Flow](https://github.com/element-hq/lk-jwt-service#%EF%B8%8F-how-it-works--token-exchange-flow). Follow the steps below while checking Docker logs (`docker-compose logs --follow`), in order to help [troubleshooting](#troubleshooting) any issues. To test that LiveKit is successfully integrated with Continuwuity, you will need to replicate its [Token Exchange Flow](https://github.com/element-hq/lk-jwt-service#%EF%B8%8F-how-it-works--token-exchange-flow).
First, you will need an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html). First, you will need an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html).
Then, using that token, fetch the discovery endpoints for MatrixRTC services: Then, using that token, fetch the discovery endpoints for MatrixRTC services
```bash ```bash
curl -H "Authorization: Bearer <session-access-token>" \ curl -X POST -H "Authorization: Bearer <session-access-token>" \
https://matrix.example.com/_matrix/client/unstable/org.matrix.msc4143/rtc/transports https://matrix.example.com/_matrix/client/unstable/org.matrix.msc4143/rtc/transports
``` ```
@@ -387,7 +318,7 @@ ## Testing
You can then send this payload to the lk-jwt-service: You can then send this payload to the lk-jwt-service:
```bash ```bash
curl -X POST -d @payload.json https://livekit.example.com/get_token ~$ curl -X POST -d @payload.json https://livekit.example.com/get_token
``` ```
The lk-jwt-service will, after checking against Continuwuity, answer with a `jwt` token to create a LiveKit media room: The lk-jwt-service will, after checking against Continuwuity, answer with a `jwt` token to create a LiveKit media room:
@@ -400,31 +331,22 @@ ## Testing
## Troubleshooting ## Troubleshooting
To debug any issues, you can place a call or redo the Testing instructions, and check the container logs for any specific errors. Use `docker-compose logs --follow` to follow these logs in real-time. To debug any issues, you can place a call or redo the Testing instructions, and check the container logs for any specific errors. Use `docker-compose logs --follow` to follow them in real-time.
### Common errors in Element Call UI ### Common errors in Element Call UI
- `MISSING_MATRIX_RTC_FOCUS`: LiveKit is missing from Continuwuity's config file - `MISSING_MATRIX_RTC_FOCUS`: LiveKit is missing from Continuwuity's config file
- "Waiting for media" popup always showing: a LiveKit URL has been configured in Continuwuity, but your client cannot connect to it for some reason - "Waiting for media" popup always showing: a LiveKit URL has been configured in Continuwuity, but your client cannot connect to it for some reason
For browser-based clients, you can also inspect connections using DevTools' Networking tab, to see which requests are erroring out.
### Docker loopback networking issues ### Docker loopback networking issues
Some distros do not allow Docker containers to connect to its host's public IP by default. This would cause `lk-jwt-service` to fail connecting to `livekit` or `continuwuity` on the same host. As a result, you would see connection refused/connection timeouts log entries in the JWT service, even when `LIVEKIT_URL` has been configured correctly. Some distros do not allow Docker containers to connect to its host's public IP by default. This would cause `lk-jwt-service` to fail connecting to `livekit` or `continuwuity` on the same host. As a result, you would see connection refused/connection timeouts log entries in the JWT service, even when `LIVEKIT_URL` has been configured correctly.
You can also test that this is the case by cURLing from a sidecar container:
```bash
docker run --rm --net container:lk-jwt-service docker.io/curlimages/curl https://livekit.example.com
# --- some errors ---
```
To alleviate this, you can try one of the following workarounds: To alleviate this, you can try one of the following workarounds:
- Use `network_mode: host` for the `lk-jwt-service` container (instead of the default bridge networking). - Use `network_mode: host` for the `lk-jwt-service` container (instead of the default bridge networking).
- Add an `extra_hosts` file mapping livekit's (and continuwuity's) domain name to a locally-reachable address: - Add an `extra_hosts` file mapping livekit's (and continuwuity's) domain name to a localhost address:
```diff ```diff
# in docker-compose.yaml # in docker-compose.yaml
@@ -438,7 +360,12 @@ # --- some errors ---
- (**untested, use at your own risk**) Implement an iptables workaround as shown [here](https://forums.docker.com/t/unable-to-connect-to-host-service-from-inside-docker-container/145749/6). - (**untested, use at your own risk**) Implement an iptables workaround as shown [here](https://forums.docker.com/t/unable-to-connect-to-host-service-from-inside-docker-container/145749/6).
After implementing the changes and restarting your compose, `lk-jwt-service` should now connect to your other services. The sidecar container test above should now return an `OK` from LiveKit. After implementing the changes and restarting your compose, you can test whether the connection works by cURLing from a sidecar container:
```bash
~$ docker run --rm --net container:lk-jwt-service docker.io/curlimages/curl https://livekit.example.com
OK
```
### Workaround for non-federating servers ### Workaround for non-federating servers
+2 -2
View File
@@ -16,8 +16,8 @@
}, },
{ {
"type": "file", "type": "file",
"name": "rpm", "name": "fedora",
"label": "RPM" "label": "Fedora"
}, },
{ {
"type": "file", "type": "file",
+9 -12
View File
@@ -44,7 +44,7 @@ ### Prerequisites
- Alternatively, if you want both client and federation traffic on `:443`, you can configure `CONTINUWUITY_WELL_KNOWN` following some of the [examples](#choose-your-reverse-proxy) below. - Alternatively, if you want both client and federation traffic on `:443`, you can configure `CONTINUWUITY_WELL_KNOWN` following some of the [examples](#choose-your-reverse-proxy) below.
:::tip Split-domain setups :::tip Split-domain setups
For more setups with `.well-known` delegation and split-domain deployments, consult the [Delegation/Split-domain](../guides/delegation) page. For more setups with `.well-known` delegation and split-domain deployments, consult the [Delegation/Split-domain](../advanced/delegation) page.
::: :::
## Docker Compose ## Docker Compose
@@ -79,7 +79,7 @@ ### Choose Your Reverse Proxy
nameserver 1.1.1.1 nameserver 1.1.1.1
``` ```
Consult the [**DNS tuning guide (recommended)**](../guides/dns.mdx) for full solutions to this issue. Consult the [**DNS tuning guide (recommended)**](../advanced/dns.mdx) for full solutions to this issue.
::: :::
#### Caddy (using Caddyfile) #### Caddy (using Caddyfile)
@@ -185,15 +185,13 @@ ## Testing
Test that your setup works by following these [instructions](./generic.mdx#how-do-i-know-it-works) Test that your setup works by following these [instructions](./generic.mdx#how-do-i-know-it-works)
Check your container logs using `docker-compose logs --follow` to debug any issues. See the [Troubleshooting](../troubleshooting.mdx) page for common errors and how to fix them.
## Other deployment methods ## Other deployment methods
### Docker - Quick Run ### Docker - Quick Run
:::warning For testing only :::note For testing only
The instructions below are only meant for a quick demo of Continuwuity with **federation disabled**. The instructions below are only meant for a quick demo of Continuwuity.
For production deployment, we recommend using [Docker Compose](#docker-compose). For production deployment, we recommend using [Docker Compose](#docker-compose)
::: :::
Get a working Continuwuity server with an admin user in four steps: Get a working Continuwuity server with an admin user in four steps:
@@ -213,7 +211,7 @@ ### Docker - Quick Run
-e CONTINUWUITY_SERVER_NAME="example.com" \ -e CONTINUWUITY_SERVER_NAME="example.com" \
-e CONTINUWUITY_DATABASE_PATH="/var/lib/continuwuity" \ -e CONTINUWUITY_DATABASE_PATH="/var/lib/continuwuity" \
-e CONTINUWUITY_ADDRESS="0.0.0.0" \ -e CONTINUWUITY_ADDRESS="0.0.0.0" \
-e CONTINUWUITY_ALLOW_FEDERATION="false" \ -e CONTINUWUITY_ALLOW_REGISTRATION="false" \
--name continuwuity \ --name continuwuity \
forgejo.ellis.link/continuwuation/continuwuity:latest \ forgejo.ellis.link/continuwuation/continuwuity:latest \
/sbin/conduwuit /sbin/conduwuit
@@ -235,9 +233,9 @@ ### Docker - Quick Run
Pick your own username and password! Pick your own username and password!
``` ```
4. Configure your reverse proxy to forward HTTPS traffic to Continuwuity at port 8008. See [Docker Compose](#docker-compose) or the [Generic instructions](./generic.mdx#setting-up-the-reverse-proxy) for examples. 4. Configure your reverse proxy to forward HTTPS traffic to Continuwuity at port 8008. See [Docker Compose](#docker-compose) for examples.
Once configured, log in to your server with any Matrix client, and register for an account with the registration token from step 3. If you did not configure step 4., log in via the `http://<your_server_ip>:8008` address. You will be automatically invited to the admin room where you can [manage your server](../reference/admin). Once configured, log in to your server with any Matrix client, and register for an account with the registration token from step 3. You'll automatically be invited to the admin room where you can [manage your server](../reference/admin).
### (Optional) Building Custom Images ### (Optional) Building Custom Images
@@ -269,7 +267,6 @@ ### Accessing the Server's Console
## Next steps ## Next steps
- For smooth federation, set up a caching resolver according to the [**DNS tuning guide**](../guides/dns.mdx) (recommended) - For smooth federation, set up a caching resolver according to the [**DNS tuning guide**](../advanced/dns.mdx) (recommended)
- To set up Audio/Video communication, see the [**Calls**](../calls.mdx) page. - To set up Audio/Video communication, see the [**Calls**](../calls.mdx) page.
- Consult the [Maintenance](../maintenance.mdx) page for guidance on maintaining your homeserver.
- If you want to set up an appservice, take a look at the [**Appservice Guide**](../appservices.mdx). - If you want to set up an appservice, take a look at the [**Appservice Guide**](../appservices.mdx).
+189
View File
@@ -0,0 +1,189 @@
# RPM Installation Guide
Continuwuity is available as RPM packages for Fedora and compatible distributions.
We do not currently have infrastructure to build RPMs for RHEL and compatible distributions, but this is a work in progress.
The RPM packaging files are maintained in the `fedora/` directory:
- `continuwuity.spec.rpkg` - RPM spec file using rpkg macros for building from git
- `continuwuity.service` - Systemd service file for the server
- `RPM-GPG-KEY-continuwuity.asc` - GPG public key for verifying signed packages
RPM packages built by CI are signed with our GPG key (RSA, ID: `6595 E8DB 9191 D39A 46D6 A514 4BA7 F590 DF0B AA1D`). # spellchecker:disable-line
```bash
# Import the signing key
sudo rpm --import https://forgejo.ellis.link/api/packages/continuwuation/rpm/repository.key
# Verify a downloaded package
rpm --checksig continuwuity-*.rpm
```
## Installation methods
**Stable releases** (recommended)
```bash
# Add the repository and install
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable.repo
sudo dnf install continuwuity
```
**Development builds** from main branch
```bash
# Add the dev repository and install
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/dev.repo
sudo dnf install continuwuity
```
**Feature branch builds** (example: `tom/new-feature`)
```bash
# Branch names are sanitized (slashes become hyphens, lowercase only)
sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/tom-new-feature.repo
sudo dnf install continuwuity
```
**Manual repository configuration** (alternative method)
```bash
cat << 'EOF' | sudo tee /etc/yum.repos.d/continuwuity.repo
[continuwuity]
name=Continuwuity - Matrix homeserver
baseurl=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable
enabled=1
gpgcheck=1
gpgkey=https://forgejo.ellis.link/api/packages/continuwuation/rpm/repository.key
EOF
sudo dnf install continuwuity
```
## Package management
**Automatic updates** with DNF Automatic
```bash
# Install and configure
sudo dnf install dnf-automatic
sudo nano /etc/dnf/automatic.conf # Set: apply_updates = yes
sudo systemctl enable --now dnf-automatic.timer
```
**Manual updates**
```bash
# Check for updates
sudo dnf check-update continuwuity
# Update to latest version
sudo dnf update continuwuity
```
**Switching channels** (stable/dev/feature branches)
```bash
# List enabled repositories
dnf repolist | grep continuwuation
# Disable current repository
sudo dnf config-manager --set-disabled continuwuation-stable # or -dev, or branch name
# Enable desired repository
sudo dnf config-manager --set-enabled continuwuation-dev # or -stable, or branch name
# Update to the new channel's version
sudo dnf update continuwuity
```
**Verifying installation**
```bash
# Check installed version
rpm -q continuwuity
# View package information
rpm -qi continuwuity
# List installed files
rpm -ql continuwuity
# Verify package integrity
rpm -V continuwuity
```
## Service management and removal
**Systemd service commands**
```bash
# Start the service
sudo systemctl start conduwuit
# Enable on boot
sudo systemctl enable conduwuit
# Check status
sudo systemctl status conduwuit
# View logs
sudo journalctl -u conduwuit -f
```
**Uninstallation**
```bash
# Stop and disable the service
sudo systemctl stop conduwuit
sudo systemctl disable conduwuit
# Remove the package
sudo dnf remove continuwuity
# Remove the repository (optional)
sudo rm /etc/yum.repos.d/continuwuation-*.repo
```
## Troubleshooting
**GPG key errors**: Temporarily disable GPG checking
```bash
sudo dnf --nogpgcheck install continuwuity
```
**Repository metadata issues**: Clear and rebuild cache
```bash
sudo dnf clean all
sudo dnf makecache
```
**Finding specific versions**
```bash
# List all available versions
dnf --showduplicates list continuwuity
# Install a specific version
sudo dnf install continuwuity-<version>
```
## Building locally
Build the RPM locally using rpkg:
```bash
# Install dependencies
sudo dnf install rpkg rpm-build cargo-rpm-macros systemd-rpm-macros
# Clone the repository
git clone https://forgejo.ellis.link/continuwuation/continuwuity.git
cd continuwuity
# Build SRPM
rpkg srpm
# Build RPM
rpmbuild --rebuild *.src.rpm
```
+6 -14
View File
@@ -47,15 +47,9 @@ #### Performance-optimised builds
### Nix ### Nix
If you wish to generate a static binary, you can do so using Nix: `nix build git+https://forgejo.ellis.link/continuwuation/continuwuity#packageName`, where `packageName` is one of: Theres a Nix package defined in our flake, available for Linux and MacOS. Add continuwuity as an input to your flake, and use `inputs.continuwuity.packages.${system}.default` to get a working Continuwuity package.
- `default-static-x86_64` If you simply wish to generate a binary using Nix, you can run `nix build git+https://forgejo.ellis.link/continuwuation/continuwuity` to generate a binary in `result/bin/conduwuit`.
- `default-static-aarch64`
- `max-perf-static-x86_64`
- `max-perf-haswell-static-x86_64`
- `max-perf-static-aarch64`
`max-perf` takes longer to build, but has more runtime optimizations. Haswell builds are optimized for modern CPUs.
### Compiling ### Compiling
@@ -170,7 +164,7 @@ ## Exposing ports in the firewall or the router
are: `ufw allow 8448/tcp` and `ufw allow 443/tcp`. are: `ufw allow 8448/tcp` and `ufw allow 443/tcp`.
:::tip Alternative port/domain setups :::tip Alternative port/domain setups
If you would like to use only port 443, a different port, or a subdomain for the homeserver, you will need to set up `.well-known` delegation. Consult the `[global.well_known]` section of the config file, and the [**Delegation/Split-domain**](../guides/delegation) page to learn more about these kinds of deployments. If you would like to use only port 443, a different port, or a subdomain for the homeserver, you will need to set up `.well-known` delegation. Consult the `[global.well_known]` section of the config file, and the [**Delegation/Split-domain**](../advanced/delegation) page to learn more about these kinds of deployments.
::: :::
## Setting up the Reverse Proxy ## Setting up the Reverse Proxy
@@ -207,7 +201,7 @@ ### Other Reverse Proxies
- `/_matrix/client` - core Client-Server APIs. These should be available on port :443 - `/_matrix/client` - core Client-Server APIs. These should be available on port :443
- `/_continuwuity/` - Continuwuity's integrated account management interface for OAuth-compatible clients - `/_conduwuit/` and `/_continuwuity/` - ad-hoc Continuwuity routes for password resets, email verification, and server details such as `/local_user_count` and `/server_version`.
You can optionally reverse proxy the following individual routes: You can optionally reverse proxy the following individual routes:
@@ -266,7 +260,7 @@ ## Starting Your Server
``` ```
You can then open [a Matrix client][matrix-clients], You can then open [a Matrix client][matrix-clients],
enter your homeserver address, and register with the provided token. enter your homeserver address, and try to register with the provided token.
By default, the first user is the instance's first admin. They will be added By default, the first user is the instance's first admin. They will be added
to the `#admin:example.com` room and be able to [issue admin commands](../reference/admin/index.md). to the `#admin:example.com` room and be able to [issue admin commands](../reference/admin/index.md).
@@ -300,8 +294,6 @@ # For client-server endpoints
## What's next? ## What's next?
- For smooth federation, set up a caching resolver according to the [**DNS tuning guide**](../guides/dns.mdx) (recommended) - For smooth federation, set up a caching resolver according to the [**DNS tuning guide**](../advanced/dns.mdx) (recommended)
- To configure OIDC login with an identity provider, see the [**delegated authentication guide**](../guides/oidc.mdx).
- For Audio/Video call functionality see the [**Calls**](../calls.md) page. - For Audio/Video call functionality see the [**Calls**](../calls.md) page.
- Consult the [Maintenance](../maintenance.mdx) page for guidance on maintaining your homeserver.
- If you want to set up an appservice, take a look at the [**Appservice Guide**](../appservices.md). - If you want to set up an appservice, take a look at the [**Appservice Guide**](../appservices.md).
+1 -8
View File
@@ -47,16 +47,9 @@ ### Available options
- `extraEnvironment`: Extra environment variables to pass to the Continuwuity server - `extraEnvironment`: Extra environment variables to pass to the Continuwuity server
- `package`: The Continuwuity package to use, defaults to `pkgs.matrix-continuwuity` - `package`: The Continuwuity package to use, defaults to `pkgs.matrix-continuwuity`
- You may want to override this to be from our flake, for faster updates and unstable versions: - You may want to override this to be from our flake, for faster updates and unstable versions:
```nix ```nix
package = inputs.continuwuity.packages.${pkgs.stdenv.hostPlatform.system}.packageName; package = inputs.continuwuity.packages.${pkgs.stdenv.hostPlatform.system}.default;
``` ```
Where `packageName` is one of:
- `default`
- `max-perf`: Takes longer to build, but has more runtime optimizations
- `max-perf-haswell`: Optimized for modern CPUs, don't use if your CPU is not Haswell or later.
- `admin.enable`: Whether to add the `conduwuit` binary to `PATH` for administration (enabled by default) - `admin.enable`: Whether to add the `conduwuit` binary to `PATH` for administration (enabled by default)
- `settings`: The Continuwuity configuration - `settings`: The Continuwuity configuration
-165
View File
@@ -1,165 +0,0 @@
import { Tab, Tabs} from '@rspress/core/theme';
# RPM Installation Guide
## Overview
Continuwuity versions v0.5.10+ are available as RPM packages for the following distributions:
<Tabs groupId="distro">
<Tab label="Fedora">
Available for upstream-supported Fedora versions (including compatible distributions, such as Ultramarine Linux) and Rawhide through Terra.
</Tab>
<Tab label="EL">
Available for Enterprise Linux (RHEL, AlmaLinux, Rocky Linux, etc.) **10+** through Terra.
:::warning Oracle Linux support
Due to upstream limitations, Terra is only usable on Oracle Linux if you use [upstream EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/)
rather than Oracle's rebuilds of EPEL. Oracle tends to lag behind on new EL versions, both major and minor—for example, as of the time of writing, 10.2 has been
available for over a month through other ELs, but Oracle has not yet released corresponding updates—so you may encounter further compatibility issues with EPEL.
**For this reason, it is recommended that you use another EL distribution if at all possible.**
:::
</Tab>
<Tab label="SUSE">
Available for openSUSE Tumbleweed and [supported Leap versions](https://en.opensuse.org/Lifetime) through the openSUSE Build Service.
</Tab>
</Tabs>
## Installation methods
### Stable releases (recommended)
<Tabs groupId="distro">
<Tab label="Fedora">
1. Follow [Terra's directions for adding the Terra repo on your distribution](https://docs.terrapkg.com/usage/installing/#fedora-and-derivatives).
2. Install the `continuwuity` package.
</Tab>
<Tab label="EL">
1. Follow [Terra's directions for adding the Terra repo on your distribution](https://docs.terrapkg.com/usage/installing/#enterprise-linux). Be sure to acquire/enable relevant dependencies!
2. Install the `continuwuity` package.
</Tab>
<Tab label="SUSE">
openSUSE packages are available through an openSUSE Build Service project namespace provided by a community member.
1. Navigate to [this page](https://software.opensuse.org/download.html?project=home%3Ajulian45&package=continuwuity).
2. Click on the "Add repository and install manually" text to expand install instructions.
3. Follow the provided instructions for your version of openSUSE.
</Tab>
</Tabs>
### Nightly releases
Nightly versions are built from the latest commit on the `main` branch every 24 hours.
<Tabs groupId="distro">
<Tab label="Fedora">
1. Follow [Terra's directions for adding the Terra repo on your distribution](https://docs.terrapkg.com/usage/installing/#fedora-and-derivatives).
2. Install the `continuwuity-nightly` package.
</Tab>
<Tab label="EL">
1. Follow [Terra's directions for adding the Terra repo on your distribution](https://docs.terrapkg.com/usage/installing/#enterprise-linux). Be sure to acquire/enable relevant dependencies!
2. Install the `continuwuity-nightly` package.
</Tab>
<Tab label="SUSE">
Nightly packages for openSUSE are **not currently available**.
If there is an ongoing need for these, please ask in `#continuwuity:continuwuity.org` or [open an issue on Forgejo](https://forgejo.ellis.link/continuwuation/continuwuity/issues/new).
In the meantime, you may follow the [generic deployment instructions](generic).
</Tab>
</Tabs>
## Service management and removal
**Systemd service commands**
```bash
# Start the service
sudo systemctl start conduwuit
# Enable on boot
sudo systemctl enable conduwuit
# Check status
sudo systemctl status conduwuit
# View logs
sudo journalctl -u conduwuit -f
```
**Uninstallation**
```bash
# Stop and disable the service
sudo systemctl stop conduwuit
sudo systemctl disable conduwuit
# Remove the package
sudo dnf remove continuwuity # replace `dnf` with `zypper` on openSUSE
```
## Troubleshooting
**GPG key errors**: Temporarily disable GPG checking
```bash
sudo dnf --nogpgcheck install continuwuity
```
**Repository metadata issues**: Clear and rebuild cache
```bash
sudo dnf clean all
sudo dnf makecache
```
**Finding specific versions**
```bash
# List all available versions
dnf --showduplicates list continuwuity
# Install a specific version
sudo dnf install continuwuity-<version>
```
## Building locally
<Tabs groupId="distro">
<Tab label="Fedora">
:::details Directly from Continuwuity source
Build the RPM locally using rpkg:
```bash
# Install dependencies
sudo dnf install rpkg rpm-build cargo-rpm-macros systemd-rpm-macros
# Clone the repository
git clone https://forgejo.ellis.link/continuwuation/continuwuity.git
cd continuwuity
# Build SRPM
rpkg srpm
# Build RPM
rpmbuild --rebuild *.src.rpm
```
:::
Or, use Terra:
1. Follow [Terra's prep directions](https://docs.terrapkg.com/contributing/getting-started/#preparation) to bootstrap your development environment.
2. `git clone` the [Terra sources repo](https://github.com/terrapkg/packages).
3. Follow [Terra's build instructions](https://docs.terrapkg.com/contributing/getting-started/#building), using the path `anda/misc/continuwuity/nightly/pkg`. If desiring a build of the latest stable version, replace `nightly` with `stable` instead.
</Tab>
<Tab label="EL">
1. Follow [Terra's prep directions](https://docs.terrapkg.com/contributing/getting-started/#preparation) to bootstrap your development environment.
2. `git clone` the [Terra sources repo](https://github.com/terrapkg/packages).
3. Follow [Terra's build instructions](https://docs.terrapkg.com/contributing/getting-started/#building), using the path `anda/misc/continuwuity/nightly/pkg`. If desiring a build of the latest stable version, replace `nightly` with `stable` instead.
</Tab>
<Tab label="SUSE">
1. Navigate to [this package's location in the openSUSE Build Service web UI](https://build.opensuse.org/package/show/home:julian45/continuwuity).
2. Click on the "Checkout package" text and follow the instructions to acquire a local copy of the packaging source.
3. In the `_service` file, adjust the revision within the `<param name="revision">` tag (approx. line 6) to your desired git repo revision.
4. Run `osc service ra` to update your copy of the Continuwuity sources and vendored packages.
5. Run `osc build` to locally build an openSUSE-ready RPM.
</Tab>
</Tabs>
-56
View File
@@ -1,56 +0,0 @@
# Configuring delegated authentication
Continuwuity supports delegating user authentication to an external identity provider that implements the OpenID Connect specification, such as Authentik, kanidm, or Keycloak.
:::warning{title="OIDC versus OAuth, and supported clients"}
**OIDC** is not to be confused with **OAuth**. In the context of Matrix, OAuth is the protocol that Matrix clients use to authenticate with the _homeserver_. OIDC is the protocol that the _homeserver_ uses to communicate with the _identity provider_. Continuwuity supports OAuth by default, alongside the legacy **UIAA** authentication framework.
When OIDC is configured, Continuwuity will disable its support for legacy authentication. **Only clients that support OAuth**, such as the Element family of clients, will be able to log in when OIDC is configured. If your client of choice shows an error when you try to log in after configuring OIDC, it likely does not support OAuth. This is an issue with your client, not Continuwuity, and should be reported to your client's developers.
:::
A simple OIDC configuration is as easy as creating a new OIDC application in your identity provider's settings and supplying Continuwuity with the client ID and client secret. This guide will use kanidm as an example, but the described steps are broadly applicable to other identity providers.
First, create a new application for Continuwuity in your identity provider.
```sh
# Here, `c10y` is the client ID that kanidm will use, and `Continuwuity` is the display name.
# Other identity providers may generate a client ID for you.
# Use the domain that clients can reach Continuwuity at, which may not be the same as your server name
# if you have configured well-known delegation.
kanidm system oauth2 create c10y Continuwuity https://matrix.yourdomain.com
```
Configure the redirect URL that Continuwuity uses.
```sh
kanidm system oauth2 add-redirect-url c10y https://matrix.yourdomain.com/_continuwuity/oidc/complete
```
Allow Continuwuity to request the `openid` scope. Other identity providers may not require this step.
```sh
kanidm system oauth2 update-scope-map c10y idm_all_persons openid
```
Find the client secret that was generated. Other identity providers may show this information in their web UI.
```sh
kanidm system oauth2 show-basic-secret c10y
d1qgx352kkuvs1j70b6w293d65x68jve1f7b27fyk90gjhpr
```
Configure Continuwuity with the client ID, client secret, and discovery URL. kanidm has a different discovery URL for each client, but other identity providers may have a single discovery URL at the root of their domain.
```toml
[global.oauth.oidc]
# `/.well-known/openid-configuration` will be appended automatically
discovery_url = "https://idm.example.com/oauth2/openid/c10y"
# This may be randomly generated by your identity provider. kanidm requires
# you to set it manually when you create the application.
client_id = "c10y"
# From the previous step
client_secret = "d1qgx352kkuvs1j70b6w293d65x68jve1f7b27fyk90gjhpr"
```
Finally, restart Continuwuity, and log out and back in again. Your client should prompt you to continue in your web browser and open a webpage with the Continuwuity logo that allows you to continue in your identity provider. Once you log in successfully, you will be prompted to choose a user ID -- to link your existing account, enter its user ID, and then your old password when prompted.
Continuwuity offers several additional configuration options to tweak its integration with your identity provider. Review the `[global.oauth.oidc]` section towards the bottom of the [reference configuration](../reference/config) for a complete list of options and documentation.
@@ -6,10 +6,10 @@
"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": 16, "id": 13,
"mention_room": true, "mention_room": true,
"date": "2026-07-13", "date": "2026-05-08",
"message": "[Continuwuity 26.6.2](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v26.6.1) has just been released! This release fixes a severe bug with OIDC that could cause users' accounts to be flagged as deactivated. If you use OIDC, please update as soon as possible." "message": "[v0.5.9](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.9) has been released, fixing a few low-severity federation-related vulnerabilities. It is recommended you read the changelog and update as soon as possible. There are no new features or other changes in this release, only related bugfixes. Deployments tracking the main branch should also update to the latest commit."
} }
] ]
} }
-4
View File
@@ -27,7 +27,3 @@ ## `!admin appservices show-appservice-config`
## `!admin appservices list-registered` ## `!admin appservices list-registered`
List all the currently registered appservices List all the currently registered appservices
## `!admin appservices ensure-puppets-active`
Ensure no appservice puppets are marked as deactivated. This is a debug command to fix issues caused by a faulty database migration in Continuwuity 26.6.0
+1 -17
View File
@@ -10,13 +10,7 @@ ## `!admin debug echo`
## `!admin debug get-auth-chain` ## `!admin debug get-auth-chain`
Loads the auth_chain of a PDU, reporting how long it took Get the auth_chain of a PDU
## `!admin debug show-auth-chain`
Walks & displays the auth_chain of a PDU in a mermaid graph format.
This is useless to basically anyone but developers, and is also probably slow and memory hungry.
## `!admin debug parse-pdu` ## `!admin debug parse-pdu`
@@ -50,12 +44,6 @@ ## `!admin debug get-room-state`
Of course the check is still done on the actual client API. Of course the check is still done on the actual client API.
## `!admin debug get-state-at`
Gets all the room state events at the specified event.
State at event might not be available for some PDUs, such as rejected ones.
## `!admin debug get-signing-keys` ## `!admin debug get-signing-keys`
Get and display signing keys from local cache or remote server Get and display signing keys from local cache or remote server
@@ -146,10 +134,6 @@ ## `!admin debug send-test-email`
Send a test email to the invoking admin's email address Send a test email to the invoking admin's email address
## `!admin debug rooms-by-extremity-count`
Lists room IDs by forward extremity count in descending order
## `!admin debug tester` ## `!admin debug tester`
Developer test stubs Developer test stubs
-1
View File
@@ -14,7 +14,6 @@ ## Categories
- [`!admin appservices`](appservices/): Commands for managing appservices - [`!admin appservices`](appservices/): Commands for managing appservices
- [`!admin users`](users/): Commands for managing local users - [`!admin users`](users/): Commands for managing local users
- [`!admin token`](token/): Commands for managing registration tokens - [`!admin token`](token/): Commands for managing registration tokens
- [`!admin oidc`](oidc/): Commands for managing OIDC
- [`!admin rooms`](rooms/): Commands for managing rooms - [`!admin rooms`](rooms/): Commands for managing rooms
- [`!admin federation`](federation/): Commands for managing federation - [`!admin federation`](federation/): Commands for managing federation
- [`!admin server`](server/): Commands for managing the server - [`!admin server`](server/): Commands for managing the server
-13
View File
@@ -1,13 +0,0 @@
<!-- This file is generated by `cargo xtask generate-docs`. Do not edit. -->
# `!admin oidc`
Commands for managing OIDC
## `!admin oidc link`
Link a user ID to the given subject claim
## `!admin oidc unlink`
Unlink the given subject claim from its associated user ID
+6 -2
View File
@@ -104,9 +104,13 @@ ## `!admin query resolver`
resolver service resolver service
### `!admin query resolver cache` ### `!admin query resolver destinations-cache`
Query the destinations or overrides cache, depending on the value of the `overrides` flag (default false) Query the destinations cache
### `!admin query resolver overrides-cache`
Query the overrides cache
### `!admin query resolver flush-cache` ### `!admin query resolver flush-cache`
+4 -8
View File
@@ -12,6 +12,10 @@ ## `!admin users reset-password`
Reset user password Reset user password
## `!admin users issue-password-reset-link`
Issue a self-service password reset link for a user
## `!admin users get-email` ## `!admin users get-email`
Get a user's associated email address Get a user's associated email address
@@ -92,14 +96,6 @@ ## `!admin users list-users`
List local users in the database List local users in the database
## `!admin users list-invited-rooms`
Lists all the rooms (local and remote) that the specified user is invited to
## `!admin users reject-all-invites`
Manually make a user reject all current invites
## `!admin users list-joined-rooms` ## `!admin users list-joined-rooms`
Lists all the rooms (local and remote) that the specified user is joined in Lists all the rooms (local and remote) that the specified user is joined in
+2 -2
View File
@@ -10,7 +10,7 @@ ## Continuwuity issues
### Slow joins to rooms ### Slow joins to rooms
Some slowness is to be expected if you're the first person on your homeserver to join a room (which will Some slowness is to be expected if you're the first person on your homserver to join a room (which will
always be the case for single-user homeservers). In this situation, your homeserver has to verify the signatures of always be the case for single-user homeservers). In this situation, your homeserver has to verify the signatures of
all of the state events sent by other servers before your join. To make this process as fast as possible, make sure you have all of the state events sent by other servers before your join. To make this process as fast as possible, make sure you have
multiple fast, trusted servers listed in `trusted_servers` in your configuration, and ensure multiple fast, trusted servers listed in `trusted_servers` in your configuration, and ensure
@@ -60,7 +60,7 @@ ### DNS server overload
Matrix federation is extremely heavy and sends wild amounts of DNS requests. This makes normal resolvers like the ones above unsuitable for its activity. Ultimately, the best solution/fix for this is to selfhost a high quality caching DNS resolver such as Unbound, and configure Continuwuity to use it. Matrix federation is extremely heavy and sends wild amounts of DNS requests. This makes normal resolvers like the ones above unsuitable for its activity. Ultimately, the best solution/fix for this is to selfhost a high quality caching DNS resolver such as Unbound, and configure Continuwuity to use it.
Follow the [**DNS tuning guide**](./guides/dns) for details on setting it up. Follow the [**DNS tuning guide**](./advanced/dns) for details on setting it up.
### Intermittent federation failures to a specific server ### Intermittent federation failures to a specific server
Generated
+24 -24
View File
@@ -3,11 +3,11 @@
"advisory-db": { "advisory-db": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1783840254, "lastModified": 1777645914,
"narHash": "sha256-XjyvZk0f3YiinVHmkGOotmLBAzvK+LwEJYj2QqJ5pn8=", "narHash": "sha256-P1T7QVQS13OvkXEuEhI91CLaQfyv6iqV9vW8IBLLDYg=",
"owner": "rustsec", "owner": "rustsec",
"repo": "advisory-db", "repo": "advisory-db",
"rev": "6e3286f4efa8c142fb33e5ea4342c8db6693cf34", "rev": "d6ba1f7070ba91f45efe372d68eb648be67d0417",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -18,11 +18,11 @@
}, },
"crane": { "crane": {
"locked": { "locked": {
"lastModified": 1783203018, "lastModified": 1777335812,
"narHash": "sha256-G6R9IT/xwFuu+CYBWDUAok6AdC4ERC4ZfPPFtEpxnZE=", "narHash": "sha256-bEg5xoAxAwsyfnGhkEX7RJViTIBIYPd8ISg4O1c0HFc=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "80db5bdc391be8a1794f6d8a2d56e3a84ebcede2", "rev": "5e0fb2f64edff2822249f21293b8304dedaaf676",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -39,11 +39,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1783844668, "lastModified": 1777624102,
"narHash": "sha256-3MOpw4y3reoErRLvFBDz0t9aG6FWXUj7leKvUns5eQA=", "narHash": "sha256-thSyElkje577x/kAbP72nHlfiFc1a+tCudskLPHXe9s=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "4e49ef62eedaa5c149d62b63b3f53844e1cc45d7", "rev": "4d81601e0b73f20d81d066754ad0e7d1e7f75a06",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -74,11 +74,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1782949081, "lastModified": 1775087534,
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -89,11 +89,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1783776592, "lastModified": 1777268161,
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=", "narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3", "rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -105,11 +105,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1782614948, "lastModified": 1774748309,
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=", "narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c", "rev": "333c4e0545a6da976206c74db8773a1645b5870a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -132,11 +132,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1783779020, "lastModified": 1777583169,
"narHash": "sha256-vpm418WZa9l1KUl6HRs+Ga+SIfE+D2/sV4olwTlilYc=", "narHash": "sha256-dVJ4+wrRKc8oIgp3rLOFSq1obt/sCKlXy3h47qof/w0=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "5be5e89cf0145d73a85c805726821b4adfc3af48", "rev": "aa64e4828a2bbba44463c1229a81c748d3cce583",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -153,11 +153,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1780220602, "lastModified": 1775636079,
"narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", "narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "db947814a175b7ca6ded66e21383d938df01c227", "rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
"type": "github" "type": "github"
}, },
"original": { "original": {
+14
View File
@@ -0,0 +1,14 @@
{ inputs, ... }:
{
perSystem =
{
pkgs,
self',
...
}:
{
_module.args.craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (
pkgs: self'.packages.stable-toolchain
);
};
}
+1
View File
@@ -1,6 +1,7 @@
{ {
imports = [ imports = [
./rust.nix ./rust.nix
./crane.nix
./packages ./packages
./devshell.nix ./devshell.nix
./fmt.nix ./fmt.nix
+3 -4
View File
@@ -1,6 +1,7 @@
{ inputs, ... }: { {
perSystem = perSystem =
{ {
craneLib,
self', self',
lib, lib,
pkgs, pkgs,
@@ -8,9 +9,7 @@
}: }:
{ {
# basic nix shell containing all things necessary to build continuwuity in all flavors manually (on x86_64-linux) # basic nix shell containing all things necessary to build continuwuity in all flavors manually (on x86_64-linux)
devShells.default = devShells.default = craneLib.devShell {
((inputs.crane.mkLib pkgs).overrideToolchain (pkgs: self'.packages.stable-toolchain)).devShell
{
packages = [ packages = [
self'.packages.rocksdb self'.packages.rocksdb
pkgs.nodejs pkgs.nodejs
+7 -20
View File
@@ -2,14 +2,14 @@
lib, lib,
self, self,
stdenv, stdenv,
rocksdb, liburing,
craneLib, craneLib,
pkg-config, pkg-config,
liburing, callPackage,
rustPlatform, rustPlatform,
cargoExtraArgs ? "", cargoExtraArgs ? "",
rustflags ? "", rustflags ? "",
target_cpu ? null, rocksdb ? callPackage ./rocksdb.nix { },
profile ? "release", profile ? "release",
}: }:
let let
@@ -28,31 +28,18 @@ let
}; };
attrs = { attrs = {
__structuredAttrs = true;
strictDeps = true;
inherit src; inherit src;
nativeBuildInputs = [ nativeBuildInputs = [
pkg-config pkg-config
rustPlatform.bindgenHook rustPlatform.bindgenHook
]; ];
buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ liburing ]; buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ liburing ];
env = { env = {
CARGO_PROFILE = profile;
RUSTFLAGS = rustflags;
GIT_COMMIT_HASH = self.rev or self.dirtyRev or "";
GIT_COMMIT_HASH_SHORT = self.shortRev or self.dirtyShortRev or "";
}
// (lib.optionalAttrs (rocksdb != null) {
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include"; ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
ROCKSDB_LIB_DIR = "${rocksdb}/lib"; ROCKSDB_LIB_DIR = "${rocksdb}/lib";
}) CARGO_PROFILE = profile;
// (lib.optionalAttrs (target_cpu != null) { RUSTFLAGS = rustflags;
TARGET_CPU = target_cpu; };
});
}; };
in in
craneLib.buildPackage ( craneLib.buildPackage (
@@ -61,7 +48,7 @@ craneLib.buildPackage (
cargoArtifacts = craneLib.buildDepsOnly attrs; cargoArtifacts = craneLib.buildDepsOnly attrs;
# Needed to make continuwuity link to rocksdb # Needed to make continuwuity link to rocksdb
postFixup = lib.optionalString (stdenv.hostPlatform.isLinux && rocksdb != null) '' postFixup = lib.optionalString stdenv.hostPlatform.isLinux ''
old_rpath="$(patchelf --print-rpath $out/bin/conduwuit)" old_rpath="$(patchelf --print-rpath $out/bin/conduwuit)"
extra_rpath="${ extra_rpath="${
lib.makeLibraryPath [ lib.makeLibraryPath [
+9 -68
View File
@@ -1,5 +1,4 @@
{ {
inputs,
self, self,
... ...
}: }:
@@ -7,87 +6,29 @@
perSystem = perSystem =
{ {
self', self',
lib,
pkgs, pkgs,
inputs',
system,
craneLib, craneLib,
mkToolchain,
... ...
}: }:
{ {
packages = packages = {
let rocksdb = pkgs.callPackage ./rocksdb.nix { };
mkPackages =
pkgs:
let
fnx = inputs'.fenix.packages;
isStatic = pkgs.stdenv.hostPlatform.isMusl;
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain (
_:
if isStatic then
fnx.combine [
self'.packages.stable-toolchain
(mkToolchain fnx.targets.${pkgs.stdenv.hostPlatform.config}).rust-std
]
else
self'.packages.stable-toolchain
);
# extra features via `cargoExtraArgs`
cargoExtraArgs = "-F http3";
default = pkgs.callPackage ./continuwuity.nix { default = pkgs.callPackage ./continuwuity.nix {
inherit self craneLib; inherit self craneLib;
# extra features via `cargoExtraArgs`
liburing = (if isStatic then pkgs.pkgsStatic else pkgs).liburing; cargoExtraArgs = "-F http3";
rocksdb = if isStatic then null else self'.packages.rocksdb;
inherit cargoExtraArgs;
# extra RUSTFLAGS via `rustflags` # extra RUSTFLAGS via `rustflags`
# the stuff below is required for http3 # the stuff below is required for http3
rustflags = "--cfg reqwest_unstable"; rustflags = "--cfg reqwest_unstable";
}; };
# users may also override this with other cargo profiles to build for other feature sets # users may also override this with other cargo profiles to build for other feature sets
# for features configuration see `default` package which enables http3 by default #
# other examples include:
max-perf = default.override { #
# compiles slower but with more thorough optimizations # - release-high-perf
max-perf = self'.packages.default.override {
profile = "release-max-perf"; profile = "release-max-perf";
cargoExtraArgs = "${cargoExtraArgs} -F release_max_log_level";
}; };
max-perf-haswell = max-perf.override {
# compiles explicitly for haswell arch cpus
target_cpu = "haswell";
}; };
in
{
inherit default max-perf max-perf-haswell;
};
in
{
rocksdb = pkgs.callPackage ./rocksdb.nix { };
}
// (mkPackages pkgs)
// (lib.mapAttrs' (name: value: lib.nameValuePair "${name}-static-x86_64" value) (
mkPackages (
import inputs.nixpkgs {
localSystem = system;
crossSystem = "x86_64-unknown-linux-musl";
}
)
))
// (lib.mapAttrs' (name: value: lib.nameValuePair "${name}-static-aarch64" value) (
mkPackages (
import inputs.nixpkgs {
localSystem = system;
crossSystem = "aarch64-unknown-linux-musl";
}
)
));
}; };
} }
+5 -7
View File
@@ -1,7 +1,5 @@
{ {
# stdenv, stdenv,
# enableJemalloc ? stdenv.hostPlatform.isLinux,
enableJemalloc ? false,
rocksdb, rocksdb,
fetchFromGitea, fetchFromGitea,
rust-jemalloc-sys-unprefixed, rust-jemalloc-sys-unprefixed,
@@ -15,16 +13,16 @@
# #
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17 # [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
jemalloc = rust-jemalloc-sys-unprefixed; jemalloc = rust-jemalloc-sys-unprefixed;
inherit enableJemalloc; enableJemalloc = stdenv.hostPlatform.isLinux;
}).overrideAttrs }).overrideAttrs
({ ({
version = "continuwuity-v0.5.0-unstable-2026-05-19"; version = "continuwuity-v0.5.0-unstable-2026-03-27";
src = fetchFromGitea { src = fetchFromGitea {
domain = "forgejo.ellis.link"; domain = "forgejo.ellis.link";
owner = "continuwuation"; owner = "continuwuation";
repo = "rocksdb"; repo = "rocksdb";
rev = "3756b2b905e13216d8b56bcc783d814e7b073aff"; rev = "463f47afceebfe088f6922420265546bd237f249";
hash = "sha256-rSv4fr2bf9JJwdodgeuPCuceeh7k97KVxrAOC0wyPQY="; hash = "sha256-1ef75IDMs5Hba4VWEyXPJb02JyShy5k4gJfzGDhopRk=";
}; };
# We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155 # We have this already at https://forgejo.ellis.link/continuwuation/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
+9 -13
View File
@@ -2,26 +2,22 @@
{ {
perSystem = perSystem =
{ {
system,
lib, lib,
inputs',
pkgs, pkgs,
... ...
}: }:
let
mkToolchain =
target:
target.fromToolchainName {
name = (lib.importTOML "${inputs.self}/rust-toolchain.toml").toolchain.channel;
sha256 = "sha256-OATSZm98Es5kIFuqaba+UvkQtFsVgJEBMmS+t6od5/U=";
};
in
{ {
_module.args = { inherit mkToolchain; };
packages = packages =
let let
fnx = inputs'.fenix.packages; fnx = inputs.fenix.packages.${system};
stable-toolchain = (mkToolchain fnx).toolchain;
stable-toolchain = fnx.fromToolchainFile {
file = inputs.self + "/rust-toolchain.toml";
# See also `rust-toolchain.toml`
sha256 = "sha256-sqSWJDUxc+zaz1nBWMAJKTAGBuGWP25GCftIOlCEAtA=";
};
in in
{ {
inherit stable-toolchain; inherit stable-toolchain;
+207 -653
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -25,6 +25,6 @@
"@rspress/core": "^2.0.0", "@rspress/core": "^2.0.0",
"@rspress/plugin-client-redirects": "^2.0.0", "@rspress/plugin-client-redirects": "^2.0.0",
"@rspress/plugin-sitemap": "^2.0.0", "@rspress/plugin-sitemap": "^2.0.0",
"typescript": "^7.0.0" "typescript": "^6.0.0"
} }
} }
+3 -19
View File
@@ -5,7 +5,7 @@
"osvVulnerabilityAlerts": true, "osvVulnerabilityAlerts": true,
"lockFileMaintenance": { "lockFileMaintenance": {
"enabled": true, "enabled": true,
"schedule": ["* * * * 0,6"] "schedule": ["at any time"]
}, },
"platformAutomerge": true, "platformAutomerge": true,
"nix": { "nix": {
@@ -66,17 +66,6 @@
"matchUpdateTypes": ["minor", "patch"], "matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions-non-major" "groupName": "github-actions-non-major"
}, },
{
"description": "Batch GitHub Actions digest updates",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["digest"],
"groupName": "github-actions-digest",
"automerge": true,
"automergeStrategy": "fast-forward",
"schedule": [
"* 0-7 * * 2"
]
},
{ {
"description": "Batch patch-level Node.js dependency updates", "description": "Batch patch-level Node.js dependency updates",
"matchManagers": ["npm"], "matchManagers": ["npm"],
@@ -94,10 +83,7 @@
"matchPackageNames": ["crate-ci/typos"], "matchPackageNames": ["crate-ci/typos"],
"matchUpdateTypes": ["minor", "patch"], "matchUpdateTypes": ["minor", "patch"],
"automerge": true, "automerge": true,
"automergeStrategy": "fast-forward", "automergeStrategy": "fast-forward"
"schedule": [
"* 0-7 * * 3"
]
}, },
{ {
"description": "Auto-merge renovatebot docker image updates", "description": "Auto-merge renovatebot docker image updates",
@@ -105,9 +91,7 @@
"matchPackageNames": ["ghcr.io/renovatebot/renovate"], "matchPackageNames": ["ghcr.io/renovatebot/renovate"],
"automerge": true, "automerge": true,
"automergeStrategy": "fast-forward", "automergeStrategy": "fast-forward",
"schedule": [ "extends": ["schedule:earlyMondays"]
"* 0-7 * * 1"
]
} }
], ],
"customManagers": [ "customManagers": [
+1 -1
View File
@@ -10,7 +10,7 @@
[toolchain] [toolchain]
profile = "minimal" profile = "minimal"
channel = "1.97.0" channel = "1.92.0"
components = [ components = [
# For rust-analyzer # For rust-analyzer
"rust-src", "rust-src",
-1
View File
@@ -92,7 +92,6 @@ serde-saphyr.workspace = true
tokio.workspace = true tokio.workspace = true
tracing-subscriber.workspace = true tracing-subscriber.workspace = true
tracing.workspace = true tracing.workspace = true
resolvematrix.workspace = true
[lints] [lints]
workspace = true workspace = true
+11 -26
View File
@@ -1,5 +1,5 @@
use clap::Parser; use clap::Parser;
use conduwuit::{Err, Result}; use conduwuit::Result;
use crate::{ use crate::{
appservice::{self, AppserviceCommand}, appservice::{self, AppserviceCommand},
@@ -8,7 +8,6 @@
debug::{self, DebugCommand}, debug::{self, DebugCommand},
federation::{self, FederationCommand}, federation::{self, FederationCommand},
media::{self, MediaCommand}, media::{self, MediaCommand},
oidc::{self, OidcCommand},
query::{self, QueryCommand}, query::{self, QueryCommand},
room::{self, RoomCommand}, room::{self, RoomCommand},
server::{self, ServerCommand}, server::{self, ServerCommand},
@@ -17,50 +16,46 @@
}; };
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(name = conduwuit_core::BRANDING, version = conduwuit_core::version())] #[command(name = conduwuit_core::name(), version = conduwuit_core::version())]
pub enum AdminCommand { pub enum AdminCommand {
/// Commands for managing appservices
#[command(subcommand)] #[command(subcommand)]
/// Commands for managing appservices
Appservices(AppserviceCommand), Appservices(AppserviceCommand),
/// Commands for managing local users
#[command(subcommand)] #[command(subcommand)]
/// Commands for managing local users
Users(UserCommand), Users(UserCommand),
/// Commands for managing registration tokens
#[command(subcommand)] #[command(subcommand)]
/// Commands for managing registration tokens
Token(TokenCommand), Token(TokenCommand),
/// Commands for managing OIDC
#[command(subcommand)] #[command(subcommand)]
Oidc(OidcCommand),
/// Commands for managing rooms /// Commands for managing rooms
#[command(subcommand)]
Rooms(RoomCommand), Rooms(RoomCommand),
/// Commands for managing federation
#[command(subcommand)] #[command(subcommand)]
/// Commands for managing federation
Federation(FederationCommand), Federation(FederationCommand),
/// Commands for managing the server
#[command(subcommand)] #[command(subcommand)]
/// Commands for managing the server
Server(ServerCommand), Server(ServerCommand),
/// Commands for managing media
#[command(subcommand)] #[command(subcommand)]
/// Commands for managing media
Media(MediaCommand), Media(MediaCommand),
/// Commands for checking integrity
#[command(subcommand)] #[command(subcommand)]
/// Commands for checking integrity
Check(CheckCommand), Check(CheckCommand),
/// Commands for debugging things
#[command(subcommand)] #[command(subcommand)]
/// Commands for debugging things
Debug(DebugCommand), Debug(DebugCommand),
/// Low-level queries for database getters and iterators
#[command(subcommand)] #[command(subcommand)]
/// Low-level queries for database getters and iterators
Query(QueryCommand), Query(QueryCommand),
} }
@@ -85,16 +80,6 @@ pub(super) async fn process(command: AdminCommand, context: &Context<'_>) -> Res
context.bail_restricted()?; context.bail_restricted()?;
token::process(command, context).await token::process(command, context).await
}, },
| Oidc(command) => {
// OIDC commands are all restricted
context.bail_restricted()?;
if !context.services.oidc.enabled() {
return Err!("OIDC is not configured");
}
oidc::process(command, context).await
},
| Rooms(command) => room::process(command, context).await, | Rooms(command) => room::process(command, context).await,
| Federation(command) => federation::process(command, context).await, | Federation(command) => federation::process(command, context).await,
| Server(command) => server::process(command, context).await, | Server(command) => server::process(command, context).await,
+13 -34
View File
@@ -1,8 +1,10 @@
use conduwuit::{Err, Result, checked}; use conduwuit::{Err, Result, checked};
use futures::{FutureExt, StreamExt, TryFutureExt}; use futures::{FutureExt, StreamExt, TryFutureExt};
impl crate::Context<'_> { use crate::admin_command;
pub(super) async fn register(&self) -> Result {
#[admin_command]
pub(super) async fn register(&self) -> Result {
let body = &self.body; let body = &self.body;
let body_len = self.body.len(); let body_len = self.body.len();
if body_len < 2 if body_len < 2
@@ -29,9 +31,10 @@ pub(super) async fn register(&self) -> Result {
}, },
} }
.await .await
} }
pub(super) async fn unregister(&self, appservice_identifier: String) -> Result { #[admin_command]
pub(super) async fn unregister(&self, appservice_identifier: String) -> Result {
match self match self
.services .services
.appservice .appservice
@@ -42,9 +45,10 @@ pub(super) async fn unregister(&self, appservice_identifier: String) -> Result {
| Ok(()) => write!(self, "Appservice unregistered."), | Ok(()) => write!(self, "Appservice unregistered."),
} }
.await .await
} }
pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result { #[admin_command]
pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result {
match self match self
.services .services
.appservice .appservice
@@ -58,9 +62,10 @@ pub(super) async fn show_appservice_config(&self, appservice_identifier: String)
}, },
} }
.await .await
} }
pub(super) async fn list_registered(&self) -> Result { #[admin_command]
pub(super) async fn list_registered(&self) -> Result {
self.services self.services
.appservice .appservice
.iter_ids() .iter_ids()
@@ -72,30 +77,4 @@ pub(super) async fn list_registered(&self) -> Result {
write!(self, "Appservices ({len}): {list}") write!(self, "Appservices ({len}): {list}")
}) })
.await .await
}
pub(super) async fn ensure_puppets_active(&self) -> Result {
self.services
.users
.stream_local_users()
.filter_map(async |user_id| {
if self.services.appservice.is_user_id(&user_id).await {
Some(user_id)
} else {
None
}
})
.for_each(async |user_id| {
self.services
.users
.convert_to_shadow_account(&user_id)
.await
.expect("should be able to convert to shadow");
})
.await;
write!(self, "All appservice puppets have been marked as active.").await?;
Ok(())
}
} }
-6
View File
@@ -37,10 +37,4 @@ pub enum AppserviceCommand {
/// List all the currently registered appservices /// List all the currently registered appservices
#[clap(alias("list"))] #[clap(alias("list"))]
ListRegistered, ListRegistered,
/// Ensure no appservice puppets are marked as deactivated.
/// This is a debug command to fix issues caused by a faulty database
/// migration in Continuwuity 26.6.0.
#[clap(hide = true)]
EnsurePuppetsActive,
} }
+4 -9
View File
@@ -1,17 +1,13 @@
use conduwuit::Result; use conduwuit::Result;
use conduwuit_macros::implement;
use futures::StreamExt; use futures::StreamExt;
use crate::Context; use crate::Context;
impl Context<'_> { #[implement(Context, params = "<'_>")]
pub(super) async fn check_all_users(&self) -> Result { pub(super) async fn check_all_users(&self) -> Result {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let users = self let users = self.services.users.stream().collect::<Vec<_>>().await;
.services
.users
.stream_local_users()
.collect::<Vec<_>>()
.await;
let query_time = timer.elapsed(); let query_time = timer.elapsed();
let total = users.len(); let total = users.len();
@@ -24,5 +20,4 @@ pub(super) async fn check_all_users(&self) -> Result {
{ok_count:?}\n```" {ok_count:?}\n```"
)) ))
.await .await
}
} }
+135 -455
View File
@@ -1,12 +1,12 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::HashMap,
fmt::Write, fmt::Write,
iter::once, iter::once,
time::{Instant, SystemTime}, time::{Instant, SystemTime},
}; };
use conduwuit::{ use conduwuit::{
Err, Result, at, debug_error, err, info, Err, Result, debug_error, err, info,
matrix::{ matrix::{
Event, Event,
pdu::{PduEvent, PduId, RawPduId}, pdu::{PduEvent, PduId, RawPduId},
@@ -20,13 +20,9 @@
}; };
use futures::{FutureExt, StreamExt, TryStreamExt}; use futures::{FutureExt, StreamExt, TryStreamExt};
use lettre::message::Mailbox; use lettre::message::Mailbox;
use resolvematrix::{
resolution::ResolvedDestination,
server::{MatrixResolver, MatrixResolverBuilder},
};
use ruma::{ use ruma::{
CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId,
OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, UInt, OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId,
api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw, api::federation::event::get_room_state, events::AnyStateEvent, serde::Raw,
}; };
use service::rooms::{ use service::rooms::{
@@ -35,61 +31,16 @@
}; };
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use crate::PAGE_SIZE; use crate::admin_command;
#[derive(Clone, Copy, Eq, PartialEq)] #[admin_command]
enum NodeStatus { pub(super) async fn echo(&self, message: Vec<String>) -> Result {
Normal(bool),
SoftFailed(bool),
Rejected(bool),
}
struct AuthChild {
node_id: String,
event_id: OwnedEventId,
depth: UInt,
ts: UInt,
first_seen: bool,
pdu: Option<PduEvent>,
}
fn render_node(
graph: &mut String,
node_id: &str,
event_id: &EventId,
name: &str,
status: NodeStatus,
) -> Result {
let evt_str = event_id.to_string();
let status_label = match status {
| NodeStatus::Normal(false) => format!("{evt_str}: {name}"),
| NodeStatus::Normal(true) => format!("{evt_str}: {name} (missing locally)"),
| NodeStatus::SoftFailed(false) => format!("{evt_str}: {name} (soft-failed)"),
| NodeStatus::SoftFailed(true) =>
format!("{evt_str}: {name} (soft-failed & missing locally)"),
| NodeStatus::Rejected(false) => format!("{evt_str}: {name} (rejected)"),
| NodeStatus::Rejected(true) => format!("{evt_str}: {name} (rejected & missing locally)"),
};
writeln!(graph, "{node_id}[\"{}\"]", status_label.as_str())?;
match status {
| NodeStatus::Rejected(_) => writeln!(graph, "class {node_id} rejected;")?,
| NodeStatus::SoftFailed(_) => writeln!(graph, "class {node_id} soft_failed;")?,
| NodeStatus::Normal(_) => {},
}
Ok(())
}
impl crate::Context<'_> {
pub(super) async fn echo(&self, message: Vec<String>) -> Result {
let message = message.join(" "); let message = message.join(" ");
self.write_str(&message).await self.write_str(&message).await
} }
pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result { #[admin_command]
pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
let Ok(event) = self.services.rooms.timeline.get_pdu_json(&event_id).await else { let Ok(event) = self.services.rooms.timeline.get_pdu_json(&event_id).await else {
return Err!("Event not found."); return Err!("Event not found.");
}; };
@@ -116,162 +67,10 @@ pub(super) async fn get_auth_chain(&self, event_id: OwnedEventId) -> Result {
let out = format!("Loaded auth chain with length {count} in {elapsed:?}"); let out = format!("Loaded auth chain with length {count} in {elapsed:?}");
self.write_str(&out).await self.write_str(&out).await
} }
pub(super) async fn show_auth_chain(&self, event_id: OwnedEventId) -> Result { #[admin_command]
let node_status = async |event_id: &EventId, missing: bool| -> NodeStatus { pub(super) async fn parse_pdu(&self) -> Result {
if self
.services
.rooms
.pdu_metadata
.is_event_rejected(event_id)
.await
{
NodeStatus::Rejected(missing)
} else if self
.services
.rooms
.pdu_metadata
.is_event_soft_failed(event_id)
.await
{
NodeStatus::SoftFailed(missing)
} else {
NodeStatus::Normal(missing)
}
};
let Ok(root) = self.services.rooms.timeline.get_pdu(&event_id).await else {
return Err!("Event not found.");
};
let mut graph = String::from(
"```mermaid\n%% This is a mermaid graph. You can plug this output into\n\
%% https://mermaid.live/edit to visualise it on-the-fly.\nflowchart TD\n\
classDef rejected fill:#ffe5e5,stroke:#cc0000,stroke-width:2px,color:#000;\n\
classDef soft_failed fill:#fff6cc,stroke:#c9a400,stroke-width:2px,color:#000;\n"
);
let mut node_ids: HashMap<OwnedEventId, String> = HashMap::new();
let mut cached_events: HashMap<OwnedEventId, PduEvent> =
HashMap::from([(event_id.clone(), root.clone())]);
let mut scheduled: HashSet<OwnedEventId> = HashSet::from([event_id.clone()]);
let mut visited: HashSet<OwnedEventId> = HashSet::new();
let mut stack = vec![root];
let mut next_node_id = 0_usize;
let node_id_for = |event_id: &OwnedEventId,
node_ids: &mut HashMap<OwnedEventId, String>,
next_node_id: &mut usize| {
node_ids
.entry(event_id.clone())
.or_insert_with(|| {
let id = format!("n{}", *next_node_id);
*next_node_id = next_node_id.saturating_add(1);
id
})
.clone()
};
let node_name = |e: &PduEvent| {
if let Some(state_key) = e.state_key() {
format!("{},'{}'", e.event_type(), state_key)
} else {
format!("{}", e.event_type())
}
};
while let Some(event) = stack.pop() {
let current_event_id = event.event_id().to_owned();
if !visited.insert(current_event_id.clone()) {
continue;
}
let current_node_id =
node_id_for(&current_event_id, &mut node_ids, &mut next_node_id);
let current_status = node_status(&current_event_id, false).await;
render_node(
&mut graph,
&current_node_id,
&current_event_id,
&node_name(&event),
current_status,
)?;
let mut children = Vec::with_capacity(event.auth_events.len());
for auth_event_id in event.auth_events().rev() {
let auth_event_id = auth_event_id.to_owned();
let auth_node_id = node_id_for(&auth_event_id, &mut node_ids, &mut next_node_id);
writeln!(graph, "{current_node_id} --> {auth_node_id}")?;
let first_seen = scheduled.insert(auth_event_id.clone());
let auth_pdu = if let Some(auth_pdu) = cached_events.get(&auth_event_id) {
// NOTE: events might be referenced multiple times (like the create event)
// so this saves some cheeky db lookup time
Some(auth_pdu.clone())
} else if first_seen {
match self.services.rooms.timeline.get_pdu(&auth_event_id).await {
| Ok(auth_event) => {
cached_events.insert(auth_event_id.clone(), auth_event.clone());
Some(auth_event)
},
| Err(_) => None,
}
} else {
None
};
// NOTE: Depth is used as the primary sorting key here, even though it has no
// bearing on state resolution or anything. Timestamp is used as a
// tiebreaker, failing back to lexicographical comparison.
let (depth, ts) = auth_pdu
.as_ref()
.map_or((UInt::MAX, UInt::MAX), |pdu| (pdu.depth, pdu.origin_server_ts));
children.push(AuthChild {
node_id: auth_node_id,
event_id: auth_event_id,
depth,
ts,
first_seen,
pdu: auth_pdu,
});
}
children.sort_by(|a, b| {
a.depth
.cmp(&b.depth)
.then(a.ts.cmp(&b.ts))
.then(a.event_id.as_str().cmp(b.event_id.as_str()))
});
for child in children.into_iter().rev() {
if !child.first_seen {
continue;
}
if let Some(child_pdu) = child.pdu {
// We have this PDU so will want to traverse it.
stack.push(child_pdu);
} else {
// We don't have this PDU locally so we can't traverse its auth events,
// but we can still render it as a node.
render_node(
&mut graph,
&child.node_id,
&child.event_id,
"",
node_status(&child.event_id, true).await,
)?;
}
}
}
graph.push_str("```\n");
self.write_str(&graph).await
}
pub(super) async fn parse_pdu(&self) -> Result {
if self.body.len() < 2 if self.body.len() < 2
|| !self.body[0].trim().starts_with("```") || !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&EMPTY).trim() != "```" || self.body.last().unwrap_or(&EMPTY).trim() != "```"
@@ -289,17 +88,17 @@ pub(super) async fn parse_pdu(&self) -> Result {
| Ok(hash) => { | Ok(hash) => {
let event_id = EventId::parse(format!("${hash}")); let event_id = EventId::parse(format!("${hash}"));
match serde_json::from_value::<PduEvent>(serde_json::to_value(value)?) { match serde_json::from_value::<PduEvent>(serde_json::to_value(value)?) {
| Err(e) => | Err(e) => return Err!("EventId: {event_id:?}\nCould not parse event: {e}"),
return Err!("EventId: {event_id:?}\nCould not parse event: {e}"),
| Ok(pdu) => write!(self, "EventId: {event_id:?}\n{pdu:#?}"), | Ok(pdu) => write!(self, "EventId: {event_id:?}\n{pdu:#?}"),
} }
}, },
}, },
} }
.await .await
} }
pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result { #[admin_command]
pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
let mut outlier = false; let mut outlier = false;
let mut pdu_json = self let mut pdu_json = self
.services .services
@@ -312,43 +111,28 @@ pub(super) async fn get_pdu(&self, event_id: OwnedEventId) -> Result {
outlier = true; outlier = true;
pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id).await; pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id).await;
} }
let rejected = self
.services
.rooms
.pdu_metadata
.is_event_rejected(&event_id)
.await;
let soft_failed = self
.services
.rooms
.pdu_metadata
.is_event_soft_failed(&event_id)
.await;
match pdu_json { match pdu_json {
| Err(_) => return Err!("PDU not found locally."), | Err(_) => return Err!("PDU not found locally."),
| Ok(json) => { | Ok(json) => {
let text = serde_json::to_string_pretty(&json)?; let text = serde_json::to_string_pretty(&json)?;
let msg = if rejected { let msg = if outlier {
"Rejected PDU:" "Outlier (Rejected / Soft Failed) PDU found in our database"
} else if soft_failed {
"Soft-failed PDU:"
} else if outlier {
"Outlier PDU:"
} else { } else {
"PDU:" "PDU found in our database"
}; };
write!(self, "{msg}\n```json\n{text}\n```") write!(self, "{msg}\n```json\n{text}\n```")
}, },
} }
.await .await
} }
pub(super) async fn get_short_pdu( #[admin_command]
pub(super) async fn get_short_pdu(
&self, &self,
shortroomid: ShortRoomId, shortroomid: ShortRoomId,
shorteventid: ShortEventId, shorteventid: ShortEventId,
) -> Result { ) -> Result {
let pdu_id: RawPduId = PduId { let pdu_id: RawPduId = PduId {
shortroomid, shortroomid,
shorteventid: shorteventid.into(), shorteventid: shorteventid.into(),
@@ -370,13 +154,10 @@ pub(super) async fn get_short_pdu(
}, },
} }
.await .await
} }
pub(super) async fn get_remote_pdu_list( #[admin_command]
&self, pub(super) async fn get_remote_pdu_list(&self, server: OwnedServerName, force: bool) -> Result {
server: OwnedServerName,
force: bool,
) -> Result {
if !self.services.server.config.allow_federation { if !self.services.server.config.allow_federation {
return Err!("Federation is disabled on this homeserver.",); return Err!("Federation is disabled on this homeserver.",);
} }
@@ -429,18 +210,18 @@ pub(super) async fn get_remote_pdu_list(
} }
} }
let out = format!( let out =
"Fetched {success_count} remote PDUs successfully with {failed_count} failures" format!("Fetched {success_count} remote PDUs successfully with {failed_count} failures");
);
self.write_str(&out).await self.write_str(&out).await
} }
pub(super) async fn get_remote_pdu( #[admin_command]
pub(super) async fn get_remote_pdu(
&self, &self,
event_id: OwnedEventId, event_id: OwnedEventId,
server: OwnedServerName, server: OwnedServerName,
) -> Result { ) -> Result {
if !self.services.server.config.allow_federation { if !self.services.server.config.allow_federation {
return Err!("Federation is disabled on this homeserver."); return Err!("Federation is disabled on this homeserver.");
} }
@@ -463,17 +244,15 @@ pub(super) async fn get_remote_pdu(
{ {
| Err(e) => { | Err(e) => {
return Err!( return Err!(
"Remote server did not have PDU or failed sending request to remote server: \ "Remote server did not have PDU or failed sending request to remote server: {e}"
{e}"
); );
}, },
| Ok(response) => { | Ok(response) => {
let json: CanonicalJsonObject = serde_json::from_str(response.pdu.get()) let json: CanonicalJsonObject =
.map_err(|e| { serde_json::from_str(response.pdu.get()).map_err(|e| {
warn!( warn!(
"Requested event ID {event_id} from server but failed to convert \ "Requested event ID {event_id} from server but failed to convert from \
from RawValue to CanonicalJsonObject (malformed event/response?): \ RawValue to CanonicalJsonObject (malformed event/response?): {e}"
{e}"
); );
err!(Request(Unknown( err!(Request(Unknown(
"Received response from server but failed to parse PDU" "Received response from server but failed to parse PDU"
@@ -486,7 +265,7 @@ pub(super) async fn get_remote_pdu(
.services .services
.rooms .rooms
.event_handler .event_handler
.parse_incoming_pdu(&response.pdu, None) .parse_incoming_pdu(&response.pdu)
.boxed() .boxed()
.await; .await;
@@ -495,9 +274,7 @@ pub(super) async fn get_remote_pdu(
| Err(e) => { | Err(e) => {
warn!("Failed to parse PDU: {e}"); warn!("Failed to parse PDU: {e}");
info!("Full PDU: {:?}", &response.pdu); info!("Full PDU: {:?}", &response.pdu);
return Err!( return Err!("Failed to parse PDU remote server {server} sent us: {e}");
"Failed to parse PDU remote server {server} sent us: {e}"
);
}, },
}; };
@@ -510,46 +287,10 @@ pub(super) async fn get_remote_pdu(
}, },
} }
.await .await
} }
pub(super) async fn get_state_at(&self, event_id: OwnedEventId) -> Result { #[admin_command]
self.bail_restricted()?; pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
let shortstatehash = self
.services
.rooms
.state_accessor
.pdu_shortstatehash(&event_id)
.await?;
let state_ids: Vec<OwnedEventId> = self
.services
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.map(at!(1))
.collect()
.await;
let pdus: Vec<CanonicalJsonObject> = state_ids
.iter()
.try_stream()
.and_then(|id| self.services.rooms.timeline.get_pdu_json(id))
.try_collect()
.await?;
let json = serde_json::to_string_pretty(&pdus).map_err(|e| {
err!(Database(
"Failed to convert room state events to pretty JSON, possible invalid room \
state events in our database {e}",
))
})?;
let out = format!("```json\n{json}\n```");
self.write_str(&out).await
}
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
let room_id = self.services.rooms.alias.resolve(&room).await?; let room_id = self.services.rooms.alias.resolve(&room).await?;
@@ -568,16 +309,17 @@ pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result {
let json = serde_json::to_string_pretty(&room_state).map_err(|e| { let json = serde_json::to_string_pretty(&room_state).map_err(|e| {
err!(Database( err!(Database(
"Failed to convert room state events to pretty JSON, possible invalid room \ "Failed to convert room state events to pretty JSON, possible invalid room state \
state events in our database {e}", events in our database {e}",
)) ))
})?; })?;
let out = format!("```json\n{json}\n```"); let out = format!("```json\n{json}\n```");
self.write_str(&out).await self.write_str(&out).await
} }
pub(super) async fn ping(&self, server: OwnedServerName) -> Result { #[admin_command]
pub(super) async fn ping(&self, server: OwnedServerName) -> Result {
if server == self.services.globals.server_name() { if server == self.services.globals.server_name() {
return Err!("Not allowed to send federation requests to ourselves."); return Err!("Not allowed to send federation requests to ourselves.");
} }
@@ -610,20 +352,22 @@ pub(super) async fn ping(&self, server: OwnedServerName) -> Result {
}, },
} }
.await .await
} }
pub(super) async fn force_device_list_updates(&self) -> Result { #[admin_command]
pub(super) async fn force_device_list_updates(&self) -> Result {
// Force E2EE device list updates for all users // Force E2EE device list updates for all users
self.services self.services
.users .users
.stream_local_users() .stream()
.for_each(async |user_id| self.services.users.mark_device_key_update(&user_id).await) .for_each(async |user_id| self.services.users.mark_device_key_update(&user_id).await)
.await; .await;
write!(self, "Marked all devices for all users as having new keys to update").await write!(self, "Marked all devices for all users as having new keys to update").await
} }
pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result { #[admin_command]
pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result {
let handles = &["console"]; let handles = &["console"];
if reset { if reset {
@@ -644,8 +388,7 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
}, },
| Ok(()) => { | Ok(()) => {
let value = &self.services.server.config.log; let value = &self.services.server.config.log;
let out = let out = format!("Successfully changed log level back to config value {value}");
format!("Successfully changed log level back to config value {value}");
return self.write_str(&out).await; return self.write_str(&out).await;
}, },
} }
@@ -674,9 +417,10 @@ pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool)
} }
Err!("No log level was specified.") Err!("No log level was specified.")
} }
pub(super) async fn verify_json(&self) -> Result { #[admin_command]
pub(super) async fn verify_json(&self) -> Result {
if self.body.len() < 2 if self.body.len() < 2
|| !self.body[0].trim().starts_with("```") || !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```" || self.body.last().unwrap_or(&"").trim() != "```"
@@ -700,9 +444,10 @@ pub(super) async fn verify_json(&self) -> Result {
}, },
} }
.await .await
} }
pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result { #[admin_command]
pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
use ruma::signatures::Verified; use ruma::signatures::Verified;
let mut event = self.services.rooms.timeline.get_pdu_json(&event_id).await?; let mut event = self.services.rooms.timeline.get_pdu_json(&event_id).await?;
@@ -721,10 +466,11 @@ pub(super) async fn verify_pdu(&self, event_id: OwnedEventId) -> Result {
}; };
self.write_str(msg).await self.write_str(msg).await
} }
#[tracing::instrument(skip(self), level = "info")] #[admin_command]
pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result { #[tracing::instrument(skip(self), level = "info")]
pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
if !self if !self
@@ -734,9 +480,7 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
.server_in_room(&self.services.server.name, &room_id) .server_in_room(&self.services.server.name, &room_id)
.await .await
{ {
return Err!( return Err!("We are not participating in the room / we don't know about the room ID.",);
"We are not participating in the room / we don't know about the room ID.",
);
} }
let first_pdu = self let first_pdu = self
@@ -749,10 +493,11 @@ pub(super) async fn first_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
let out = format!("{first_pdu:?}"); let out = format!("{first_pdu:?}");
self.write_str(&out).await self.write_str(&out).await
} }
#[tracing::instrument(skip(self), level = "info")] #[admin_command]
pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result { #[tracing::instrument(skip(self), level = "info")]
pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
if !self if !self
@@ -762,9 +507,7 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
.server_in_room(&self.services.server.name, &room_id) .server_in_room(&self.services.server.name, &room_id)
.await .await
{ {
return Err!( return Err!("We are not participating in the room / we don't know about the room ID.");
"We are not participating in the room / we don't know about the room ID."
);
} }
let latest_pdu = self let latest_pdu = self
@@ -777,15 +520,16 @@ pub(super) async fn latest_pdu_in_room(&self, room_id: OwnedRoomId) -> Result {
let out = format!("{latest_pdu:?}"); let out = format!("{latest_pdu:?}");
self.write_str(&out).await self.write_str(&out).await
} }
#[tracing::instrument(skip(self), level = "info")] #[admin_command]
pub(super) async fn force_set_room_state_from_server( #[tracing::instrument(skip(self), level = "info")]
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>, at_event: Option<OwnedEventId>,
) -> Result { ) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
if !self if !self
@@ -795,9 +539,7 @@ pub(super) async fn force_set_room_state_from_server(
.server_in_room(&self.services.server.name, &room_id) .server_in_room(&self.services.server.name, &room_id)
.await .await
{ {
return Err!( return Err!("We are not participating in the room / we don't know about the room ID.");
"We are not participating in the room / we don't know about the room ID."
);
} }
let at_event_id = match at_event { let at_event_id = match at_event {
@@ -832,7 +574,7 @@ pub(super) async fn force_set_room_state_from_server(
.services .services
.rooms .rooms
.event_handler .event_handler
.parse_incoming_pdu(&pdu, Some(&room_version_rules)) .parse_incoming_pdu(&pdu)
.await .await
{ {
| Ok(t) => t, | Ok(t) => t,
@@ -854,9 +596,7 @@ pub(super) async fn force_set_room_state_from_server(
}; };
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| { let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
debug_error!( debug_error!("Invalid PDU in fetching remote room state PDUs response: {value:#?}");
"Invalid PDU in fetching remote room state PDUs response: {value:#?}"
);
err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}"))) err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}")))
})?; })?;
@@ -874,10 +614,6 @@ pub(super) async fn force_set_room_state_from_server(
.await; .await;
state.insert(shortstatekey, pdu.event_id.clone()); state.insert(shortstatekey, pdu.event_id.clone());
self.services
.rooms
.pdu_metadata
.clear_pdu_markers(pdu.event_id());
} }
} }
@@ -895,10 +631,6 @@ pub(super) async fn force_set_room_state_from_server(
.rooms .rooms
.outlier .outlier
.add_pdu_outlier(&event_id, &value); .add_pdu_outlier(&event_id, &value);
self.services
.rooms
.pdu_metadata
.clear_pdu_markers(&event_id);
} }
info!("Resolving new room state"); info!("Resolving new room state");
@@ -930,7 +662,10 @@ pub(super) async fn force_set_room_state_from_server(
.force_state(room_id.clone().as_ref(), short_state_hash, added, removed, &state_lock) .force_state(room_id.clone().as_ref(), short_state_hash, added, removed, &state_lock)
.await?; .await?;
info!("Updating joined counts for room"); info!(
"Updating joined counts for room just in case (e.g. we may have found a difference in \
the room's m.room.member state"
);
self.services self.services
.rooms .rooms
.state_cache .state_cache
@@ -939,14 +674,15 @@ pub(super) async fn force_set_room_state_from_server(
self.write_str("Successfully forced the room state from the requested remote server.") self.write_str("Successfully forced the room state from the requested remote server.")
.await .await
} }
pub(super) async fn get_signing_keys( #[admin_command]
pub(super) async fn get_signing_keys(
&self, &self,
server_name: Option<OwnedServerName>, server_name: Option<OwnedServerName>,
notary: Option<OwnedServerName>, notary: Option<OwnedServerName>,
query: bool, query: bool,
) -> Result { ) -> Result {
let server_name = server_name.unwrap_or_else(|| self.services.server.name.clone()); let server_name = server_name.unwrap_or_else(|| self.services.server.name.clone());
if let Some(notary) = notary { if let Some(notary) = notary {
@@ -974,9 +710,10 @@ pub(super) async fn get_signing_keys(
let out = format!("```rs\n{signing_keys:#?}\n```"); let out = format!("```rs\n{signing_keys:#?}\n```");
self.write_str(&out).await self.write_str(&out).await
} }
pub(super) async fn get_verify_keys(&self, server_name: Option<OwnedServerName>) -> Result { #[admin_command]
pub(super) async fn get_verify_keys(&self, server_name: Option<OwnedServerName>) -> Result {
let server_name = server_name.unwrap_or_else(|| self.services.server.name.clone()); let server_name = server_name.unwrap_or_else(|| self.services.server.name.clone());
let keys = self let keys = self
@@ -993,13 +730,14 @@ pub(super) async fn get_verify_keys(&self, server_name: Option<OwnedServerName>)
} }
self.write_str(&out).await self.write_str(&out).await
} }
pub(super) async fn resolve_true_destination( #[admin_command]
pub(super) async fn resolve_true_destination(
&self, &self,
server_name: OwnedServerName, server_name: OwnedServerName,
no_cache: bool, no_cache: bool,
) -> Result { ) -> Result {
if !self.services.server.config.allow_federation { if !self.services.server.config.allow_federation {
return Err!("Federation is disabled on this homeserver.",); return Err!("Federation is disabled on this homeserver.",);
} }
@@ -1011,30 +749,18 @@ pub(super) async fn resolve_true_destination(
); );
} }
let resolver: &MatrixResolver = if no_cache { let actual = self
&MatrixResolverBuilder::new() .services
.dangerous_tls_accept_invalid_certs(self.services.server.config.allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure) .resolver
.http_client(self.services.client.dns.clone()) .resolve_actual_dest(&server_name, !no_cache)
.build()? .await?;
} else {
&self.services.client.matrix_resolver
};
let actual = resolver.resolve_server(server_name.as_str()).await?; let msg = format!("Destination: {}\nHostname URI: {}", actual.dest, actual.host);
let destination = match actual.destination {
| ResolvedDestination::Literal(addr) => addr.to_string(),
| ResolvedDestination::Named(host, port) => format!("{host}:{port}"),
};
let msg = format!(
"Destination: {}\nHostname URI (SNI): {}\nIs override?: {}\nResolution step: {}",
destination, actual.host, actual.is_override, actual.resolution_step
);
self.write_str(&msg).await self.write_str(&msg).await
} }
pub(super) async fn memory_stats(&self, opts: Option<String>) -> Result { #[admin_command]
pub(super) async fn memory_stats(&self, opts: Option<String>) -> Result {
const OPTS: &str = "abcdefghijklmnopqrstuvwxyz"; const OPTS: &str = "abcdefghijklmnopqrstuvwxyz";
let opts: String = OPTS let opts: String = OPTS
@@ -1054,11 +780,11 @@ pub(super) async fn memory_stats(&self, opts: Option<String>) -> Result {
self.write_str(&stats).await?; self.write_str(&stats).await?;
self.write_str("\n```").await?; self.write_str("\n```").await?;
Ok(()) Ok(())
} }
#[cfg(tokio_unstable)] #[cfg(tokio_unstable)]
#[admin_command]
pub(super) async fn runtime_metrics(&self) -> Result { pub(super) async fn runtime_metrics(&self) -> Result {
let out = self.services.server.metrics.runtime_metrics().map_or_else( let out = self.services.server.metrics.runtime_metrics().map_or_else(
|| "Runtime metrics are not available.".to_owned(), || "Runtime metrics are not available.".to_owned(),
|metrics| { |metrics| {
@@ -1072,43 +798,47 @@ pub(super) async fn runtime_metrics(&self) -> Result {
); );
self.write_str(&out).await self.write_str(&out).await
} }
#[cfg(not(tokio_unstable))] #[cfg(not(tokio_unstable))]
pub(super) async fn runtime_metrics(&self) -> Result { #[admin_command]
pub(super) async fn runtime_metrics(&self) -> Result {
self.write_str("Runtime metrics require building with `tokio_unstable`.") self.write_str("Runtime metrics require building with `tokio_unstable`.")
.await .await
} }
#[cfg(tokio_unstable)] #[cfg(tokio_unstable)]
#[admin_command]
pub(super) async fn runtime_interval(&self) -> Result { pub(super) async fn runtime_interval(&self) -> Result {
let out = self.services.server.metrics.runtime_interval().map_or_else( let out = self.services.server.metrics.runtime_interval().map_or_else(
|| "Runtime metrics are not available.".to_owned(), || "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"), |metrics| format!("```rs\n{metrics:#?}\n```"),
); );
self.write_str(&out).await self.write_str(&out).await
} }
#[cfg(not(tokio_unstable))] #[cfg(not(tokio_unstable))]
pub(super) async fn runtime_interval(&self) -> Result { #[admin_command]
pub(super) async fn runtime_interval(&self) -> Result {
self.write_str("Runtime metrics require building with `tokio_unstable`.") self.write_str("Runtime metrics require building with `tokio_unstable`.")
.await .await
} }
pub(super) async fn time(&self) -> Result { #[admin_command]
pub(super) async fn time(&self) -> Result {
let now = SystemTime::now(); let now = SystemTime::now();
let now = utils::time::format(now, "%+"); let now = utils::time::format(now, "%+");
self.write_str(&now).await self.write_str(&now).await
} }
pub(super) async fn database_stats( #[admin_command]
pub(super) async fn database_stats(
&self, &self,
property: Option<String>, property: Option<String>,
map: Option<String>, map: Option<String>,
) -> Result { ) -> Result {
let map_name = map.as_ref().map_or(EMPTY, String::as_str); let map_name = map.as_ref().map_or(EMPTY, String::as_str);
let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned()); let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned());
self.services self.services
@@ -1121,9 +851,10 @@ pub(super) async fn database_stats(
writeln!(self, "##### {name}:\n```\n{}\n```", res.trim()) writeln!(self, "##### {name}:\n```\n{}\n```", res.trim())
}) })
.await .await
} }
pub(super) async fn database_files(&self, map: Option<String>, level: Option<i32>) -> Result { #[admin_command]
pub(super) async fn database_files(&self, map: Option<String>, level: Option<i32>) -> Result {
let mut files: Vec<_> = self.services.db.db.file_list().collect::<Result<_>>()?; let mut files: Vec<_> = self.services.db.db.file_list().collect::<Result<_>>()?;
files.sort_by_key(|f| f.name.clone()); files.sort_by_key(|f| f.name.clone());
@@ -1151,15 +882,17 @@ pub(super) async fn database_files(&self, map: Option<String>, level: Option<i32
) )
}) })
.await .await
} }
pub(super) async fn trim_memory(&self) -> Result { #[admin_command]
pub(super) async fn trim_memory(&self) -> Result {
conduwuit::alloc::trim(None)?; conduwuit::alloc::trim(None)?;
writeln!(self, "done").await writeln!(self, "done").await
} }
pub(super) async fn send_test_email(&self) -> Result { #[admin_command]
pub(super) async fn send_test_email(&self) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
let mailer = self.services.mailer.expect_mailer()?; let mailer = self.services.mailer.expect_mailer()?;
@@ -1184,57 +917,4 @@ pub(super) async fn send_test_email(&self) -> Result {
.await?; .await?;
Ok(()) Ok(())
}
pub(super) async fn rooms_by_extremity_count(&self, page: Option<usize>) -> Result {
let page = page.unwrap_or(1);
// My Giant Chain:tm:
let mapped: HashMap<OwnedRoomId, u64> = self
.services
.rooms
.state
.all_forward_extremities()
.ready_fold(HashMap::new(), move |mut map, (room_id, _)| {
let count: u64 = map.get(&room_id).copied().unwrap_or(0);
map.insert(room_id, count.saturating_add(1));
map
})
.await
.into_iter()
.filter_map(|(room_id, count)| (count >= 2).then_some((room_id, count)))
.collect();
if mapped.is_empty() {
return Err!("No more rooms.");
}
let mut rooms = mapped.keys().collect::<Vec<_>>();
rooms.sort_by_key(|room_id| {
mapped
.get(*room_id)
.copied()
.expect("keys must have values")
});
rooms.reverse();
let body = rooms
.into_iter()
.stream()
.skip(page.saturating_sub(1).saturating_mul(PAGE_SIZE))
.take(PAGE_SIZE)
.map(|room_id| {
format!(
"{room_id}: {}",
mapped.get(room_id).copied().expect("keys must have values")
)
})
.collect::<Vec<_>>()
.await;
self.write_str(&format!(
"Rooms by extremity count ({}):\n```\n{}\n```",
body.len(),
body.join("\n")
))
.await
}
} }
+1 -23
View File
@@ -17,21 +17,12 @@ pub enum DebugCommand {
message: Vec<String>, message: Vec<String>,
}, },
/// Loads the auth_chain of a PDU, reporting how long it took. /// Get the auth_chain of a PDU
GetAuthChain { GetAuthChain {
/// An event ID (the $ character followed by the base64 reference hash) /// An event ID (the $ character followed by the base64 reference hash)
event_id: OwnedEventId, event_id: OwnedEventId,
}, },
/// Walks & displays the auth_chain of a PDU in a mermaid graph format.
///
/// This is useless to basically anyone but developers, and is also probably
/// slow and memory hungry.
ShowAuthChain {
/// The root event ID to start walking back from.
event_id: OwnedEventId,
},
/// Parse and print a PDU from a JSON /// Parse and print a PDU from a JSON
/// ///
/// The PDU event is only checked for validity and is not added to the /// The PDU event is only checked for validity and is not added to the
@@ -95,14 +86,6 @@ pub enum DebugCommand {
room_id: OwnedRoomOrAliasId, room_id: OwnedRoomOrAliasId,
}, },
/// Gets all the room state events at the specified event.
///
/// State at event might not be available for some PDUs, such as rejected
/// ones.
GetStateAt {
event_id: OwnedEventId,
},
/// Get and display signing keys from local cache or remote server. /// Get and display signing keys from local cache or remote server.
GetSigningKeys { GetSigningKeys {
server_name: Option<OwnedServerName>, server_name: Option<OwnedServerName>,
@@ -245,11 +228,6 @@ pub enum DebugCommand {
/// Send a test email to the invoking admin's email address /// Send a test email to the invoking admin's email address
SendTestEmail, SendTestEmail,
/// Lists room IDs by forward extremity count in descending order
RoomsByExtremityCount {
page: Option<usize>,
},
/// Developer test stubs /// Developer test stubs
#[command(subcommand)] #[command(subcommand)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
+21 -16
View File
@@ -1,6 +1,6 @@
use conduwuit::{Err, Result}; use conduwuit::{Err, Result};
use crate::admin_command_dispatch; use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
@@ -11,32 +11,37 @@ pub enum TesterCommand {
Timer, Timer,
} }
impl crate::Context<'_> { #[rustfmt::skip]
#[rustfmt::skip] #[admin_command]
async fn panic(&self) -> Result { async fn panic(&self) -> Result {
panic!("panicked") panic!("panicked")
} }
#[rustfmt::skip]
#[admin_command]
async fn failure(&self) -> Result {
#[rustfmt::skip]
async fn failure(&self) -> Result {
Err!("failed") Err!("failed")
} }
#[inline(never)]
#[rustfmt::skip]
#[admin_command]
async fn tester(&self) -> Result {
#[inline(never)]
#[rustfmt::skip]
async fn tester(&self) -> Result {
self.write_str("Ok").await self.write_str("Ok").await
} }
#[inline(never)] #[inline(never)]
#[rustfmt::skip] #[rustfmt::skip]
async fn timer(&self) -> Result { #[admin_command]
async fn timer(&self) -> Result {
let started = std::time::Instant::now(); let started = std::time::Instant::now();
timed(self.body); timed(self.body);
let elapsed = started.elapsed(); let elapsed = started.elapsed();
self.write_str(&format!("completed in {elapsed:#?}")).await self.write_str(&format!("completed in {elapsed:#?}")).await
}
} }
#[inline(never)] #[inline(never)]
+17 -21
View File
@@ -4,22 +4,24 @@
use futures::StreamExt; use futures::StreamExt;
use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId}; use ruma::{OwnedRoomId, OwnedServerName, OwnedUserId};
use crate::get_room_info; use crate::{admin_command, get_room_info};
impl crate::Context<'_> { #[admin_command]
pub(super) async fn disable_room(&self, room_id: OwnedRoomId) -> Result { pub(super) async fn disable_room(&self, room_id: OwnedRoomId) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
self.services.rooms.metadata.disable_room(&room_id, true); self.services.rooms.metadata.disable_room(&room_id, true);
self.write_str("Room disabled.").await self.write_str("Room disabled.").await
} }
pub(super) async fn enable_room(&self, room_id: OwnedRoomId) -> Result { #[admin_command]
pub(super) async fn enable_room(&self, room_id: OwnedRoomId) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
self.services.rooms.metadata.disable_room(&room_id, false); self.services.rooms.metadata.disable_room(&room_id, false);
self.write_str("Room enabled.").await self.write_str("Room enabled.").await
} }
pub(super) async fn incoming_federation(&self) -> Result { #[admin_command]
pub(super) async fn incoming_federation(&self) -> Result {
let msg = { let msg = {
let map = self let map = self
.services .services
@@ -35,26 +37,20 @@ pub(super) async fn incoming_federation(&self) -> Result {
); );
for (r, (e, i)) in map.iter() { for (r, (e, i)) in map.iter() {
let elapsed = i.elapsed(); let elapsed = i.elapsed();
writeln!( writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
msg,
"{} {}: {}m{}s",
r,
e,
elapsed.as_secs() / 60,
elapsed.as_secs() % 60
)?;
} }
msg msg
}; };
self.write_str(&msg).await self.write_str(&msg).await
} }
pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName) -> Result { #[admin_command]
pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName) -> Result {
let response = self let response = self
.services .services
.client .client
.external_resource .default
.get(format!("https://{server_name}/.well-known/matrix/support")) .get(format!("https://{server_name}/.well-known/matrix/support"))
.send() .send()
.await?; .await?;
@@ -95,9 +91,10 @@ pub(super) async fn fetch_support_well_known(&self, server_name: OwnedServerName
self.write_str(&format!("Got JSON response:\n\n```json\n{pretty_json}\n```")) self.write_str(&format!("Got JSON response:\n\n```json\n{pretty_json}\n```"))
.await .await
} }
pub(super) async fn remote_user_in_rooms(&self, user_id: OwnedUserId) -> Result { #[admin_command]
pub(super) async fn remote_user_in_rooms(&self, user_id: OwnedUserId) -> Result {
if user_id.server_name() == self.services.server.name { if user_id.server_name() == self.services.server.name {
return Err!( return Err!(
"User belongs to our server, please use `list-joined-rooms` user admin command \ "User belongs to our server, please use `list-joined-rooms` user admin command \
@@ -130,5 +127,4 @@ pub(super) async fn remote_user_in_rooms(&self, user_id: OwnedUserId) -> Result
self.write_str(&format!("Rooms {user_id} shares with us ({num}):\n```\n{body}\n```")) self.write_str(&format!("Rooms {user_id} shares with us ({num}):\n```\n{body}\n```"))
.await .await
}
} }
+52 -54
View File
@@ -9,14 +9,14 @@
use ruma::{OwnedEventId, OwnedMxcUri, OwnedServerName}; use ruma::{OwnedEventId, OwnedMxcUri, OwnedServerName};
use service::media::mxc::Mxc; use service::media::mxc::Mxc;
use crate::utils::parse_local_user_id; use crate::{admin_command, utils::parse_local_user_id};
impl crate::Context<'_> { #[admin_command]
pub(super) async fn delete( pub(super) async fn delete(
&self, &self,
mxc: Option<OwnedMxcUri>, mxc: Option<OwnedMxcUri>,
event_id: Option<OwnedEventId>, event_id: Option<OwnedEventId>,
) -> Result { ) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
if event_id.is_some() && mxc.is_some() { if event_id.is_some() && mxc.is_some() {
@@ -59,8 +59,8 @@ pub(super) async fn delete(
mxc_urls.push(final_url); mxc_urls.push(final_url);
} else { } else {
info!( info!(
"Found a URL in the event ID {event_id} but did not \ "Found a URL in the event ID {event_id} but did not start \
start with mxc://, ignoring" with mxc://, ignoring"
); );
} }
} }
@@ -73,29 +73,26 @@ pub(super) async fn delete(
if let Some(info) = info_obj { if let Some(info) = info_obj {
if let Some(thumbnail_url) = info.get("thumbnail_url") { if let Some(thumbnail_url) = info.get("thumbnail_url") {
debug!( debug!("Found a thumbnail_url in info key: {thumbnail_url}");
"Found a thumbnail_url in info key: {thumbnail_url}"
);
if thumbnail_url.to_string().starts_with("\"mxc://") { if thumbnail_url.to_string().starts_with("\"mxc://") {
debug!( debug!(
"Pushing thumbnail URL {thumbnail_url} to list \ "Pushing thumbnail URL {thumbnail_url} to list of \
of MXCs to delete" MXCs to delete"
); );
let final_thumbnail_url = let final_thumbnail_url =
thumbnail_url.to_string().replace('"', ""); thumbnail_url.to_string().replace('"', "");
mxc_urls.push(final_thumbnail_url); mxc_urls.push(final_thumbnail_url);
} else { } else {
info!( info!(
"Found a thumbnail URL in the event ID \ "Found a thumbnail URL in the event ID {event_id} \
{event_id} but did not start with mxc://, \ but did not start with mxc://, ignoring"
ignoring"
); );
} }
} else { } else {
info!( info!(
"No \"thumbnail_url\" key in \"info\" key, assuming \ "No \"thumbnail_url\" key in \"info\" key, assuming no \
no thumbnails." thumbnails."
); );
} }
} }
@@ -117,8 +114,8 @@ pub(super) async fn delete(
mxc_urls.push(final_url); mxc_urls.push(final_url);
} else { } else {
warn!( warn!(
"Found a URL in the event ID {event_id} but did \ "Found a URL in the event ID {event_id} but did not \
not start with mxc://, ignoring" start with mxc://, ignoring"
); );
} }
} else { } else {
@@ -134,8 +131,8 @@ pub(super) async fn delete(
} }
} else { } else {
return Err!( return Err!(
"Event ID does not have a \"content\" key, this is not a message or \ "Event ID does not have a \"content\" key, this is not a message or an \
an event type that contains media.", event type that contains media.",
); );
} }
}, },
@@ -158,15 +155,11 @@ pub(super) async fn delete(
.await .await
{ {
| Ok(()) => { | Ok(()) => {
debug_info!( debug_info!("Successfully deleted {mxc_url} from filesystem and database");
"Successfully deleted {mxc_url} from filesystem and database"
);
mxc_deletion_count = mxc_deletion_count.saturating_add(1); mxc_deletion_count = mxc_deletion_count.saturating_add(1);
}, },
| Err(e) => { | Err(e) => {
debug_warn!( debug_warn!("Failed to delete {mxc_url}, ignoring error and skipping: {e}");
"Failed to delete {mxc_url}, ignoring error and skipping: {e}"
);
continue; continue;
}, },
} }
@@ -174,8 +167,8 @@ pub(super) async fn delete(
return self return self
.write_str(&format!( .write_str(&format!(
"Deleted {mxc_deletion_count} total MXCs from our database and the \ "Deleted {mxc_deletion_count} total MXCs from our database and the filesystem \
filesystem from event ID {event_id}." from event ID {event_id}."
)) ))
.await; .await;
} }
@@ -184,9 +177,10 @@ pub(super) async fn delete(
"Please specify either an MXC using --mxc or an event ID using --event-id of the \ "Please specify either an MXC using --mxc or an event ID using --event-id of the \
message containing an image. See --help for details." message containing an image. See --help for details."
) )
} }
pub(super) async fn delete_list(&self) -> Result { #[admin_command]
pub(super) async fn delete_list(&self) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
if self.body.len() < 2 if self.body.len() < 2
@@ -230,20 +224,20 @@ pub(super) async fn delete_list(&self) -> Result {
} }
self.write_str(&format!( self.write_str(&format!(
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our \ "Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database \
database and the filesystem. {failed_parsed_mxcs} MXCs failed to be parsed from \ and the filesystem. {failed_parsed_mxcs} MXCs failed to be parsed from the database.",
the database.",
)) ))
.await .await
} }
pub(super) async fn delete_past_remote_media( #[admin_command]
pub(super) async fn delete_past_remote_media(
&self, &self,
duration: String, duration: String,
before: bool, before: bool,
after: bool, after: bool,
yes_i_want_to_delete_local_media: bool, yes_i_want_to_delete_local_media: bool,
) -> Result { ) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
if before && after { if before && after {
@@ -270,26 +264,27 @@ pub(super) async fn delete_past_remote_media(
self.write_str(&format!("Deleted {deleted_count} total files.")) self.write_str(&format!("Deleted {deleted_count} total files."))
.await .await
} }
pub(super) async fn delete_all_from_user(&self, username: String) -> Result { #[admin_command]
pub(super) async fn delete_all_from_user(&self, username: String) -> Result {
let user_id = parse_local_user_id(self.services, &username)?; let user_id = parse_local_user_id(self.services, &username)?;
let deleted_count = self.services.media.delete_from_user(&user_id).await?; let deleted_count = self.services.media.delete_from_user(&user_id).await?;
self.write_str(&format!("Deleted {deleted_count} total files.")) self.write_str(&format!("Deleted {deleted_count} total files."))
.await .await
} }
pub(super) async fn delete_all_from_server( #[admin_command]
pub(super) async fn delete_all_from_server(
&self, &self,
server_name: OwnedServerName, server_name: OwnedServerName,
yes_i_want_to_delete_local_media: bool, yes_i_want_to_delete_local_media: bool,
) -> Result { ) -> Result {
self.bail_restricted()?; self.bail_restricted()?;
if server_name == self.services.globals.server_name() && !yes_i_want_to_delete_local_media if server_name == self.services.globals.server_name() && !yes_i_want_to_delete_local_media {
{
return Err!("This command only works for remote media by default.",); return Err!("This command only works for remote media by default.",);
} }
@@ -338,21 +333,23 @@ pub(super) async fn delete_all_from_server(
self.write_str(&format!("Deleted {deleted_count} total files.")) self.write_str(&format!("Deleted {deleted_count} total files."))
.await .await
} }
pub(super) async fn get_file_info(&self, mxc: OwnedMxcUri) -> Result { #[admin_command]
pub(super) async fn get_file_info(&self, mxc: OwnedMxcUri) -> Result {
let mxc: Mxc<'_> = mxc.as_str().try_into()?; let mxc: Mxc<'_> = mxc.as_str().try_into()?;
let metadata = self.services.media.get_metadata(&mxc).await; let metadata = self.services.media.get_metadata(&mxc).await;
self.write_str(&format!("```\n{metadata:#?}\n```")).await self.write_str(&format!("```\n{metadata:#?}\n```")).await
} }
pub(super) async fn get_remote_file( #[admin_command]
pub(super) async fn get_remote_file(
&self, &self,
mxc: OwnedMxcUri, mxc: OwnedMxcUri,
server: Option<OwnedServerName>, server: Option<OwnedServerName>,
timeout: u32, timeout: u32,
) -> Result { ) -> Result {
let mxc: Mxc<'_> = mxc.as_str().try_into()?; let mxc: Mxc<'_> = mxc.as_str().try_into()?;
let timeout = Duration::from_millis(timeout.into()); let timeout = Duration::from_millis(timeout.into());
let mut result = self let mut result = self
@@ -367,16 +364,17 @@ pub(super) async fn get_remote_file(
self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```")) self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```"))
.await .await
} }
pub(super) async fn get_remote_thumbnail( #[admin_command]
pub(super) async fn get_remote_thumbnail(
&self, &self,
mxc: OwnedMxcUri, mxc: OwnedMxcUri,
server: Option<OwnedServerName>, server: Option<OwnedServerName>,
timeout: u32, timeout: u32,
width: u32, width: u32,
height: u32, height: u32,
) -> Result { ) -> Result {
let mxc: Mxc<'_> = mxc.as_str().try_into()?; let mxc: Mxc<'_> = mxc.as_str().try_into()?;
let timeout = Duration::from_millis(timeout.into()); let timeout = Duration::from_millis(timeout.into());
let dim = Dim::new(width, height, None); let dim = Dim::new(width, height, None);
@@ -392,9 +390,10 @@ pub(super) async fn get_remote_thumbnail(
self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```")) self.write_str(&format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```"))
.await .await
} }
pub(super) async fn delete_url_preview(&self, url: Option<String>, all: bool) -> Result { #[admin_command]
pub(super) async fn delete_url_preview(&self, url: Option<String>, all: bool) -> Result {
if all { if all {
self.services.media.clear_url_previews().await; self.services.media.clear_url_previews().await;
@@ -407,5 +406,4 @@ pub(super) async fn delete_url_preview(&self, url: Option<String>, all: bool) ->
self.write_str(&format!("Deleted cached URL preview for: {url}")) self.write_str(&format!("Deleted cached URL preview for: {url}"))
.await .await
}
} }
+1 -2
View File
@@ -16,7 +16,6 @@
pub(crate) mod debug; pub(crate) mod debug;
pub(crate) mod federation; pub(crate) mod federation;
pub(crate) mod media; pub(crate) mod media;
pub(crate) mod oidc;
pub(crate) mod query; pub(crate) mod query;
pub(crate) mod room; pub(crate) mod room;
pub(crate) mod server; pub(crate) mod server;
@@ -27,7 +26,7 @@
extern crate conduwuit_core as conduwuit; extern crate conduwuit_core as conduwuit;
extern crate conduwuit_service as service; extern crate conduwuit_service as service;
pub(crate) use conduwuit_macros::admin_command_dispatch; pub(crate) use conduwuit_macros::{admin_command, admin_command_dispatch};
pub(crate) use crate::{context::Context, utils::get_room_info}; pub(crate) use crate::{context::Context, utils::get_room_info};
-25
View File
@@ -1,25 +0,0 @@
use conduwuit::Result;
use crate::utils::parse_active_local_user_id;
impl crate::Context<'_> {
pub(super) async fn oidc_link(&self, user_id: String, subject: String) -> Result {
let user_id = parse_active_local_user_id(self.services, &user_id).await?;
self.services.oidc.link_user(&user_id, &subject);
self.write_str(&format!("Subject `{subject}` linked to account `{user_id}`."))
.await?;
Ok(())
}
pub(super) async fn oidc_unlink(&self, subject: String) -> Result {
self.services.oidc.unlink_user(&subject);
self.write_str(&format!("Subject `{subject}` unlinked."))
.await?;
Ok(())
}
}
-22
View File
@@ -1,22 +0,0 @@
mod commands;
use clap::Subcommand;
use conduwuit::Result;
use conduwuit_macros::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
pub enum OidcCommand {
/// Link a user ID to the given subject claim.
#[clap(name = "link")]
OidcLink {
user_id: String,
subject: String,
},
/// Unlink the given subject claim from its associated user ID.
#[clap(name = "unlink")]
OidcUnlink {
subject: String,
},
}
+8 -8
View File
@@ -4,7 +4,7 @@
use futures::StreamExt; use futures::StreamExt;
use ruma::{OwnedRoomId, OwnedUserId, exports::serde::Serialize}; use ruma::{OwnedRoomId, OwnedUserId, exports::serde::Serialize};
use crate::admin_command_dispatch; use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
@@ -31,13 +31,13 @@ pub enum AccountDataCommand {
}, },
} }
impl crate::Context<'_> { #[admin_command]
async fn changes_since( async fn changes_since(
&self, &self,
user_id: OwnedUserId, user_id: OwnedUserId,
since: u64, since: u64,
room_id: Option<OwnedRoomId>, room_id: Option<OwnedRoomId>,
) -> Result { ) -> Result {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Vec<_> = self let results: Vec<_> = self
.services .services
@@ -49,14 +49,15 @@ async fn changes_since(
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")) self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"))
.await .await
} }
async fn account_data_get( #[admin_command]
async fn account_data_get(
&self, &self,
user_id: OwnedUserId, user_id: OwnedUserId,
kind: String, kind: String,
room_id: Option<OwnedRoomId>, room_id: Option<OwnedRoomId>,
) -> Result { ) -> Result {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let result = self let result = self
.services .services
@@ -76,5 +77,4 @@ async fn account_data_get(
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{json}\n```")) self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{json}\n```"))
.await .await
}
} }
+43 -51
View File
@@ -13,7 +13,7 @@
use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; use futures::{FutureExt, Stream, StreamExt, TryStreamExt};
use tokio::time::Instant; use tokio::time::Instant;
use crate::admin_command_dispatch; use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
@@ -159,8 +159,8 @@ pub enum RawCommand {
}, },
} }
impl crate::Context<'_> { #[admin_command]
pub(super) async fn compact( pub(super) async fn compact(
&self, &self,
map: Option<Vec<String>>, map: Option<Vec<String>>,
start: Option<String>, start: Option<String>,
@@ -169,7 +169,7 @@ pub(super) async fn compact(
into: Option<usize>, into: Option<usize>,
parallelism: Option<usize>, parallelism: Option<usize>,
exhaustive: bool, exhaustive: bool,
) -> Result { ) -> Result {
use conduwuit_database::compact::Options; use conduwuit_database::compact::Options;
let default_all_maps: Option<_> = map.is_none().then(|| { let default_all_maps: Option<_> = map.is_none().then(|| {
@@ -221,9 +221,10 @@ pub(super) async fn compact(
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("Jobs completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")) self.write_str(&format!("Jobs completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"))
.await .await
} }
pub(super) async fn raw_count(&self, map: Option<String>, prefix: Option<String>) -> Result { #[admin_command]
pub(super) async fn raw_count(&self, map: Option<String>, prefix: Option<String>) -> Result {
let prefix = prefix.as_deref().unwrap_or(EMPTY); let prefix = prefix.as_deref().unwrap_or(EMPTY);
let timer = Instant::now(); let timer = Instant::now();
@@ -235,9 +236,10 @@ pub(super) async fn raw_count(&self, map: Option<String>, prefix: Option<String>
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{count:#?}\n```")) self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{count:#?}\n```"))
.await .await
} }
pub(super) async fn raw_keys(&self, map: String, prefix: Option<String>) -> Result { #[admin_command]
pub(super) async fn raw_keys(&self, map: String, prefix: Option<String>) -> Result {
writeln!(self, "```").boxed().await?; writeln!(self, "```").boxed().await?;
let map = self.services.db.get(map.as_str())?; let map = self.services.db.get(map.as_str())?;
@@ -253,13 +255,10 @@ pub(super) async fn raw_keys(&self, map: String, prefix: Option<String>) -> Resu
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}")) self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}"))
.await .await
} }
pub(super) async fn raw_keys_sizes( #[admin_command]
&self, pub(super) async fn raw_keys_sizes(&self, map: Option<String>, prefix: Option<String>) -> Result {
map: Option<String>,
prefix: Option<String>,
) -> Result {
let prefix = prefix.as_deref().unwrap_or(EMPTY); let prefix = prefix.as_deref().unwrap_or(EMPTY);
let timer = Instant::now(); let timer = Instant::now();
@@ -278,13 +277,10 @@ pub(super) async fn raw_keys_sizes(
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("```\n{result:#?}\n```\n\nQuery completed in {query_time:?}")) self.write_str(&format!("```\n{result:#?}\n```\n\nQuery completed in {query_time:?}"))
.await .await
} }
pub(super) async fn raw_keys_total( #[admin_command]
&self, pub(super) async fn raw_keys_total(&self, map: Option<String>, prefix: Option<String>) -> Result {
map: Option<String>,
prefix: Option<String>,
) -> Result {
let prefix = prefix.as_deref().unwrap_or(EMPTY); let prefix = prefix.as_deref().unwrap_or(EMPTY);
let timer = Instant::now(); let timer = Instant::now();
@@ -299,13 +295,10 @@ pub(super) async fn raw_keys_total(
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("```\n{result:#?}\n\n```\n\nQuery completed in {query_time:?}")) self.write_str(&format!("```\n{result:#?}\n\n```\n\nQuery completed in {query_time:?}"))
.await .await
} }
pub(super) async fn raw_vals_sizes( #[admin_command]
&self, pub(super) async fn raw_vals_sizes(&self, map: Option<String>, prefix: Option<String>) -> Result {
map: Option<String>,
prefix: Option<String>,
) -> Result {
let prefix = prefix.as_deref().unwrap_or(EMPTY); let prefix = prefix.as_deref().unwrap_or(EMPTY);
let timer = Instant::now(); let timer = Instant::now();
@@ -325,13 +318,10 @@ pub(super) async fn raw_vals_sizes(
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("```\n{result:#?}\n```\n\nQuery completed in {query_time:?}")) self.write_str(&format!("```\n{result:#?}\n```\n\nQuery completed in {query_time:?}"))
.await .await
} }
pub(super) async fn raw_vals_total( #[admin_command]
&self, pub(super) async fn raw_vals_total(&self, map: Option<String>, prefix: Option<String>) -> Result {
map: Option<String>,
prefix: Option<String>,
) -> Result {
let prefix = prefix.as_deref().unwrap_or(EMPTY); let prefix = prefix.as_deref().unwrap_or(EMPTY);
let timer = Instant::now(); let timer = Instant::now();
@@ -347,19 +337,17 @@ pub(super) async fn raw_vals_total(
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("```\n{result:#?}\n\n```\n\nQuery completed in {query_time:?}")) self.write_str(&format!("```\n{result:#?}\n\n```\n\nQuery completed in {query_time:?}"))
.await .await
} }
pub(super) async fn raw_iter(&self, map: String, prefix: Option<String>) -> Result { #[admin_command]
pub(super) async fn raw_iter(&self, map: String, prefix: Option<String>) -> Result {
writeln!(self, "```").await?; writeln!(self, "```").await?;
let map = self.services.db.get(&map)?; let map = self.services.db.get(&map)?;
let timer = Instant::now(); let timer = Instant::now();
prefix prefix
.as_deref() .as_deref()
.map_or_else( .map_or_else(|| map.raw_stream().boxed(), |prefix| map.raw_stream_prefix(prefix).boxed())
|| map.raw_stream().boxed(),
|prefix| map.raw_stream_prefix(prefix).boxed(),
)
.map_ok(apply!(2, String::from_utf8_lossy)) .map_ok(apply!(2, String::from_utf8_lossy))
.map_ok(apply!(2, Cow::into_owned)) .map_ok(apply!(2, Cow::into_owned))
.try_for_each(|keyval| writeln!(self, "{keyval:?}")) .try_for_each(|keyval| writeln!(self, "{keyval:?}"))
@@ -369,14 +357,15 @@ pub(super) async fn raw_iter(&self, map: String, prefix: Option<String>) -> Resu
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}")) self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}"))
.await .await
} }
pub(super) async fn raw_keys_from( #[admin_command]
pub(super) async fn raw_keys_from(
&self, &self,
map: String, map: String,
start: String, start: String,
limit: Option<usize>, limit: Option<usize>,
) -> Result { ) -> Result {
writeln!(self, "```").await?; writeln!(self, "```").await?;
let map = self.services.db.get(&map)?; let map = self.services.db.get(&map)?;
@@ -391,14 +380,15 @@ pub(super) async fn raw_keys_from(
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}")) self.write_str(&format!("\n```\n\nQuery completed in {query_time:?}"))
.await .await
} }
pub(super) async fn raw_iter_from( #[admin_command]
pub(super) async fn raw_iter_from(
&self, &self,
map: String, map: String,
start: String, start: String,
limit: Option<usize>, limit: Option<usize>,
) -> Result { ) -> Result {
let map = self.services.db.get(&map)?; let map = self.services.db.get(&map)?;
let timer = Instant::now(); let timer = Instant::now();
let result = map let result = map
@@ -412,9 +402,10 @@ pub(super) async fn raw_iter_from(
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```")) self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"))
.await .await
} }
pub(super) async fn raw_del(&self, map: String, key: String) -> Result { #[admin_command]
pub(super) async fn raw_del(&self, map: String, key: String) -> Result {
let map = self.services.db.get(&map)?; let map = self.services.db.get(&map)?;
let timer = Instant::now(); let timer = Instant::now();
map.remove(&key); map.remove(&key);
@@ -422,9 +413,10 @@ pub(super) async fn raw_del(&self, map: String, key: String) -> Result {
let query_time = timer.elapsed(); let query_time = timer.elapsed();
self.write_str(&format!("Operation completed in {query_time:?}")) self.write_str(&format!("Operation completed in {query_time:?}"))
.await .await
} }
pub(super) async fn raw_get(&self, map: String, key: String) -> Result { #[admin_command]
pub(super) async fn raw_get(&self, map: String, key: String) -> Result {
let map = self.services.db.get(&map)?; let map = self.services.db.get(&map)?;
let timer = Instant::now(); let timer = Instant::now();
let handle = map.get(&key).await?; let handle = map.get(&key).await?;
@@ -433,13 +425,13 @@ pub(super) async fn raw_get(&self, map: String, key: String) -> Result {
let result = String::from_utf8_lossy(&handle); let result = String::from_utf8_lossy(&handle);
self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{result:?}\n```")) self.write_str(&format!("Query completed in {query_time:?}:\n\n```rs\n{result:?}\n```"))
.await .await
} }
pub(super) async fn raw_maps(&self) -> Result { #[admin_command]
pub(super) async fn raw_maps(&self) -> Result {
let list: Vec<_> = self.services.db.iter().map(at!(0)).copied().collect(); let list: Vec<_> = self.services.db.iter().map(at!(0)).copied().collect();
self.write_str(&format!("{list:#?}")).await self.write_str(&format!("{list:#?}")).await
}
} }
fn with_maps_or<'a>( fn with_maps_or<'a>(
+52 -45
View File
@@ -1,22 +1,23 @@
use clap::Subcommand; use clap::Subcommand;
use conduwuit::{Err, Result, utils::time}; use conduwuit::{Err, Result, utils::time};
use resolvematrix::resolution::Resolution; use futures::StreamExt;
use ruma::OwnedServerName; use ruma::OwnedServerName;
use crate::admin_command_dispatch; use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
/// Resolver service and caches /// Resolver service and caches
pub enum ResolverCommand { pub enum ResolverCommand {
/// Query the destinations or overrides cache, depending on the value of the /// Query the destinations cache
/// `overrides` flag (default false) DestinationsCache {
Cache {
server_name: Option<OwnedServerName>, server_name: Option<OwnedServerName>,
},
#[arg(short, long)] /// Query the overrides cache
overrides: Option<bool>, OverridesCache {
name: Option<String>,
}, },
/// Flush a given server from the resolver caches or flush them completely /// Flush a given server from the resolver caches or flush them completely
@@ -38,61 +39,67 @@ pub enum ResolverCommand {
}, },
} }
impl crate::Context<'_> { #[admin_command]
async fn cache( async fn destinations_cache(&self, server_name: Option<OwnedServerName>) -> Result {
&self, use service::resolver::cache::CachedDest;
server_name: Option<OwnedServerName>,
overrides: Option<bool>,
) -> Result {
writeln!(self, "| Server Name | Destination | SNI | Override | Step | Expires |").await?;
writeln!(self, "| ----------- | ----------- | --- | -------- | ---- | ------- |").await?;
let entries = self.services.client.matrix_resolver.get_all_cache_entries(); writeln!(self, "| Server Name | Destination | Hostname | Expires |").await?;
writeln!(self, "| ----------- | ----------- | -------- | ------- |").await?;
for (host, entry) in entries { let mut destinations = self.services.resolver.cache.destinations().boxed();
if let Some(ors) = overrides
&& entry.is_override != ors while let Some((name, CachedDest { dest, host, expire })) = destinations.next().await {
{ if let Some(server_name) = server_name.as_ref() {
if name != *server_name {
continue; continue;
} }
if let Some(server_name) = server_name.as_ref()
&& host != *server_name
{
continue;
} }
let Resolution { let expire = time::format(expire, "%+");
destination, self.write_str(&format!("| {name} | {dest} | {host} | {expire} |\n"))
is_override,
host: sni_host,
resolution_step,
} = entry.resolution;
let expires = time::format(entry.expires_at, "%Y-%m-%dT%H:%M:%S%.3f%z");
self.write_str(&format!(
"| {host} | {destination:?} | {sni_host} | {is_override:?} | {resolution_step} \
| {expires} |\n"
))
.await?; .await?;
} }
Ok(()) Ok(())
}
#[admin_command]
async fn overrides_cache(&self, server_name: Option<String>) -> Result {
use service::resolver::cache::CachedOverride;
writeln!(self, "| Server Name | IP | Port | Expires | Overriding |").await?;
writeln!(self, "| ----------- | --- | ----:| ------- | ---------- |").await?;
let mut overrides = self.services.resolver.cache.overrides().boxed();
while let Some((name, CachedOverride { ips, port, expire, overriding })) =
overrides.next().await
{
if let Some(server_name) = server_name.as_ref() {
if name != *server_name {
continue;
}
} }
async fn flush_cache(&self, name: Option<OwnedServerName>, all: bool) -> Result { let expire = time::format(expire, "%+");
self.write_str(&format!("| {name} | {ips:?} | {port} | {expire} | {overriding:?} |\n"))
.await?;
}
Ok(())
}
#[admin_command]
async fn flush_cache(&self, name: Option<OwnedServerName>, all: bool) -> Result {
if all { if all {
self.services.client.matrix_resolver.clear_cache(); self.services.resolver.cache.clear().await;
self.services.client.dns_resolver.clear_cache(); writeln!(self, "Resolver caches cleared!").await
writeln!(self, "Resolver and DNS caches cleared!").await
} else if let Some(name) = name { } else if let Some(name) = name {
self.services self.services.resolver.cache.del_destination(&name);
.client self.services.resolver.cache.del_override(&name);
.matrix_resolver
.remove_cache_entry(name.as_str());
self.write_str(&format!("Cleared {name} from resolver caches!")) self.write_str(&format!("Cleared {name} from resolver caches!"))
.await .await
} else { } else {
Err!("Missing name. Supply a name or use --all to flush the whole cache.") Err!("Missing name. Supply a name or use --all to flush the whole cache.")
} }
}
} }
+7 -7
View File
@@ -3,7 +3,7 @@
use futures::TryStreamExt; use futures::TryStreamExt;
use ruma::OwnedRoomOrAliasId; use ruma::OwnedRoomOrAliasId;
use crate::admin_command_dispatch; use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
@@ -23,8 +23,8 @@ pub enum RoomTimelineCommand {
}, },
} }
impl crate::Context<'_> { #[admin_command]
pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result { pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.rooms.alias.resolve(&room_id).await?; let room_id = self.services.rooms.alias.resolve(&room_id).await?;
let result = self let result = self
@@ -35,14 +35,15 @@ pub(super) async fn last(&self, room_id: OwnedRoomOrAliasId) -> Result {
.await?; .await?;
self.write_str(&format!("{result:#?}")).await self.write_str(&format!("{result:#?}")).await
} }
pub(super) async fn pdus( #[admin_command]
pub(super) async fn pdus(
&self, &self,
room_id: OwnedRoomOrAliasId, room_id: OwnedRoomOrAliasId,
from: Option<String>, from: Option<String>,
limit: Option<usize>, limit: Option<usize>,
) -> Result { ) -> Result {
let room_id = self.services.rooms.alias.resolve(&room_id).await?; let room_id = self.services.rooms.alias.resolve(&room_id).await?;
let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?; let from: Option<PduCount> = from.as_deref().map(str::parse).transpose()?;
@@ -57,5 +58,4 @@ pub(super) async fn pdus(
.await?; .await?;
self.write_str(&format!("```\n{result:#?}\n```")).await self.write_str(&format!("```\n{result:#?}\n```")).await
}
} }
+6 -6
View File
@@ -2,7 +2,7 @@
use conduwuit::Result; use conduwuit::Result;
use ruma::{OwnedEventId, OwnedRoomOrAliasId}; use ruma::{OwnedEventId, OwnedRoomOrAliasId};
use crate::admin_command_dispatch; use crate::{admin_command, admin_command_dispatch};
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
@@ -17,8 +17,8 @@ pub enum ShortCommand {
}, },
} }
impl crate::Context<'_> { #[admin_command]
pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result { pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
let shortid = self let shortid = self
.services .services
.rooms .rooms
@@ -27,13 +27,13 @@ pub(super) async fn short_event_id(&self, event_id: OwnedEventId) -> Result {
.await?; .await?;
self.write_str(&format!("{shortid:#?}")).await self.write_str(&format!("{shortid:#?}")).await
} }
pub(super) async fn short_room_id(&self, room_id: OwnedRoomOrAliasId) -> Result { #[admin_command]
pub(super) async fn short_room_id(&self, room_id: OwnedRoomOrAliasId) -> Result {
let room_id = self.services.rooms.alias.resolve(&room_id).await?; let room_id = self.services.rooms.alias.resolve(&room_id).await?;
let shortid = self.services.rooms.short.get_shortroomid(&room_id).await?; let shortid = self.services.rooms.short.get_shortroomid(&room_id).await?;
self.write_str(&format!("{shortid:#?}")).await self.write_str(&format!("{shortid:#?}")).await
}
} }

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