mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a6acd1c82 | |||
| d260c4fcc2 | |||
| fa15de9764 | |||
| e6c7a4ae60 | |||
| 5bed4ad81d | |||
| 587abe9d14 | |||
| c499042a76 | |||
| 86e450a835 | |||
| 4c796029bb | |||
| fc3615c46b | |||
| 7375f7a68e |
@@ -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
-11
@@ -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",
|
||||
@@ -6216,7 +6216,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
dependencies = [
|
||||
"askama 0.15.1",
|
||||
"cargo_metadata",
|
||||
|
||||
+1
-1
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -1759,10 +1759,6 @@
|
||||
#
|
||||
#config_reload_signal = true
|
||||
|
||||
# This item is undocumented. Please contribute documentation for it.
|
||||
#
|
||||
#ldap = false
|
||||
|
||||
[global.tls]
|
||||
|
||||
# Path to a valid TLS certificate file.
|
||||
@@ -1930,6 +1926,8 @@
|
||||
#
|
||||
#admin_filter = ""
|
||||
|
||||
[global.antispam]
|
||||
|
||||
[global.antispam.meowlnir]
|
||||
|
||||
# The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
||||
|
||||
@@ -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🩵"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
tag-message = "chore: Release v{{version}}"
|
||||
tag-prefix = ""
|
||||
shared-version = true
|
||||
|
||||
publish = false
|
||||
|
||||
sign-commit = true
|
||||
sign-tag = true
|
||||
|
||||
@@ -13,8 +13,7 @@ use conduwuit::{
|
||||
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."))))?;
|
||||
|
||||
@@ -6,7 +6,7 @@ use conduwuit::{
|
||||
};
|
||||
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."))))?;
|
||||
|
||||
+11
-6
@@ -53,8 +53,7 @@ use crate::{Result, err, error::Error, utils::sys};
|
||||
### For more information, see:
|
||||
### https://continuwuity.org/configuration.html
|
||||
"#,
|
||||
ignore = "config_paths catchall well_known tls blurhashing \
|
||||
allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure antispam"
|
||||
ignore = "config_paths catchall"
|
||||
)]
|
||||
pub struct Config {
|
||||
// Paths to config file(s). Not supposed to be set manually in the config file,
|
||||
@@ -105,7 +104,7 @@ pub struct Config {
|
||||
#[serde(default = "default_port")]
|
||||
port: ListeningPort,
|
||||
|
||||
// external structure; separate section
|
||||
/// display: nested
|
||||
#[serde(default)]
|
||||
pub tls: TlsConfig,
|
||||
|
||||
@@ -724,7 +723,7 @@ pub struct Config {
|
||||
#[serde(default = "default_default_room_version")]
|
||||
pub default_room_version: RoomVersionId,
|
||||
|
||||
// external structure; separate section
|
||||
/// display: nested
|
||||
#[serde(default)]
|
||||
pub well_known: WellKnownConfig,
|
||||
|
||||
@@ -2030,19 +2029,22 @@ pub struct Config {
|
||||
/// etc. This is a hidden argument that should NOT be used in production as
|
||||
/// it is highly insecure and I will personally yell at you if I catch you
|
||||
/// using this.
|
||||
///
|
||||
/// display: hidden
|
||||
#[serde(default)]
|
||||
pub allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure:
|
||||
bool,
|
||||
|
||||
// external structure; separate section
|
||||
/// display: nested
|
||||
#[serde(default)]
|
||||
pub ldap: LdapConfig,
|
||||
|
||||
/// Configuration for antispam support
|
||||
/// display: nested
|
||||
#[serde(default)]
|
||||
pub antispam: Option<Antispam>,
|
||||
|
||||
// external structure; separate section
|
||||
/// display: nested
|
||||
#[serde(default)]
|
||||
pub blurhashing: BlurhashConfig,
|
||||
#[serde(flatten)]
|
||||
@@ -2259,8 +2261,11 @@ struct ListeningAddr {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[config_example_generator(filename = "conduwuit-example.toml", section = "global.antispam")]
|
||||
pub struct Antispam {
|
||||
/// display: nested
|
||||
pub meowlnir: Option<MeowlnirConfig>,
|
||||
/// display: nested
|
||||
pub draupnir: Option<DraupnirConfig>,
|
||||
}
|
||||
|
||||
|
||||
+69
-35
@@ -78,6 +78,8 @@ fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result<To
|
||||
}
|
||||
|
||||
let mut summary: Vec<TokenStream2> = Vec::new();
|
||||
let mut nested_displays: Vec<TokenStream2> = Vec::new();
|
||||
|
||||
if let Fields::Named(FieldsNamed { named, .. }) = &input.fields {
|
||||
for field in named {
|
||||
let Some(ident) = &field.ident else {
|
||||
@@ -92,35 +94,6 @@ fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result<To
|
||||
continue;
|
||||
};
|
||||
|
||||
let doc = get_doc_comment(field)
|
||||
.unwrap_or_else(|| undocumented.into())
|
||||
.trim_end()
|
||||
.to_owned();
|
||||
|
||||
let doc = if doc.ends_with('#') {
|
||||
format!("{doc}\n")
|
||||
} else {
|
||||
format!("{doc}\n#\n")
|
||||
};
|
||||
|
||||
let default = get_doc_comment_line(field, "default")
|
||||
.or_else(|| get_default(field))
|
||||
.unwrap_or_default();
|
||||
|
||||
let default = if !default.is_empty() {
|
||||
format!(" {default}")
|
||||
} else {
|
||||
default
|
||||
};
|
||||
|
||||
if let Some(file) = file.as_mut() {
|
||||
file.write_fmt(format_args!("\n{doc}"))
|
||||
.expect("written to config file");
|
||||
|
||||
file.write_fmt(format_args!("#{ident} ={default}\n"))
|
||||
.expect("written to config file");
|
||||
}
|
||||
|
||||
let display = get_doc_comment_line(field, "display");
|
||||
let display_directive = |key| {
|
||||
display
|
||||
@@ -129,17 +102,77 @@ fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result<To
|
||||
.flat_map(|display| display.split(' '))
|
||||
.any(|directive| directive == key)
|
||||
};
|
||||
let is_nested = display_directive("nested");
|
||||
let is_hidden = display_directive("hidden");
|
||||
|
||||
if !display_directive("hidden") {
|
||||
let value = if display_directive("sensitive") {
|
||||
quote! { "***********" }
|
||||
// Only generate config file entries for non-nested, visible types
|
||||
if !is_nested && !is_hidden {
|
||||
let doc = get_doc_comment(field)
|
||||
.unwrap_or_else(|| undocumented.into())
|
||||
.trim_end()
|
||||
.to_owned();
|
||||
|
||||
let doc = if doc.ends_with('#') {
|
||||
format!("{doc}\n")
|
||||
} else {
|
||||
quote! { format_args!("{:?}", self.#ident) }
|
||||
format!("{doc}\n#\n")
|
||||
};
|
||||
|
||||
let name = ident.to_string();
|
||||
let default = get_doc_comment_line(field, "default")
|
||||
.or_else(|| get_default(field))
|
||||
.unwrap_or_default();
|
||||
|
||||
let default = if !default.is_empty() {
|
||||
format!(" {default}")
|
||||
} else {
|
||||
default
|
||||
};
|
||||
|
||||
if let Some(file) = file.as_mut() {
|
||||
file.write_fmt(format_args!("\n{doc}"))
|
||||
.expect("written to config file");
|
||||
|
||||
file.write_fmt(format_args!("#{ident} ={default}\n"))
|
||||
.expect("written to config file");
|
||||
}
|
||||
}
|
||||
|
||||
// Generate Display implementation for all fields
|
||||
let name = ident.to_string();
|
||||
|
||||
if display_directive("sensitive") {
|
||||
summary.push(quote! {
|
||||
writeln!(out, "| {} | {} |", #name, #value)?;
|
||||
writeln!(out, "| {} | {} |", #name, "***********")?;
|
||||
});
|
||||
} else if is_nested {
|
||||
let is_option = matches!(type_name.as_str(), "Option");
|
||||
if is_option {
|
||||
summary.push(quote! {
|
||||
writeln!(out, "| {} | {} |", #name,
|
||||
if self.#ident.is_some() { "[configured]" } else { "None" })?;
|
||||
});
|
||||
|
||||
nested_displays.push(quote! {
|
||||
if let Some(nested) = &self.#ident {
|
||||
writeln!(out)?;
|
||||
writeln!(out, "## {}", #name)?;
|
||||
write!(out, "{}", nested)?;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
summary.push(quote! {
|
||||
writeln!(out, "| {} | [configured] |", #name)?;
|
||||
});
|
||||
|
||||
nested_displays.push(quote! {
|
||||
writeln!(out)?;
|
||||
writeln!(out, "## {}", #name)?;
|
||||
write!(out, "{}", &self.#ident)?;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
summary.push(quote! {
|
||||
writeln!(out, "| {} | {:?} |", #name, self.#ident)?;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -159,6 +192,7 @@ fn generate_example(input: &ItemStruct, args: &[Meta], write: bool) -> Result<To
|
||||
writeln!(out, "| name | value |")?;
|
||||
writeln!(out, "| :--- | :--- |")?;
|
||||
#( #summary )*
|
||||
#( #nested_displays )*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 @@ impl crate::Service for Service {
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user