Skip to content

Add server announcement room configuration - #20353

Closed
anoadragon453 wants to merge 7 commits into
developfrom
anoa/server_announcements_config_create_rooms
Closed

anoadragon453 wants to merge 7 commits into
developfrom
anoa/server_announcements_config_create_rooms

Conversation

@anoadragon453

Copy link
Copy Markdown
Member

WIP

Pull Request Checklist

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
    • Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry.
  • Code style is correct (run the linters)

@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Schema Diff

Please check that this looks as expected!

common (sqlite)

Unchanged

common (postgres)

Unchanged

main (sqlite)

--- /tmp/schema_diff_tgjuazit/before/main/full_schemas/9999/full.sql.sqlite	2026-10-15 20:58:05.209617672 +0000
+++ /tmp/schema_diff_tgjuazit/after/main/full_schemas/9999/full.sql.sqlite	2026-10-15 20:57:51.511489320 +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_tgjuazit/before/main/full_schemas/9999/full.sql.postgres	2026-10-15 20:58:05.905624351 +0000
+++ /tmp/schema_diff_tgjuazit/after/main/full_schemas/9999/full.sql.postgres	2026-10-15 20:57:52.284496693 +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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schema/synapse-config.schema.yaml should have been updated instead, and then ./scripts-dev/gen_config_documentation.py schema/synapse-config.schema.yaml > docs/usage/configuration/config_documentation.md run to generate the md documentation.

Comment on lines +4209 to +4212
* `id` (string, required): A unique, nonempty configuration identifier. This is not
a Matrix room ID. Keep it stable: it maps the configuration entry to its existing
room, so changing the name, topic, avatar, or allowed users does not create a new
room. Changing `id` creates a new mapping rather than renaming the existing one.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `id` (string, required): A unique, nonempty configuration identifier. This is not
a Matrix room ID. Keep it stable: it maps the configuration entry to its existing
room, so changing the name, topic, avatar, or allowed users does not create a new
room. Changing `id` creates a new mapping rather than renaming the existing one.
* `id` (string, required): A unique, nonempty configuration identifier. This is not
a Matrix room ID. It should not be changed after the associated room has been created.
It maps the configuration entry to its existing
room, so changing the name, topic, avatar, or allowed users does not create a new
room. Changing `id` creates a new mapping rather than renaming the existing one.


Each entry is an object with the following options:

* `id` (string, required): A unique, nonempty configuration identifier. This is not

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess when an announcement room is purged, the mapping should also be deleted.

* `room_topic` (string|null): An optional topic for the room. Defaults to `null`.
* `room_avatar_url` (string|null): A valid `mxc://` URI for the **room** avatar.
Mutually exclusive with `room_avatar_path`. Defaults to `null`.
* `room_avatar_path` (string|null): A path to a local file to upload as the **room**

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `room_avatar_path` (string|null): A path to a local file to upload as the **room**
* `room_avatar_path` (string|null): A path to a local file to upload as the room

Comment on lines +4219 to +4221
paths are resolved against the process's working directory. Files must be
readable by the **main process**, where uploads take place; they do not need to
be available on workers. Using this option requires

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: This is a concern. There's only ever one media worker. Why can't that worker upload the file?


async def check_events(
self, requester: Requester, events_and_context: Sequence[EventPersistencePair]
) -> None:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) -> None:
) -> None:
"""Check that one or more events may be sent in a local
server announcement room.
Prevent users other than those configured from sending events
in the room. Also prevent any state events, as the rooms are managed by
configuration.
"""

Comment on lines +90 to +91
# In particular, allow removed users to leave during cleanup.
# Matrix auth still checks whether the transition is valid.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# In particular, allow removed users to leave during cleanup.
# Matrix auth still checks whether the transition is valid.

Comment on lines +85 to +89
if (
event.sender != creator
and event.state_key == event.sender
and event.membership in (Membership.JOIN, Membership.LEAVE)
):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (
event.sender != creator
and event.state_key == event.sender
and event.membership in (Membership.JOIN, Membership.LEAVE)
):
# We need to allow users to join themselves so that we can generate
# join events for allowed users.
#
# We need to allow users to leave themselves so that we can generate
# leave events for users when purging an announcement room.
#
# In both cases, the homeserver is sending membership events on
# behalf of users.
if (
event.sender != creator
and event.state_key == event.sender
and event.membership in (Membership.JOIN, Membership.LEAVE)
):

Comment on lines +78 to +80
"""Cache an uploaded avatar for an existing announcement room mapping.

The caller supplies the file path, SHA-256 hex digest and uploaded MXC URI.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Cache an uploaded avatar for an existing announcement room mapping.
The caller supplies the file path, SHA-256 hex digest and uploaded MXC URI.
"""Cache an uploaded avatar for an existing announcement room mapping.
The sha256 hash allows one to check whether the file has changed on
disk.

Comment on lines +14 to +16
-- Mappings are retained even when announcements are removed from configuration.
-- There is deliberately no foreign key to rooms: these mappings must not block
-- normal room purges or disappear as a side effect of a purge.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think we should purge mappings when a room is purged. What if someone messes up an announcement room and wants to start over but keep the same nice ID. They wouldn't have any way to do so other than editing the database by hand.

A better lifecycle would be:

  1. An administrator purges the announcement room.
  2. Purging also deletes its announcement mapping.
  3. On the next startup/reconciliation, an entry with that same id creates a new Matrix room and stores the new mapping.

If the administrator wants it gone permanently, they remove the configuration entry as well. If they want to start over, they keep it. That makes the config id identify the logical announcement room, rather than permanently binding it to one Matrix room ID.

anoadragon453

This comment was marked as duplicate.

@MadLittleMods MadLittleMods added A-Server-Announcements Message all users on a homeserver at once (separate from "Server Notices") A-Config labels Oct 15, 2026
@anoadragon453
anoadragon453 force-pushed the anoa/server_announcements_config_create_rooms branch from e939ae4 to 59a39c5 Compare October 15, 2026 20:55
@anoadragon453

anoadragon453 commented Oct 19, 2026 •

Copy link
Copy Markdown
Member Author

Superseded by #20372 and a future PR. This PR ended up needing to be broken into smaller chunks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Config A-Server-Announcements Message all users on a homeserver at once (separate from "Server Notices")

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants