mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-10 15:35:46 +00:00
Compare commits
12 Commits
v0.5.4
...
jade/docs-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8e476626f | ||
|
|
4e55e1ea90 | ||
|
|
f5f3108d5f | ||
|
|
d1e1ee6156 | ||
|
|
ae16a45515 | ||
|
|
077bda23a6 | ||
|
|
a2bf0c1223 | ||
|
|
b9b1ff87f2 | ||
|
|
3c0146d437 | ||
|
|
7485d4aa91 | ||
|
|
39bdb4c5a2 | ||
|
|
55fb3b8848 |
@@ -23,7 +23,7 @@ repos:
|
||||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.43.2
|
||||
rev: v1.43.4
|
||||
hooks:
|
||||
- id: typos
|
||||
- id: typos
|
||||
|
||||
1
changelog.d/1249.bugfix.md
Normal file
1
changelog.d/1249.bugfix.md
Normal file
@@ -0,0 +1 @@
|
||||
Fixed invites sent to other users in the same homeserver not being properly sent down sync. Users with missing or broken invites should clear their client caches after updating to make them appear.
|
||||
1
changelog.d/1349.feature
Normal file
1
changelog.d/1349.feature
Normal file
@@ -0,0 +1 @@
|
||||
Introduce a resolver command to allow flushing a server from the cache or to flush the complete cache. Contributed by @Omar007
|
||||
@@ -19,6 +19,16 @@
|
||||
src: /assets/logo.svg
|
||||
alt: continuwuity logo
|
||||
|
||||
beforeFeatures:
|
||||
- title: Matrix for Discord users
|
||||
details: New to Matrix? Learn how Matrix compares to Discord
|
||||
link: https://joinmatrix.org/guide/matrix-vs-discord/
|
||||
buttonText: Find Out the Difference
|
||||
- title: How Matrix Works
|
||||
details: Learn how Matrix works under the hood, and what that means
|
||||
link: https://matrix.org/docs/matrix-concepts/elements-of-matrix/
|
||||
buttonText: Read the Guide
|
||||
|
||||
features:
|
||||
- title: 🚀 High Performance
|
||||
details: Built with Rust for exceptional speed and efficiency. Designed to run smoothly even on modest hardware.
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"message": "Welcome to Continuwuity! Important announcements about the project will appear here."
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"id": 9,
|
||||
"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🩵"
|
||||
"date": "2026-02-09",
|
||||
"message": "Yesterday we released [v0.5.4](https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v0.5.4). Bugfixes, performance improvements and more moderation features! There's also a security fix, so please update as soon as possible. Don't forget to join [our announcements channel](https://matrix.to/#/!jIdNjSM5X-V5JVx2h2kAhUZIIQ08GyzPL55NFZAH1vM/%2489TY9CqRg4-ff1MGo3Ulc5r5X4pakfdzT-99RD8Docc?via=ellis.link&via=explodie.org&via=matrix.org) to get important information sooner <3 "
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -112,6 +112,19 @@ ### `!admin query resolver overrides-cache`
|
||||
|
||||
Query the overrides cache
|
||||
|
||||
### `!admin query resolver flush-cache`
|
||||
|
||||
Flush a given server from the resolver caches or flush them completely
|
||||
|
||||
* Examples:
|
||||
* Flush a specific server:
|
||||
|
||||
`!admin query resolver flush-cache matrix.example.com`
|
||||
|
||||
* Flush all resolver caches completely:
|
||||
|
||||
`!admin query resolver flush-cache --all`
|
||||
|
||||
## `!admin query pusher`
|
||||
|
||||
pusher service
|
||||
|
||||
@@ -20,6 +20,16 @@ ### Lost access to admin room
|
||||
|
||||
## General potential issues
|
||||
|
||||
### Configuration not working as expected
|
||||
|
||||
Sometimes you can make a mistake in your configuration that
|
||||
means things don't get passed to Continuwuity correctly.
|
||||
This is particularly easy to do with environment variables.
|
||||
To check what configuration Continuwuity actually sees, you can
|
||||
use the `!admin server show-config` command in your admin room.
|
||||
Beware that this prints out any secrets in your configuration,
|
||||
so you might want to delete the result afterwards!
|
||||
|
||||
### Potential DNS issues when using Docker
|
||||
|
||||
Docker's DNS setup for containers in a non-default network intercepts queries to
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -124,7 +124,6 @@
|
||||
"integrity": "sha512-m7L3oi4evTDODcY+Qk3cmY/p7GCaauSRe00D0AkXVohNvxFBt7F49uPwBSThS24I9d31zFuAED2jFqBeBlDqWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rspack/core": "2.0.0-alpha.1",
|
||||
"@swc/helpers": "^0.5.18",
|
||||
@@ -377,7 +376,6 @@
|
||||
"integrity": "sha512-tU8rUVaPyC8o8k4ezgigRVQuZhBAC41KWdwZZ0BldN6o+QXSEIb722RnxCTpa9FGK2riqcwJgM+OqqcqXsFpmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@mdx-js/mdx": "^3.1.1",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
@@ -564,7 +562,6 @@
|
||||
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
@@ -688,7 +685,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -942,7 +938,8 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
@@ -2972,7 +2969,6 @@
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -2983,7 +2979,6 @@
|
||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -3020,7 +3015,6 @@
|
||||
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3488,7 +3482,6 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clap::Subcommand;
|
||||
use conduwuit::{Result, utils::time};
|
||||
use conduwuit::{Err, Result, utils::time};
|
||||
use futures::StreamExt;
|
||||
use ruma::OwnedServerName;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
/// Resolver service and caches
|
||||
pub enum ResolverCommand {
|
||||
/// Query the destinations cache
|
||||
@@ -18,6 +19,14 @@ pub enum ResolverCommand {
|
||||
OverridesCache {
|
||||
name: Option<String>,
|
||||
},
|
||||
|
||||
/// Flush a specific server from the resolver caches or everything
|
||||
FlushCache {
|
||||
name: Option<OwnedServerName>,
|
||||
|
||||
#[arg(short, long)]
|
||||
all: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
@@ -69,3 +78,18 @@ async fn overrides_cache(&self, server_name: Option<String>) -> Result {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn flush_cache(&self, name: Option<OwnedServerName>, all: bool) -> Result {
|
||||
if all {
|
||||
self.services.resolver.cache.clear().await;
|
||||
writeln!(self, "Resolver caches cleared!").await
|
||||
} else if let Some(name) = name {
|
||||
self.services.resolver.cache.del_destination(&name);
|
||||
self.services.resolver.cache.del_override(&name);
|
||||
self.write_str(&format!("Cleared {name} from resolver caches!"))
|
||||
.await
|
||||
} else {
|
||||
Err!("Missing name. Supply a name or use --all to flush the whole cache.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +406,10 @@ pub async fn get_admins(&self) -> Vec<OwnedUserId> {
|
||||
|
||||
/// Checks whether a given user is an admin of this server
|
||||
pub async fn user_is_admin(&self, user_id: &UserId) -> bool {
|
||||
if self.services.globals.server_user == user_id {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self
|
||||
.services
|
||||
.server
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{cmp, collections::HashMap};
|
||||
use std::{cmp, collections::HashMap, future::ready};
|
||||
|
||||
use conduwuit::{
|
||||
Err, Pdu, Result, debug, debug_info, debug_warn, error, info,
|
||||
Err, Event, Pdu, Result, debug, debug_info, debug_warn, error, info,
|
||||
result::NotFound,
|
||||
utils::{
|
||||
IterStream, ReadyExt,
|
||||
@@ -15,8 +15,9 @@
|
||||
use ruma::{
|
||||
OwnedRoomId, OwnedUserId, RoomId, UserId,
|
||||
events::{
|
||||
GlobalAccountDataEventType, StateEventType, push_rules::PushRulesEvent,
|
||||
room::member::MembershipState,
|
||||
AnyStrippedStateEvent, GlobalAccountDataEventType, StateEventType,
|
||||
push_rules::PushRulesEvent,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
push::Ruleset,
|
||||
serde::Raw,
|
||||
@@ -162,6 +163,14 @@ async fn migrate(services: &Services) -> Result<()> {
|
||||
populate_userroomid_leftstate_table(services).await?;
|
||||
}
|
||||
|
||||
if db["global"]
|
||||
.get(FIXED_LOCAL_INVITE_STATE_MARKER)
|
||||
.await
|
||||
.is_not_found()
|
||||
{
|
||||
fix_local_invite_state(services).await?;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
services.globals.db.database_version().await,
|
||||
DATABASE_VERSION,
|
||||
@@ -721,3 +730,46 @@ async fn populate_userroomid_leftstate_table(services: &Services) -> Result {
|
||||
db.db.sort()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const FIXED_LOCAL_INVITE_STATE_MARKER: &str = "fix_local_invite_state";
|
||||
async fn fix_local_invite_state(services: &Services) -> Result {
|
||||
// Clean up the effects of !1249 by caching stripped state for invites
|
||||
|
||||
type KeyVal<'a> = (Key<'a>, Raw<Vec<AnyStrippedStateEvent>>);
|
||||
type Key<'a> = (&'a UserId, &'a RoomId);
|
||||
|
||||
let db = &services.db;
|
||||
let cork = db.cork_and_sync();
|
||||
let userroomid_invitestate = services.db["userroomid_invitestate"].clone();
|
||||
|
||||
// for each user invited to a room
|
||||
let fixed = userroomid_invitestate.stream()
|
||||
// if they're a local user on this homeserver
|
||||
.try_filter(|((user_id, _), _): &KeyVal<'_>| ready(services.globals.user_is_local(user_id)))
|
||||
.and_then(async |((user_id, room_id), stripped_state): KeyVal<'_>| Ok::<_, conduwuit::Error>((user_id.to_owned(), room_id.to_owned(), stripped_state.deserialize()?)))
|
||||
.try_fold(0_usize, async |mut fixed, (user_id, room_id, stripped_state)| {
|
||||
// and their invite state is None
|
||||
if stripped_state.is_empty()
|
||||
// and they are actually invited to the room
|
||||
&& let Ok(membership_event) = services.rooms.state_accessor.room_state_get(&room_id, &StateEventType::RoomMember, user_id.as_str()).await
|
||||
&& membership_event.get_content::<RoomMemberEventContent>().is_ok_and(|content| content.membership == MembershipState::Invite)
|
||||
// and the invite was sent by a local user
|
||||
&& services.globals.user_is_local(&membership_event.sender) {
|
||||
|
||||
// build and save stripped state for their invite in the database
|
||||
let stripped_state = services.rooms.state.summary_stripped(&membership_event, &room_id).await;
|
||||
userroomid_invitestate.put((&user_id, &room_id), Json(stripped_state));
|
||||
fixed = fixed.saturating_add(1);
|
||||
}
|
||||
|
||||
Ok(fixed)
|
||||
})
|
||||
.await?;
|
||||
|
||||
drop(cork);
|
||||
info!(?fixed, "Fixed local invite state cache entries.");
|
||||
|
||||
db["global"].insert(FIXED_LOCAL_INVITE_STATE_MARKER, []);
|
||||
db.db.sort()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use conduwuit::{
|
||||
Result, err, implement,
|
||||
Pdu, Result, err, implement,
|
||||
matrix::{Event, StateKey},
|
||||
};
|
||||
use futures::{Stream, StreamExt, TryFutureExt};
|
||||
@@ -84,7 +84,7 @@ pub async fn room_state_get(
|
||||
room_id: &RoomId,
|
||||
event_type: &StateEventType,
|
||||
state_key: &str,
|
||||
) -> Result<impl Event> {
|
||||
) -> Result<Pdu> {
|
||||
self.services
|
||||
.state
|
||||
.get_room_shortstatehash(room_id)
|
||||
|
||||
@@ -30,6 +30,7 @@ struct Services {
|
||||
config: Dep<config::Service>,
|
||||
globals: Dep<globals::Service>,
|
||||
metadata: Dep<rooms::metadata::Service>,
|
||||
state: Dep<rooms::state::Service>,
|
||||
state_accessor: Dep<rooms::state_accessor::Service>,
|
||||
users: Dep<users::Service>,
|
||||
}
|
||||
@@ -64,6 +65,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
config: args.depend::<config::Service>("config"),
|
||||
globals: args.depend::<globals::Service>("globals"),
|
||||
metadata: args.depend::<rooms::metadata::Service>("rooms::metadata"),
|
||||
state: args.depend::<rooms::state::Service>("rooms::state"),
|
||||
state_accessor: args
|
||||
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
||||
users: args.depend::<users::Service>("users"),
|
||||
|
||||
@@ -118,10 +118,8 @@ pub async fn update_membership(
|
||||
self.mark_as_joined(user_id, room_id);
|
||||
},
|
||||
| MembershipState::Invite => {
|
||||
// TODO: make sure that passing None for `last_state` is correct behavior.
|
||||
// the call from `append_pdu` used to use `services.state.summary_stripped`
|
||||
// to fill that parameter.
|
||||
self.mark_as_invited(user_id, room_id, pdu.sender(), None, None)
|
||||
let last_state = self.services.state.summary_stripped(pdu, room_id).await;
|
||||
self.mark_as_invited(user_id, room_id, pdu.sender(), Some(last_state), None)
|
||||
.await?;
|
||||
},
|
||||
| MembershipState::Leave | MembershipState::Ban => {
|
||||
|
||||
@@ -105,3 +105,68 @@ body:not(.notTopArrived) header.rp-nav {
|
||||
.rspress-logo {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
/* pre-hero */
|
||||
.custom-section {
|
||||
padding: 4rem 1.5rem;
|
||||
background: var(--rp-c-bg);
|
||||
}
|
||||
|
||||
.custom-cards {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.custom-card {
|
||||
padding: 2rem;
|
||||
border: 1px solid var(--rp-c-divider-light);
|
||||
border-radius: 12px;
|
||||
background: var(--rp-c-bg-soft);
|
||||
text-decoration: none;
|
||||
color: var(--rp-c-text-1);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 280px;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.custom-card:hover {
|
||||
border-color: var(--rp-c-brand);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.custom-card h3 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--rp-c-text-0);
|
||||
}
|
||||
|
||||
.custom-card p {
|
||||
margin: 0 0 1.5rem 0;
|
||||
color: var(--rp-c-text-2);
|
||||
line-height: 1.6;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.custom-card-button {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background: var(--rp-c-brand);
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.custom-card:hover .custom-card-button {
|
||||
background: var(--rp-c-brand-light);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,23 @@ function HomeLayout(props: HomeLayoutProps) {
|
||||
|
||||
return (
|
||||
<BasicHomeLayout
|
||||
beforeFeatures={
|
||||
frontmatter.beforeFeatures ? (
|
||||
<section className="custom-section">
|
||||
<div className="rp-container">
|
||||
<div className="custom-cards">
|
||||
{frontmatter.beforeFeatures.map((item: any, index: number) => (
|
||||
<a key={index} href={item.link} className="custom-card" target="_blank" rel="noopener noreferrer">
|
||||
<h3>{item.title}</h3>
|
||||
<p>{item.details}</p>
|
||||
<span className="custom-card-button">{item.buttonText || 'Learn More'} →</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : <></>
|
||||
}
|
||||
afterFeatures={
|
||||
(frontmatter.doc) ?
|
||||
<main className="rp-doc-layout__doc-container">
|
||||
|
||||
Reference in New Issue
Block a user