Repository navigation
[Server Announcements] [Create/Configure Rooms] Configure announcement rooms and reconcile them on startup - #20372
anoadragon453 wants to merge 3 commits into
Conversation
Schema DiffPlease check that this looks as expected! common (sqlite)Unchanged common (postgres)Unchanged main (sqlite)--- /tmp/schema_diff_yujy0ztl/before/main/full_schemas/9999/full.sql.sqlite 2026-10-19 11:27:10.659194123 +0000
+++ /tmp/schema_diff_yujy0ztl/after/main/full_schemas/9999/full.sql.sqlite 2026-10-19 11:26:55.652875229 +0000
@@ -1219,20 +1219,27 @@
-- all subsequent sticky events for the same room with higher
-- `stream_id`s are also unsent.
--
-- Not a foreign key because we must still support expiration of sticky events.
sticky_events_stream_position INTEGER NOT NULL,
-- It's enough to track one position per (destination, room_id) pair.
PRIMARY KEY (destination, room_id)
);
CREATE INDEX destination_rooms_sticky_events_backlog_room_id ON destination_rooms_sticky_events_backlog (room_id);
+CREATE TABLE server_announcement_rooms (
+ id TEXT PRIMARY KEY NOT NULL,
+ room_id TEXT UNIQUE NOT NULL,
+ avatar_path TEXT,
+ avatar_sha256 TEXT,
+ avatar_mxc TEXT
+);
CREATE UNIQUE INDEX receipts_graph_unique_index ON receipts_graph (room_id, receipt_type, user_id) WHERE thread_id IS NULL;
CREATE UNIQUE INDEX receipts_linearized_unique_index ON receipts_linearized (room_id, receipt_type, user_id) WHERE thread_id IS NULL;
CREATE INDEX device_lists_changes_in_room_by_room_idx ON device_lists_changes_in_room (room_id, stream_id) ;
CREATE INDEX events_jump_to_date_idx ON events (room_id, origin_server_ts) WHERE NOT outlier;
CREATE INDEX room_membership_user_room_idx ON room_memberships (user_id, room_id) ;
CREATE UNIQUE INDEX profiles_full_user_id_key ON profiles (full_user_id) ;
CREATE UNIQUE INDEX full_users_unique_idx ON user_filters (full_user_id, filter_id) ;
CREATE INDEX event_push_actions_stream_highlight_index ON event_push_actions (highlight, stream_ordering) WHERE highlight=0;
CREATE INDEX current_state_events_stream_ordering_idx ON current_state_events (event_stream_ordering) ;
CREATE INDEX local_current_membership_stream_ordering_idx ON local_current_membership (event_stream_ordering) ;
main (postgres)--- /tmp/schema_diff_yujy0ztl/before/main/full_schemas/9999/full.sql.postgres 2026-10-19 11:27:11.379209414 +0000
+++ /tmp/schema_diff_yujy0ztl/after/main/full_schemas/9999/full.sql.postgres 2026-10-19 11:26:56.384890799 +0000
@@ -1022,20 +1022,27 @@
CREATE TABLE scheduled_tasks (
id text NOT NULL,
action text NOT NULL,
status text NOT NULL,
"timestamp" bigint NOT NULL,
resource_id text,
params text,
result text,
error text
);
+CREATE TABLE server_announcement_rooms (
+ id text NOT NULL,
+ room_id text NOT NULL,
+ avatar_path text,
+ avatar_sha256 text,
+ avatar_mxc text
+);
CREATE TABLE server_keys_json (
server_name text NOT NULL,
key_id text NOT NULL,
from_server text NOT NULL,
ts_added_ms bigint NOT NULL,
ts_valid_until_ms bigint NOT NULL,
key_json bytea NOT NULL
);
CREATE TABLE server_signature_keys (
server_name text,
@@ -1572,20 +1579,24 @@
ALTER TABLE ONLY room_stats_current
ADD CONSTRAINT room_stats_current_pkey PRIMARY KEY (room_id);
ALTER TABLE ONLY room_tags_revisions
ADD CONSTRAINT room_tag_revisions_uniqueness UNIQUE (user_id, room_id);
ALTER TABLE ONLY room_tags
ADD CONSTRAINT room_tag_uniqueness UNIQUE (user_id, room_id, tag);
ALTER TABLE ONLY rooms
ADD CONSTRAINT rooms_pkey PRIMARY KEY (room_id);
ALTER TABLE ONLY scheduled_tasks
ADD CONSTRAINT scheduled_tasks_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY server_announcement_rooms
+ ADD CONSTRAINT server_announcement_rooms_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY server_announcement_rooms
+ ADD CONSTRAINT server_announcement_rooms_room_id_key UNIQUE (room_id);
ALTER TABLE ONLY server_keys_json
ADD CONSTRAINT server_keys_json_uniqueness UNIQUE (server_name, key_id, from_server);
ALTER TABLE ONLY server_signature_keys
ADD CONSTRAINT server_signature_keys_server_name_key_id_key UNIQUE (server_name, key_id);
ALTER TABLE ONLY sessions
ADD CONSTRAINT sessions_session_type_session_id_key UNIQUE (session_type, session_id);
ALTER TABLE ONLY sliding_sync_connection_positions
ADD CONSTRAINT sliding_sync_connection_positions_pkey PRIMARY KEY (connection_position);
ALTER TABLE ONLY sliding_sync_connection_required_state
ADD CONSTRAINT sliding_sync_connection_required_state_pkey PRIMARY KEY (required_state_id);
state (sqlite)Unchanged state (postgres)Unchanged |
8190e6f to
e638bf5
Compare
e638bf5 to
9a307d4
Compare
Allow storing mappings between server announcement room config and configured Matrix rooms.
9a307d4 to
326f828
Compare
Create and update the room state of server announcement rooms that are configured. Rooms that are removed from configuration are kept, but no longer given special treatment. To remove old announcement rooms, server admins can purge them in the usual manner. Purging these rooms also removes the announcement `id` -> matrix room ID mapping in the `server_announcement_rooms` table.
326f828 to
ffdc35f
Compare
| None. | ||
| """ | ||
|
|
||
| async def get_server_announcement_room(self, announcement_id: str) -> str | None: |
There was a problem hiding this comment.
nit: because this is only internal naming
I found it confusing when reviewing that the "Announcement Room Id" was announcement_id. I understand we cannot use room_id either.
Maybe something like config_id would make things less confusing
| # We don't want announcement room events to federate to other homeservers. | ||
| # If this room is somehow federating, then we shouldn't use it. | ||
| raise ConfigError( | ||
| f'Announcement room {room.id} ({room_id}) has "m.federate": true ' | ||
| "in its create event. Please create another room (with a new id)", | ||
| ("experimental_features", "server_announcements", "rooms", room.id), |
There was a problem hiding this comment.
🥲 not sure how this can happen, but the safeguard is nice
| # Check that all configured announcement sending users exist. | ||
| for user_id in room.allowed_user_ids: | ||
| user = await self._store.get_user_by_id(user_id) | ||
| if user is None or user.is_deactivated: |
There was a problem hiding this comment.
Seems reasonable to handle deactivated account.
I wonder if we should just warn instead of raising an error in that case.
Being forced to reconfigure after the account is de-activated could create some friction between user management and ops.
| is_server_announcements_room = ( | ||
| await self._store.get_server_announcement_id(room.room_id) is not None | ||
| ) | ||
|
|
||
| if not is_server_notices_room or is_server_announcements_room: | ||
| continue | ||
|
|
||
| # we found a room which our user shares with the system notice | ||
| # user | ||
| return room.room_id |
There was a problem hiding this comment.
To prevent calling self._store.get_server_announcement_id for every room, we could instead:
| is_server_announcements_room = ( | |
| await self._store.get_server_announcement_id(room.room_id) is not None | |
| ) | |
| if not is_server_notices_room or is_server_announcements_room: | |
| continue | |
| # we found a room which our user shares with the system notice | |
| # user | |
| return room.room_id | |
| if ( | |
| is_server_notices_room | |
| and await self._store.get_server_announcement_id(room.room_id) is None | |
| ): | |
| # we found a room which our user shares with the system notice | |
| # user | |
| return room.room_id |
Epic ticket: #20240
Part 1 of implementing #20344
This PR implements configuring server announcement rooms. Server admins can configure one or more announcement rooms and decide who is allowed to send events in it. The homeserver will then actually create the room, and configure it. The reasoning behind the choices in state events are in #20344.
Preventing room members from modifying state will come in a follow-up PR.
Recommended to review commit-by-commit.
Pull Request Checklist
EventStoretoEventWorkerStore.".code blocks.