Compare commits

..

15 commits

Author SHA1 Message Date
Tom Foster
7be651a307 refactor(ci): Use dnf builddep for RPM build dependencies
Move build dependency installation after SRPM generation to use dnf
builddep, eliminating duplication between CI config and spec file.
Dependencies are now sourced directly from the SRPM, ensuring consistency
and reducing maintenance.

Also fix test installation to properly fail when dependencies cannot be
met, removing the fallback to --nodeps which could hide packaging issues.
2025-09-04 10:29:45 +01:00
Tom Foster
7a18888aa9 feat(ci): Add Fedora RPM package build workflow
Build and publish RPM packages for Fedora using rpkg and official
rust-packaging macros. GPG sign packages with Ed25519 repository key
and deploy to Forgejo package registry.

Publishes packages to organised groups:
- continuwuity (binary): base group (stable/dev/branch-name)
- continuwuity-debuginfo: GROUP-debug
- continuwuity (source RPM): GROUP-src

Workflow triggers on pushes to relevant paths and version tags (v*).
Tagged releases use clean version numbers (v1.2.3 becomes 1.2.3-1)
while branch builds use git SHA versioning.

Include GPG public key for package verification and documentation
for RPM repository configuration and installation methods.
2025-09-04 10:29:45 +01:00
nex
467aed3028 chore: Add Ginger's GH noreply email to mailmap 2025-09-02 16:36:56 +00:00
Ginger
99b44bbf09 Update conduwuit-example.toml 2025-09-01 17:50:09 +00:00
Ginger
95aeff8cdc Set the DB path as an env var in systemd service files to prevent footgunning 2025-09-01 17:50:09 +00:00
nexy7574
9e62e66ae4 chore(PR956): Update admin docs 2025-09-01 11:27:58 +00:00
nexy7574
76b93e252d feat: Only inject vias when manual ones aren't provided during join 2025-09-01 11:27:58 +00:00
nexy7574
66d479e2eb fix: Make remote leave helper a public fn 2025-09-01 11:27:58 +00:00
nexy7574
241371463e feat: Force leave remote rooms admin command 2025-09-01 11:27:58 +00:00
nexy7574
d970df5fd2
perf(MSC4323): Parallelise some check futs 2025-09-01 12:13:37 +01:00
nexy7574
4e644961f3
perf(MSC4323): Remove redundant authorisation checks 2025-09-01 12:13:37 +01:00
nexy7574
35cf9af5c8
feat(MSC4323): Add versions flag 2025-09-01 12:13:37 +01:00
nexy7574
04e796176a
style(MSC4323): Satisfy our linting overlords 2025-09-01 12:13:37 +01:00
nexy7574
9783940105
feat(MSC4323): Advertise suspension support in capabilities 2025-09-01 12:13:37 +01:00
nexy7574
1e430f9470
feat(MSC4323): Implement agnostic suspension endpoint 2025-09-01 12:13:37 +01:00
21 changed files with 257 additions and 104 deletions

View file

@ -81,7 +81,7 @@ jobs:
# Aggressive GC since cache restores don't increment counter # Aggressive GC since cache restores don't increment counter
echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV
- name: Install build dependencies - name: Install base RPM tools
run: | run: |
dnf install -y --setopt=keepcache=1 \ dnf install -y --setopt=keepcache=1 \
wget \ wget \
@ -91,25 +91,14 @@ jobs:
rpkg \ rpkg \
cargo-rpm-macros \ cargo-rpm-macros \
systemd-rpm-macros \ systemd-rpm-macros \
clang \
liburing-devel \
rust \
cargo \
gcc \
gcc-c++ \
make \
openssl-devel \
pkg-config \
python3-pip python3-pip
- name: Setup build environment and build SRPM - name: Setup build environment and build SRPM
run: | run: |
# Configure git for rpkg
git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global --add safe.directory "$GITHUB_WORKSPACE"
git config --global user.email "ci@continuwuity.org" git config --global user.email "ci@continuwuity.org"
git config --global user.name "Continuwuity" git config --global user.name "Continuwuity"
# Setup RPM build tree
rpmdev-setuptree rpmdev-setuptree
cd "$GITHUB_WORKSPACE" cd "$GITHUB_WORKSPACE"
@ -143,10 +132,8 @@ jobs:
fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg
fi fi
# Build the SRPM
rpkg srpm --outdir "$HOME/rpmbuild/SRPMS" rpkg srpm --outdir "$HOME/rpmbuild/SRPMS"
# Show SRPM info
ls -la $HOME/rpmbuild/SRPMS/ ls -la $HOME/rpmbuild/SRPMS/
- name: Setup GPG for RPM signing - name: Setup GPG for RPM signing
@ -157,23 +144,32 @@ jobs:
exit 0 exit 0
fi fi
# Import the signing key
echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import
# Get the key ID (look for the sec line, not the uid line) # Get the key ID (look for the sec line, not the uid line)
KEY_ID=$(gpg --list-secret-keys --keyid-format=long | grep "^sec" | head -1 | awk '{print $2}' | cut -d'/' -f2) KEY_ID=$(gpg --list-secret-keys --keyid-format=long | grep "^sec" | head -1 | awk '{print $2}' | cut -d'/' -f2)
echo "Using GPG key: $KEY_ID" echo "Using GPG key: $KEY_ID"
# Configure RPM macros for signing
cat > ~/.rpmmacros << EOF cat > ~/.rpmmacros << EOF
%_signature gpg %_signature gpg
%_gpg_name $KEY_ID %_gpg_name $KEY_ID
%__gpg /usr/bin/gpg %__gpg /usr/bin/gpg
EOF EOF
- 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 - name: Build RPM from SRPM
run: | run: |
# Find the SRPM file
SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1) SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1)
if [ -z "$SRPM" ]; then if [ -z "$SRPM" ]; then
@ -183,7 +179,6 @@ jobs:
echo "Building from SRPM: $SRPM" echo "Building from SRPM: $SRPM"
# Build the binary RPM
rpmbuild --rebuild "$SRPM" \ rpmbuild --rebuild "$SRPM" \
--define "_topdir $HOME/rpmbuild" \ --define "_topdir $HOME/rpmbuild" \
--define "_sourcedir $GITHUB_WORKSPACE" \ --define "_sourcedir $GITHUB_WORKSPACE" \
@ -197,32 +192,20 @@ jobs:
exit 0 exit 0
fi fi
# Track signing failures
FAILED_COUNT=0
TOTAL_COUNT=0
# Export GPG_TTY to avoid terminal warnings # Export GPG_TTY to avoid terminal warnings
export GPG_TTY=/dev/null export GPG_TTY=/dev/null
# Sign all RPMs (binary and source)
for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do
echo "Signing: $(basename $rpm)" echo "Signing: $(basename $rpm)"
TOTAL_COUNT=$((TOTAL_COUNT + 1))
# Use expect or provide empty passphrase via stdin for batch signing # Use expect or provide empty passphrase via stdin for batch signing
if ! echo "" | rpmsign --addsign "$rpm" 2>&1; then if ! echo "" | rpmsign --addsign "$rpm" 2>&1; then
echo "ERROR: Failed to sign $rpm" echo "ERROR: Failed to sign $rpm"
FAILED_COUNT=$((FAILED_COUNT + 1)) exit 1
fi fi
done done
# Fail if any RPMs failed to sign echo "Successfully signed all RPMs"
if [ "$FAILED_COUNT" -gt 0 ]; then
echo "ERROR: Failed to sign $FAILED_COUNT out of $TOTAL_COUNT RPMs"
exit 1
fi
echo "Successfully signed all $TOTAL_COUNT RPMs"
- name: Verify RPM signatures - name: Verify RPM signatures
run: | run: |
@ -232,15 +215,12 @@ jobs:
exit 0 exit 0
fi fi
# Import our public key for verification
echo "Importing GPG public key for verification..." echo "Importing GPG public key for verification..."
rpm --import fedora/RPM-GPG-KEY-continuwuity.asc rpm --import fedora/RPM-GPG-KEY-continuwuity.asc
# Track verification failures
FAILED_COUNT=0 FAILED_COUNT=0
TOTAL_COUNT=0 TOTAL_COUNT=0
# Verify all RPMs
for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do
echo -n "Verifying $(basename $rpm): " echo -n "Verifying $(basename $rpm): "
TOTAL_COUNT=$((TOTAL_COUNT + 1)) TOTAL_COUNT=$((TOTAL_COUNT + 1))
@ -281,8 +261,8 @@ jobs:
echo "" echo ""
rpm -qpl "$RPM" rpm -qpl "$RPM"
# Actually install it (would need --nodeps if dependencies aren't met) # Actually install it
dnf install -y "$RPM" || rpm -ivh --nodeps "$RPM" dnf install -y "$RPM"
# Verify installation # Verify installation
rpm -qa | grep continuwuity rpm -qa | grep continuwuity
@ -305,11 +285,9 @@ jobs:
run: | run: |
mkdir -p artifacts mkdir -p artifacts
# Copy all RPMs to artifacts directory
find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \;
find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \;
# Create metadata file
cd artifacts cd artifacts
echo "Build Information:" > BUILD_INFO.txt echo "Build Information:" > BUILD_INFO.txt
echo "==================" >> BUILD_INFO.txt echo "==================" >> BUILD_INFO.txt
@ -336,7 +314,6 @@ jobs:
! -name "*.src.rpm" \ ! -name "*.src.rpm" \
-type f) -type f)
# Create temp directory for this artifact
mkdir -p upload-bin mkdir -p upload-bin
cp $BIN_RPM upload-bin/ cp $BIN_RPM upload-bin/
@ -367,7 +344,6 @@ jobs:
exit 0 exit 0
fi fi
# Extract version from RPM filename
RPM_BASENAME=$(basename "$RPM") RPM_BASENAME=$(basename "$RPM")
echo "Publishing: $RPM_BASENAME" echo "Publishing: $RPM_BASENAME"
@ -383,7 +359,6 @@ jobs:
GROUP=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30) GROUP=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30)
fi fi
# Extract package info from RPM for deletion
PACKAGE_INFO=$(rpm -qpi "$RPM" 2>/dev/null) PACKAGE_INFO=$(rpm -qpi "$RPM" 2>/dev/null)
PACKAGE_NAME=$(echo "$PACKAGE_INFO" | grep "^Name" | awk '{print $3}') PACKAGE_NAME=$(echo "$PACKAGE_INFO" | grep "^Name" | awk '{print $3}')
PACKAGE_VERSION=$(echo "$PACKAGE_INFO" | grep "^Version" | awk '{print $3}') PACKAGE_VERSION=$(echo "$PACKAGE_INFO" | grep "^Version" | awk '{print $3}')
@ -393,15 +368,20 @@ jobs:
# Full version includes release # Full version includes release
FULL_VERSION="${PACKAGE_VERSION}-${PACKAGE_RELEASE}" FULL_VERSION="${PACKAGE_VERSION}-${PACKAGE_RELEASE}"
# Try to delete existing package first (ignore errors if it doesn't exist) # 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" echo "Removing any existing package: $PACKAGE_NAME-$FULL_VERSION.$PACKAGE_ARCH"
curl -X DELETE \ RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ -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" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH")
|| echo "Package didn't exist or deletion failed (this is OK)" 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
# Upload to Forgejo package registry
# Using the RPM registry endpoint with group support
curl --fail-with-body \ curl --fail-with-body \
-X PUT \ -X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
@ -422,7 +402,6 @@ jobs:
for DEBUG_RPM in $DEBUG_RPMS; do for DEBUG_RPM in $DEBUG_RPMS; do
echo "Publishing: $(basename "$DEBUG_RPM")" echo "Publishing: $(basename "$DEBUG_RPM")"
# Extract debug RPM info
DEBUG_INFO=$(rpm -qpi "$DEBUG_RPM" 2>/dev/null) DEBUG_INFO=$(rpm -qpi "$DEBUG_RPM" 2>/dev/null)
DEBUG_NAME=$(echo "$DEBUG_INFO" | grep "^Name" | awk '{print $3}') DEBUG_NAME=$(echo "$DEBUG_INFO" | grep "^Name" | awk '{print $3}')
DEBUG_VERSION=$(echo "$DEBUG_INFO" | grep "^Version" | awk '{print $3}') DEBUG_VERSION=$(echo "$DEBUG_INFO" | grep "^Version" | awk '{print $3}')
@ -430,13 +409,18 @@ jobs:
DEBUG_ARCH=$(echo "$DEBUG_INFO" | grep "^Architecture" | awk '{print $2}') DEBUG_ARCH=$(echo "$DEBUG_INFO" | grep "^Architecture" | awk '{print $2}')
DEBUG_FULL_VERSION="${DEBUG_VERSION}-${DEBUG_RELEASE}" DEBUG_FULL_VERSION="${DEBUG_VERSION}-${DEBUG_RELEASE}"
# Try to delete existing debug package first # Must delete existing package first (Forgejo limitation)
curl -X DELETE \ RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ -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" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH")
|| echo "Debug package didn't exist or deletion failed (this is OK)" 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
# Upload debug RPM
curl --fail-with-body \ curl --fail-with-body \
-X PUT \ -X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
@ -455,19 +439,24 @@ jobs:
echo "Publishing source RPM: $(basename "$SRPM")" echo "Publishing source RPM: $(basename "$SRPM")"
echo "Publishing to group: ${GROUP}-src" echo "Publishing to group: ${GROUP}-src"
# Extract SRPM info
SRPM_INFO=$(rpm -qpi "$SRPM" 2>/dev/null) SRPM_INFO=$(rpm -qpi "$SRPM" 2>/dev/null)
SRPM_NAME=$(echo "$SRPM_INFO" | grep "^Name" | awk '{print $3}') SRPM_NAME=$(echo "$SRPM_INFO" | grep "^Name" | awk '{print $3}')
SRPM_VERSION=$(echo "$SRPM_INFO" | grep "^Version" | awk '{print $3}') SRPM_VERSION=$(echo "$SRPM_INFO" | grep "^Version" | awk '{print $3}')
SRPM_RELEASE=$(echo "$SRPM_INFO" | grep "^Release" | awk '{print $3}') SRPM_RELEASE=$(echo "$SRPM_INFO" | grep "^Release" | awk '{print $3}')
SRPM_FULL_VERSION="${SRPM_VERSION}-${SRPM_RELEASE}" SRPM_FULL_VERSION="${SRPM_VERSION}-${SRPM_RELEASE}"
# Try to delete existing SRPM first # Must delete existing SRPM first (Forgejo limitation)
echo "Removing any existing SRPM: $SRPM_NAME-$SRPM_FULL_VERSION.src" echo "Removing any existing SRPM: $SRPM_NAME-$SRPM_FULL_VERSION.src"
curl -X DELETE \ RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ -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" \ "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src")
|| echo "SRPM didn't exist or deletion failed (this is OK)" 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 \ curl --fail-with-body \
-X PUT \ -X PUT \

View file

@ -13,3 +13,4 @@ Rudi Floren <rudi.floren@gmail.com> <rudi.floren@googlemail.com>
Tamara Schmitz <tamara.zoe.schmitz@posteo.de> <15906939+tamara-schmitz@users.noreply.github.com> Tamara Schmitz <tamara.zoe.schmitz@posteo.de> <15906939+tamara-schmitz@users.noreply.github.com>
Timo Kösters <timo@koesters.xyz> Timo Kösters <timo@koesters.xyz>
x4u <xi.zhu@protonmail.ch> <14617923-x4u@users.noreply.gitlab.com> x4u <xi.zhu@protonmail.ch> <14617923-x4u@users.noreply.gitlab.com>
Ginger <ginger@gingershaped.computer> <75683114+gingershaped@users.noreply.github.com>

22
Cargo.lock generated
View file

@ -4058,7 +4058,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.10.1" version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@ -4078,7 +4078,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.10.0" version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -4090,7 +4090,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.18.0" version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"assign", "assign",
@ -4113,7 +4113,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.13.0" version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"base64 0.22.1", "base64 0.22.1",
@ -4145,7 +4145,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.28.1" version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap 2.10.0", "indexmap 2.10.0",
@ -4170,7 +4170,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"bytes", "bytes",
"headers", "headers",
@ -4192,7 +4192,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.9.5" version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"js_int", "js_int",
"thiserror 2.0.12", "thiserror 2.0.12",
@ -4201,7 +4201,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identity-service-api" name = "ruma-identity-service-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -4211,7 +4211,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.13.0" version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"proc-macro-crate", "proc-macro-crate",
@ -4226,7 +4226,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -4238,7 +4238,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.15.0" version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ed25519-dalek", "ed25519-dalek",

View file

@ -352,7 +352,7 @@ version = "0.1.2"
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma" git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes" #branch = "conduwuit-changes"
rev = "b753738047d1f443aca870896ef27ecaacf027da" rev = "8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd"
features = [ features = [
"compat", "compat",
"rand", "rand",

View file

@ -20,6 +20,7 @@ StandardError=journal+console
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit"
TTYReset=yes TTYReset=yes
# uncomment to allow buffer to be cleared every restart # uncomment to allow buffer to be cleared every restart

View file

@ -79,9 +79,11 @@
# This is the only directory where continuwuity will save its data, # This is the only directory where continuwuity will save its data,
# including media. Note: this was previously "/var/lib/matrix-conduit". # including media. Note: this was previously "/var/lib/matrix-conduit".
# #
# YOU NEED TO EDIT THIS. # YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a `systemd` service.
# The service file sets it to `/var/lib/conduwuit` using an environment variable
# and also grants write access.
# #
# example: "/var/lib/continuwuity" # example: "/var/lib/conduwuit"
# #
#database_path = #database_path =

View file

@ -16,6 +16,7 @@ Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit"
ExecStart=/usr/sbin/conduwuit ExecStart=/usr/sbin/conduwuit

View file

@ -21,6 +21,7 @@ This document contains the help content for the `admin` command-line program.
* [`admin users list-joined-rooms`↴](#admin-users-list-joined-rooms) * [`admin users list-joined-rooms`↴](#admin-users-list-joined-rooms)
* [`admin users force-join-room`↴](#admin-users-force-join-room) * [`admin users force-join-room`↴](#admin-users-force-join-room)
* [`admin users force-leave-room`↴](#admin-users-force-leave-room) * [`admin users force-leave-room`↴](#admin-users-force-leave-room)
* [`admin users force-leave-remote-room`↴](#admin-users-force-leave-remote-room)
* [`admin users force-demote`↴](#admin-users-force-demote) * [`admin users force-demote`↴](#admin-users-force-demote)
* [`admin users make-user-admin`↴](#admin-users-make-user-admin) * [`admin users make-user-admin`↴](#admin-users-make-user-admin)
* [`admin users put-room-tag`↴](#admin-users-put-room-tag) * [`admin users put-room-tag`↴](#admin-users-put-room-tag)
@ -295,6 +296,7 @@ You can find the ID using the `list-appservices` command.
* `list-joined-rooms` — - Lists all the rooms (local and remote) that the specified user is joined in * `list-joined-rooms` — - Lists all the rooms (local and remote) that the specified user is joined in
* `force-join-room` — - Manually join a local user to a room * `force-join-room` — - Manually join a local user to a room
* `force-leave-room` — - Manually leave a local user from a room * `force-leave-room` — - Manually leave a local user from a room
* `force-leave-remote-room` — - Manually leave a remote room for a local user
* `force-demote` — - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits * `force-demote` — - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits
* `make-user-admin` — - Grant server-admin privileges to a user * `make-user-admin` — - Grant server-admin privileges to a user
* `put-room-tag` — - Puts a room tag for the specified user and room ID * `put-room-tag` — - Puts a room tag for the specified user and room ID
@ -449,6 +451,19 @@ Reverses the effects of the `suspend` command, allowing the user to send message
## `admin users force-leave-remote-room`
- Manually leave a remote room for a local user
**Usage:** `admin users force-leave-remote-room <USER_ID> <ROOM_ID>`
###### **Arguments:**
* `<USER_ID>`
* `<ROOM_ID>`
## `admin users force-demote` ## `admin users force-demote`
- Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits

View file

@ -15,6 +15,7 @@ Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml"
Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_LOG_TO_JOURNALD=true"
Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N"
Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit"
ExecStart=/usr/bin/conduwuit ExecStart=/usr/bin/conduwuit

View file

@ -1,8 +1,8 @@
use std::{collections::BTreeMap, fmt::Write as _}; use std::{collections::BTreeMap, fmt::Write as _};
use api::client::{ use api::client::{
full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url, full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room,
update_displayname, update_avatar_url, update_displayname,
}; };
use conduwuit::{ use conduwuit::{
Err, Result, debug, debug_warn, error, info, is_equal_to, Err, Result, debug, debug_warn, error, info, is_equal_to,
@ -926,3 +926,29 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result {
)) ))
.await .await
} }
#[admin_command]
pub(super) async fn force_leave_remote_room(
&self,
user_id: String,
room_id: OwnedRoomOrAliasId,
) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
let (room_id, _) = self
.services
.rooms
.alias
.resolve_with_servers(&room_id, None)
.await?;
assert!(
self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user"
);
remote_leave_room(self.services, &user_id, &room_id, None)
.boxed()
.await?;
self.write_str(&format!("{user_id} has been joined to {room_id}.",))
.await
}

View file

@ -103,6 +103,12 @@ pub enum UserCommand {
room_id: OwnedRoomOrAliasId, room_id: OwnedRoomOrAliasId,
}, },
/// - Manually leave a remote room for a local user.
ForceLeaveRemoteRoom {
user_id: String,
room_id: OwnedRoomOrAliasId,
},
/// - Forces the specified user to drop their power levels to the room /// - Forces the specified user to drop their power levels to the room
/// default, if their permissions allow and the auth check permits /// default, if their permissions allow and the auth check permits
ForceDemote { ForceDemote {

View file

@ -0,0 +1,3 @@
mod suspend;
pub(crate) use self::suspend::*;

View file

@ -0,0 +1,89 @@
use axum::extract::State;
use conduwuit::{Err, Result};
use futures::future::{join, join3};
use ruma::api::client::admin::{get_suspended, set_suspended};
use crate::Ruma;
/// # `GET /_matrix/client/v1/admin/suspend/{userId}`
///
/// Check the suspension status of a target user
pub(crate) async fn get_suspended_status(
State(services): State<crate::State>,
body: Ruma<get_suspended::v1::Request>,
) -> Result<get_suspended::v1::Response> {
let sender_user = body.sender_user();
let (admin, active) =
join(services.users.is_admin(sender_user), services.users.is_active(&body.user_id)).await;
if !admin {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
if !services.globals.user_is_local(&body.user_id) {
return Err!(Request(InvalidParam("Can only check the suspended status of local users")));
}
if !active {
return Err!(Request(NotFound("Unknown user")));
}
Ok(get_suspended::v1::Response::new(
services.users.is_suspended(&body.user_id).await?,
))
}
/// # `PUT /_matrix/client/v1/admin/suspend/{userId}`
///
/// Set the suspension status of a target user
pub(crate) async fn put_suspended_status(
State(services): State<crate::State>,
body: Ruma<set_suspended::v1::Request>,
) -> Result<set_suspended::v1::Response> {
let sender_user = body.sender_user();
let (sender_admin, active, target_admin) = join3(
services.users.is_admin(sender_user),
services.users.is_active(&body.user_id),
services.users.is_admin(&body.user_id),
)
.await;
if !sender_admin {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
if !services.globals.user_is_local(&body.user_id) {
return Err!(Request(InvalidParam("Can only set the suspended status of local users")));
}
if !active {
return Err!(Request(NotFound("Unknown user")));
}
if body.user_id == *sender_user {
return Err!(Request(Forbidden("You cannot suspend yourself")));
}
if target_admin {
return Err!(Request(Forbidden("You cannot suspend another server administrator")));
}
if services.users.is_suspended(&body.user_id).await? == body.suspended {
// No change
return Ok(set_suspended::v1::Response::new(body.suspended));
}
let action = if body.suspended {
services
.users
.suspend_account(&body.user_id, sender_user)
.await;
"suspended"
} else {
services.users.unsuspend_account(&body.user_id).await;
"unsuspended"
};
if services.config.admin_room_notices {
// Notify the admin room that an account has been un/suspended
services
.admin
.send_text(&format!("{} has been {} by {}.", body.user_id, action, sender_user))
.await;
}
Ok(set_suspended::v1::Response::new(body.suspended))
}

View file

@ -19,7 +19,7 @@ use crate::Ruma;
/// of this server. /// of this server.
pub(crate) async fn get_capabilities_route( pub(crate) async fn get_capabilities_route(
State(services): State<crate::State>, State(services): State<crate::State>,
_body: Ruma<get_capabilities::v3::Request>, body: Ruma<get_capabilities::v3::Request>,
) -> Result<get_capabilities::v3::Response> { ) -> Result<get_capabilities::v3::Response> {
let available: BTreeMap<RoomVersionId, RoomVersionStability> = let available: BTreeMap<RoomVersionId, RoomVersionStability> =
Server::available_room_versions().collect(); Server::available_room_versions().collect();
@ -45,5 +45,14 @@ pub(crate) async fn get_capabilities_route(
json!({"enabled": services.config.forget_forced_upon_leave}), json!({"enabled": services.config.forget_forced_upon_leave}),
)?; )?;
if services
.users
.is_admin(body.sender_user.as_ref().unwrap())
.await
{
// Advertise suspension API
capabilities.set("uk.timedout.msc4323", json!({"suspend":true, "lock": false}))?;
}
Ok(get_capabilities::v3::Response { capabilities }) Ok(get_capabilities::v3::Response { capabilities })
} }

View file

@ -156,6 +156,8 @@ pub(crate) async fn join_room_by_id_or_alias_route(
.await?; .await?;
let mut servers = body.via.clone(); let mut servers = body.via.clone();
if servers.is_empty() {
debug!("No via servers provided for join, injecting some.");
servers.extend( servers.extend(
services services
.rooms .rooms
@ -182,6 +184,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
if let Some(server) = room_id.server_name() { if let Some(server) = room_id.server_name() {
servers.push(server.to_owned()); servers.push(server.to_owned());
} }
}
servers.sort_unstable(); servers.sort_unstable();
servers.dedup(); servers.dedup();

View file

@ -215,7 +215,7 @@ pub async fn leave_room(
Ok(()) Ok(())
} }
async fn remote_leave_room( pub async fn remote_leave_room(
services: &Services, services: &Services,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,

View file

@ -29,7 +29,7 @@ pub(crate) use self::{
}; };
pub use self::{ pub use self::{
join::join_room_by_id_helper, join::join_room_by_id_helper,
leave::{leave_all_rooms, leave_room}, leave::{leave_all_rooms, leave_room, remote_leave_room},
}; };
use crate::{Ruma, client::full_user_deactivate}; use crate::{Ruma, client::full_user_deactivate};

View file

@ -1,5 +1,6 @@
pub(super) mod account; pub(super) mod account;
pub(super) mod account_data; pub(super) mod account_data;
pub(super) mod admin;
pub(super) mod alias; pub(super) mod alias;
pub(super) mod appservice; pub(super) mod appservice;
pub(super) mod backup; pub(super) mod backup;
@ -43,6 +44,7 @@ pub(super) mod well_known;
pub use account::full_user_deactivate; pub use account::full_user_deactivate;
pub(super) use account::*; pub(super) use account::*;
pub(super) use account_data::*; pub(super) use account_data::*;
pub(super) use admin::*;
pub(super) use alias::*; pub(super) use alias::*;
pub(super) use appservice::*; pub(super) use appservice::*;
pub(super) use backup::*; pub(super) use backup::*;
@ -55,7 +57,7 @@ pub(super) use keys::*;
pub(super) use media::*; pub(super) use media::*;
pub(super) use media_legacy::*; pub(super) use media_legacy::*;
pub(super) use membership::*; pub(super) use membership::*;
pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room}; pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room};
pub(super) use message::*; pub(super) use message::*;
pub(super) use openid::*; pub(super) use openid::*;
pub(super) use presence::*; pub(super) use presence::*;

View file

@ -58,6 +58,7 @@ pub(crate) async fn get_supported_versions_route(
("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */ ("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */
("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */ ("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */ ("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */
]), ]),
}; };

View file

@ -184,6 +184,8 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
"/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary", "/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary",
get(client::get_room_summary_legacy) get(client::get_room_summary_legacy)
) )
.ruma_route(&client::get_suspended_status)
.ruma_route(&client::put_suspended_status)
.ruma_route(&client::well_known_support) .ruma_route(&client::well_known_support)
.ruma_route(&client::well_known_client) .ruma_route(&client::well_known_client)
.route("/_conduwuit/server_version", get(client::conduwuit_server_version)) .route("/_conduwuit/server_version", get(client::conduwuit_server_version))

View file

@ -126,9 +126,11 @@ pub struct Config {
/// This is the only directory where continuwuity will save its data, /// This is the only directory where continuwuity will save its data,
/// including media. Note: this was previously "/var/lib/matrix-conduit". /// including media. Note: this was previously "/var/lib/matrix-conduit".
/// ///
/// YOU NEED TO EDIT THIS. /// YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a `systemd` service.
/// The service file sets it to `/var/lib/conduwuit` using an environment variable
/// and also grants write access.
/// ///
/// example: "/var/lib/continuwuity" /// example: "/var/lib/conduwuit"
pub database_path: PathBuf, pub database_path: PathBuf,
/// continuwuity supports online database backups using RocksDB's Backup /// continuwuity supports online database backups using RocksDB's Backup