Compare commits

..

4 Commits

Author SHA1 Message Date
Ginger e3072c4392 fix: Move packaging files from dist/ to pkg/ 2025-09-02 12:20:23 -04:00
Ginger c0c772639f fix: Remove unnecessary user and directory modifications
systemd creates a dynamic user for
continuwuity and manages directories for
it automatically, so the debian postinst
script no longer needs to do that.
2025-09-02 10:44:47 -04:00
Ginger 2e687e0451 fix: Update debian systemd unit path 2025-09-02 10:37:25 -04:00
Ginger 19c9a896a6 feat: Move packaging scripts into dist/ and consolidate the service files 2025-09-02 09:35:15 -04:00
49 changed files with 672 additions and 1178 deletions
+1 -20
View File
@@ -13,12 +13,6 @@ outputs:
slug:
description: 'Combined OS slug (e.g. Ubuntu-22.04)'
value: ${{ steps.detect.outputs.slug }}
node_major:
description: 'Major version of Node.js if available (e.g. 22)'
value: ${{ steps.detect.outputs.node_major }}
node_version:
description: 'Full Node.js version if available (e.g. 22.19.0)'
value: ${{ steps.detect.outputs.node_version }}
runs:
using: composite
@@ -36,20 +30,7 @@ runs:
# Create combined slug
OS_SLUG="${OS_NAME}-${OS_VERSION}"
# Detect Node.js version if available
if command -v node >/dev/null 2>&1; then
NODE_VERSION=$(node --version | sed 's/v//')
NODE_MAJOR=$(echo $NODE_VERSION | cut -d. -f1)
echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT
echo "node_major=${NODE_MAJOR}" >> $GITHUB_OUTPUT
echo "🔍 Detected Node.js: v${NODE_VERSION}"
else
echo "node_version=" >> $GITHUB_OUTPUT
echo "node_major=" >> $GITHUB_OUTPUT
echo "🔍 Node.js not found"
fi
# Set OS outputs
# Set outputs
echo "name=${OS_NAME}" >> $GITHUB_OUTPUT
echo "version=${OS_VERSION}" >> $GITHUB_OUTPUT
echo "slug=${OS_SLUG}" >> $GITHUB_OUTPUT
+7 -1
View File
@@ -2,12 +2,18 @@ name: sccache
description: |
Install sccache for caching builds in GitHub Actions.
inputs:
token:
description: 'A Github PAT'
required: false
runs:
using: composite
steps:
- name: Install sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
uses: https://github.com/mozilla-actions/sccache-action@v0.0.9
with:
token: ${{ inputs.token }}
- name: Configure sccache
uses: https://github.com/actions/github-script@v7
with:
+11 -1
View File
@@ -88,9 +88,19 @@ runs:
# Shared toolchain cache across all Rust versions
key: toolchain-${{ steps.runner-os.outputs.slug }}
- name: Debug GitHub token availability
shell: bash
run: |
if [ -z "${{ inputs.github-token }}" ]; then
echo "⚠️ No GitHub token provided - sccache will use fallback download method"
else
echo "✅ GitHub token provided for sccache"
fi
- name: Setup sccache
uses: https://git.tomfos.tr/tom/sccache-action@v1
uses: https://github.com/mozilla-actions/sccache-action@v0.0.9
with:
token: ${{ inputs.github-token }}
- name: Cache build artifacts
id: build-cache
-154
View File
@@ -1,154 +0,0 @@
name: Build / Debian DEB
concurrency:
group: "build-debian-${{ forge.ref }}"
cancel-in-progress: true
on:
push:
branches:
- '**'
tags:
- 'v*'
paths:
- 'pkg/debian/**'
- 'src/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.forgejo/workflows/build-debian.yml'
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
container: ["ubuntu-latest", "ubuntu-previous", "debian-latest", "debian-oldstable"]
container:
image: "ghcr.io/tcpipuk/act-runner:${{ matrix.container }}"
steps:
- name: Get Debian version
id: debian-version
run: |
VERSION=$(cat /etc/debian_version)
DISTRIBUTION=$(lsb_release -sc 2>/dev/null)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT
echo "Debian distribution: $DISTRIBUTION ($VERSION)"
- name: Checkout repository with full history
uses: https://code.forgejo.org/actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Cargo registry
uses: https://code.forgejo.org/actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-debian-${{ steps.debian-version.outputs.distribution }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-debian-${{ steps.debian-version.outputs.distribution }}-
- 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: Setup Rust nightly
uses: ./.forgejo/actions/setup-rust
with:
rust-version: nightly
github-token: ${{ secrets.GH_PUBLIC_RO }}
- name: Get package version and component
id: package-meta
run: |
BASE_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.name == \"conduwuit\").version" | sed 's/[^a-zA-Z0-9.+]/~/g')
# VERSION is the package version, COMPONENT is used in
# apt's repository config like a git repo branch
if [[ "${{ forge.ref }}" == "refs/tags/"* ]]; then
# Use the "stable" component for tagged releases
COMPONENT="stable"
VERSION=$BASE_VERSION
else
# Use the "dev" component for development builds
SHA=$(echo "${{ forge.sha }}" | cut -c1-7)
DATE=$(date +%Y%m%d)
if [ "${{ forge.ref_name }}" = "main" ]; then
COMPONENT="dev"
else
# Use the sanitized ref name as the component for feature branches
COMPONENT="dev-$(echo '${{ forge.ref_name }}' | sed 's/[^a-zA-Z0-9.+]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30)"
fi
CLEAN_COMPONENT=$(echo $COMPONENT | sed 's/[^a-zA-Z0-9.+]/~/g')
VERSION="$BASE_VERSION~git$DATE.$SHA-$CLEAN_COMPONENT"
fi
echo "component=$COMPONENT" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Component: $COMPONENT"
echo "Version: $VERSION"
- name: Install cargo-deb
run: |
if command -v cargo-deb &> /dev/null; then
echo "cargo-deb already available"
else
echo "Installing cargo-deb"
cargo-binstall -y --no-symlinks cargo-deb
fi
- name: Install build dependencies
run: |
apt-get update -y
# Build dependencies for rocksdb
apt-get install -y clang liburing-dev
- name: Run cargo-deb
id: cargo-deb
run: |
DEB_PATH=$(cargo deb --deb-version ${{ steps.package-meta.outputs.version }})
echo "path=$DEB_PATH" >> $GITHUB_OUTPUT
- name: Test deb installation
run: |
echo "Installing: ${{ steps.cargo-deb.outputs.path }}"
apt-get install -y ${{ steps.cargo-deb.outputs.path }}
dpkg -s continuwuity
[ -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: Upload deb artifact
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: continuwuity-${{ steps.debian-version.outputs.distribution }}
path: ${{ steps.cargo-deb.outputs.path }}
- name: Publish to Forgejo package registry
if: ${{ forge.event_name == 'push' || forge.event_name == 'workflow_dispatch' }}
run: |
OWNER="continuwuation"
DISTRIBUTION=${{ steps.debian-version.outputs.distribution }}
COMPONENT=${{ steps.package-meta.outputs.component }}
DEB=${{ steps.cargo-deb.outputs.path }}
echo "Publishing: $DEB in component $COMPONENT for distribution $DISTRIBUTION"
curl --fail-with-body \
-X PUT \
-H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \
--upload-file "$DEB" \
"${{ forge.server_url }}/api/packages/$OWNER/debian/pool/$DISTRIBUTION/$COMPONENT/upload"
+1 -14
View File
@@ -49,23 +49,10 @@ jobs:
cp ./docs/static/_headers ./public/_headers
echo "Copied .well-known files and _headers to ./public"
- name: Detect runner environment
id: runner-env
uses: ./.forgejo/actions/detect-runner-os
- name: Setup Node.js
if: steps.runner-env.outputs.node_major == '' || steps.runner-env.outputs.node_major < '20'
uses: https://github.com/actions/setup-node@v4
with:
node-version: 22
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ steps.runner-env.outputs.slug }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ steps.runner-env.outputs.slug }}-node-
node-version: 20
- name: Install dependencies
run: npm install --save-dev wrangler@latest
-4
View File
@@ -1,11 +1,7 @@
name: Checks / Prek
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
+14 -51
View File
@@ -3,18 +3,7 @@ concurrency:
group: "release-image-${{ github.ref }}"
on:
pull_request:
paths-ignore:
- "*.md"
- "**/*.md"
- ".gitlab-ci.yml"
- ".gitignore"
- "renovate.json"
- "pkg/**"
- "docs/**"
push:
branches:
- main
paths-ignore:
- "*.md"
- "**/*.md"
@@ -22,6 +11,7 @@ on:
- ".gitignore"
- "renovate.json"
- "pkg/**"
- "docker/**"
- "docs/**"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@@ -53,9 +43,6 @@ jobs:
let images = []
if (process.env.BUILTIN_REGISTRY_ENABLED === "true") {
images.push(builtinImage)
} else {
// Fallback to official registry for forks/PRs without credentials
images.push('forgejo.ellis.link/continuwuation/continuwuity')
}
core.setOutput('images', images.join("\n"))
core.setOutput('images_list', images.join(","))
@@ -101,22 +88,15 @@ jobs:
with:
persist-credentials: false
- name: Install rust
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: rust-toolchain
uses: ./.forgejo/actions/rust-toolchain
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
- name: Set up QEMU
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: docker/setup-qemu-action@v3
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.BUILTIN_REGISTRY }}
@@ -142,21 +122,15 @@ jobs:
run: |
calculatedSha=$(git rev-parse --short ${{ github.sha }})
echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV
echo "Short SHA: $calculatedSha"
- name: Get Git commit timestamps
run: |
timestamp=$(git log -1 --pretty=%ct)
echo "TIMESTAMP=$timestamp" >> $GITHUB_ENV
echo "Commit timestamp: $timestamp"
run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
- uses: ./.forgejo/actions/timelord
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
with:
key: timelord-v0
path: .
- name: Cache Rust registry
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: actions/cache@v3
with:
path: |
@@ -166,7 +140,6 @@ jobs:
.cargo/registry/src
key: rust-registry-image-${{hashFiles('**/Cargo.lock') }}
- name: Cache cargo target
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-cargo-target
uses: actions/cache@v3
with:
@@ -174,7 +147,6 @@ jobs:
cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
key: cargo-target-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}-${{hashFiles('**/Cargo.lock') }}-${{steps.rust-toolchain.outputs.rustc_version}}
- name: Cache apt cache
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt
uses: actions/cache@v3
with:
@@ -182,7 +154,6 @@ jobs:
var-cache-apt-${{ matrix.slug }}
key: var-cache-apt-${{ matrix.slug }}
- name: Cache apt lib
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
id: cache-apt-lib
uses: actions/cache@v3
with:
@@ -190,7 +161,6 @@ jobs:
var-lib-apt-${{ matrix.slug }}
key: var-lib-apt-${{ matrix.slug }}
- name: inject cache into docker
if: ${{ env.BUILDKIT_ENDPOINT == '' }}
uses: https://github.com/reproducible-containers/buildkit-cache-dance@v3.3.0
with:
cache-map: |
@@ -213,7 +183,7 @@ jobs:
context: .
file: "docker/Dockerfile"
build-args: |
GIT_COMMIT_HASH=${{ github.sha }}
GIT_COMMIT_HASH=${{ github.sha }})
GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }}
GIT_REMOTE_URL=${{github.event.repository.html_url }}
GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }}
@@ -223,23 +193,27 @@ jobs:
cache-from: type=gha
# cache-to: type=gha,mode=max
sbom: true
outputs: |
${{ env.BUILTIN_REGISTRY_ENABLED == 'true' && format('type=image,"name={0}",push-by-digest=true,name-canonical=true,push=true', needs.define-variables.outputs.images_list) || format('type=image,"name={0}",push=false', needs.define-variables.outputs.images_list) }}
type=local,dest=/tmp/binaries
outputs: type=image,"name=${{ needs.define-variables.outputs.images_list }}",push-by-digest=true,name-canonical=true,push=true
env:
SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}
# For publishing multi-platform manifests
- name: Export digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
# Binary extracted via local output for all builds
- name: Rename extracted binary
run: mv /tmp/binaries/sbin/conduwuit /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
- name: Extract binary from container (image)
id: extract-binary-image
run: |
mkdir -p /tmp/binaries
digest="${{ steps.build.outputs.digest }}"
echo "container_id=$(docker create --platform ${{ matrix.platform }} ${{ needs.define-variables.outputs.images_list }}@$digest)" >> $GITHUB_OUTPUT
- name: Extract binary from container (copy)
run: docker cp ${{ steps.extract-binary-image.outputs.container_id }}:/sbin/conduwuit /tmp/binaries/conduwuit-${{ matrix.target_cpu }}-${{ matrix.slug }}-${{ matrix.profile }}
- name: Extract binary from container (cleanup)
run: docker rm ${{ steps.extract-binary-image.outputs.container_id }}
- name: Upload binary artifact
uses: forgejo/upload-artifact@v4
@@ -249,7 +223,6 @@ jobs:
if-no-files-found: error
- name: Upload digest
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/upload-artifact@v4
with:
name: digests-${{ matrix.slug }}
@@ -262,7 +235,6 @@ jobs:
needs: [define-variables, build-image]
steps:
- name: Download digests
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: forgejo/download-artifact@v4
with:
path: /tmp/digests
@@ -270,7 +242,6 @@ jobs:
merge-multiple: true
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Login to builtin registry
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.BUILTIN_REGISTRY }}
@@ -278,15 +249,9 @@ jobs:
password: ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
uses: docker/setup-buildx-action@v3
with:
# Use persistent BuildKit if BUILDKIT_ENDPOINT is set (e.g. tcp://buildkit:8125)
driver: ${{ env.BUILDKIT_ENDPOINT != '' && 'remote' || 'docker-container' }}
endpoint: ${{ env.BUILDKIT_ENDPOINT || '' }}
- name: Extract metadata (tags) for Docker
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
id: meta
uses: docker/metadata-action@v5
with:
@@ -304,7 +269,6 @@ jobs:
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
- name: Create manifest list and push
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
working-directory: /tmp/digests
env:
IMAGES: ${{needs.define-variables.outputs.images}}
@@ -322,7 +286,6 @@ jobs:
done
- name: Inspect image
if: ${{ env.BUILTIN_REGISTRY_ENABLED == 'true' }}
env:
IMAGES: ${{needs.define-variables.outputs.images}}
shell: bash
+1 -1
View File
@@ -70,7 +70,7 @@ jobs:
package-cache-
- name: Self-hosted Renovate
uses: https://github.com/renovatebot/github-action@v43.0.11
uses: https://github.com/renovatebot/github-action@v43.0.9
env:
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}
RENOVATE_DRY_RUN: ${{ inputs.dryRun || 'false' }}
-1
View File
@@ -13,4 +13,3 @@ Rudi Floren <rudi.floren@gmail.com> <rudi.floren@googlemail.com>
Tamara Schmitz <tamara.zoe.schmitz@posteo.de> <15906939+tamara-schmitz@users.noreply.github.com>
Timo Kösters <timo@koesters.xyz>
x4u <xi.zhu@protonmail.ch> <14617923-x4u@users.noreply.gitlab.com>
Ginger <ginger@gingershaped.computer> <75683114+gingershaped@users.noreply.github.com>
+1 -2
View File
@@ -7,6 +7,5 @@
"continuwuity",
"homeserver",
"homeservers"
],
"rust-analyzer.cargo.features": ["full"]
]
}
Generated
+417 -521
View File
File diff suppressed because it is too large Load Diff
+35 -19
View File
@@ -48,15 +48,15 @@ features = ["ffi", "std", "union"]
version = "0.6.2"
[workspace.dependencies.ctor]
version = "0.5.0"
version = "0.2.9"
[workspace.dependencies.cargo_toml]
version = "0.22"
version = "0.21"
default-features = false
features = ["features"]
[workspace.dependencies.toml]
version = "0.9.5"
version = "0.8.14"
default-features = false
features = ["parse"]
@@ -411,28 +411,25 @@ default-features = false
# optional opentelemetry, performance measurements, flamegraphs, etc for performance measurements and monitoring
[workspace.dependencies.opentelemetry]
version = "0.30.0"
version = "0.21.0"
[workspace.dependencies.tracing-flame]
version = "0.2.0"
[workspace.dependencies.tracing-opentelemetry]
version = "0.31.0"
version = "0.22.0"
[workspace.dependencies.opentelemetry_sdk]
version = "0.30.0"
version = "0.21.2"
features = ["rt-tokio"]
[workspace.dependencies.opentelemetry-otlp]
version = "0.30.0"
features = ["http", "trace", "logs", "metrics"]
[workspace.dependencies.opentelemetry-jaeger-propagator]
version = "0.30.0"
[workspace.dependencies.opentelemetry-jaeger]
version = "0.20.0"
features = ["rt-tokio"]
# optional sentry metrics for crash/panic reporting
[workspace.dependencies.sentry]
version = "0.42.0"
version = "0.37.0"
default-features = false
features = [
"backtrace",
@@ -448,9 +445,9 @@ features = [
]
[workspace.dependencies.sentry-tracing]
version = "0.42.0"
version = "0.37.0"
[workspace.dependencies.sentry-tower]
version = "0.42.0"
version = "0.37.0"
# jemalloc usage
[workspace.dependencies.tikv-jemalloc-sys]
@@ -479,7 +476,7 @@ features = ["use_std"]
version = "0.4"
[workspace.dependencies.nix]
version = "0.30.1"
version = "0.29.0"
default-features = false
features = ["resource"]
@@ -501,7 +498,7 @@ version = "0.4.3"
default-features = false
[workspace.dependencies.termimad]
version = "0.34.0"
version = "0.31.2"
default-features = false
[workspace.dependencies.checked_ops]
@@ -539,11 +536,11 @@ version = "0.2"
version = "0.2"
[workspace.dependencies.minicbor]
version = "2.1.1"
version = "0.26.3"
features = ["std"]
[workspace.dependencies.minicbor-serde]
version = "0.6.0"
version = "0.4.1"
features = ["std"]
[workspace.dependencies.maplit]
@@ -767,6 +764,25 @@ incremental = true
[profile.dev.package.conduwuit_core]
inherits = "dev"
#rustflags = [
# '--cfg', 'conduwuit_mods',
# '-Ztime-passes',
# '-Zmir-opt-level=0',
# '-Ztls-model=initial-exec',
# '-Cprefer-dynamic=true',
# '-Zstaticlib-prefer-dynamic=true',
# '-Zstaticlib-allow-rdylib-deps=true',
# '-Zpacked-bundled-libs=false',
# '-Zplt=true',
# '-Clink-arg=-Wl,--as-needed',
# '-Clink-arg=-Wl,--allow-shlib-undefined',
# '-Clink-arg=-Wl,-z,lazy',
# '-Clink-arg=-Wl,-z,unique',
# '-Clink-arg=-Wl,-z,nodlopen',
# '-Clink-arg=-Wl,-z,nodelete',
#]
[profile.dev.package.xtask-generate-commands]
inherits = "dev"
[profile.dev.package.conduwuit]
inherits = "dev"
#rustflags = [
+4 -10
View File
@@ -591,19 +591,13 @@
#
#default_room_version = 11
# Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
# Jaeger exporter. Traces will be sent via OTLP to a collector (such as
# Jaeger) that supports the OpenTelemetry Protocol.
# This item is undocumented. Please contribute documentation for it.
#
# Configure your OTLP endpoint using the OTEL_EXPORTER_OTLP_ENDPOINT
# environment variable (defaults to http://localhost:4318).
#
#allow_otlp = false
#allow_jaeger = false
# Filter for OTLP tracing spans. This controls which spans are exported
# to the OTLP collector.
# This item is undocumented. Please contribute documentation for it.
#
#otlp_filter = "info"
#jaeger_filter = "info"
# If the 'perf_measurements' compile-time feature is enabled, enables
# collecting folded stack trace profile of tracing spans using
+16 -41
View File
@@ -199,57 +199,32 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
EOF
# Extract dynamically linked dependencies
RUN <<'DEPS_EOF'
RUN <<EOF
set -o xtrace
mkdir /out/libs /out/libs-root
# Process each binary
mkdir /out/libs
mkdir /out/libs-root
for BINARY in /out/sbin/*; do
if lddtree_output=$(lddtree "$BINARY" 2>/dev/null) && [ -n "$lddtree_output" ]; then
echo "$lddtree_output" | awk '{print $(NF-0) " " $1}' | sort -u -k 1,1 | \
awk '{dest = ($2 ~ /^\//) ? "/out/libs-root" $2 : "/out/libs/" $2; print "install -D " $1 " " dest}' | \
while read cmd; do eval "$cmd"; done
fi
lddtree "$BINARY" | awk '{print $(NF-0) " " $1}' | sort -u -k 1,1 | awk '{print "install", "-D", $1, (($2 ~ /^\//) ? "/out/libs-root" $2 : "/out/libs/" $2)}' | xargs -I {} sh -c {}
done
# Show what will be copied to runtime
echo "=== Libraries being copied to runtime image:"
find /out/libs* -type f 2>/dev/null | sort || echo "No libraries found"
DEPS_EOF
FROM ubuntu:latest AS prepper
# Create layer structure
RUN mkdir -p /layer1/etc/ssl/certs \
/layer2/usr/lib \
/layer3/sbin /layer3/sbom
# Copy SSL certs and root-path libraries to layer1 (ultra-stable)
COPY --from=base /etc/ssl/certs /layer1/etc/ssl/certs
COPY --from=builder /out/libs-root/ /layer1/
# Copy application libraries to layer2 (semi-stable)
COPY --from=builder /out/libs/ /layer2/usr/lib/
# Copy binaries and SBOM to layer3 (volatile)
COPY --from=builder /out/sbin/ /layer3/sbin/
COPY --from=builder /out/sbom/ /layer3/sbom/
# Fix permissions after copying
RUN chmod -R 755 /layer1 /layer2 /layer3
EOF
FROM scratch
WORKDIR /
# Copy ultra-stable layer (SSL certs, system libraries)
COPY --from=prepper /layer1/ /
# Copy root certs for tls into image
# You can also mount the certs from the host
# --volume /etc/ssl/certs:/etc/ssl/certs:ro
COPY --from=base /etc/ssl/certs /etc/ssl/certs
# Copy semi-stable layer (application libraries)
COPY --from=prepper /layer2/ /
# Copy our build
COPY --from=builder /out/sbin/ /sbin/
# Copy SBOM
COPY --from=builder /out/sbom/ /sbom/
# Copy volatile layer (binaries, SBOM)
COPY --from=prepper /layer3/ /
# Copy dynamic libraries to root
COPY --from=builder /out/libs-root/ /
COPY --from=builder /out/libs/ /usr/lib/
# Inform linker where to find libraries
ENV LD_LIBRARY_PATH=/usr/lib
Generated
+19 -1
View File
@@ -513,6 +513,23 @@
"type": "github"
}
},
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1753385396,
"narHash": "sha256-/Hvy1yTH/0D5aa7bc+/uqFugCQq4InTdwlRw88vA5IY=",
"ref": "10.4.fb",
"rev": "28d4b7276c16ed3e28af1bd96162d6442ce25923",
"revCount": 13318,
"type": "git",
"url": "https://forgejo.ellis.link/continuwuation/rocksdb"
},
"original": {
"ref": "10.4.fb",
"type": "git",
"url": "https://forgejo.ellis.link/continuwuation/rocksdb"
}
},
"root": {
"inputs": {
"attic": "attic",
@@ -522,7 +539,8 @@
"flake-compat": "flake-compat_3",
"flake-utils": "flake-utils",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_5"
"nixpkgs": "nixpkgs_5",
"rocksdb": "rocksdb"
}
},
"rust-analyzer-src": {
+5 -7
View File
@@ -16,6 +16,10 @@
flake-utils.url = "github:numtide/flake-utils?ref=main";
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixpkgs-unstable";
rocksdb = {
url = "git+https://forgejo.ellis.link/continuwuation/rocksdb?ref=10.4.fb";
flake = false;
};
};
outputs =
@@ -61,13 +65,7 @@
inherit (self) liburing;
}).overrideAttrs
(old: {
src = pkgsHost.fetchFromGitea {
domain = "forgejo.ellis.link";
owner = "continuwuation";
repo = "rocksdb";
rev = "10.4.fb";
sha256 = "sha256-/Hvy1yTH/0D5aa7bc+/uqFugCQq4InTdwlRw88vA5IY=";
};
src = inputs.rocksdb;
version = "v10.4.fb";
cmakeFlags =
pkgs.lib.subtractLists [
+4 -20
View File
@@ -1,29 +1,13 @@
# Continuwuity for Debian
This document provides information about downloading and deploying the Debian package. You can also use this guide for other deb-based distributions such as Ubuntu.
This document provides information about downloading and deploying the Debian package. You can also use this guide for other `apt`-based distributions such as Ubuntu.
### Installation
To add the Continuwuation apt repository:
```bash
# Replace with `"dev"` for bleeding-edge builds at your own risk
export COMPONENT="stable"
# Import the Continuwuation signing key
sudo curl https://forgejo.ellis.link/api/packages/continuwuation/debian/repository.key -o /etc/apt/keyrings/forgejo-continuwuation.asc
# Add a new apt source list pointing to the repository
echo "deb [signed-by=/etc/apt/keyrings/forgejo-continuwuation.asc] https://forgejo.ellis.link/api/packages/continuwuation/debian $(lsb_release -sc) $COMPONENT" | sudo tee /etc/apt/sources.list.d/continuwuation.list
# Update remote package lists
sudo apt update
```
To install continuwuity:
```bash
sudo apt install continuwuity
```
The `continuwuity` package conflicts with the old `conduwuit` package and will remove it automatically when installed.
See the [generic deployment guide](../deploying/generic.md) for additional information about using the Debian package.
No `apt` repository is currently available. This feature is in development.
### Configuration
After installation, Continuwuity places the example configuration at `/etc/conduwuit/conduwuit.toml` as the default configuration file. The configuration file indicates which settings you must change before starting the service.
@@ -32,7 +16,7 @@ ### Configuration
### Running
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop Continuwuity. The binary installs at `/usr/bin/conduwuit`.
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop Continuwuity. The binary installs at `/usr/sbin/conduwuit`.
By default, this package assumes that Continuwuity runs behind a reverse proxy. The default configuration options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS. To federate properly, you must set up TLS certificates and certificate renewal.
-1
View File
@@ -89,7 +89,6 @@ serde_yaml.workspace = true
tokio.workspace = true
tracing-subscriber.workspace = true
tracing.workspace = true
ctor.workspace = true
[lints]
workspace = true
+8 -1
View File
@@ -281,8 +281,15 @@ pub(super) async fn get_remote_pdu(
vec![(event_id, value, room_id)]
};
info!("Attempting to handle event ID {event_id} as backfilled PDU");
self.services
.rooms
.timeline
.backfill_pdu(&server, response.pdu)
.await?;
let text = serde_json::to_string_pretty(&json)?;
let msg = "Got PDU from specified server:";
let msg = "Got PDU from specified server and handled as backfilled";
write!(self, "{msg}. Event body:\n```json\n{text}\n```")
},
}
+1 -1
View File
@@ -360,7 +360,7 @@ pub(super) async fn get_remote_thumbnail(
) -> Result {
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
let timeout = Duration::from_millis(timeout.into());
let dim = Dim::new(width, height, None, None);
let dim = Dim::new(width, height, None);
let mut result = self
.services
.media
-2
View File
@@ -29,8 +29,6 @@
pub(crate) const PAGE_SIZE: usize = 100;
use ctor::{ctor, dtor};
conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {}
conduwuit::rustc_flags_capture! {}
+1 -1
View File
@@ -57,5 +57,5 @@ pub(super) async fn pdus(
.try_collect()
.await?;
self.write_str(&format!("```\n{result:#?}\n```")).await
self.write_str(&format!("{result:#?}")).await
}
-1
View File
@@ -93,7 +93,6 @@ serde.workspace = true
sha1.workspace = true
tokio.workspace = true
tracing.workspace = true
ctor.workspace = true
[lints]
workspace = true
+1 -1
View File
@@ -99,7 +99,7 @@ pub(crate) async fn get_content_thumbnail_route(
) -> Result<get_content_thumbnail::v1::Response> {
let user = body.sender_user();
let dim = Dim::from_ruma(body.width, body.height, body.method.clone(), body.animated)?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let mxc = Mxc {
server_name: &body.server_name,
media_id: &body.media_id,
+1 -1
View File
@@ -322,7 +322,7 @@ pub(crate) async fn get_content_thumbnail_legacy_route(
media_id: &body.media_id,
};
let dim = Dim::from_ruma(body.width, body.height, body.method.clone(), body.animated)?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
match services.media.get_thumbnail(&mxc, &dim).await? {
| Some(FileMeta {
content,
+6 -5
View File
@@ -35,6 +35,7 @@
};
use tracing::warn;
use super::utils::{count_to_token, parse_pagination_token as parse_token};
use crate::Ruma;
/// list of safe and common non-state events to ignore if the user is ignored
@@ -84,14 +85,14 @@ pub(crate) async fn get_message_events_route(
let from: PduCount = body
.from
.as_deref()
.map(str::parse)
.map(parse_token)
.transpose()?
.unwrap_or_else(|| match body.dir {
| Direction::Forward => PduCount::min(),
| Direction::Backward => PduCount::max(),
});
let to: Option<PduCount> = body.to.as_deref().map(str::parse).transpose()?;
let to: Option<PduCount> = body.to.as_deref().map(parse_token).transpose()?;
let limit: usize = body
.limit
@@ -180,8 +181,8 @@ pub(crate) async fn get_message_events_route(
.collect();
Ok(get_message_events::v3::Response {
start: from.to_string(),
end: next_token.as_ref().map(PduCount::to_string),
start: count_to_token(from),
end: next_token.map(count_to_token),
chunk,
state,
})
@@ -320,7 +321,7 @@ pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Opti
filter.matches(pdu).then_some(item)
}
#[cfg_attr(debug_assertions, ctor::ctor)]
#[cfg_attr(debug_assertions, conduwuit::ctor)]
fn _is_sorted() {
debug_assert!(
IGNORED_MESSAGE_TYPES.is_sorted(),
+1
View File
@@ -37,6 +37,7 @@
pub(super) mod unstable;
pub(super) mod unversioned;
pub(super) mod user_directory;
pub(super) mod utils;
pub(super) mod voip;
pub(super) mod well_known;
+4 -3
View File
@@ -18,6 +18,7 @@
events::{TimelineEventType, relation::RelationType},
};
use super::utils::{count_to_token, parse_pagination_token as parse_token};
use crate::Ruma;
/// # `GET /_matrix/client/r0/rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
@@ -110,14 +111,14 @@ async fn paginate_relations_with_filter(
dir: Direction,
) -> Result<get_relating_events::v1::Response> {
let start: PduCount = from
.map(str::parse)
.map(parse_token)
.transpose()?
.unwrap_or_else(|| match dir {
| Direction::Forward => PduCount::min(),
| Direction::Backward => PduCount::max(),
});
let to: Option<PduCount> = to.map(str::parse).transpose()?;
let to: Option<PduCount> = to.map(parse_token).transpose()?;
// Use limit or else 30, with maximum 100
let limit: usize = limit
@@ -192,7 +193,7 @@ async fn paginate_relations_with_filter(
| Direction::Forward => events.last(),
| Direction::Backward => events.first(),
}
.map(|(count, _)| count.to_string())
.map(|(count, _)| count_to_token(*count))
} else {
None
};
+6 -1
View File
@@ -18,7 +18,7 @@ pub(crate) async fn get_room_event_route(
let event = services
.rooms
.timeline
.get_remote_pdu(room_id, event_id)
.get_pdu(event_id)
.map_err(|_| err!(Request(NotFound("Event {} not found.", event_id))));
let visible = services
@@ -33,6 +33,11 @@ pub(crate) async fn get_room_event_route(
return Err!(Request(Forbidden("You don't have permission to view this event.")));
}
debug_assert!(
event.event_id() == event_id && event.room_id() == room_id,
"Fetched PDU must match requested"
);
event.add_age().ok();
Ok(get_room_event::v3::Response { event: event.into_format() })
+28
View File
@@ -0,0 +1,28 @@
use conduwuit::{
Result, err,
matrix::pdu::{PduCount, ShortEventId},
};
/// Parse a pagination token, trying ShortEventId first, then falling back to
/// PduCount
pub(crate) fn parse_pagination_token(token: &str) -> Result<PduCount> {
// Try parsing as ShortEventId first
if let Ok(shorteventid) = token.parse::<ShortEventId>() {
// ShortEventId maps directly to a PduCount in our database
Ok(PduCount::Normal(shorteventid))
} else if let Ok(count) = token.parse::<u64>() {
// Fallback to PduCount for backwards compatibility
Ok(PduCount::Normal(count))
} else if let Ok(count) = token.parse::<i64>() {
// Also handle negative counts for backfilled events
Ok(PduCount::from_signed(count))
} else {
Err(err!(Request(InvalidParam("Invalid pagination token"))))
}
}
/// Convert a PduCount to a token string (using the underlying ShortEventId)
pub(crate) fn count_to_token(count: PduCount) -> String {
// The PduCount's unsigned value IS the ShortEventId
count.into_unsigned().to_string()
}
+1 -1
View File
@@ -67,7 +67,7 @@ pub(crate) async fn get_content_thumbnail_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<get_content_thumbnail::v1::Request>,
) -> Result<get_content_thumbnail::v1::Response> {
let dim = Dim::from_ruma(body.width, body.height, body.method.clone(), body.animated)?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let mxc = Mxc {
server_name: services.globals.server_name(),
media_id: &body.media_id,
+31 -82
View File
@@ -1,13 +1,12 @@
#![allow(deprecated)]
use std::{borrow::Borrow, time::Instant, vec};
use std::borrow::Borrow;
use axum::extract::State;
use conduwuit::{
Err, Event, Result, at, debug, err, info,
Err, Result, at, err,
matrix::event::gen_event_id_canonical_json,
trace,
utils::stream::{BroadbandExt, IterStream, TryBroadbandExt},
utils::stream::{IterStream, TryBroadbandExt},
warn,
};
use conduwuit_service::Services;
@@ -26,14 +25,12 @@
use crate::Ruma;
/// helper method for /send_join v1 and v2
#[tracing::instrument(skip(services, pdu, omit_members), fields(room_id = room_id.as_str(), origin = origin.as_str()))]
async fn create_join_event(
services: &Services,
origin: &ServerName,
room_id: &RoomId,
pdu: &RawJsonValue,
omit_members: bool,
) -> Result<create_join_event::v2::RoomState> {
) -> Result<create_join_event::v1::RoomState> {
if !services.rooms.metadata.exists(room_id).await {
return Err!(Request(NotFound("Room is unknown to this server.")));
}
@@ -56,10 +53,8 @@ async fn create_join_event(
// We do not add the event_id field to the pdu here because of signature and
// hashes checks
trace!("Getting room version");
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
trace!("Generating event ID and converting to canonical json");
let Ok((event_id, mut value)) = gen_event_id_canonical_json(pdu, &room_version_id) else {
// Event could not be converted to canonical json
return Err!(Request(BadJson("Could not convert event to canonical json.")));
@@ -108,6 +103,7 @@ async fn create_join_event(
)));
}
// ACL check sender user server name
let sender: OwnedUserId = serde_json::from_value(
value
.get("sender")
@@ -117,6 +113,12 @@ async fn create_join_event(
)
.map_err(|e| err!(Request(BadJson(warn!("sender property is not a valid user ID: {e}")))))?;
services
.rooms
.event_handler
.acl_check(sender.server_name(), room_id)
.await?;
// check if origin server is trying to send for another server
if sender.server_name() != origin {
return Err!(Request(Forbidden("Not allowed to join on behalf of another server.")));
@@ -178,6 +180,11 @@ async fn create_join_event(
}
}
services
.server_keys
.hash_and_sign_event(&mut value, &room_version_id)
.map_err(|e| err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}")))))?;
let origin: OwnedServerName = serde_json::from_value(
value
.get("origin")
@@ -187,12 +194,6 @@ async fn create_join_event(
)
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
trace!("Signing send_join event");
services
.server_keys
.hash_and_sign_event(&mut value, &room_version_id)
.map_err(|e| err!(Request(InvalidParam(warn!("Failed to sign send_join event: {e}")))))?;
let mutex_lock = services
.rooms
.event_handler
@@ -200,7 +201,6 @@ async fn create_join_event(
.lock(room_id)
.await;
trace!("Acquired send_join mutex, persisting join event");
let pdu_id = services
.rooms
.event_handler
@@ -210,7 +210,7 @@ async fn create_join_event(
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
drop(mutex_lock);
trace!("Fetching current state IDs");
let state_ids: Vec<OwnedEventId> = services
.rooms
.state_accessor
@@ -219,23 +219,9 @@ async fn create_join_event(
.collect()
.await;
trace!(%omit_members, "Constructing current state");
let state = state_ids
.iter()
.try_stream()
.broad_filter_map(|event_id| async move {
if omit_members {
if let Ok(e) = event_id.as_ref() {
let pdu = services.rooms.timeline.get_pdu(e).await;
if pdu.is_ok_and(|p| p.kind().to_cow_str() == "m.room.member") {
trace!("omitting member event {e:?} from returned state");
// skip members
return None;
}
}
}
Some(event_id)
})
.broad_and_then(|event_id| services.rooms.timeline.get_pdu_json(event_id))
.broad_and_then(|pdu| {
services
@@ -248,7 +234,6 @@ async fn create_join_event(
.await?;
let starting_events = state_ids.iter().map(Borrow::borrow);
trace!("Constructing auth chain");
let auth_chain = services
.rooms
.auth_chain
@@ -265,37 +250,13 @@ async fn create_join_event(
.try_collect()
.boxed()
.await?;
info!(fast_join = %omit_members, "Sending join event to other servers");
services.sending.send_pdu_room(room_id, &pdu_id).await?;
debug!("Finished sending join event");
let servers_in_room: Option<Vec<_>> = if !omit_members {
None
} else {
trace!("Fetching list of servers in room");
let servers: Vec<String> = services
.rooms
.state_cache
.room_servers(room_id)
.map(|sn| sn.as_str().to_owned())
.collect()
.await;
// If there's no servers, just add us
let servers = if servers.is_empty() {
warn!("Failed to find any servers, adding our own server name as a last resort");
vec![services.globals.server_name().to_string()]
} else {
trace!("Found {} servers in room", servers.len());
servers
};
Some(servers)
};
debug!("Returning send_join data");
Ok(create_join_event::v2::RoomState {
Ok(create_join_event::v1::RoomState {
auth_chain,
state,
event: to_raw_value(&CanonicalJsonValue::Object(value)).ok(),
members_omitted: omit_members,
servers_in_room,
})
}
@@ -333,23 +294,11 @@ pub(crate) async fn create_join_event_v1_route(
}
}
let now = Instant::now();
let room_state = create_join_event(&services, body.origin(), &body.room_id, &body.pdu, false)
let room_state = create_join_event(&services, body.origin(), &body.room_id, &body.pdu)
.boxed()
.await?;
let transformed = create_join_event::v1::RoomState {
auth_chain: room_state.auth_chain,
state: room_state.state,
event: room_state.event,
};
info!(
"Finished sending a join for {} in {} in {:?}",
body.origin(),
&body.room_id,
now.elapsed()
);
Ok(create_join_event::v1::Response { room_state: transformed })
Ok(create_join_event::v1::Response { room_state })
}
/// # `PUT /_matrix/federation/v2/send_join/{roomId}/{eventId}`
@@ -380,17 +329,17 @@ pub(crate) async fn create_join_event_v2_route(
}
}
let now = Instant::now();
let room_state =
create_join_event(&services, body.origin(), &body.room_id, &body.pdu, body.omit_members)
let create_join_event::v1::RoomState { auth_chain, state, event } =
create_join_event(&services, body.origin(), &body.room_id, &body.pdu)
.boxed()
.await?;
info!(
"Finished sending a join for {} in {} in {:?}",
body.origin(),
&body.room_id,
now.elapsed()
);
let room_state = create_join_event::v2::RoomState {
members_omitted: false,
auth_chain,
state,
event,
servers_in_room: None,
};
Ok(create_join_event::v2::Response { room_state })
}
+5 -14
View File
@@ -714,21 +714,12 @@ pub struct Config {
#[serde(default)]
pub well_known: WellKnownConfig,
/// Enable OpenTelemetry OTLP tracing export. This replaces the deprecated
/// Jaeger exporter. Traces will be sent via OTLP to a collector (such as
/// Jaeger) that supports the OpenTelemetry Protocol.
///
/// Configure your OTLP endpoint using the OTEL_EXPORTER_OTLP_ENDPOINT
/// environment variable (defaults to http://localhost:4318).
#[serde(default, alias = "allow_jaeger")]
pub allow_otlp: bool,
#[serde(default)]
pub allow_jaeger: bool,
/// Filter for OTLP tracing spans. This controls which spans are exported
/// to the OTLP collector.
///
/// default: "info"
#[serde(default = "default_otlp_filter", alias = "jaeger_filter")]
pub otlp_filter: String,
#[serde(default = "default_jaeger_filter")]
pub jaeger_filter: String,
/// If the 'perf_measurements' compile-time feature is enabled, enables
/// collecting folded stack trace profile of tracing spans using
@@ -2376,7 +2367,7 @@ fn default_tracing_flame_filter() -> String {
.to_owned()
}
fn default_otlp_filter() -> String {
fn default_jaeger_filter() -> String {
cfg!(debug_assertions)
.then_some("trace,h2=off")
.unwrap_or("info")
+1 -5
View File
@@ -1,10 +1,6 @@
#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss, clippy::as_conversions)]
use std::{
cmp::Ordering,
fmt::{self, Display},
str::FromStr,
};
use std::{cmp::Ordering, fmt, fmt::Display, str::FromStr};
use ruma::api::Direction;
-1
View File
@@ -66,7 +66,6 @@ serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tracing.workspace = true
ctor.workspace = true
[lints]
workspace = true
-2
View File
@@ -3,8 +3,6 @@
extern crate conduwuit_core as conduwuit;
extern crate rust_rocksdb as rocksdb;
use ctor::{ctor, dtor};
conduwuit::mod_ctor! {}
conduwuit::mod_dtor! {}
conduwuit::rustc_flags_capture! {}
+2 -2
View File
@@ -13,13 +13,13 @@ pub(super) fn flags_capture(args: TokenStream) -> TokenStream {
let ret = quote! {
pub static RUSTC_FLAGS: [&str; #flag_len] = [#( #flag ),*];
#[ctor]
#[conduwuit_core::ctor]
fn _set_rustc_flags() {
conduwuit_core::info::rustc::FLAGS.lock().insert(#crate_name, &RUSTC_FLAGS);
}
// static strings have to be yanked on module unload
#[dtor]
#[conduwuit_core::dtor]
fn _unset_rustc_flags() {
conduwuit_core::info::rustc::FLAGS.lock().remove(#crate_name);
}
+6 -13
View File
@@ -22,13 +22,11 @@ crate-type = [
]
[package.metadata.deb]
name = "continuwuity"
maintainer = "continuwuity developers <contact@continuwuity.org>"
copyright = "2024, continuwuity developers"
name = "conduwuit"
maintainer = "strawberry <strawberry@puppygock.gay>"
copyright = "2024, strawberry <strawberry@puppygock.gay>"
license-file = ["../../LICENSE", "3"]
depends = "$auto, ca-certificates"
breaks = ["conduwuit (<<0.5.0)"]
replaces = ["conduwuit (<<0.5.0)"]
extended-description = """\
a cool hard fork of Conduit, a Matrix homeserver written in Rust"""
section = "net"
@@ -128,8 +126,7 @@ perf_measurements = [
"dep:tracing-flame",
"dep:tracing-opentelemetry",
"dep:opentelemetry_sdk",
"dep:opentelemetry-otlp",
"dep:opentelemetry-jaeger-propagator",
"dep:opentelemetry-jaeger",
"conduwuit-core/perf_measurements",
"conduwuit-core/sentry_telemetry",
]
@@ -205,14 +202,11 @@ clap.workspace = true
console-subscriber.optional = true
console-subscriber.workspace = true
const-str.workspace = true
ctor.workspace = true
log.workspace = true
opentelemetry-jaeger.optional = true
opentelemetry-jaeger.workspace = true
opentelemetry.optional = true
opentelemetry.workspace = true
opentelemetry-otlp.optional = true
opentelemetry-otlp.workspace = true
opentelemetry-jaeger-propagator.optional = true
opentelemetry-jaeger-propagator.workspace = true
opentelemetry_sdk.optional = true
opentelemetry_sdk.workspace = true
sentry-tower.optional = true
@@ -232,7 +226,6 @@ tracing-subscriber.workspace = true
tracing.workspace = true
tracing-journald = { workspace = true, optional = true }
[target.'cfg(all(not(target_env = "msvc"), target_os = "linux"))'.dependencies]
hardened_malloc-rs.workspace = true
hardened_malloc-rs.optional = true
+14 -21
View File
@@ -7,8 +7,6 @@
log::{ConsoleFormat, ConsoleWriter, LogLevelReloadHandles, capture, fmt_span},
result::UnwrapOrErr,
};
#[cfg(feature = "perf_measurements")]
use opentelemetry::trace::TracerProvider;
use tracing_subscriber::{EnvFilter, Layer, Registry, fmt, layer::SubscriberExt, reload};
#[cfg(feature = "perf_measurements")]
@@ -89,35 +87,30 @@ pub(crate) fn init(
(None, None)
};
let otlp_filter = EnvFilter::try_new(&config.otlp_filter)
.map_err(|e| err!(Config("otlp_filter", "{e}.")))?;
let jaeger_filter = EnvFilter::try_new(&config.jaeger_filter)
.map_err(|e| err!(Config("jaeger_filter", "{e}.")))?;
let otlp_layer = config.allow_otlp.then(|| {
let jaeger_layer = config.allow_jaeger.then(|| {
opentelemetry::global::set_text_map_propagator(
opentelemetry_jaeger_propagator::Propagator::new(),
opentelemetry_jaeger::Propagator::new(),
);
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_http()
.build()
.expect("Failed to create OTLP exporter");
let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.build();
let tracer = provider.tracer(conduwuit_core::name());
let tracer = opentelemetry_jaeger::new_agent_pipeline()
.with_auto_split_batch(true)
.with_service_name(conduwuit_core::name())
.install_batch(opentelemetry_sdk::runtime::Tokio)
.expect("jaeger agent pipeline");
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
let (otlp_reload_filter, otlp_reload_handle) =
reload::Layer::new(otlp_filter.clone());
reload_handles.add("otlp", Box::new(otlp_reload_handle));
let (jaeger_reload_filter, jaeger_reload_handle) =
reload::Layer::new(jaeger_filter.clone());
reload_handles.add("jaeger", Box::new(jaeger_reload_handle));
Some(telemetry.with_filter(otlp_reload_filter))
Some(telemetry.with_filter(jaeger_reload_filter))
});
let subscriber = subscriber.with(flame_layer).with(otlp_layer);
let subscriber = subscriber.with(flame_layer).with(jaeger_layer);
(subscriber, flame_guard)
};
-1
View File
@@ -13,7 +13,6 @@
mod server;
mod signal;
use ctor::{ctor, dtor};
use server::Server;
rustc_flags_capture! {}
-1
View File
@@ -125,7 +125,6 @@ tokio.workspace = true
tower.workspace = true
tower-http.workspace = true
tracing.workspace = true
ctor.workspace = true
[target.'cfg(all(unix, target_os = "linux"))'.dependencies]
sd-notify.workspace = true
-1
View File
@@ -12,7 +12,6 @@
use conduwuit::{Error, Result, Server};
use conduwuit_service::Services;
use ctor::{ctor, dtor};
use futures::{Future, FutureExt, TryFutureExt};
conduwuit::mod_ctor! {}
-1
View File
@@ -117,7 +117,6 @@ webpage.optional = true
blurhash.workspace = true
blurhash.optional = true
recaptcha-verify = { version = "0.1.5", default-features = false }
ctor.workspace = true
[lints]
workspace = true
+1 -1
View File
@@ -372,7 +372,7 @@ pub async fn fetch_remote_thumbnail_legacy(
})
.await?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone(), body.animated)?;
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
self.upload_thumbnail(
&mxc,
None,
+11 -22
View File
@@ -16,13 +16,12 @@
use super::{FileMeta, data::Metadata};
/// Dimension and format specification for a thumbnail.
/// Dimension specification for a thumbnail.
#[derive(Debug)]
pub struct Dim {
pub width: u32,
pub height: u32,
pub method: Method,
pub animated: bool,
}
impl super::Service {
@@ -180,14 +179,8 @@ fn into_filemeta(data: Metadata, content: Vec<u8>) -> FileMeta {
}
impl Dim {
/// Instantiate a Dim from Ruma integers with optional method and animation
/// flag.
pub fn from_ruma(
width: UInt,
height: UInt,
method: Option<Method>,
animated: Option<bool>,
) -> Result<Self> {
/// Instantiate a Dim from Ruma integers with optional method.
pub fn from_ruma(width: UInt, height: UInt, method: Option<Method>) -> Result<Self> {
let width = width
.try_into()
.map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?;
@@ -195,19 +188,17 @@ pub fn from_ruma(
.try_into()
.map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?;
Ok(Self::new(width, height, method, animated))
Ok(Self::new(width, height, method))
}
/// Instantiate a Dim with optional method and animation flag.
/// Instantiate a Dim with optional method
#[inline]
#[must_use]
pub fn new(width: u32, height: u32, method: Option<Method>, animated: Option<bool>) -> Self {
pub fn new(width: u32, height: u32, method: Option<Method>) -> Self {
Self {
width,
height,
method: method.unwrap_or(Method::Scale),
// "When not provided, the server SHOULD NOT return an animated thumbnail"
animated: animated.unwrap_or(false),
}
}
@@ -238,7 +229,6 @@ pub fn scaled(&self, image: &Self) -> Result<Self> {
width: x,
height: y,
method: Method::Scale,
animated: self.animated,
})
}
@@ -248,11 +238,11 @@ pub fn scaled(&self, image: &Self) -> Result<Self> {
#[must_use]
pub fn normalized(&self) -> Self {
match (self.width, self.height) {
| (0..=32, 0..=32) => Self::new(32, 32, Some(Method::Crop), Some(self.animated)),
| (0..=96, 0..=96) => Self::new(96, 96, Some(Method::Crop), Some(self.animated)),
| (0..=320, 0..=240) => Self::new(320, 240, Some(Method::Scale), Some(self.animated)),
| (0..=640, 0..=480) => Self::new(640, 480, Some(Method::Scale), Some(self.animated)),
| (0..=800, 0..=600) => Self::new(800, 600, Some(Method::Scale), Some(self.animated)),
| (0..=32, 0..=32) => Self::new(32, 32, Some(Method::Crop)),
| (0..=96, 0..=96) => Self::new(96, 96, Some(Method::Crop)),
| (0..=320, 0..=240) => Self::new(320, 240, Some(Method::Scale)),
| (0..=640, 0..=480) => Self::new(640, 480, Some(Method::Scale)),
| (0..=800, 0..=600) => Self::new(800, 600, Some(Method::Scale)),
| _ => Self::default(),
}
}
@@ -270,7 +260,6 @@ fn default() -> Self {
width: 0,
height: 0,
method: Method::Scale,
animated: false,
}
}
}
-1
View File
@@ -33,7 +33,6 @@
extern crate conduwuit_core as conduwuit;
extern crate conduwuit_database as database;
use ctor::{ctor, dtor};
pub(crate) use service::{Args, Dep, Service};
pub use crate::services::Services;
+1 -1
View File
@@ -452,7 +452,7 @@ async fn get_statediff(&self, shortstatehash: ShortStateHash) -> Result<StateDif
.ok()
.take_if(|parent| *parent != 0);
debug_assert!(value.len().is_multiple_of(STRIDE), "value not aligned to stride");
debug_assert!(value.len() % STRIDE == 0, "value not aligned to stride");
let _num_values = value.len() / STRIDE;
let mut add_mode = true;
+4 -121
View File
@@ -1,8 +1,7 @@
use std::iter::once;
use conduwuit::{Err, PduEvent};
use conduwuit_core::{
Result, debug, debug_warn, err, implement, info,
Result, debug, debug_warn, implement, info,
matrix::{
event::Event,
pdu::{PduCount, PduId, RawPduId},
@@ -12,7 +11,7 @@
};
use futures::{FutureExt, StreamExt};
use ruma::{
CanonicalJsonObject, EventId, RoomId, ServerName,
RoomId, ServerName,
api::federation,
events::{
StateEventType, TimelineEventType, room::power_levels::RoomPowerLevelsEventContent,
@@ -101,7 +100,7 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
.boxed();
while let Some(ref backfill_server) = servers.next().await {
info!("Asking {backfill_server} for backfill in {room_id}");
info!("Asking {backfill_server} for backfill");
let response = self
.services
.sending
@@ -129,126 +128,10 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re
}
}
warn!("No servers could backfill, but backfill was needed in room {room_id}");
info!("No servers could backfill, but backfill was needed in room {room_id}");
Ok(())
}
#[implement(super::Service)]
#[tracing::instrument(name = "get_remote_pdu", level = "debug", skip(self))]
pub async fn get_remote_pdu(&self, room_id: &RoomId, event_id: &EventId) -> Result<PduEvent> {
let local = self.get_pdu(event_id).await;
if local.is_ok() {
// We already have this PDU, no need to backfill
debug!("We already have {event_id} in {room_id}, no need to backfill.");
return local;
}
debug!("Preparing to fetch event {event_id} in room {room_id} from remote servers.");
// Similar to backfill_if_required, but only for a single PDU
// Fetch a list of servers to try
if self
.services
.state_cache
.room_joined_count(room_id)
.await
.is_ok_and(|count| count <= 1)
&& !self
.services
.state_accessor
.is_world_readable(room_id)
.await
{
// Room is empty (1 user or none), there is no one that can backfill
return Err!(Request(NotFound("No one can backfill this PDU, room is empty.")));
}
let power_levels: RoomPowerLevelsEventContent = self
.services
.state_accessor
.room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "")
.await
.unwrap_or_default();
let room_mods = power_levels.users.iter().filter_map(|(user_id, level)| {
if level > &power_levels.users_default && !self.services.globals.user_is_local(user_id) {
Some(user_id.server_name())
} else {
None
}
});
let canonical_room_alias_server = once(
self.services
.state_accessor
.get_canonical_alias(room_id)
.await,
)
.filter_map(Result::ok)
.map(|alias| alias.server_name().to_owned())
.stream();
let mut servers = room_mods
.stream()
.map(ToOwned::to_owned)
.chain(canonical_room_alias_server)
.chain(
self.services
.server
.config
.trusted_servers
.iter()
.map(ToOwned::to_owned)
.stream(),
)
.ready_filter(|server_name| !self.services.globals.server_is_ours(server_name))
.filter_map(|server_name| async move {
self.services
.state_cache
.server_in_room(&server_name, room_id)
.await
.then_some(server_name)
})
.boxed();
while let Some(ref backfill_server) = servers.next().await {
info!("Asking {backfill_server} for event {}", event_id);
let value = self
.services
.sending
.send_federation_request(backfill_server, federation::event::get_event::v1::Request {
event_id: event_id.to_owned(),
include_unredacted_content: Some(false),
})
.await
.and_then(|response| {
serde_json::from_str::<CanonicalJsonObject>(response.pdu.get()).map_err(|e| {
err!(BadServerResponse(debug_warn!(
"Error parsing incoming event {e:?} from {backfill_server}"
)))
})
});
let pdu = match value {
| Ok(value) => {
self.services
.event_handler
.handle_incoming_pdu(backfill_server, room_id, event_id, value, false)
.boxed()
.await?;
debug!("Successfully backfilled {event_id} from {backfill_server}");
Some(self.get_pdu(event_id).await)
},
| Err(e) => {
warn!("{backfill_server} failed to provide backfill for room {room_id}: {e}");
None
},
};
if let Some(pdu) = pdu {
debug!("Fetched {event_id} from {backfill_server}");
return pdu;
}
}
Err!("No servers could be used to fetch {} in {}.", room_id, event_id)
}
#[implement(super::Service)]
#[tracing::instrument(skip(self, pdu), level = "debug")]
pub async fn backfill_pdu(&self, origin: &ServerName, pdu: Box<RawJsonValue>) -> Result<()> {
+2 -1
View File
@@ -3,7 +3,8 @@
use conduwuit::{
Err, PduCount, PduEvent, Result, at, err,
result::{LogErr, NotFound},
utils::{self, stream::TryReadyExt},
utils,
utils::stream::TryReadyExt,
};
use database::{Database, Deserialized, Json, KeyVal, Map};
use futures::{FutureExt, Stream, TryFutureExt, TryStreamExt, future::select_ok, pin_mut};