From 1e430f9470f37f8e59e010ac083d140b45e63367 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sun, 31 Aug 2025 16:55:47 +0100 Subject: [PATCH 01/15] feat(MSC4323): Implement agnostic suspension endpoint --- Cargo.lock | 22 +++++----- Cargo.toml | 2 +- src/api/client/admin/mod.rs | 3 ++ src/api/client/admin/suspend.rs | 77 +++++++++++++++++++++++++++++++++ src/api/client/mod.rs | 2 + src/api/router.rs | 2 + 6 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 src/api/client/admin/mod.rs create mode 100644 src/api/client/admin/suspend.rs diff --git a/Cargo.lock b/Cargo.lock index 2b044a1f..a3962c31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4058,7 +4058,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "ruma" version = "0.10.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "assign", "js_int", @@ -4078,7 +4078,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.10.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "js_int", "ruma-common", @@ -4090,7 +4090,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "as_variant", "assign", @@ -4113,7 +4113,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "as_variant", "base64 0.22.1", @@ -4145,7 +4145,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "as_variant", "indexmap 2.10.0", @@ -4170,7 +4170,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "bytes", "headers", @@ -4192,7 +4192,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "js_int", "thiserror 2.0.12", @@ -4201,7 +4201,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "js_int", "ruma-common", @@ -4211,7 +4211,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "cfg-if", "proc-macro-crate", @@ -4226,7 +4226,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "js_int", "ruma-common", @@ -4238,7 +4238,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.15.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "base64 0.22.1", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 9452066c..e2af2d94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -352,7 +352,7 @@ version = "0.1.2" [workspace.dependencies.ruma] git = "https://forgejo.ellis.link/continuwuation/ruwuma" #branch = "conduwuit-changes" -rev = "b753738047d1f443aca870896ef27ecaacf027da" +rev = "8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" features = [ "compat", "rand", diff --git a/src/api/client/admin/mod.rs b/src/api/client/admin/mod.rs new file mode 100644 index 00000000..8355a600 --- /dev/null +++ b/src/api/client/admin/mod.rs @@ -0,0 +1,3 @@ +mod suspend; + +pub(crate) use self::suspend::*; diff --git a/src/api/client/admin/suspend.rs b/src/api/client/admin/suspend.rs new file mode 100644 index 00000000..29682694 --- /dev/null +++ b/src/api/client/admin/suspend.rs @@ -0,0 +1,77 @@ +use axum::extract::State; +use conduwuit::{Err, Result}; +use ruma::api::client::admin::{get_suspended, set_suspended}; + +use crate::Ruma; + +/// # `GET /_matrix/client/v1/admin/suspend/{userId}` +/// +/// Check the suspension status of a target user +pub(crate) async fn get_suspended_status( + State(services): State, + body: Ruma, +) -> Result { + let sender_user = body.sender_user(); + if !services.users.is_admin(sender_user).await { + return Err!(Request(Forbidden("Only server administrators can use this endpoint"))); + }; + if !services.globals.user_is_local(&body.user_id) { + return Err!(Request(InvalidParam("Can only check the suspended status of local users"))); + }; + if !services.users.is_active(&body.user_id).await { + return Err!(Request(NotFound("Unknown user"))); + } + Ok(get_suspended::v1::Response::new( + services.users.is_suspended(&body.user_id).await?, + )) +} + +/// # `PUT /_matrix/client/v1/admin/suspend/{userId}` +/// +/// Set the suspension status of a target user +pub(crate) async fn put_suspended_status( + State(services): State, + body: Ruma, +) -> Result { + let sender_user = body.sender_user(); + if !services.users.is_admin(sender_user).await { + return Err!(Request(Forbidden("Only server administrators can use this endpoint"))); + }; + if !services.globals.user_is_local(&body.user_id) { + return Err!(Request(InvalidParam("Can only set the suspended status of local users"))); + }; + if !services.users.is_active(&body.user_id).await { + return Err!(Request(NotFound("Unknown user"))); + } + if body.user_id == *sender_user { + return Err!(Request(Forbidden("You cannot suspend yourself"))); + } + if services.users.is_admin(&body.user_id).await { + return Err!(Request(Forbidden("You cannot suspend another admin"))); + } + if services.users.is_suspended(&body.user_id).await? == body.suspended { + // No change + return Ok(set_suspended::v1::Response::new(body.suspended)); + } + + let action = if body.suspended { + services + .users + .suspend_account(&body.user_id, sender_user) + .await; + "suspended" + } else { + services.users.unsuspend_account(&body.user_id).await; + "unsuspended" + }; + + if services.config.admin_room_notices { + // Notify the admin room that an account has been un/suspended + services + .admin + .send_text(&format!("{} has been {} by {}.", body.user_id, action, sender_user)) + .await; + } + + Ok(set_suspended::v1::Response::new(body.suspended)) +} diff --git a/src/api/client/mod.rs b/src/api/client/mod.rs index e4be20b7..e53b26d9 100644 --- a/src/api/client/mod.rs +++ b/src/api/client/mod.rs @@ -1,5 +1,6 @@ pub(super) mod account; pub(super) mod account_data; +pub(super) mod admin; pub(super) mod alias; pub(super) mod appservice; pub(super) mod backup; @@ -43,6 +44,7 @@ pub(super) mod well_known; pub use account::full_user_deactivate; pub(super) use account::*; pub(super) use account_data::*; +pub(super) use admin::*; pub(super) use alias::*; pub(super) use appservice::*; pub(super) use backup::*; diff --git a/src/api/router.rs b/src/api/router.rs index 8072fa5b..42934f70 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -184,6 +184,8 @@ pub fn build(router: Router, server: &Server) -> Router { "/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary", get(client::get_room_summary_legacy) ) + .ruma_route(&client::get_suspended_status) + .ruma_route(&client::put_suspended_status) .ruma_route(&client::well_known_support) .ruma_route(&client::well_known_client) .route("/_conduwuit/server_version", get(client::conduwuit_server_version)) From 9783940105b070346204b99b0cfc6a23558c63b8 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sun, 31 Aug 2025 17:07:19 +0100 Subject: [PATCH 02/15] feat(MSC4323): Advertise suspension support in capabilities --- src/api/client/capabilities.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/api/client/capabilities.rs b/src/api/client/capabilities.rs index c42c6dfd..2f9d6299 100644 --- a/src/api/client/capabilities.rs +++ b/src/api/client/capabilities.rs @@ -23,6 +23,11 @@ pub(crate) async fn get_capabilities_route( ) -> Result { let available: BTreeMap = Server::available_room_versions().collect(); + let authenticated = _body.sender_user.as_ref().is_some() + && services + .users + .is_active_local(_body.sender_user.as_ref().unwrap()) + .await; let mut capabilities = Capabilities::default(); capabilities.room_versions = RoomVersionsCapability { @@ -45,5 +50,15 @@ pub(crate) async fn get_capabilities_route( json!({"enabled": services.config.forget_forced_upon_leave}), )?; + if authenticated + && services + .users + .is_admin(_body.sender_user.as_ref().unwrap()) + .await + { + // Advertise suspension API + capabilities.set("uk.timedout.msc4323", json!({"suspend":true, "lock": false}))?; + } + Ok(get_capabilities::v3::Response { capabilities }) } From 04e796176a7dd2bf33f04d49bcadbb54a3feb501 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sun, 31 Aug 2025 17:10:56 +0100 Subject: [PATCH 03/15] style(MSC4323): Satisfy our linting overlords --- src/api/client/admin/suspend.rs | 8 ++++---- src/api/client/capabilities.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/api/client/admin/suspend.rs b/src/api/client/admin/suspend.rs index 29682694..0d3493f6 100644 --- a/src/api/client/admin/suspend.rs +++ b/src/api/client/admin/suspend.rs @@ -14,10 +14,10 @@ pub(crate) async fn get_suspended_status( let sender_user = body.sender_user(); if !services.users.is_admin(sender_user).await { return Err!(Request(Forbidden("Only server administrators can use this endpoint"))); - }; + } if !services.globals.user_is_local(&body.user_id) { return Err!(Request(InvalidParam("Can only check the suspended status of local users"))); - }; + } if !services.users.is_active(&body.user_id).await { return Err!(Request(NotFound("Unknown user"))); } @@ -36,10 +36,10 @@ pub(crate) async fn put_suspended_status( let sender_user = body.sender_user(); if !services.users.is_admin(sender_user).await { return Err!(Request(Forbidden("Only server administrators can use this endpoint"))); - }; + } if !services.globals.user_is_local(&body.user_id) { return Err!(Request(InvalidParam("Can only set the suspended status of local users"))); - }; + } if !services.users.is_active(&body.user_id).await { return Err!(Request(NotFound("Unknown user"))); } diff --git a/src/api/client/capabilities.rs b/src/api/client/capabilities.rs index 2f9d6299..1fcb073f 100644 --- a/src/api/client/capabilities.rs +++ b/src/api/client/capabilities.rs @@ -19,14 +19,14 @@ use crate::Ruma; /// of this server. pub(crate) async fn get_capabilities_route( State(services): State, - _body: Ruma, + body: Ruma, ) -> Result { let available: BTreeMap = Server::available_room_versions().collect(); - let authenticated = _body.sender_user.as_ref().is_some() + let authenticated = body.sender_user.as_ref().is_some() && services .users - .is_active_local(_body.sender_user.as_ref().unwrap()) + .is_active_local(body.sender_user.as_ref().unwrap()) .await; let mut capabilities = Capabilities::default(); @@ -53,7 +53,7 @@ pub(crate) async fn get_capabilities_route( if authenticated && services .users - .is_admin(_body.sender_user.as_ref().unwrap()) + .is_admin(body.sender_user.as_ref().unwrap()) .await { // Advertise suspension API From 35cf9af5c8a62dd362c9ecf4980d88291a2e9994 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sun, 31 Aug 2025 17:44:32 +0100 Subject: [PATCH 04/15] feat(MSC4323): Add versions flag --- src/api/client/unversioned.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/client/unversioned.rs b/src/api/client/unversioned.rs index a4136d1a..7f19bc94 100644 --- a/src/api/client/unversioned.rs +++ b/src/api/client/unversioned.rs @@ -58,6 +58,7 @@ pub(crate) async fn get_supported_versions_route( ("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */ ("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */ ("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */ + ("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */ ]), }; From 4e644961f3f7c3a955618caff3b3927b8f7e8931 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sun, 31 Aug 2025 18:32:32 +0100 Subject: [PATCH 05/15] perf(MSC4323): Remove redundant authorisation checks --- src/api/client/capabilities.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/api/client/capabilities.rs b/src/api/client/capabilities.rs index 1fcb073f..afa61498 100644 --- a/src/api/client/capabilities.rs +++ b/src/api/client/capabilities.rs @@ -23,11 +23,6 @@ pub(crate) async fn get_capabilities_route( ) -> Result { let available: BTreeMap = Server::available_room_versions().collect(); - let authenticated = body.sender_user.as_ref().is_some() - && services - .users - .is_active_local(body.sender_user.as_ref().unwrap()) - .await; let mut capabilities = Capabilities::default(); capabilities.room_versions = RoomVersionsCapability { @@ -50,11 +45,10 @@ pub(crate) async fn get_capabilities_route( json!({"enabled": services.config.forget_forced_upon_leave}), )?; - if authenticated - && services - .users - .is_admin(body.sender_user.as_ref().unwrap()) - .await + if services + .users + .is_admin(body.sender_user.as_ref().unwrap()) + .await { // Advertise suspension API capabilities.set("uk.timedout.msc4323", json!({"suspend":true, "lock": false}))?; From d970df5fd239239554192887c5c0a634935ee3df Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Mon, 1 Sep 2025 11:36:39 +0100 Subject: [PATCH 06/15] perf(MSC4323): Parallelise some check futs --- src/api/client/admin/suspend.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/api/client/admin/suspend.rs b/src/api/client/admin/suspend.rs index 0d3493f6..bab1cb5a 100644 --- a/src/api/client/admin/suspend.rs +++ b/src/api/client/admin/suspend.rs @@ -1,5 +1,6 @@ use axum::extract::State; use conduwuit::{Err, Result}; +use futures::future::{join, join3}; use ruma::api::client::admin::{get_suspended, set_suspended}; use crate::Ruma; @@ -12,13 +13,16 @@ pub(crate) async fn get_suspended_status( body: Ruma, ) -> Result { let sender_user = body.sender_user(); - if !services.users.is_admin(sender_user).await { + + let (admin, active) = + join(services.users.is_admin(sender_user), services.users.is_active(&body.user_id)).await; + if !admin { return Err!(Request(Forbidden("Only server administrators can use this endpoint"))); } if !services.globals.user_is_local(&body.user_id) { return Err!(Request(InvalidParam("Can only check the suspended status of local users"))); } - if !services.users.is_active(&body.user_id).await { + if !active { return Err!(Request(NotFound("Unknown user"))); } Ok(get_suspended::v1::Response::new( @@ -34,20 +38,28 @@ pub(crate) async fn put_suspended_status( body: Ruma, ) -> Result { let sender_user = body.sender_user(); - if !services.users.is_admin(sender_user).await { + + let (sender_admin, active, target_admin) = join3( + services.users.is_admin(sender_user), + services.users.is_active(&body.user_id), + services.users.is_admin(&body.user_id), + ) + .await; + + if !sender_admin { return Err!(Request(Forbidden("Only server administrators can use this endpoint"))); } if !services.globals.user_is_local(&body.user_id) { return Err!(Request(InvalidParam("Can only set the suspended status of local users"))); } - if !services.users.is_active(&body.user_id).await { + if !active { return Err!(Request(NotFound("Unknown user"))); } if body.user_id == *sender_user { return Err!(Request(Forbidden("You cannot suspend yourself"))); } - if services.users.is_admin(&body.user_id).await { - return Err!(Request(Forbidden("You cannot suspend another admin"))); + if target_admin { + return Err!(Request(Forbidden("You cannot suspend another server administrator"))); } if services.users.is_suspended(&body.user_id).await? == body.suspended { // No change From 241371463e108b036b596c23d4658439016e69e1 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Wed, 30 Jul 2025 19:19:32 +0100 Subject: [PATCH 07/15] feat: Force leave remote rooms admin command --- src/admin/user/commands.rs | 30 ++++++++++++++++++++++++++++-- src/admin/user/mod.rs | 6 ++++++ src/api/client/membership/leave.rs | 2 +- src/api/client/membership/mod.rs | 2 +- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index 56864a32..37ab030c 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -1,8 +1,8 @@ use std::{collections::BTreeMap, fmt::Write as _}; use api::client::{ - full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url, - update_displayname, + full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room, + update_avatar_url, update_displayname, }; use conduwuit::{ Err, Result, debug, debug_warn, error, info, is_equal_to, @@ -926,3 +926,29 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result { )) .await } + +#[admin_command] +pub(super) async fn force_leave_remote_room( + &self, + user_id: String, + room_id: OwnedRoomOrAliasId, +) -> Result { + let user_id = parse_local_user_id(self.services, &user_id)?; + let (room_id, _) = self + .services + .rooms + .alias + .resolve_with_servers(&room_id, None) + .await?; + + assert!( + self.services.globals.user_is_local(&user_id), + "Parsed user_id must be a local user" + ); + remote_leave_room(self.services, &user_id, &room_id, None) + .boxed() + .await?; + + self.write_str(&format!("{user_id} has been joined to {room_id}.",)) + .await +} diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs index 656cacaf..366f7dd5 100644 --- a/src/admin/user/mod.rs +++ b/src/admin/user/mod.rs @@ -103,6 +103,12 @@ pub enum UserCommand { room_id: OwnedRoomOrAliasId, }, + /// - Manually leave a remote room for a local user. + ForceLeaveRemoteRoom { + user_id: String, + room_id: OwnedRoomOrAliasId, + }, + /// - Forces the specified user to drop their power levels to the room /// default, if their permissions allow and the auth check permits ForceDemote { diff --git a/src/api/client/membership/leave.rs b/src/api/client/membership/leave.rs index f4f1666b..0aadd833 100644 --- a/src/api/client/membership/leave.rs +++ b/src/api/client/membership/leave.rs @@ -215,7 +215,7 @@ pub async fn leave_room( Ok(()) } -async fn remote_leave_room( +pub async fn remote_leave_room( services: &Services, user_id: &UserId, room_id: &RoomId, diff --git a/src/api/client/membership/mod.rs b/src/api/client/membership/mod.rs index 7a6f19ad..691419f6 100644 --- a/src/api/client/membership/mod.rs +++ b/src/api/client/membership/mod.rs @@ -29,7 +29,7 @@ pub(crate) use self::{ }; pub use self::{ join::join_room_by_id_helper, - leave::{leave_all_rooms, leave_room}, + leave::{leave_all_rooms, leave_room, remote_leave_room}, }; use crate::{Ruma, client::full_user_deactivate}; From 66d479e2eb3e25f20ac74860e82c9c33ad069c21 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Wed, 30 Jul 2025 19:29:33 +0100 Subject: [PATCH 08/15] fix: Make remote leave helper a public fn --- src/api/client/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/client/mod.rs b/src/api/client/mod.rs index e53b26d9..c8ca7757 100644 --- a/src/api/client/mod.rs +++ b/src/api/client/mod.rs @@ -57,7 +57,7 @@ pub(super) use keys::*; pub(super) use media::*; pub(super) use media_legacy::*; pub(super) use membership::*; -pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room}; +pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room}; pub(super) use message::*; pub(super) use openid::*; pub(super) use presence::*; From 76b93e252dc3b4c380772ad4eaad0463b458600e Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Thu, 31 Jul 2025 17:48:30 +0100 Subject: [PATCH 09/15] feat: Only inject vias when manual ones aren't provided during join --- src/api/client/membership/join.rs | 49 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/api/client/membership/join.rs b/src/api/client/membership/join.rs index dc170cbf..f3434bf5 100644 --- a/src/api/client/membership/join.rs +++ b/src/api/client/membership/join.rs @@ -156,31 +156,34 @@ pub(crate) async fn join_room_by_id_or_alias_route( .await?; let mut servers = body.via.clone(); - servers.extend( - services - .rooms - .state_cache - .servers_invite_via(&room_id) - .map(ToOwned::to_owned) - .collect::>() - .await, - ); + if servers.is_empty() { + debug!("No via servers provided for join, injecting some."); + servers.extend( + services + .rooms + .state_cache + .servers_invite_via(&room_id) + .map(ToOwned::to_owned) + .collect::>() + .await, + ); - servers.extend( - services - .rooms - .state_cache - .invite_state(sender_user, &room_id) - .await - .unwrap_or_default() - .iter() - .filter_map(|event| event.get_field("sender").ok().flatten()) - .filter_map(|sender: &str| UserId::parse(sender).ok()) - .map(|user| user.server_name().to_owned()), - ); + servers.extend( + services + .rooms + .state_cache + .invite_state(sender_user, &room_id) + .await + .unwrap_or_default() + .iter() + .filter_map(|event| event.get_field("sender").ok().flatten()) + .filter_map(|sender: &str| UserId::parse(sender).ok()) + .map(|user| user.server_name().to_owned()), + ); - if let Some(server) = room_id.server_name() { - servers.push(server.to_owned()); + if let Some(server) = room_id.server_name() { + servers.push(server.to_owned()); + } } servers.sort_unstable(); From 9e62e66ae4a46f3e742644a86c9c699eb5625f47 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 30 Aug 2025 21:31:23 +0100 Subject: [PATCH 10/15] chore(PR956): Update admin docs --- docs/admin_reference.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/admin_reference.md b/docs/admin_reference.md index 18e039e4..1c9d4fb0 100644 --- a/docs/admin_reference.md +++ b/docs/admin_reference.md @@ -21,6 +21,7 @@ This document contains the help content for the `admin` command-line program. * [`admin users list-joined-rooms`↴](#admin-users-list-joined-rooms) * [`admin users force-join-room`↴](#admin-users-force-join-room) * [`admin users force-leave-room`↴](#admin-users-force-leave-room) +* [`admin users force-leave-remote-room`↴](#admin-users-force-leave-remote-room) * [`admin users force-demote`↴](#admin-users-force-demote) * [`admin users make-user-admin`↴](#admin-users-make-user-admin) * [`admin users put-room-tag`↴](#admin-users-put-room-tag) @@ -295,6 +296,7 @@ You can find the ID using the `list-appservices` command. * `list-joined-rooms` — - Lists all the rooms (local and remote) that the specified user is joined in * `force-join-room` — - Manually join a local user to a room * `force-leave-room` — - Manually leave a local user from a room +* `force-leave-remote-room` — - Manually leave a remote room for a local user * `force-demote` — - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits * `make-user-admin` — - Grant server-admin privileges to a user * `put-room-tag` — - Puts a room tag for the specified user and room ID @@ -449,6 +451,19 @@ Reverses the effects of the `suspend` command, allowing the user to send message +## `admin users force-leave-remote-room` + +- Manually leave a remote room for a local user + +**Usage:** `admin users force-leave-remote-room ` + +###### **Arguments:** + +* `` +* `` + + + ## `admin users force-demote` - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits From 95aeff8cdcd35cf5bcfdd08c7c28618c6d579f02 Mon Sep 17 00:00:00 2001 From: Ginger Date: Fri, 29 Aug 2025 12:16:21 -0400 Subject: [PATCH 11/15] Set the DB path as an env var in systemd service files to prevent footgunning --- arch/conduwuit.service | 1 + debian/conduwuit.service | 1 + fedora/conduwuit.service | 1 + src/core/config/mod.rs | 6 ++++-- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/arch/conduwuit.service b/arch/conduwuit.service index 34c3995e..18c34f33 100644 --- a/arch/conduwuit.service +++ b/arch/conduwuit.service @@ -20,6 +20,7 @@ StandardError=journal+console Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" +Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit" TTYReset=yes # uncomment to allow buffer to be cleared every restart diff --git a/debian/conduwuit.service b/debian/conduwuit.service index da78f09f..ec2505b5 100644 --- a/debian/conduwuit.service +++ b/debian/conduwuit.service @@ -16,6 +16,7 @@ Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml" Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" +Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit" ExecStart=/usr/sbin/conduwuit diff --git a/fedora/conduwuit.service b/fedora/conduwuit.service index 6ab2af46..f37c7798 100644 --- a/fedora/conduwuit.service +++ b/fedora/conduwuit.service @@ -15,6 +15,7 @@ Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml" Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" +Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit" ExecStart=/usr/bin/conduwuit diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index e8518ed4..58a39a75 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -126,9 +126,11 @@ pub struct Config { /// This is the only directory where continuwuity will save its data, /// including media. Note: this was previously "/var/lib/matrix-conduit". /// - /// YOU NEED TO EDIT THIS. + /// YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a `systemd` service. + /// The service file sets it to `/var/lib/conduwuit` using an environment variable + /// and also grants write access. /// - /// example: "/var/lib/continuwuity" + /// example: "/var/lib/conduwuit" pub database_path: PathBuf, /// continuwuity supports online database backups using RocksDB's Backup From 99b44bbf0946b1ad426ed0bf719674300576721c Mon Sep 17 00:00:00 2001 From: Ginger Date: Fri, 29 Aug 2025 12:30:48 -0400 Subject: [PATCH 12/15] Update conduwuit-example.toml --- conduwuit-example.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/conduwuit-example.toml b/conduwuit-example.toml index f0e510b4..fa65cbf2 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -79,9 +79,11 @@ # This is the only directory where continuwuity will save its data, # including media. Note: this was previously "/var/lib/matrix-conduit". # -# YOU NEED TO EDIT THIS. +# YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a `systemd` service. +# The service file sets it to `/var/lib/conduwuit` using an environment variable +# and also grants write access. # -# example: "/var/lib/continuwuity" +# example: "/var/lib/conduwuit" # #database_path = From 467aed3028705ec89adfa8b2b58e746323595eed Mon Sep 17 00:00:00 2001 From: nex Date: Tue, 2 Sep 2025 16:36:56 +0000 Subject: [PATCH 13/15] chore: Add Ginger's GH noreply email to mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index fa267e13..7c25737d 100644 --- a/.mailmap +++ b/.mailmap @@ -13,3 +13,4 @@ Rudi Floren Tamara Schmitz <15906939+tamara-schmitz@users.noreply.github.com> Timo Kösters x4u <14617923-x4u@users.noreply.gitlab.com> +Ginger <75683114+gingershaped@users.noreply.github.com> From 7a18888aa9b4a53443a5c47b9ea508649f41f711 Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Sun, 31 Aug 2025 18:23:38 +0100 Subject: [PATCH 14/15] feat(ci): Add Fedora RPM package build workflow Build and publish RPM packages for Fedora using rpkg and official rust-packaging macros. GPG sign packages with Ed25519 repository key and deploy to Forgejo package registry. Publishes packages to organised groups: - continuwuity (binary): base group (stable/dev/branch-name) - continuwuity-debuginfo: GROUP-debug - continuwuity (source RPM): GROUP-src Workflow triggers on pushes to relevant paths and version tags (v*). Tagged releases use clean version numbers (v1.2.3 becomes 1.2.3-1) while branch builds use git SHA versioning. Include GPG public key for package verification and documentation for RPM repository configuration and installation methods. --- .forgejo/workflows/build-fedora.yml | 480 ++++++++++++++++++++++++++++ docs/SUMMARY.md | 1 + docs/deploying/fedora.md | 201 ++++++++++++ fedora/RPM-GPG-KEY-continuwuity.asc | 16 + fedora/continuwuity.spec.rpkg | 3 +- 5 files changed, 699 insertions(+), 2 deletions(-) create mode 100644 .forgejo/workflows/build-fedora.yml create mode 100644 docs/deploying/fedora.md create mode 100644 fedora/RPM-GPG-KEY-continuwuity.asc diff --git a/.forgejo/workflows/build-fedora.yml b/.forgejo/workflows/build-fedora.yml new file mode 100644 index 00000000..60404896 --- /dev/null +++ b/.forgejo/workflows/build-fedora.yml @@ -0,0 +1,480 @@ +name: Build / Fedora RPM + +concurrency: + group: "build-fedora-${{ github.ref }}" + cancel-in-progress: true + +on: + push: + branches: + - '**' + tags: + - 'v*' + paths: + - 'fedora/**' + - 'src/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.forgejo/workflows/build-fedora.yml' + workflow_dispatch: + +jobs: + build: + runs-on: fedora-latest + steps: + - name: Detect Fedora version + id: fedora + run: | + VERSION=$(rpm -E %fedora) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Fedora version: $VERSION" + + - name: Checkout repository with full history + uses: https://code.forgejo.org/actions/checkout@v4 + with: + fetch-depth: 0 + + + - name: Cache DNF packages + uses: https://code.forgejo.org/actions/cache@v4 + with: + path: | + /var/cache/dnf + /var/cache/yum + key: dnf-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('fedora/continuwuity.spec.rpkg') }}-v1 + restore-keys: | + dnf-fedora${{ steps.fedora.outputs.version }}- + + - name: Cache Cargo registry + uses: https://code.forgejo.org/actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: cargo-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + cargo-fedora${{ steps.fedora.outputs.version }}- + + - name: Cache Rust build dependencies + uses: https://code.forgejo.org/actions/cache@v4 + with: + path: | + ~/rpmbuild/BUILD/*/target/release/deps + ~/rpmbuild/BUILD/*/target/release/build + ~/rpmbuild/BUILD/*/target/release/.fingerprint + ~/rpmbuild/BUILD/*/target/release/incremental + key: rust-deps-fedora${{ steps.fedora.outputs.version }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + rust-deps-fedora${{ steps.fedora.outputs.version }}- + + - name: Setup sccache + uses: https://github.com/mozilla-actions/sccache-action@v0.0.9 + with: + token: ${{ secrets.GH_PUBLIC_RO }} + + - name: Configure sccache environment + run: | + echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV + echo "CMAKE_C_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV + echo "CMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_ENV + echo "SCCACHE_CACHE_SIZE=10G" >> $GITHUB_ENV + # Aggressive GC since cache restores don't increment counter + echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV + + - name: Install build dependencies + run: | + dnf install -y --setopt=keepcache=1 \ + wget \ + rpm-build \ + rpm-sign \ + rpmdevtools \ + rpkg \ + cargo-rpm-macros \ + systemd-rpm-macros \ + clang \ + liburing-devel \ + rust \ + cargo \ + gcc \ + gcc-c++ \ + make \ + openssl-devel \ + pkg-config \ + python3-pip + + - name: Setup build environment and build SRPM + run: | + # Configure git for rpkg + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git config --global user.email "ci@continuwuity.org" + git config --global user.name "Continuwuity" + + # Setup RPM build tree + rpmdev-setuptree + + cd "$GITHUB_WORKSPACE" + + # Determine release suffix and version based on ref type and branch + if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + # Tags get clean version numbers for stable releases + RELEASE_SUFFIX="" + TAG_NAME="${{ github.ref_name }}" + # Extract version from tag (remove v prefix if present) + TAG_VERSION=$(echo "$TAG_NAME" | sed 's/^v//') + + # Create spec file with tag version + sed -e "s/^Version:.*$/Version: $TAG_VERSION/" \ + -e "s/^Release:.*$/Release: 1%{?dist}/" \ + fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg + elif [ "${{ github.ref_name }}" = "main" ]; then + # Main branch gets .dev suffix + RELEASE_SUFFIX=".dev" + + # Replace the Release line to include our suffix + sed "s/^Release:.*$/Release: 1${RELEASE_SUFFIX}%{?dist}/" \ + fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg + else + # Other branches get sanitized branch name as suffix + SAFE_BRANCH=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/_/g' | cut -c1-20) + RELEASE_SUFFIX=".${SAFE_BRANCH}" + + # Replace the Release line to include our suffix + sed "s/^Release:.*$/Release: 1${RELEASE_SUFFIX}%{?dist}/" \ + fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg + fi + + # Build the SRPM + rpkg srpm --outdir "$HOME/rpmbuild/SRPMS" + + # Show SRPM info + ls -la $HOME/rpmbuild/SRPMS/ + + - name: Setup GPG for RPM signing + run: | + # Skip if no signing key is configured + if [ -z "${{ secrets.RPM_SIGNING_KEY }}" ]; then + echo "No RPM signing key configured - skipping signing setup" + exit 0 + fi + + # Import the signing key + echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import + + # Get the key ID (look for the sec line, not the uid line) + KEY_ID=$(gpg --list-secret-keys --keyid-format=long | grep "^sec" | head -1 | awk '{print $2}' | cut -d'/' -f2) + echo "Using GPG key: $KEY_ID" + + # Configure RPM macros for signing + cat > ~/.rpmmacros << EOF + %_signature gpg + %_gpg_name $KEY_ID + %__gpg /usr/bin/gpg + EOF + + - name: Build RPM from SRPM + run: | + # Find the SRPM file + SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1) + + if [ -z "$SRPM" ]; then + echo "Error: No SRPM file found" + exit 1 + fi + + echo "Building from SRPM: $SRPM" + + # Build the binary RPM + rpmbuild --rebuild "$SRPM" \ + --define "_topdir $HOME/rpmbuild" \ + --define "_sourcedir $GITHUB_WORKSPACE" \ + --nocheck # Skip %check section to avoid test dependencies + + - name: Sign RPM packages + run: | + # Skip if no signing key is configured + if [ -z "${{ secrets.RPM_SIGNING_KEY }}" ]; then + echo "No RPM signing key configured - skipping package signing" + exit 0 + fi + + # Track signing failures + FAILED_COUNT=0 + TOTAL_COUNT=0 + + # Export GPG_TTY to avoid terminal warnings + export GPG_TTY=/dev/null + + # Sign all RPMs (binary and source) + for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do + echo "Signing: $(basename $rpm)" + TOTAL_COUNT=$((TOTAL_COUNT + 1)) + + # Use expect or provide empty passphrase via stdin for batch signing + if ! echo "" | rpmsign --addsign "$rpm" 2>&1; then + echo "ERROR: Failed to sign $rpm" + FAILED_COUNT=$((FAILED_COUNT + 1)) + fi + done + + # Fail if any RPMs failed to sign + if [ "$FAILED_COUNT" -gt 0 ]; then + echo "ERROR: Failed to sign $FAILED_COUNT out of $TOTAL_COUNT RPMs" + exit 1 + fi + + echo "Successfully signed all $TOTAL_COUNT RPMs" + + - name: Verify RPM signatures + run: | + # Skip if no signing key is configured + if [ -z "${{ secrets.RPM_SIGNING_KEY }}" ]; then + echo "No RPM signing key configured - skipping signature verification" + exit 0 + fi + + # Import our public key for verification + echo "Importing GPG public key for verification..." + rpm --import fedora/RPM-GPG-KEY-continuwuity.asc + + # Track verification failures + FAILED_COUNT=0 + TOTAL_COUNT=0 + + # Verify all RPMs + for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do + echo -n "Verifying $(basename $rpm): " + TOTAL_COUNT=$((TOTAL_COUNT + 1)) + + if rpm --checksig "$rpm"; then + echo " ✓" + else + echo " ✗ FAILED" + FAILED_COUNT=$((FAILED_COUNT + 1)) + fi + done + + # Fail if any RPMs failed verification + if [ "$FAILED_COUNT" -gt 0 ]; then + echo "ERROR: $FAILED_COUNT out of $TOTAL_COUNT RPMs failed signature verification" + exit 1 + fi + + echo "Successfully verified all $TOTAL_COUNT RPM signatures" + + - name: Test RPM installation + run: | + # Find the main binary RPM (exclude debug and source RPMs) + RPM=$(find "$HOME/rpmbuild/RPMS" -name "continuwuity-*.rpm" \ + ! -name "*debuginfo*" \ + ! -name "*debugsource*" \ + ! -name "*.src.rpm" | head -1) + + if [ -z "$RPM" ]; then + echo "Error: No binary RPM file found" + exit 1 + fi + + echo "Testing installation of: $RPM" + + # Dry run first + rpm -qpi "$RPM" + echo "" + rpm -qpl "$RPM" + + # Actually install it (would need --nodeps if dependencies aren't met) + dnf install -y "$RPM" || rpm -ivh --nodeps "$RPM" + + # Verify installation + rpm -qa | grep continuwuity + + # Check that the binary exists + [ -f /usr/bin/conduwuit ] && echo "✅ Binary installed successfully" + [ -f /usr/lib/systemd/system/conduwuit.service ] && echo "✅ Systemd service installed" + [ -f /etc/conduwuit/conduwuit.toml ] && echo "✅ Config file installed" + + - name: List built packages + run: | + echo "Binary RPMs:" + find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec ls -la {} \; + + echo "" + echo "Source RPMs:" + find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec ls -la {} \; + + - name: Collect artifacts + run: | + mkdir -p artifacts + + # Copy all RPMs to artifacts directory + find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; + find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; + + # Create metadata file + cd artifacts + echo "Build Information:" > BUILD_INFO.txt + echo "==================" >> BUILD_INFO.txt + echo "Git commit: ${{ github.sha }}" >> BUILD_INFO.txt + echo "Git branch: ${{ github.ref_name }}" >> BUILD_INFO.txt + echo "Build date: $(date -u +%Y-%m-%d_%H:%M:%S_UTC)" >> BUILD_INFO.txt + echo "" >> BUILD_INFO.txt + echo "Package contents:" >> BUILD_INFO.txt + echo "-----------------" >> BUILD_INFO.txt + for rpm in *.rpm; do + echo "" >> BUILD_INFO.txt + echo "File: $rpm" >> BUILD_INFO.txt + rpm -qpi "$rpm" 2>/dev/null | grep -E "^(Name|Version|Release|Architecture|Size)" >> BUILD_INFO.txt + done + + ls -la + + - name: Upload binary RPM artifact + run: | + # Find the main binary RPM (exclude debug and source RPMs) + BIN_RPM=$(find artifacts -name "continuwuity-*.rpm" \ + ! -name "*debuginfo*" \ + ! -name "*debugsource*" \ + ! -name "*.src.rpm" \ + -type f) + + # Create temp directory for this artifact + mkdir -p upload-bin + cp $BIN_RPM upload-bin/ + + - name: Upload binary RPM + uses: https://code.forgejo.org/actions/upload-artifact@v3 + with: + name: continuwuity + path: upload-bin/ + + - name: Upload debug RPM artifact + uses: https://code.forgejo.org/actions/upload-artifact@v3 + with: + name: continuwuity-debug + path: artifacts/*debuginfo*.rpm + + - name: Publish to RPM Package Registry + if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + run: | + # Find the main binary RPM (exclude debug and source RPMs) + RPM=$(find artifacts -name "continuwuity-*.rpm" \ + ! -name "*debuginfo*" \ + ! -name "*debugsource*" \ + ! -name "*.src.rpm" \ + -type f | head -1) + + if [ -z "$RPM" ]; then + echo "No binary RPM found to publish" + exit 0 + fi + + # Extract version from RPM filename + RPM_BASENAME=$(basename "$RPM") + echo "Publishing: $RPM_BASENAME" + + # Determine the group based on ref type and branch + if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + GROUP="stable" + # For tags, extract the tag name for version info + TAG_NAME="${{ github.ref_name }}" + elif [ "${{ github.ref_name }}" = "main" ]; then + GROUP="dev" + else + # Use sanitized branch name as group for feature branches + GROUP=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30) + fi + + # Extract package info from RPM for deletion + PACKAGE_INFO=$(rpm -qpi "$RPM" 2>/dev/null) + PACKAGE_NAME=$(echo "$PACKAGE_INFO" | grep "^Name" | awk '{print $3}') + PACKAGE_VERSION=$(echo "$PACKAGE_INFO" | grep "^Version" | awk '{print $3}') + PACKAGE_RELEASE=$(echo "$PACKAGE_INFO" | grep "^Release" | awk '{print $3}') + PACKAGE_ARCH=$(echo "$PACKAGE_INFO" | grep "^Architecture" | awk '{print $2}') + + # Full version includes release + FULL_VERSION="${PACKAGE_VERSION}-${PACKAGE_RELEASE}" + + # Try to delete existing package first (ignore errors if it doesn't exist) + echo "Removing any existing package: $PACKAGE_NAME-$FULL_VERSION.$PACKAGE_ARCH" + curl -X DELETE \ + -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH" \ + || echo "Package didn't exist or deletion failed (this is OK)" + + # Upload to Forgejo package registry + # Using the RPM registry endpoint with group support + curl --fail-with-body \ + -X PUT \ + -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ + -H "Content-Type: application/x-rpm" \ + -T "$RPM" \ + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/upload" + + echo "" + echo "✅ Published binary RPM to: https://forgejo.ellis.link/continuwuation/-/packages/rpm/continuwuity/" + echo "Group: $GROUP" + + # Upload debug RPMs to separate group + DEBUG_RPMS=$(find artifacts -name "*debuginfo*.rpm") + if [ -n "$DEBUG_RPMS" ]; then + echo "" + echo "Publishing debug RPMs to group: ${GROUP}-debug" + + for DEBUG_RPM in $DEBUG_RPMS; do + echo "Publishing: $(basename "$DEBUG_RPM")" + + # Extract debug RPM info + DEBUG_INFO=$(rpm -qpi "$DEBUG_RPM" 2>/dev/null) + DEBUG_NAME=$(echo "$DEBUG_INFO" | grep "^Name" | awk '{print $3}') + DEBUG_VERSION=$(echo "$DEBUG_INFO" | grep "^Version" | awk '{print $3}') + DEBUG_RELEASE=$(echo "$DEBUG_INFO" | grep "^Release" | awk '{print $3}') + DEBUG_ARCH=$(echo "$DEBUG_INFO" | grep "^Architecture" | awk '{print $2}') + DEBUG_FULL_VERSION="${DEBUG_VERSION}-${DEBUG_RELEASE}" + + # Try to delete existing debug package first + curl -X DELETE \ + -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH" \ + || echo "Debug package didn't exist or deletion failed (this is OK)" + + # Upload debug RPM + curl --fail-with-body \ + -X PUT \ + -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ + -H "Content-Type: application/x-rpm" \ + -T "$DEBUG_RPM" \ + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/upload" + done + + echo "✅ Published debug RPMs to group: ${GROUP}-debug" + fi + + # Also upload the SRPM to separate group + SRPM=$(find artifacts -name "*.src.rpm" | head -1) + if [ -n "$SRPM" ]; then + echo "" + echo "Publishing source RPM: $(basename "$SRPM")" + echo "Publishing to group: ${GROUP}-src" + + # Extract SRPM info + SRPM_INFO=$(rpm -qpi "$SRPM" 2>/dev/null) + SRPM_NAME=$(echo "$SRPM_INFO" | grep "^Name" | awk '{print $3}') + SRPM_VERSION=$(echo "$SRPM_INFO" | grep "^Version" | awk '{print $3}') + SRPM_RELEASE=$(echo "$SRPM_INFO" | grep "^Release" | awk '{print $3}') + SRPM_FULL_VERSION="${SRPM_VERSION}-${SRPM_RELEASE}" + + # Try to delete existing SRPM first + echo "Removing any existing SRPM: $SRPM_NAME-$SRPM_FULL_VERSION.src" + curl -X DELETE \ + -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src" \ + || echo "SRPM didn't exist or deletion failed (this is OK)" + + curl --fail-with-body \ + -X PUT \ + -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ + -H "Content-Type: application/x-rpm" \ + -T "$SRPM" \ + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/upload" + + echo "✅ Published source RPM to group: ${GROUP}-src" + fi diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index fa097238..8936a1ef 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -10,6 +10,7 @@ - [Kubernetes](deploying/kubernetes.md) - [Arch Linux](deploying/arch-linux.md) - [Debian](deploying/debian.md) + - [Fedora](deploying/fedora.md) - [FreeBSD](deploying/freebsd.md) - [TURN](turn.md) - [Appservices](appservices.md) diff --git a/docs/deploying/fedora.md b/docs/deploying/fedora.md new file mode 100644 index 00000000..0130809b --- /dev/null +++ b/docs/deploying/fedora.md @@ -0,0 +1,201 @@ +# RPM Installation Guide + +Continuwuity is available as RPM packages for Fedora, RHEL, and compatible distributions. + +The RPM packaging files are maintained in the `fedora/` directory: +- `continuwuity.spec.rpkg` - RPM spec file using rpkg macros for building from git +- `continuwuity.service` - Systemd service file for the server +- `RPM-GPG-KEY-continuwuity.asc` - GPG public key for verifying signed packages + +RPM packages built by CI are signed with our GPG key (Ed25519, ID: `5E0FF73F411AAFCA`). + +```bash +# Import the signing key +sudo rpm --import https://forgejo.ellis.link/continuwuation/continuwuity/raw/branch/main/fedora/RPM-GPG-KEY-continuwuity.asc + +# Verify a downloaded package +rpm --checksig continuwuity-*.rpm +``` + +## Installation methods + +**Stable releases** (recommended) + +```bash +# Add the repository and install +sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable/continuwuation.repo +sudo dnf install continuwuity +``` + +**Development builds** from main branch + +```bash +# Add the dev repository and install +sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/dev/continuwuation.repo +sudo dnf install continuwuity +``` + +**Feature branch builds** (example: `tom/new-feature`) + +```bash +# Branch names are sanitized (slashes become hyphens, lowercase only) +sudo dnf config-manager addrepo --from-repofile=https://forgejo.ellis.link/api/packages/continuwuation/rpm/tom-new-feature/continuwuation.repo +sudo dnf install continuwuity +``` + +**Direct installation** without adding repository + +```bash +# Latest stable release +sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable/continuwuity + +# Latest development build +sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/dev/continuwuity + +# Specific feature branch +sudo dnf install https://forgejo.ellis.link/api/packages/continuwuation/rpm/branch-name/continuwuity +``` + +**Manual repository configuration** (alternative method) + +```bash +cat << 'EOF' | sudo tee /etc/yum.repos.d/continuwuity.repo +[continuwuity] +name=Continuwuity - Matrix homeserver +baseurl=https://forgejo.ellis.link/api/packages/continuwuation/rpm/stable +enabled=1 +gpgcheck=1 +gpgkey=https://forgejo.ellis.link/continuwuation/continuwuity/raw/branch/main/fedora/RPM-GPG-KEY-continuwuity.asc +EOF + +sudo dnf install continuwuity +``` + +## Package management + +**Automatic updates** with DNF Automatic + +```bash +# Install and configure +sudo dnf install dnf-automatic +sudo nano /etc/dnf/automatic.conf # Set: apply_updates = yes +sudo systemctl enable --now dnf-automatic.timer +``` + +**Manual updates** + +```bash +# Check for updates +sudo dnf check-update continuwuity + +# Update to latest version +sudo dnf update continuwuity +``` + +**Switching channels** (stable/dev/feature branches) + +```bash +# List enabled repositories +dnf repolist | grep continuwuation + +# Disable current repository +sudo dnf config-manager --set-disabled continuwuation-stable # or -dev, or branch name + +# Enable desired repository +sudo dnf config-manager --set-enabled continuwuation-dev # or -stable, or branch name + +# Update to the new channel's version +sudo dnf update continuwuity +``` + +**Verifying installation** + +```bash +# Check installed version +rpm -q continuwuity + +# View package information +rpm -qi continuwuity + +# List installed files +rpm -ql continuwuity + +# Verify package integrity +rpm -V continuwuity +``` + +## Service management and removal + +**Systemd service commands** + +```bash +# Start the service +sudo systemctl start conduwuit + +# Enable on boot +sudo systemctl enable conduwuit + +# Check status +sudo systemctl status conduwuit + +# View logs +sudo journalctl -u conduwuit -f +``` + +**Uninstallation** + +```bash +# Stop and disable the service +sudo systemctl stop conduwuit +sudo systemctl disable conduwuit + +# Remove the package +sudo dnf remove continuwuity + +# Remove the repository (optional) +sudo rm /etc/yum.repos.d/continuwuation-*.repo +``` + +## Troubleshooting + +**GPG key errors**: Temporarily disable GPG checking + +```bash +sudo dnf --nogpgcheck install continuwuity +``` + +**Repository metadata issues**: Clear and rebuild cache + +```bash +sudo dnf clean all +sudo dnf makecache +``` + +**Finding specific versions** + +```bash +# List all available versions +dnf --showduplicates list continuwuity + +# Install a specific version +sudo dnf install continuwuity- +``` + +## Building locally + +Build the RPM locally using rpkg: + +```bash +# Install dependencies +sudo dnf install rpkg rpm-build cargo-rpm-macros systemd-rpm-macros + +# Clone the repository +git clone https://forgejo.ellis.link/continuwuation/continuwuity.git +cd continuwuity + +# Build SRPM +rpkg srpm + +# Build RPM +rpmbuild --rebuild *.src.rpm +``` diff --git a/fedora/RPM-GPG-KEY-continuwuity.asc b/fedora/RPM-GPG-KEY-continuwuity.asc new file mode 100644 index 00000000..031128ac --- /dev/null +++ b/fedora/RPM-GPG-KEY-continuwuity.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEaLM5LhYJKwYBBAHaRw8BAQdAlnMcp/fMzYfwqeExDsEx2qfZg8NjamGh0slC +9bkUpQW0O0NvbnRpbnV3dWl0eSBDSSAoUlBNIFBhY2thZ2UgU2lnbmluZykgPGNp +QGNvbnRpbnV3dWl0eS5vcmc+iJYEExYIAD4WIQShcq3anZQUJ0FNTm1eD/c/QRqv +ygUCaLM5LgIbAwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRBeD/c/ +QRqvyk3QAQCjurJLpDANckZflsEVRwxDOUCrED4LdyWpbOuVmhGikwD/fGwkpdUa +ngP1l+bhlprJN5J1P5UOeNZtKce0vFZaBwC4MwRoszkuFgkrBgEEAdpHDwEBB0CJ +RpQlzJt/TdYx8AOkNIYan6qbxijjjpZWDIbZp95CfIj1BBgWCAAmFiEEoXKt2p2U +FCdBTU5tXg/3P0Ear8oFAmizOS4CGwIFCQPCZwAAgQkQXg/3P0Ear8p2IAQZFggA +HRYhBN/tBNmxKe70FHf4CRoGaPIa/K9qBQJoszkuAAoJEBoGaPIa/K9qf7EBAJ9D +pdKRji4gy9LWR3w9Ha7Tekmw7kSPGYLZlkDqjiuCAQCCupMGB9r2XPc2/G/KIV+7 +HpWfIANhPsCn1Q9kcloCCIv9AQCy+xDsdtkOw7JnB4g1EKfPlPhN6j3Cjk1vlG2N +WN/p2AEAkozKVDAbvWEi/s7W9DNWckXm1SS0Og/sv5nGV8okIg4= +=dxDr +-----END PGP PUBLIC KEY BLOCK----- diff --git a/fedora/continuwuity.spec.rpkg b/fedora/continuwuity.spec.rpkg index f2efa383..4ff7dbe6 100644 --- a/fedora/continuwuity.spec.rpkg +++ b/fedora/continuwuity.spec.rpkg @@ -1,6 +1,5 @@ -# This should be run using rpkg-util: https://docs.pagure.org/rpkg-util +# This should be run using rpkg: https://docs.pagure.org/rpkg # it requires Internet access and is not suitable for Fedora main repos -# TODO: rpkg-util is no longer maintained, find a replacement Name: continuwuity Version: {{{ git_repo_version }}} From 7be651a3075624394ae23363b64517c753d80856 Mon Sep 17 00:00:00 2001 From: Tom Foster Date: Thu, 4 Sep 2025 10:29:17 +0100 Subject: [PATCH 15/15] refactor(ci): Use dnf builddep for RPM build dependencies Move build dependency installation after SRPM generation to use dnf builddep, eliminating duplication between CI config and spec file. Dependencies are now sourced directly from the SRPM, ensuring consistency and reducing maintenance. Also fix test installation to properly fail when dependencies cannot be met, removing the fallback to --nodeps which could hide packaging issues. --- .forgejo/workflows/build-fedora.yml | 107 +++++++++++++--------------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/.forgejo/workflows/build-fedora.yml b/.forgejo/workflows/build-fedora.yml index 60404896..c101ed04 100644 --- a/.forgejo/workflows/build-fedora.yml +++ b/.forgejo/workflows/build-fedora.yml @@ -81,7 +81,7 @@ jobs: # Aggressive GC since cache restores don't increment counter echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV - - name: Install build dependencies + - name: Install base RPM tools run: | dnf install -y --setopt=keepcache=1 \ wget \ @@ -91,25 +91,14 @@ jobs: rpkg \ cargo-rpm-macros \ systemd-rpm-macros \ - clang \ - liburing-devel \ - rust \ - cargo \ - gcc \ - gcc-c++ \ - make \ - openssl-devel \ - pkg-config \ python3-pip - name: Setup build environment and build SRPM run: | - # Configure git for rpkg git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global user.email "ci@continuwuity.org" git config --global user.name "Continuwuity" - # Setup RPM build tree rpmdev-setuptree cd "$GITHUB_WORKSPACE" @@ -143,10 +132,8 @@ jobs: fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg fi - # Build the SRPM rpkg srpm --outdir "$HOME/rpmbuild/SRPMS" - # Show SRPM info ls -la $HOME/rpmbuild/SRPMS/ - name: Setup GPG for RPM signing @@ -157,23 +144,32 @@ jobs: exit 0 fi - # Import the signing key echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import # Get the key ID (look for the sec line, not the uid line) KEY_ID=$(gpg --list-secret-keys --keyid-format=long | grep "^sec" | head -1 | awk '{print $2}' | cut -d'/' -f2) echo "Using GPG key: $KEY_ID" - # Configure RPM macros for signing cat > ~/.rpmmacros << EOF %_signature gpg %_gpg_name $KEY_ID %__gpg /usr/bin/gpg EOF + - name: Install build dependencies from SRPM + run: | + SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1) + + if [ -z "$SRPM" ]; then + echo "Error: No SRPM file found" + exit 1 + fi + + echo "Installing build dependencies from: $(basename $SRPM)" + dnf builddep -y "$SRPM" + - name: Build RPM from SRPM run: | - # Find the SRPM file SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1) if [ -z "$SRPM" ]; then @@ -183,7 +179,6 @@ jobs: echo "Building from SRPM: $SRPM" - # Build the binary RPM rpmbuild --rebuild "$SRPM" \ --define "_topdir $HOME/rpmbuild" \ --define "_sourcedir $GITHUB_WORKSPACE" \ @@ -197,32 +192,20 @@ jobs: exit 0 fi - # Track signing failures - FAILED_COUNT=0 - TOTAL_COUNT=0 - # Export GPG_TTY to avoid terminal warnings export GPG_TTY=/dev/null - # Sign all RPMs (binary and source) for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do echo "Signing: $(basename $rpm)" - TOTAL_COUNT=$((TOTAL_COUNT + 1)) # Use expect or provide empty passphrase via stdin for batch signing if ! echo "" | rpmsign --addsign "$rpm" 2>&1; then echo "ERROR: Failed to sign $rpm" - FAILED_COUNT=$((FAILED_COUNT + 1)) + exit 1 fi done - # Fail if any RPMs failed to sign - if [ "$FAILED_COUNT" -gt 0 ]; then - echo "ERROR: Failed to sign $FAILED_COUNT out of $TOTAL_COUNT RPMs" - exit 1 - fi - - echo "Successfully signed all $TOTAL_COUNT RPMs" + echo "Successfully signed all RPMs" - name: Verify RPM signatures run: | @@ -232,15 +215,12 @@ jobs: exit 0 fi - # Import our public key for verification echo "Importing GPG public key for verification..." rpm --import fedora/RPM-GPG-KEY-continuwuity.asc - # Track verification failures FAILED_COUNT=0 TOTAL_COUNT=0 - # Verify all RPMs for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do echo -n "Verifying $(basename $rpm): " TOTAL_COUNT=$((TOTAL_COUNT + 1)) @@ -281,8 +261,8 @@ jobs: echo "" rpm -qpl "$RPM" - # Actually install it (would need --nodeps if dependencies aren't met) - dnf install -y "$RPM" || rpm -ivh --nodeps "$RPM" + # Actually install it + dnf install -y "$RPM" # Verify installation rpm -qa | grep continuwuity @@ -305,11 +285,9 @@ jobs: run: | mkdir -p artifacts - # Copy all RPMs to artifacts directory find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; - # Create metadata file cd artifacts echo "Build Information:" > BUILD_INFO.txt echo "==================" >> BUILD_INFO.txt @@ -336,7 +314,6 @@ jobs: ! -name "*.src.rpm" \ -type f) - # Create temp directory for this artifact mkdir -p upload-bin cp $BIN_RPM upload-bin/ @@ -367,7 +344,6 @@ jobs: exit 0 fi - # Extract version from RPM filename RPM_BASENAME=$(basename "$RPM") echo "Publishing: $RPM_BASENAME" @@ -383,7 +359,6 @@ jobs: GROUP=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30) fi - # Extract package info from RPM for deletion PACKAGE_INFO=$(rpm -qpi "$RPM" 2>/dev/null) PACKAGE_NAME=$(echo "$PACKAGE_INFO" | grep "^Name" | awk '{print $3}') PACKAGE_VERSION=$(echo "$PACKAGE_INFO" | grep "^Version" | awk '{print $3}') @@ -393,15 +368,20 @@ jobs: # Full version includes release FULL_VERSION="${PACKAGE_VERSION}-${PACKAGE_RELEASE}" - # Try to delete existing package first (ignore errors if it doesn't exist) + # Forgejo's RPM registry cannot overwrite existing packages, so we must delete first + # 404 is OK if package doesn't exist yet echo "Removing any existing package: $PACKAGE_NAME-$FULL_VERSION.$PACKAGE_ARCH" - curl -X DELETE \ + RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ - "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH" \ - || echo "Package didn't exist or deletion failed (this is OK)" + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH") + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + + if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then + echo "ERROR: Failed to delete package (HTTP $HTTP_CODE)" + echo "$RESPONSE" | head -n -1 + exit 1 + fi - # Upload to Forgejo package registry - # Using the RPM registry endpoint with group support curl --fail-with-body \ -X PUT \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ @@ -422,7 +402,6 @@ jobs: for DEBUG_RPM in $DEBUG_RPMS; do echo "Publishing: $(basename "$DEBUG_RPM")" - # Extract debug RPM info DEBUG_INFO=$(rpm -qpi "$DEBUG_RPM" 2>/dev/null) DEBUG_NAME=$(echo "$DEBUG_INFO" | grep "^Name" | awk '{print $3}') DEBUG_VERSION=$(echo "$DEBUG_INFO" | grep "^Version" | awk '{print $3}') @@ -430,13 +409,18 @@ jobs: DEBUG_ARCH=$(echo "$DEBUG_INFO" | grep "^Architecture" | awk '{print $2}') DEBUG_FULL_VERSION="${DEBUG_VERSION}-${DEBUG_RELEASE}" - # Try to delete existing debug package first - curl -X DELETE \ + # Must delete existing package first (Forgejo limitation) + RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ - "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH" \ - || echo "Debug package didn't exist or deletion failed (this is OK)" + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH") + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + + if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then + echo "ERROR: Failed to delete debug package (HTTP $HTTP_CODE)" + echo "$RESPONSE" | head -n -1 + exit 1 + fi - # Upload debug RPM curl --fail-with-body \ -X PUT \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ @@ -455,19 +439,24 @@ jobs: echo "Publishing source RPM: $(basename "$SRPM")" echo "Publishing to group: ${GROUP}-src" - # Extract SRPM info SRPM_INFO=$(rpm -qpi "$SRPM" 2>/dev/null) SRPM_NAME=$(echo "$SRPM_INFO" | grep "^Name" | awk '{print $3}') SRPM_VERSION=$(echo "$SRPM_INFO" | grep "^Version" | awk '{print $3}') SRPM_RELEASE=$(echo "$SRPM_INFO" | grep "^Release" | awk '{print $3}') SRPM_FULL_VERSION="${SRPM_VERSION}-${SRPM_RELEASE}" - # Try to delete existing SRPM first + # Must delete existing SRPM first (Forgejo limitation) echo "Removing any existing SRPM: $SRPM_NAME-$SRPM_FULL_VERSION.src" - curl -X DELETE \ + RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ - "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src" \ - || echo "SRPM didn't exist or deletion failed (this is OK)" + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src") + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + + if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then + echo "ERROR: Failed to delete SRPM (HTTP $HTTP_CODE)" + echo "$RESPONSE" | head -n -1 + exit 1 + fi curl --fail-with-body \ -X PUT \