mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-02 05:45:47 +00:00
Compare commits
12 Commits
v0.5.4
...
jade/get-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a1db9e7cc | ||
|
|
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
|
||||
79
docs/onboarding.mdx
Normal file
79
docs/onboarding.mdx
Normal file
@@ -0,0 +1,79 @@
|
||||
# A Quick-Start Guide to Matrix
|
||||
|
||||
### What is Matrix?
|
||||
|
||||
[Matrix](https://matrix.org) is an open, federated, and extensible network for decentralized communication. Think of it like email, but for instant messaging:
|
||||
|
||||
- You create an account with a provider (called a **homeserver**)
|
||||
- You can talk to anyone on Matrix, regardless of which homeserver they use
|
||||
- You can use different apps (called **clients**) to access your account
|
||||
- Your direct messages are end-to-end encrypted by default
|
||||
|
||||
### What's Continuwuity?
|
||||
|
||||
Continuwuity is a homeserver implementation. It's the software you use to set up your own provider, that you control! It's designed to run on few resources, and be easy to set up and maintain compared to similar software.
|
||||
|
||||
### Join a Continuwuity-Powered Homeserver
|
||||
|
||||
The easiest way to try Matrix is to create an account on an existing homeserver.
|
||||
|
||||
:::tip Continuwuity Partnered Homeservers
|
||||
These homeservers are vetted by the Continuwuity team and follow our [partnered server guidelines](./community/ops-guidelines.mdx).
|
||||
:::
|
||||
|
||||
- **[continuwuity.rocks](https://continuwuity.rocks)** - A public demo server operated by the Continuwuity Team
|
||||
- **[federated.nexus](https://federated.nexus)** - A community resource hosting multiple FOSS services, including Matrix
|
||||
|
||||
### Join a Friend
|
||||
|
||||
If you have a friend who runs their own Matrix homeserver, ask them if you can create an account there! If you don't know anybody, consider partnering up to run a server for your community.
|
||||
|
||||
### Join Another Public Homeserver
|
||||
|
||||
There are many public Matrix homeservers you can join, which of then run other software. Here are some resources:
|
||||
|
||||
- [Join Matrix Homeserver List](https://joinmatrix.org/servers/) - A curated list of public homeservers
|
||||
- [matrix.org](https://matrix.org) - The flagship homeserver (note: very large and sometimes slower due to high usage)
|
||||
|
||||
:::info About choosing a homeserver
|
||||
|
||||
Your choice of homeserver is important but not permanent. While your Matrix ID (like `@username:homeserver.org`) will include your homeserver's name, you can communicate with anyone on Matrix regardless of their homeserver. Think of it like choosing an email provider - you can still email anyone, no matter which provider you use!
|
||||
:::
|
||||
|
||||
## Registering an Account
|
||||
|
||||
To interact with a Matrix server, you use a client. There are many matrix clients to choose from - [here's a list of some of them](https://matrix.org/ecosystem/clients/) - but to keep things simple we'll use [Element](https://app.element.io/#/register) or [Cinny](https://app.cinny.in/register/continuwuity.rocks) for this guide - pick what you prefer. Cinny looks closer to Discord, while Element looks like Teams or Slack.
|
||||
|
||||
Once you've opened the client, click on Register / Create an Account, and edit the Homeserver to match the one you decided on. On Element, you might have to click the Edit button to do that.
|
||||
|
||||
Fill out the username and password that you'd like to set. If your server is invite-only you might get asked to enter an invite code - this should have been given to you by the person that invited you.
|
||||
|
||||
After registration, you'll have a Matrix ID that looks like `@username:homeserver.org`. This is your unique identifier across the entire Matrix network.
|
||||
|
||||
:::warning Important: Save your Security Key!
|
||||
Matrix uses end-to-end encryption to keep your messages private. During setup, you'll be asked to save a **Security Key** or **Security Phrase**. Store this somewhere safe (like a password manager) - you'll need it to access your encrypted messages on new devices!
|
||||
:::
|
||||
|
||||
## What's Next?
|
||||
|
||||
Now that you have a Matrix account, you can:
|
||||
|
||||
- **Join public rooms** - Explore communities and conversations. Try [#continuwuity:continuwuity.org](https://matrix.to/#/#continuwuity:continuwuity.org) to chat with us!
|
||||
- **Start private chats** - Message friends directly or create group chats
|
||||
- **Explore Spaces** - Spaces are collections of rooms, similar to Discord servers or Slack workspaces
|
||||
- **Try different clients** - Check out [this list](https://matrix.org/ecosystem/clients/), or ask a friend what they prefer!
|
||||
|
||||
|
||||
|
||||
### Other guides
|
||||
|
||||
- [Matrix vs Discord Guide](https://joinmatrix.org/guide/matrix-vs-discord/) - Coming from Discord?
|
||||
- [Matrix Chat Basics](https://matrix.org/docs/chat_basics/) - Official Matrix.org documentation
|
||||
- [Join Matrix Guide](https://joinmatrix.org/guide/)
|
||||
- [Matrix Features Guide](https://joinmatrix.org/guide/features/) - Deep dive into Matrix features
|
||||
|
||||
---
|
||||
|
||||
## Ready to Run Your Own Homeserver?
|
||||
|
||||
If you want to run your own Matrix homeserver and have some technical knowledge, Continuwuity is an excellent choice. [Check out our guide for getting started with Continuwuity.](./introduction.mdx)
|
||||
@@ -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
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user