From 94d8e283060d399f13b21ebcffd3077971135e00 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Mon, 13 Jul 2026 21:17:09 +0300 Subject: [PATCH] Support readonly datastores for matrix-media-repo The configuration template only rendered a datastore when its for_kinds list was non-empty, even though the inline documentation (matching upstream's) describes forKinds: [] as the way to make a datastore readonly. That made the documented migration scenario impossible: moving media between the file and s3 datastores while keeping the old one readable. Introduce matrix_media_repo_datastore_file_enabled and matrix_media_repo_datastore_s3_enabled, which default to the previous kinds-based behavior. A readonly datastore is now expressed by enabling the datastore explicitly while assigning it no kinds. Fixes #4303 Co-Authored-By: Claude Fable 5 --- roles/custom/matrix-media-repo/defaults/main.yml | 16 ++++++++++++++++ .../matrix-media-repo/tasks/validate_config.yml | 4 ++-- .../templates/media-repo/media-repo.yaml.j2 | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/roles/custom/matrix-media-repo/defaults/main.yml b/roles/custom/matrix-media-repo/defaults/main.yml index ca4f8b5e6..991f92f78 100755 --- a/roles/custom/matrix-media-repo/defaults/main.yml +++ b/roles/custom/matrix-media-repo/defaults/main.yml @@ -413,6 +413,14 @@ matrix_media_repo_shared_secret_auth_token: "PutSomeRandomSecureValueHere" # thumbnails and other misc data is also stored in these places. The media repo, when looking # for a datastore to use, will always use the smallest datastore first. +# Controls whether the file datastore is defined in the configuration at all. +# A datastore can be defined but disabled for uploads (making it readonly) by keeping this enabled +# while setting `matrix_media_repo_datastore_file_for_kinds: []`. This is useful when migrating +# media to another datastore, as previously stored media remains readable. +# By default, the datastore is defined whenever some kinds are assigned to it, matching the +# previous behavior of this role. +matrix_media_repo_datastore_file_enabled: "{{ (matrix_media_repo_datastore_file_for_kinds | length) > 0 }}" + # ID for the file datastore. Any unique alphanumeric string (e.g. generated via `pwgen -s 64 1`). # This value CANNOT be changed after media has been stored — matrix-media-repo ties media to this ID. matrix_media_repo_datastore_file_id: "" @@ -434,6 +442,14 @@ matrix_media_repo_datastore_file_for_kinds: ["thumbnails", "remote_media", "loca # Path to datastore, relative to matrix-media-repo directory root matrix_media_repo_datastore_opts_path: "/data/media" +# Controls whether the S3 datastore is defined in the configuration at all. +# A datastore can be defined but disabled for uploads (making it readonly) by keeping this enabled +# while setting `matrix_media_repo_datastore_s3_for_kinds: []`. This is useful when migrating +# media to another datastore, as previously stored media remains readable. +# By default, the datastore is defined whenever some kinds are assigned to it, matching the +# previous behavior of this role. +matrix_media_repo_datastore_s3_enabled: "{{ (matrix_media_repo_datastore_s3_for_kinds | length) > 0 }}" + # ID for the S3 datastore. Any unique alphanumeric string (e.g. generated via `pwgen -s 64 1`). # This value CANNOT be changed after media has been stored — matrix-media-repo ties media to this ID. matrix_media_repo_datastore_s3_id: "" diff --git a/roles/custom/matrix-media-repo/tasks/validate_config.yml b/roles/custom/matrix-media-repo/tasks/validate_config.yml index 9c7dd61c7..6884d9bcb 100644 --- a/roles/custom/matrix-media-repo/tasks/validate_config.yml +++ b/roles/custom/matrix-media-repo/tasks/validate_config.yml @@ -50,5 +50,5 @@ - {'name': 'matrix_media_repo_database_hostname', when: true} - {'name': 'matrix_media_repo_container_labels_traefik_internal_media_entrypoints', when: "{{ matrix_media_repo_container_labels_traefik_internal_media_enabled }}"} - {'name': 'matrix_media_repo_container_labels_traefik_internal_matrix_client_media_entrypoints', when: "{{ matrix_media_repo_container_labels_traefik_internal_matrix_client_media_enabled }}"} - - {'name': 'matrix_media_repo_datastore_file_id', when: "{{ (matrix_media_repo_datastore_file_for_kinds | length) > 0 }}"} - - {'name': 'matrix_media_repo_datastore_s3_id', when: "{{ (matrix_media_repo_datastore_s3_for_kinds | length) > 0 }}"} + - {'name': 'matrix_media_repo_datastore_file_id', when: "{{ matrix_media_repo_datastore_file_enabled }}"} + - {'name': 'matrix_media_repo_datastore_s3_id', when: "{{ matrix_media_repo_datastore_s3_enabled }}"} diff --git a/roles/custom/matrix-media-repo/templates/media-repo/media-repo.yaml.j2 b/roles/custom/matrix-media-repo/templates/media-repo/media-repo.yaml.j2 index fa60434fa..50c54d60c 100644 --- a/roles/custom/matrix-media-repo/templates/media-repo/media-repo.yaml.j2 +++ b/roles/custom/matrix-media-repo/templates/media-repo/media-repo.yaml.j2 @@ -198,7 +198,7 @@ sharedSecretAuth: # thumbnails and other misc data is also stored in these places. The media repo, when looking # for a datastore to use, will always use the smallest datastore first. datastores: -{% if (matrix_media_repo_datastore_file_for_kinds | length) > 0 %} +{% if matrix_media_repo_datastore_file_enabled %} - type: file # ID for this datastore (cannot change). Alphanumeric recommended. id: {{ matrix_media_repo_datastore_file_id | to_json }} @@ -218,7 +218,7 @@ datastores: opts: path: {{ matrix_media_repo_datastore_opts_path | to_json }} {% endif %} -{% if (matrix_media_repo_datastore_s3_for_kinds | length) > 0 %} +{% if matrix_media_repo_datastore_s3_enabled %} - type: s3 # ID for this datastore (cannot change). Alphanumeric recommended. id: {{ matrix_media_repo_datastore_s3_id | to_json }}