mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-09-29 03:10:19 +00:00
matrix-continuwuity_latest: init from matrix-continuwuity, matrix-continuwuity: mark as vulnerable
Not going for an override here because this is cleaner, as to avoid more divergence from master. Not-cherry-picked-because: avoiding breaking changes on master for security backport
This commit is contained in:
@@ -100,5 +100,18 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
||||
];
|
||||
# Not a typo, continuwuity is a drop-in replacement for conduwuit.
|
||||
mainProgram = "conduwuit";
|
||||
knownVulnerabilities = [
|
||||
''
|
||||
Continuwuity 0.5.10 can no longer be securely ran, as it contains a critical security-related bug that is currently embargoed at the time of writing.
|
||||
Continuwuity internally tracks this as SEC8, see also: https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v26.9.1
|
||||
|
||||
The latest version of Continuwuity has been moved to `matrix-continuwuity_latest` as to not introduce breaking changes.
|
||||
It is highly advisable to upgrade to this version as fast as possible. When deploying Continuwuity using the NixOS module, you may do so using:
|
||||
|
||||
`services.matrix-continuwuity.package = pkgs.matrix-continuwuity_latest`.
|
||||
|
||||
The most notable breaking change of the 26.x release is the removal of LDAP.
|
||||
''
|
||||
];
|
||||
};
|
||||
})
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
From d25c12c343b53ea90025c11808700ad6267f4e59 Mon Sep 17 00:00:00 2001
|
||||
From: timedout <git@nexy7574.co.uk>
|
||||
Date: Wed, 29 Jul 2026 16:38:05 +0100
|
||||
Subject: [PATCH] fix(backport): SEC10
|
||||
|
||||
Reviewed-By: Ginger <ginger@gingershaped.computer>
|
||||
Co-Authored-By: Erwan Leboucher <erwanleboucher@gmail.com>
|
||||
|
||||
(cherry picked from commit 71016a0d7f79289f3bf8d7816c1fec3c85c43153)
|
||||
---
|
||||
src/api/client/sync/v5.rs | 133 +++++++++++++++++++++++++++++++-------
|
||||
1 file changed, 111 insertions(+), 22 deletions(-)
|
||||
|
||||
diff --git a/src/api/client/sync/v5.rs b/src/api/client/sync/v5.rs
|
||||
index 183f3aaac..03a4c1972 100644
|
||||
--- a/src/api/client/sync/v5.rs
|
||||
+++ b/src/api/client/sync/v5.rs
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
- collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
|
||||
+ collections::{BTreeMap, BTreeSet, HashMap, HashSet},
|
||||
ops::Deref,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -28,6 +28,7 @@
|
||||
use ruma::{
|
||||
DeviceId, OwnedEventId, OwnedRoomId, RoomId, UInt, UserId,
|
||||
api::client::sync::sync_events::{self, DeviceLists, UnreadNotificationsCount},
|
||||
+ assign,
|
||||
directory::RoomTypeFilter,
|
||||
events::{
|
||||
AnyRawAccountDataEvent, AnySyncEphemeralRoomEvent, AnySyncStateEvent, StateEventType,
|
||||
@@ -139,6 +140,13 @@ pub(crate) async fn sync_events_v5_route(
|
||||
let (all_joined_rooms, all_invited_rooms, all_knocked_rooms) =
|
||||
join3(all_joined_rooms, all_invited_rooms, all_knocked_rooms).await;
|
||||
|
||||
+ let allowed_rooms: BTreeSet<OwnedRoomId> = all_joined_rooms
|
||||
+ .iter()
|
||||
+ .chain(all_invited_rooms.iter())
|
||||
+ .chain(all_knocked_rooms.iter())
|
||||
+ .cloned()
|
||||
+ .collect();
|
||||
+
|
||||
let all_joined_rooms = all_joined_rooms.iter().map(AsRef::as_ref);
|
||||
let all_invited_rooms = all_invited_rooms.iter().map(AsRef::as_ref);
|
||||
let all_knocked_rooms = all_knocked_rooms.iter().map(AsRef::as_ref);
|
||||
@@ -192,13 +200,14 @@ pub(crate) async fn sync_events_v5_route(
|
||||
)
|
||||
.await;
|
||||
|
||||
- fetch_subscriptions(services, sync_info, &known_rooms, &mut todo_rooms).await;
|
||||
+ fetch_subscriptions(services, sync_info, &known_rooms, &allowed_rooms, &mut todo_rooms).await;
|
||||
|
||||
response.rooms = process_rooms(
|
||||
services,
|
||||
sender_user,
|
||||
next_batch,
|
||||
all_invited_rooms.clone(),
|
||||
+ all_knocked_rooms.clone(),
|
||||
&todo_rooms,
|
||||
&mut response,
|
||||
&body,
|
||||
@@ -208,6 +217,7 @@ pub(crate) async fn sync_events_v5_route(
|
||||
if response.rooms.iter().all(|(id, r)| {
|
||||
r.timeline.is_empty()
|
||||
&& r.required_state.is_empty()
|
||||
+ && r.invite_state.is_none()
|
||||
&& !response.extensions.receipts.rooms.contains_key(id)
|
||||
}) && response
|
||||
.extensions
|
||||
@@ -238,10 +248,17 @@ async fn fetch_subscriptions(
|
||||
services: &Services,
|
||||
(sender_user, sender_device, globalsince, body): SyncInfo<'_>,
|
||||
known_rooms: &KnownRooms,
|
||||
+ allowed_rooms: &BTreeSet<OwnedRoomId>,
|
||||
todo_rooms: &mut TodoRooms,
|
||||
) {
|
||||
let mut known_subscription_rooms = BTreeSet::new();
|
||||
for (room_id, room) in &body.room_subscriptions {
|
||||
+ // Silently ignore subscriptions to rooms the user is not a member of
|
||||
+ // (joined or invited).
|
||||
+ if !allowed_rooms.contains(room_id) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
let not_exists = services.rooms.metadata.exists(room_id).eq(&false);
|
||||
|
||||
let is_disabled = services.rooms.metadata.is_disabled(room_id);
|
||||
@@ -399,11 +416,13 @@ async fn handle_lists<'a, Rooms, AllRooms>(
|
||||
BTreeMap::default()
|
||||
}
|
||||
|
||||
+#[allow(clippy::too_many_arguments)]
|
||||
async fn process_rooms<'a, Rooms>(
|
||||
services: &Services,
|
||||
sender_user: &UserId,
|
||||
next_batch: u64,
|
||||
all_invited_rooms: Rooms,
|
||||
+ all_knocked_rooms: Rooms,
|
||||
todo_rooms: &TodoRooms,
|
||||
response: &mut sync_events::v5::Response,
|
||||
body: &sync_events::v5::Request,
|
||||
@@ -416,38 +435,99 @@ async fn process_rooms<'a, Rooms>(
|
||||
let roomsincecount = PduCount::Normal(*roomsince);
|
||||
|
||||
let mut timestamp: Option<_> = None;
|
||||
- let mut invite_state = None;
|
||||
let (timeline_pdus, limited);
|
||||
let new_room_id: &RoomId = (*room_id).as_ref();
|
||||
if all_invited_rooms.clone().any(is_equal_to!(new_room_id)) {
|
||||
+ let Ok(invite_count) = services
|
||||
+ .rooms
|
||||
+ .state_cache
|
||||
+ .get_invite_count(room_id, sender_user)
|
||||
+ .await
|
||||
+ else {
|
||||
+ continue;
|
||||
+ };
|
||||
+
|
||||
+ if *roomsince >= invite_count {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
// TODO: figure out a timestamp we can use for remote invites
|
||||
- invite_state = services
|
||||
+ let invite_state = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.invite_state(sender_user, room_id)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
- (timeline_pdus, limited) = (VecDeque::new(), true);
|
||||
- } else {
|
||||
- TimelinePdus { pdus: timeline_pdus, limited } = match load_timeline(
|
||||
- services,
|
||||
- sender_user,
|
||||
- room_id,
|
||||
- Some(roomsincecount),
|
||||
- Some(PduCount::from(next_batch)),
|
||||
- *timeline_limit,
|
||||
- )
|
||||
- .await
|
||||
- {
|
||||
- | Ok(value) => value,
|
||||
- | Err(err) => {
|
||||
- warn!("Encountered missing timeline in {}, error {}", room_id, err);
|
||||
- continue;
|
||||
- },
|
||||
+ rooms.insert(room_id.clone(), sync_events::v5::response::Room {
|
||||
+ initial: Some(roomsince == &0),
|
||||
+ invite_state,
|
||||
+ limited: true,
|
||||
+ ..Default::default()
|
||||
+ });
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if all_knocked_rooms.clone().any(is_equal_to!(new_room_id)) {
|
||||
+ let Ok(knock_count) = services
|
||||
+ .rooms
|
||||
+ .state_cache
|
||||
+ .get_knock_count(room_id, sender_user)
|
||||
+ .await
|
||||
+ else {
|
||||
+ continue;
|
||||
};
|
||||
+
|
||||
+ if *roomsince >= knock_count {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ let Ok(knock_state) = services
|
||||
+ .rooms
|
||||
+ .state_cache
|
||||
+ .knock_state(sender_user, room_id)
|
||||
+ .await
|
||||
+ else {
|
||||
+ continue;
|
||||
+ };
|
||||
+
|
||||
+ rooms.insert(
|
||||
+ room_id.clone(),
|
||||
+ assign!(sync_events::v5::response::Room::new(), {
|
||||
+ initial: Some(roomsince == &0),
|
||||
+ invite_state: Some(knock_state),
|
||||
+ limited: true,
|
||||
+ }),
|
||||
+ );
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if !services
|
||||
+ .rooms
|
||||
+ .state_cache
|
||||
+ .is_joined(sender_user, room_id)
|
||||
+ .await
|
||||
+ {
|
||||
+ continue;
|
||||
}
|
||||
|
||||
+ TimelinePdus { pdus: timeline_pdus, limited } = match load_timeline(
|
||||
+ services,
|
||||
+ sender_user,
|
||||
+ room_id,
|
||||
+ Some(roomsincecount),
|
||||
+ Some(PduCount::from(next_batch)),
|
||||
+ *timeline_limit,
|
||||
+ )
|
||||
+ .await
|
||||
+ {
|
||||
+ | Ok(value) => value,
|
||||
+ | Err(err) => {
|
||||
+ warn!("Encountered missing timeline in {}, error {}", room_id, err);
|
||||
+ continue;
|
||||
+ },
|
||||
+ };
|
||||
+
|
||||
if body.extensions.account_data.enabled == Some(true) {
|
||||
response.extensions.account_data.rooms.insert(
|
||||
room_id.to_owned(),
|
||||
@@ -627,7 +707,7 @@ async fn process_rooms<'a, Rooms>(
|
||||
},
|
||||
initial: Some(roomsince == &0),
|
||||
is_dm: None,
|
||||
- invite_state,
|
||||
+ invite_state: None,
|
||||
unread_notifications: UnreadNotificationsCount {
|
||||
highlight_count: Some(
|
||||
services
|
||||
@@ -753,6 +833,15 @@ async fn collect_typing_events(
|
||||
|
||||
let mut typing_response = sync_events::v5::response::Typing::default();
|
||||
for (room_id, (_, _, roomsince)) in todo_rooms {
|
||||
+ if !services
|
||||
+ .rooms
|
||||
+ .state_cache
|
||||
+ .is_joined(sender_user, room_id)
|
||||
+ .await
|
||||
+ {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
if services.rooms.typing.last_typing_update(room_id).await? <= *roomsince {
|
||||
continue;
|
||||
}
|
||||
--
|
||||
2.55.0
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
From dcb079931a929880518015794d709f93651154ca Mon Sep 17 00:00:00 2001
|
||||
From: Ginger <ginger@gingershaped.computer>
|
||||
Date: Tue, 11 Aug 2026 15:59:26 -0400
|
||||
Subject: [PATCH 1/2] fix(backport): SEC28
|
||||
|
||||
(cherry picked from commit 49a8f6f53b6f86ed6abb8952843cb3a38c530ada)
|
||||
---
|
||||
src/service/threepid/mod.rs | 6 +++++-
|
||||
1 file changed, 5 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/service/threepid/mod.rs b/src/service/threepid/mod.rs
|
||||
index dcc0b58ab..bda80f82e 100644
|
||||
--- a/src/service/threepid/mod.rs
|
||||
+++ b/src/service/threepid/mod.rs
|
||||
@@ -112,6 +112,10 @@ pub async fn send_validation_email<Template: MessageTemplate>(
|
||||
// If a validation session already exists for this client secret, we can either
|
||||
// reuse it with a new token or return early because it's already valid.
|
||||
| Some(session) => {
|
||||
+ if session.email != recipient.email {
|
||||
+ return Err!(Request(InvalidParam("Wrong email for session.")));
|
||||
+ }
|
||||
+
|
||||
match session.validation_state {
|
||||
| ValidationState::Validated => {
|
||||
// If the existing session is already valid, don't send an email.
|
||||
@@ -119,7 +123,7 @@ pub async fn send_validation_email<Template: MessageTemplate>(
|
||||
},
|
||||
| ValidationState::Pending(ref mut token) => {
|
||||
// Check ratelimiting for the target address.
|
||||
- if self.ratelimiter.check_key(&recipient.email).is_err() {
|
||||
+ if self.ratelimiter.check_key(&session.email).is_err() {
|
||||
return Err(Error::BadRequest(
|
||||
ErrorKind::LimitExceeded { retry_after: None },
|
||||
"You're sending emails too fast, try again in a few minutes.",
|
||||
--
|
||||
2.55.0
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
From a00a615799bc55bc093a6298735732565aa2b566 Mon Sep 17 00:00:00 2001
|
||||
From: Ginger <ginger@gingershaped.computer>
|
||||
Date: Tue, 11 Aug 2026 16:35:24 -0400
|
||||
Subject: [PATCH 2/2] fix(backport): SEC26
|
||||
|
||||
Reviewed-By: timedout <git@nexy7574.co.uk>
|
||||
Reviewed-By: Ginger <ginger@gingershaped.computer>
|
||||
(cherry picked from commit 700fbe472d4a8385b96f870256bfc00f9a15f809)
|
||||
---
|
||||
src/api/server/event_auth.rs | 27 ++++++++++-----------------
|
||||
src/api/server/state.rs | 13 ++++++++++++-
|
||||
src/api/server/state_ids.rs | 13 ++++++++++++-
|
||||
3 files changed, 34 insertions(+), 19 deletions(-)
|
||||
|
||||
diff --git a/src/api/server/event_auth.rs b/src/api/server/event_auth.rs
|
||||
index a9019e8e7..433e18f46 100644
|
||||
--- a/src/api/server/event_auth.rs
|
||||
+++ b/src/api/server/event_auth.rs
|
||||
@@ -1,12 +1,9 @@
|
||||
use std::{borrow::Borrow, iter::once};
|
||||
|
||||
use axum::extract::State;
|
||||
-use conduwuit::{Err, Error, Result, info, utils::stream::ReadyExt};
|
||||
+use conduwuit::{Err, Result, Event, info, utils::stream::ReadyExt};
|
||||
use futures::StreamExt;
|
||||
-use ruma::{
|
||||
- RoomId,
|
||||
- api::{client::error::ErrorKind, federation::authorization::get_event_authorization},
|
||||
-};
|
||||
+use ruma::api::federation::authorization::get_event_authorization;
|
||||
|
||||
use super::AccessCheck;
|
||||
use crate::Ruma;
|
||||
@@ -42,25 +39,21 @@ pub(crate) async fn get_event_authorization_route(
|
||||
return Err!(Request(NotFound("This server is not participating in that room.")));
|
||||
}
|
||||
|
||||
- let event = services
|
||||
+ // The event must be in the room we just authorised access to
|
||||
+ if !services
|
||||
.rooms
|
||||
.timeline
|
||||
- .get_pdu_json(&body.event_id)
|
||||
+ .get_pdu(&body.event_id)
|
||||
.await
|
||||
- .map_err(|_| Error::BadRequest(ErrorKind::NotFound, "Event not found."))?;
|
||||
-
|
||||
- let room_id_str = event
|
||||
- .get("room_id")
|
||||
- .and_then(|val| val.as_str())
|
||||
- .ok_or_else(|| Error::bad_database("Invalid event in database."))?;
|
||||
-
|
||||
- let room_id = <&RoomId>::try_from(room_id_str)
|
||||
- .map_err(|_| Error::bad_database("Invalid room_id in event in database."))?;
|
||||
+ .is_ok_and(|pdu| pdu.room_id_or_hash() == body.room_id)
|
||||
+ {
|
||||
+ return Err!(Request(NotFound("Event not found.")));
|
||||
+ }
|
||||
|
||||
let auth_chain = services
|
||||
.rooms
|
||||
.auth_chain
|
||||
- .event_ids_iter(room_id, once(body.event_id.borrow()))
|
||||
+ .event_ids_iter(&body.room_id, once(body.event_id.borrow()))
|
||||
.ready_filter_map(Result::ok)
|
||||
.filter_map(|id| async move { services.rooms.timeline.get_pdu_json(&id).await.ok() })
|
||||
.then(|pdu| services.sending.convert_to_outgoing_federation_event(pdu))
|
||||
diff --git a/src/api/server/state.rs b/src/api/server/state.rs
|
||||
index 5e1ad8ca2..05a008b74 100644
|
||||
--- a/src/api/server/state.rs
|
||||
+++ b/src/api/server/state.rs
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{borrow::Borrow, iter::once};
|
||||
|
||||
use axum::extract::State;
|
||||
-use conduwuit::{Err, Result, at, err, info, utils::IterStream};
|
||||
+use conduwuit::{Err, Event, Result, at, err, info, utils::IterStream};
|
||||
use futures::{FutureExt, StreamExt, TryStreamExt};
|
||||
use ruma::{OwnedEventId, api::federation::event::get_room_state};
|
||||
|
||||
@@ -24,6 +24,17 @@ pub(crate) async fn get_room_state_route(
|
||||
.check()
|
||||
.await?;
|
||||
|
||||
+ // The event must be in the room we just authorised access to
|
||||
+ if !services
|
||||
+ .rooms
|
||||
+ .timeline
|
||||
+ .get_pdu(&body.event_id)
|
||||
+ .await
|
||||
+ .is_ok_and(|pdu| pdu.room_id_or_hash() == body.room_id)
|
||||
+ {
|
||||
+ return Err!(Request(NotFound("Event not found.")));
|
||||
+ }
|
||||
+
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
diff --git a/src/api/server/state_ids.rs b/src/api/server/state_ids.rs
|
||||
index c9dea3116..206f3efc5 100644
|
||||
--- a/src/api/server/state_ids.rs
|
||||
+++ b/src/api/server/state_ids.rs
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{borrow::Borrow, iter::once};
|
||||
|
||||
use axum::extract::State;
|
||||
-use conduwuit::{Err, Result, at, err, info};
|
||||
+use conduwuit::{Err, Event, Result, at, err, info};
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use ruma::{OwnedEventId, api::federation::event::get_room_state_ids};
|
||||
|
||||
@@ -25,6 +25,17 @@ pub(crate) async fn get_room_state_ids_route(
|
||||
.check()
|
||||
.await?;
|
||||
|
||||
+ // The event must be in the room we just authorised access to
|
||||
+ if !services
|
||||
+ .rooms
|
||||
+ .timeline
|
||||
+ .get_pdu(&body.event_id)
|
||||
+ .await
|
||||
+ .is_ok_and(|pdu| pdu.room_id_or_hash() == body.room_id)
|
||||
+ {
|
||||
+ return Err!(Request(NotFound("Event not found.")));
|
||||
+ }
|
||||
+
|
||||
if !services
|
||||
.rooms
|
||||
.state_cache
|
||||
--
|
||||
2.55.0
|
||||
|
||||
104
pkgs/by-name/ma/matrix-continuwuity_latest/package.nix
Normal file
104
pkgs/by-name/ma/matrix-continuwuity_latest/package.nix
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
fetchFromGitea,
|
||||
pkg-config,
|
||||
bzip2,
|
||||
zstd,
|
||||
stdenv,
|
||||
rocksdb,
|
||||
nix-update-script,
|
||||
testers,
|
||||
matrix-continuwuity_latest,
|
||||
rust-jemalloc-sys-unprefixed,
|
||||
liburing,
|
||||
nixosTests,
|
||||
}:
|
||||
let
|
||||
rocksdb' =
|
||||
(rocksdb.override {
|
||||
# rocksdb does not support prefixed jemalloc, which is required on darwin
|
||||
enableJemalloc = !stdenv.hostPlatform.isDarwin;
|
||||
jemalloc = rust-jemalloc-sys-unprefixed;
|
||||
}).overrideAttrs
|
||||
(
|
||||
final: old: {
|
||||
version = "10.10.1";
|
||||
src = fetchFromGitea {
|
||||
domain = "forgejo.ellis.link";
|
||||
owner = "continuwuation";
|
||||
repo = "rocksdb";
|
||||
rev = "10.10.fb";
|
||||
hash = "sha256-1ef75IDMs5Hba4VWEyXPJb02JyShy5k4gJfzGDhopRk=";
|
||||
};
|
||||
|
||||
patches = [ ];
|
||||
}
|
||||
);
|
||||
in
|
||||
rustPlatform.buildRustPackage (finalAttrs: {
|
||||
pname = "matrix-continuwuity";
|
||||
version = "0.5.10";
|
||||
|
||||
src = fetchFromGitea {
|
||||
domain = "forgejo.ellis.link";
|
||||
owner = "continuwuation";
|
||||
repo = "continuwuity";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-oevEGYlAK/rMJhm200CkwerT5oVak8sJj0Fa6r6+J/Q=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-uvMiFURXxkLbbbwq4pG5hevsLZHQ1wVfTNvzQRTQWxE=";
|
||||
|
||||
patches = [
|
||||
./0001-fix-backport-SEC10.patch
|
||||
./0002-fix-backport-SEC28.patch
|
||||
./0003-fix-backport-SEC26.patch
|
||||
];
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
rustPlatform.bindgenHook
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
bzip2
|
||||
zstd
|
||||
rust-jemalloc-sys-unprefixed
|
||||
liburing
|
||||
];
|
||||
|
||||
env = {
|
||||
ZSTD_SYS_USE_PKG_CONFIG = true;
|
||||
ROCKSDB_INCLUDE_DIR = "${rocksdb'}/include";
|
||||
ROCKSDB_LIB_DIR = "${rocksdb'}/lib";
|
||||
};
|
||||
|
||||
passthru = {
|
||||
rocksdb = rocksdb'; # make used rocksdb version available (e.g., for backup scripts)
|
||||
updateScript = nix-update-script { };
|
||||
tests = {
|
||||
version = testers.testVersion {
|
||||
inherit (finalAttrs) version;
|
||||
package = matrix-continuwuity_latest;
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs stdenv.hostPlatform.isLinux {
|
||||
inherit (nixosTests) matrix-continuwuity;
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Matrix homeserver written in Rust, forked from conduwuit";
|
||||
homepage = "https://continuwuity.org/";
|
||||
changelog = "https://forgejo.ellis.link/continuwuation/continuwuity/releases/tag/v${finalAttrs.version}";
|
||||
license = lib.licenses.asl20;
|
||||
maintainers = with lib.maintainers; [
|
||||
bartoostveen
|
||||
nyabinary
|
||||
snaki
|
||||
];
|
||||
# Not a typo, continuwuity is a drop-in replacement for conduwuit.
|
||||
mainProgram = "conduwuit";
|
||||
};
|
||||
})
|
||||
Reference in New Issue
Block a user