Compare commits

..

8 Commits

Author SHA1 Message Date
Jade Ellis 7a6acd1c82 chore: Changelog 2026-01-13 20:29:30 +00:00
Jade Ellis d260c4fcc2 style: Fix yo unused variables 2026-01-13 20:29:30 +00:00
Jade Ellis fa15de9764 feat: Admin announce improvements
- Check announcements on first start
- Print out any fetch errors on first start in the admin room
- Randomly jitter the next check
2026-01-13 20:29:30 +00:00
Jade Ellis e6c7a4ae60 docs: Changelog 2026-01-13 00:05:20 +00:00
Jade Ellis 5bed4ad81d chore: Admin announcement 2026-01-13 00:01:28 +00:00
Jade Ellis 587abe9d14 chore: Release 2026-01-12 23:47:37 +00:00
Jade Ellis c499042a76 docs: Changelog 2026-01-12 23:45:42 +00:00
timedout 86e450a835 fix: M_BAD_JSON in send_join and send_knock 2026-01-12 17:53:37 +00:00
15 changed files with 80 additions and 263 deletions
-52
View File
@@ -1,52 +0,0 @@
name: Create release
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: "main"
fetch-depth: 0
- name: Setup Rust nightly
uses: ./.forgejo/actions/setup-rust
with:
rust-version: nightly
- name: Setup towncrier
run: uv tool install towncrier
- name: Generate release notes
run: cargo run --package xtask --no-default-features -- generate-release-notes > /tmp/RELEASE.md
- name: Create release
run: |
set -euo pipefail
jq -n --rawfile body /tmp/RELEASE.md '{"body": $body, "tag_name": "${{ forgejo.ref_name }}", "name": "${{ forgejo.ref_name }}"}' \
| curl --fail-with-body \
--data-binary @- \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--header "Authorization: token ${{ forgejo.token }}" \
-X POST \
"${{ forgejo.server_url }}/api/v1/repos/${{ forgejo.repository }}/releases"
- name: Remove news fragments
run: git rm -r changelog.d/
- name: Commit changes
run: |
git config user.email "forgejo@mail.ellis.link"
git config user.name "Ellis Git"
git commit -am "chore: Post-release cleanup"
git push origin HEAD:refs/heads/main
+21
View File
@@ -1,3 +1,24 @@
# Continuwuity 0.5.3 (2026-01-12)
## Features
- Improve the display of nested configuration with the `!admin server show-config` command. Contributed by @Jade (#1279)
## Bugfixes
- Fixed `M_BAD_JSON` error when sending invites to other servers or when providing joins. Contributed by @nex (#1286)
## Docs
- Improve admin command documentation generation. Contributed by @ginger (#1280)
## Misc
- Improve timeout-related code for federation and URL previews. Contributed by @Jade (#1278)
# Continuwuity 0.5.2 (2026-01-09)
## Features
Generated
+11 -62
View File
@@ -954,7 +954,7 @@ dependencies = [
[[package]]
name = "conduwuit"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"clap",
"conduwuit_admin",
@@ -986,7 +986,7 @@ dependencies = [
[[package]]
name = "conduwuit_admin"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"clap",
"conduwuit_api",
@@ -1008,7 +1008,7 @@ dependencies = [
[[package]]
name = "conduwuit_api"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"async-trait",
"axum 0.7.9",
@@ -1041,14 +1041,14 @@ dependencies = [
[[package]]
name = "conduwuit_build_metadata"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"built",
]
[[package]]
name = "conduwuit_core"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"argon2",
"arrayvec",
@@ -1109,7 +1109,7 @@ dependencies = [
[[package]]
name = "conduwuit_database"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"async-channel",
"conduwuit_core",
@@ -1128,7 +1128,7 @@ dependencies = [
[[package]]
name = "conduwuit_macros"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"itertools 0.14.0",
"proc-macro2",
@@ -1138,7 +1138,7 @@ dependencies = [
[[package]]
name = "conduwuit_router"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"axum 0.7.9",
"axum-client-ip",
@@ -1173,7 +1173,7 @@ dependencies = [
[[package]]
name = "conduwuit_service"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"async-trait",
"base64 0.22.1",
@@ -1214,7 +1214,7 @@ dependencies = [
[[package]]
name = "conduwuit_web"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"askama 0.14.0",
"axum 0.7.9",
@@ -1671,18 +1671,6 @@ version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
[[package]]
name = "duct"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e66e9c0c03d094e1a0ba1be130b849034aa80c3a2ab8ee94316bc809f3fa684"
dependencies = [
"libc",
"os_pipe",
"shared_child",
"shared_thread",
]
[[package]]
name = "dunce"
version = "1.0.5"
@@ -3386,16 +3374,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "os_pipe"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "parking"
version = "2.2.1"
@@ -4819,40 +4797,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shared_child"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7"
dependencies = [
"libc",
"sigchld",
"windows-sys 0.60.2",
]
[[package]]
name = "shared_thread"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52b86057fcb5423f5018e331ac04623e32d6b5ce85e33300f92c79a1973928b0"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "sigchld"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1"
dependencies = [
"libc",
"os_pipe",
"signal-hook",
]
[[package]]
name = "signal-hook"
version = "0.3.18"
@@ -6266,14 +6216,13 @@ dependencies = [
[[package]]
name = "xtask"
version = "0.5.2"
version = "0.5.3"
dependencies = [
"askama 0.15.1",
"cargo_metadata",
"clap",
"conduwuit",
"conduwuit_admin",
"duct",
]
[[package]]
+1 -1
View File
@@ -12,7 +12,7 @@ license = "Apache-2.0"
# See also `rust-toolchain.toml`
readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
version = "0.5.2"
version = "0.5.3"
[workspace.metadata.crane]
name = "conduwuit"
+1
View File
@@ -0,0 +1 @@
The announcement checker will now announce errors it encounters in the first run to the admin room, plus a few other misc improvements. Contributed by @Jade
@@ -6,10 +6,10 @@
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
},
{
"id": 7,
"mention_room": true,
"date": "2025-12-30",
"message": "Continuwuity v0.5.1 has been released. **The release contains a fix for the critical vulnerability [GHSA-m5p2-vccg-8c9v](https://github.com/continuwuity/continuwuity/security/advisories/GHSA-m5p2-vccg-8c9v) (embargoed) affecting all Conduit-derived servers. Update as soon as possible.**\n\nThis has been *actively exploited* to attempt account takeover and forge events bricking the Continuwuity rooms. The new space is accessible at [Continuwuity (room list)](https://matrix.to/#/!8cR4g-i9ucof69E4JHNg9LbPVkGprHb3SzcrGBDDJgk?via=continuwuity.org&via=starstruck.systems&via=gingershaped.computer)\n"
"id": 8,
"mention_room": false,
"date": "2026-01-12",
"message": "Hey everyone!\n\nJust letting you know we've released [v0.5.3](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.3) - this one is a bit of a hotfix for an issue with inviting and allowing others to join rooms.\n\nIf you appreceate the round-the-clock work we've been doing to keep your servers secure over this holiday period, we'd really appreciate your support - you can sponsor individuals on our team using the 'sponsor' button at the top of [our GitHub repository](https://github.com/continuwuity/continuwuity). If you can't do that, even a star helps - spreading the word and advocating for our project helps keep it going.\n\nHave a lovely rest of your year \\\n[Jade \\(she/her\\)](https://matrix.to/#/%40jade%3Aellis.link) \n🩵"
}
]
}
+2 -12
View File
@@ -13,8 +13,7 @@
use conduwuit_service::Services;
use futures::{FutureExt, StreamExt, TryStreamExt};
use ruma::{
CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId,
ServerName,
CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, ServerName,
api::federation::membership::create_join_event,
events::{
StateEventType,
@@ -178,15 +177,6 @@ async fn create_join_event(
}
}
let origin: OwnedServerName = serde_json::from_value(
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
.clone()
.into(),
)
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
trace!("Signing send_join event");
services
.server_keys
@@ -204,7 +194,7 @@ async fn create_join_event(
let pdu_id = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, room_id, &event_id, value.clone(), true)
.handle_incoming_pdu(sender.server_name(), room_id, &event_id, value.clone(), true)
.boxed()
.await?
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
+2 -11
View File
@@ -6,7 +6,7 @@
};
use futures::FutureExt;
use ruma::{
OwnedServerName, OwnedUserId,
OwnedUserId,
RoomVersionId::*,
api::federation::knock::send_knock,
events::{
@@ -136,15 +136,6 @@ pub(crate) async fn create_knock_event_v1_route(
return Err!(Request(InvalidParam("state_key does not match sender user of event.")));
}
let origin: OwnedServerName = serde_json::from_value(
value
.get("origin")
.ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
.clone()
.into(),
)
.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
let mut event: JsonObject = serde_json::from_str(body.pdu.get())
.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
@@ -163,7 +154,7 @@ pub(crate) async fn create_knock_event_v1_route(
let pdu_id = services
.rooms
.event_handler
.handle_incoming_pdu(&origin, &body.room_id, &event_id, value.clone(), true)
.handle_incoming_pdu(sender.server_name(), &body.room_id, &event_id, value.clone(), true)
.boxed()
.await?
.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
+21 -2
View File
@@ -18,8 +18,9 @@
use std::{sync::Arc, time::Duration};
use async_trait::async_trait;
use conduwuit::{Result, Server, debug, info, warn};
use conduwuit::{Result, Server, debug, error, info, warn};
use database::{Deserialized, Map};
use rand::Rng;
use ruma::events::{Mentions, room::message::RoomMessageEventContent};
use serde::Deserialize;
use tokio::{
@@ -86,9 +87,27 @@ async fn worker(self: Arc<Self>) -> Result<()> {
return Ok(());
}
// Run the first check immediately and send errors to admin room
if let Err(e) = self.check().await {
error!(?e, "Failed to check for announcements on startup");
self.services
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Failed to check for announcements on startup: {e}"
)))
.await
.ok();
}
let first_check_jitter = {
let mut rng = rand::thread_rng();
let jitter_percent = rng.gen_range(-50.0..=10.0);
self.interval.mul_f64(1.0 + jitter_percent / 100.0)
};
let mut i = interval(self.interval);
i.set_missed_tick_behavior(MissedTickBehavior::Delay);
i.reset_after(self.interval);
i.reset_after(first_check_jitter);
loop {
tokio::select! {
() = self.interrupt.notified() => break,
+2 -7
View File
@@ -9,18 +9,13 @@ readme.workspace = true
repository.workspace = true
version.workspace = true
[features]
default = ["generate-docs"]
generate-docs = ["dep:conduwuit", "dep:conduwuit-admin"]
[dependencies]
conduwuit-admin.workspace = true
conduwuit.workspace = true
clap.workspace = true
conduwuit = { workspace = true, optional = true }
conduwuit-admin = { workspace = true, optional = true }
askama = "0.15.1"
cargo_metadata = "0.23.1"
duct = "1.1.1"
[lints]
workspace = true
+1 -7
View File
@@ -1,6 +1,5 @@
mod tasks;
use cargo_metadata::MetadataCommand;
use clap::Parser;
use crate::tasks::Task;
@@ -23,10 +22,5 @@ struct Args {
fn main() -> impl std::process::Termination {
let BaseArgs { task, args } = BaseArgs::parse();
let metadata = MetadataCommand::new()
.no_deps()
.exec()
.expect("should have been able to run cargo");
task.invoke(metadata, args)
task.invoke(args)
}
+8 -1
View File
@@ -2,6 +2,8 @@
use std::{collections::HashMap, fs::File, io::Write, path::{Path, PathBuf}};
use cargo_metadata::MetadataCommand;
use crate::tasks::TaskResult;
trait FileOutput {
@@ -66,9 +68,14 @@ pub(crate) struct Args {
}
#[expect(clippy::needless_pass_by_value)]
pub(super) fn run(metadata: cargo_metadata::Metadata, common_args: crate::Args, task_args: Args) -> TaskResult<()> {
pub(super) fn run(common_args: crate::Args, task_args: Args) -> TaskResult<()> {
let mut queue = FileQueue::default();
let metadata = MetadataCommand::new()
.no_deps()
.exec()
.expect("should have been able to run cargo");
let root = task_args.root.unwrap_or_else(|| metadata.workspace_root.join_os("docs/"));
admin_commands::generate(&mut queue)?;
@@ -1,57 +0,0 @@
use askama::Template;
use duct::cmd;
use crate::tasks::TaskResult;
#[derive(askama::Template)]
#[template(path = "release-notes.md")]
struct ReleaseNotes<'a> {
version: &'a str,
header: &'a str,
changelog: &'a str,
}
#[derive(clap::Args)]
pub(crate) struct Args;
pub(super) fn run(_: cargo_metadata::Metadata, _: crate::Args, _: Args) -> TaskResult<()> {
const TAG_PREFIX: &str = "v";
let tag = cmd!("git", "describe", "--exact")
.stdout_capture()
.read()
.expect("failed to get current tag");
let version = tag
.strip_prefix(TAG_PREFIX)
.expect("tag did not start with expected prefix");
eprintln!("Generating release notes for {version}");
let header = cmd!("git", "tag", "-l", &tag, "--format", "%(contents)")
.stdout_capture()
.read()
.expect("failed to read tag contents");
let mut changelog = cmd!("towncrier", "build", "--draft", "--version", &version)
.stdout_capture()
.stderr_null()
.read()
.expect("failed to run towncrier");
// towncrier generates a title with the project name and date, which we don't want. remove it here.
// split off the first line and make sure it's a markdown heading
assert!(changelog.starts_with("# "), "expected h1 at the start of towncrier output");
let changelog = changelog.split_off(changelog.find("\n\n").unwrap());
let rendered = ReleaseNotes {
version,
header: header.trim(),
changelog: changelog.trim()
}.render()?;
println!("{rendered}");
Ok(())
}
+6 -12
View File
@@ -1,33 +1,29 @@
type TaskResult<T> = Result<T, Box<dyn std::error::Error>>;
#[macro_export]
macro_rules! tasks {
(
$(
$(#[$meta:meta])? $module:ident: $desc:literal
$module:ident: $desc:literal
),*
) => {
$(
$(#[cfg($meta)])?
pub(super) mod $module;
)*
$(pub(super) mod $module;)*
#[derive(clap::Subcommand)]
#[allow(non_camel_case_types)]
pub(super) enum Task {
$(
$(#[cfg($meta)])?
#[clap(about = $desc, long_about = None)]
$module($module::Args),
)*
}
impl Task {
pub(super) fn invoke(self, metadata: cargo_metadata::Metadata, common_args: $crate::Args) -> TaskResult<impl std::process::Termination> {
pub(super) fn invoke(self, common_args: $crate::Args) -> TaskResult<impl std::process::Termination> {
match self {
$(
$(#[cfg($meta)])?
Self::$module(task_args) => {
$module::run(metadata, common_args, task_args)
$module::run(common_args, task_args)
},
)*
}
@@ -37,7 +33,5 @@ pub(super) fn invoke(self, metadata: cargo_metadata::Metadata, common_args: $cra
}
tasks! {
#[feature = "generate-docs"]
generate_docs: "Generate various documentation files. This is run automatically when compiling the website.",
generate_release_notes: "Generate release notes from towncrier and a git tag. Used by CI."
generate_docs: "Generate various documentation files. This is run automatically when compiling the website."
}
-35
View File
@@ -1,35 +0,0 @@
# Continuwuity {{ version }}
{{ header }}
{{ changelog }}
---
## Get Continuwuity
### Docker Images
- Forgejo: `forgejo.ellis.link/continuwuation/continuwuity:v{{ version }}`
Mirrors (may be outdated):
- Docker Hub: `docker.io/continuwuity/continuwuity:v{{ version }}`
- GitHub: `ghcr.io/continuwuity/continuwuity:v{{ version }}`
- GitLab: `registry.gitlab.com/continuwuity/continuwuity:v{{ version }}`
You can also use the `latest` tag for the most recent release.
### First-party Packages
- [Debian packages](https://forgejo.ellis.link/continuwuation/-/packages/debian/continuwuity/)
- [Fedora RPM packages](https://forgejo.ellis.link/continuwuation/-/packages/rpm/continuwuity/)
### Community Packages
[![Packaging status](https://repology.org/badge/vertical-allrepos/continuwuity.svg?minversion={{ version }})](https://repology.org/project/continuwuity/versions)
### Binaries
Plain binaries are distributed below. They require glibc and liburing to work - most distributions already have these installed.
### From Source
See our [deployment documentation](https://continuwuity.org/deploying.html) for instructions on building from source.