Matrix Docker Ansible eploy
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

281 строка
17 KiB

  1. # SPDX-FileCopyrightText: 2026 Slavi Pantaleev
  2. #
  3. # SPDX-License-Identifier: AGPL-3.0-or-later
  4. ---
  5. # What this proves: the bridge starts, reads the configuration and registration
  6. # the role rendered, opens its appservice port, and is the Messenger-side image
  7. # at the version the role pins.
  8. #
  9. # The extra thing worth proving for this role in particular is that
  10. # `matrix_bridge_mautrix_meta_messenger_meta_mode` reaches every place it feeds:
  11. # one upstream codebase serves several Meta networks, and the mode is what picks
  12. # which one. The scenario runs the bridge in `facebook-tor` mode rather than the
  13. # role's default `messenger`, so the assertions below can tell the two apart.
  14. #
  15. # It does NOT bridge anything - there is no Facebook or Messenger account on the
  16. # other side and there is deliberately never going to be one. See
  17. # docs/molecule-testing.md.
  18. - name: Verify mautrix-meta-messenger
  19. hosts: all
  20. become: true
  21. vars_files:
  22. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/vars.yml"
  23. - "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/../../../molecule-shared/playbook-context.yml"
  24. # Lazily evaluated, so they are only resolved by the tasks that come after the
  25. # matching slurp. Deliberately not set_fact: the rendered configuration
  26. # contains the bridge's own Go templates (`{{.}}` in `username_template`), and
  27. # a stored fact gets templated again on every lookup, which would try to
  28. # evaluate those as Jinja.
  29. vars:
  30. mautrix_meta_messenger_config: "{{ mautrix_meta_messenger_config_file.content | b64decode | from_yaml }}"
  31. mautrix_meta_messenger_registration: "{{ mautrix_meta_messenger_registration_file.content | b64decode | from_yaml }}"
  32. mautrix_meta_messenger_labels_rendered: "{{ mautrix_meta_messenger_labels.content | b64decode }}"
  33. gather_facts: false
  34. tasks:
  35. # Read from the role's own defaults rather than pinned in molecule.yml, so
  36. # the version assertion below compares the running image against what the
  37. # role ships instead of against the scenario itself.
  38. - name: Load the role's defaults under a separate name
  39. ansible.builtin.include_vars:
  40. file: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/main.yml"
  41. name: mautrix_meta_messenger_role_defaults
  42. - name: Wait for the mautrix-meta-messenger service to become active
  43. ansible.builtin.systemd_service:
  44. name: matrix-mautrix-meta-messenger.service
  45. register: mautrix_meta_messenger_service
  46. until: mautrix_meta_messenger_service.status.ActiveState == 'active'
  47. retries: 30
  48. delay: 5
  49. failed_when: false
  50. # `Restart=always` means a bridge crash-looping on a configuration it cannot
  51. # read still reports `active`, so the restart counter is checked too. It is
  52. # asserted `is defined` because `| int` turns a missing property into 0 and
  53. # would pass vacuously.
  54. - name: Assert the service is active and has not been restarting
  55. ansible.builtin.assert:
  56. that:
  57. - mautrix_meta_messenger_service.status.ActiveState == 'active'
  58. - mautrix_meta_messenger_service.status.NRestarts is defined
  59. - mautrix_meta_messenger_service.status.NRestarts | int == 0
  60. fail_msg: >-
  61. matrix-mautrix-meta-messenger.service is
  62. {{ mautrix_meta_messenger_service.status.ActiveState | default('unknown') }}
  63. after {{ mautrix_meta_messenger_service.status.NRestarts | default('?') }}
  64. automatic restart(s)
  65. success_msg: "matrix-mautrix-meta-messenger.service is active and has not restarted"
  66. # The appservice port is the bridge's own listener, the one a homeserver
  67. # would push transactions to. It opening at all means the bridge got through
  68. # reading its configuration, through its /whoami check against the
  69. # homeserver, and through setting itself up.
  70. - name: Wait for the bridge to open its appservice port
  71. ansible.builtin.command:
  72. argv:
  73. - docker
  74. - run
  75. - --rm
  76. - --network={{ matrix_bridge_mautrix_meta_messenger_container_network }}
  77. - "{{ molecule_shared_image_curl }}"
  78. - --silent
  79. - --output
  80. - /dev/null
  81. - --write-out
  82. - "HTTP_STATUS=%{http_code}"
  83. - "http://matrix-mautrix-meta-messenger:29319/_matrix/mau/live"
  84. register: mautrix_meta_messenger_live
  85. changed_when: false
  86. until: "'HTTP_STATUS=000' not in mautrix_meta_messenger_live.stdout"
  87. retries: 24
  88. delay: 5
  89. failed_when: false
  90. - name: Assert the bridge answers on its appservice port
  91. ansible.builtin.assert:
  92. that:
  93. - "'HTTP_STATUS=000' not in mautrix_meta_messenger_live.stdout"
  94. fail_msg: >-
  95. The bridge did not answer on its appservice port
  96. ({{ mautrix_meta_messenger_live.stdout | default('no output') }})
  97. success_msg: "The bridge answers on its appservice port"
  98. - name: Read the configuration the role rendered
  99. ansible.builtin.slurp:
  100. src: "{{ matrix_bridge_mautrix_meta_messenger_config_path }}/config.yaml"
  101. register: mautrix_meta_messenger_config_file
  102. # Each of these differs from what the bridge would use on its own, so their
  103. # presence means the role's configuration is what the bridge is running on
  104. # rather than something that happened to agree with it.
  105. - name: Assert the rendered configuration carries this scenario's values
  106. ansible.builtin.assert:
  107. that:
  108. - mautrix_meta_messenger_config.homeserver.address == matrix_bridge_mautrix_meta_messenger_homeserver_address
  109. - mautrix_meta_messenger_config.homeserver.domain == matrix_bridge_mautrix_meta_messenger_homeserver_domain
  110. - mautrix_meta_messenger_config.appservice.bot.username == matrix_bridge_mautrix_meta_messenger_appservice_username
  111. - mautrix_meta_messenger_config.appservice.as_token == matrix_bridge_mautrix_meta_messenger_appservice_token
  112. - mautrix_meta_messenger_config.appservice.hs_token == matrix_bridge_mautrix_meta_messenger_homeserver_token
  113. - mautrix_meta_messenger_config.bridge.command_prefix == matrix_bridge_mautrix_meta_messenger_bridge_command_prefix
  114. - mautrix_meta_messenger_config.logging.min_level == matrix_bridge_mautrix_meta_messenger_logging_min_level
  115. # matrix_admin is empty in the shared context, so the only per-domain
  116. # permission left is the one the role derives from the homeserver domain.
  117. - mautrix_meta_messenger_config.bridge.permissions[matrix_bridge_mautrix_meta_messenger_homeserver_domain] == 'user'
  118. # From the shared context rather than from this scenario, but the role
  119. # is what has to carry it into the configuration.
  120. - mautrix_meta_messenger_config.encryption.allow == matrix_bridges_encryption_enabled
  121. fail_msg: "The rendered configuration does not carry the scenario's values"
  122. success_msg: "The rendered configuration carries the scenario's values"
  123. # The mode is the variable that makes this role family unusual, and it is
  124. # not written into the configuration as-is: the role expands it into an
  125. # appservice id, a ghost username prefix, a bot displayname and the bridge's
  126. # `tor` switch. Each of these holds a different value under the role's
  127. # default `messenger` mode, so together they are what proves the choice
  128. # propagated rather than being ignored.
  129. - name: Assert the configuration reflects the Meta mode the scenario selected
  130. ansible.builtin.assert:
  131. that:
  132. - mautrix_meta_messenger_config.appservice.id == 'facebook-tor'
  133. - mautrix_meta_messenger_config.appservice.username_template.startswith('facebook_')
  134. - not mautrix_meta_messenger_config.appservice.username_template.startswith('messenger_')
  135. - mautrix_meta_messenger_config.network.tor
  136. - mautrix_meta_messenger_config.appservice.bot.displayname == 'Facebook-tor bridge bot'
  137. - matrix_bridge_mautrix_meta_messenger_bridge_displayname_suffix in mautrix_meta_messenger_config.network.displayname_template
  138. fail_msg: >-
  139. The rendered configuration does not reflect
  140. matrix_bridge_mautrix_meta_messenger_meta_mode=facebook-tor
  141. success_msg: "The rendered configuration reflects the Meta mode the scenario selected"
  142. # The URI must stay a bare path. go-sqlite3 takes it as a filename, so a
  143. # `sqlite:///` prefix makes the bridge die at startup - which is what this
  144. # role's default used to do.
  145. - name: Assert the configuration points the bridge at the sqlite database the role derived
  146. ansible.builtin.assert:
  147. that:
  148. - mautrix_meta_messenger_config.database.type == matrix_bridge_mautrix_meta_messenger_database_engine
  149. - mautrix_meta_messenger_config.database.uri == mautrix_meta_messenger_role_defaults.matrix_bridge_mautrix_meta_messenger_sqlite_database_path_in_container
  150. fail_msg: >-
  151. database.uri is {{ mautrix_meta_messenger_config.database.uri | default('unset') }},
  152. which is not the bare in-container path the role defines
  153. success_msg: "The rendered configuration points the bridge at the sqlite database the role derived"
  154. # The public address is what the role builds out of the three exposure
  155. # variables; it is the same endpoint the Traefik labels below route to.
  156. - name: Assert the configuration carries the public address the exposure settings imply
  157. ansible.builtin.assert:
  158. that:
  159. - mautrix_meta_messenger_config.appservice.public_address == 'https://bridges.molecule.local/bridges/meta-messenger'
  160. fail_msg: >-
  161. appservice.public_address is
  162. {{ mautrix_meta_messenger_config.appservice.public_address | default('unset') }}
  163. success_msg: "The rendered configuration carries the public address the exposure settings imply"
  164. # The registration file is the half of the appservice handshake the
  165. # homeserver reads, and it is generated by the role rather than by the
  166. # bridge, so it is worth checking on its own.
  167. - name: Read the appservice registration the role rendered
  168. ansible.builtin.slurp:
  169. src: "{{ matrix_bridge_mautrix_meta_messenger_config_path }}/registration.yaml"
  170. register: mautrix_meta_messenger_registration_file
  171. - name: Assert the registration carries the scenario's tokens, id and bot user
  172. ansible.builtin.assert:
  173. that:
  174. - mautrix_meta_messenger_registration.as_token == matrix_bridge_mautrix_meta_messenger_appservice_token
  175. - mautrix_meta_messenger_registration.hs_token == matrix_bridge_mautrix_meta_messenger_homeserver_token
  176. - mautrix_meta_messenger_registration.id == 'facebook-tor'
  177. - mautrix_meta_messenger_registration.sender_localpart == '_bot_' + matrix_bridge_mautrix_meta_messenger_appservice_username
  178. - mautrix_meta_messenger_registration.url == 'http://matrix-mautrix-meta-messenger:29319'
  179. fail_msg: "The appservice registration does not carry the scenario's tokens, id and bot user"
  180. success_msg: "The appservice registration carries the scenario's tokens, id and bot user"
  181. # The namespaces are regexes the role assembles, so rather than comparing
  182. # them as strings - which would only re-derive the role's own escaping -
  183. # they are checked by what they do: cover the bot, cover this mode's ghosts,
  184. # and not cover the ghosts of the mode the role would have defaulted to.
  185. - name: Assert the registration namespaces cover the bot and this mode's ghost users
  186. ansible.builtin.assert:
  187. that:
  188. - mautrix_meta_messenger_ghost_regex | length > 0
  189. - mautrix_meta_messenger_bot_regex | length > 0
  190. - mautrix_meta_messenger_facebook_ghost_mxid is match(mautrix_meta_messenger_ghost_regex)
  191. - mautrix_meta_messenger_messenger_ghost_mxid is not match(mautrix_meta_messenger_ghost_regex)
  192. - mautrix_meta_messenger_bot_mxid is match(mautrix_meta_messenger_bot_regex)
  193. fail_msg: "The appservice registration namespaces do not cover the bot and this mode's ghost users"
  194. success_msg: "The appservice registration namespaces cover the bot and this mode's ghost users"
  195. vars:
  196. mautrix_meta_messenger_user_regexes: "{{ mautrix_meta_messenger_registration.namespaces.users | map(attribute='regex') | list }}"
  197. mautrix_meta_messenger_ghost_regex: "{{ mautrix_meta_messenger_user_regexes | select('search', 'facebook_') | first | default('') }}"
  198. mautrix_meta_messenger_bot_regex: "{{ mautrix_meta_messenger_user_regexes | reject('search', 'facebook_') | first | default('') }}"
  199. mautrix_meta_messenger_facebook_ghost_mxid: "@facebook_1234567890:{{ matrix_bridge_mautrix_meta_messenger_homeserver_domain }}"
  200. mautrix_meta_messenger_messenger_ghost_mxid: "@messenger_1234567890:{{ matrix_bridge_mautrix_meta_messenger_homeserver_domain }}"
  201. mautrix_meta_messenger_bot_mxid: "@{{ matrix_bridge_mautrix_meta_messenger_appservice_username }}:{{ matrix_bridge_mautrix_meta_messenger_homeserver_domain }}"
  202. # The cheap proof that the data path reached the process and is writable by
  203. # the uid the role runs the container as.
  204. - name: Look for the bridge's sqlite database under the role's data path
  205. ansible.builtin.stat:
  206. path: "{{ matrix_bridge_mautrix_meta_messenger_data_path }}/mautrix-meta.db"
  207. register: mautrix_meta_messenger_database
  208. - name: Assert the bridge created its database where the role put its data path
  209. ansible.builtin.assert:
  210. that:
  211. - mautrix_meta_messenger_database.stat.exists
  212. - mautrix_meta_messenger_database.stat.uid | int == matrix_user_uid | int
  213. fail_msg: >-
  214. The bridge did not create its database under
  215. {{ matrix_bridge_mautrix_meta_messenger_data_path }}, or it is not owned by
  216. uid {{ matrix_user_uid }}
  217. success_msg: "The bridge created its database under the role's data path, as the role's uid"
  218. - name: Read the image of the running container
  219. ansible.builtin.command:
  220. argv:
  221. - docker
  222. - container
  223. - inspect
  224. - matrix-mautrix-meta-messenger
  225. - --format
  226. - "{{ '{{' }} .Config.Image {{ '}}' }}"
  227. register: mautrix_meta_messenger_image
  228. changed_when: false
  229. # Both Meta bridges are published to the same image repository, with
  230. # Instagram's tags carrying an `ig-` prefix, so the tag is compared whole
  231. # rather than by substring: an `ig-` prefix would mean this role pulled the
  232. # other bridge's image.
  233. - name: Assert the running container is the Messenger image at the version defaults/main.yml pins
  234. ansible.builtin.assert:
  235. that:
  236. - mautrix_meta_messenger_image.stdout.endswith(':' + mautrix_meta_messenger_role_defaults.matrix_bridge_mautrix_meta_messenger_version)
  237. fail_msg: >-
  238. The running container is {{ mautrix_meta_messenger_image.stdout }}, which is not
  239. the Messenger image at the pinned version
  240. {{ mautrix_meta_messenger_role_defaults.matrix_bridge_mautrix_meta_messenger_version }}
  241. success_msg: "The running container is the Messenger image at the version defaults/main.yml pins"
  242. - name: Read the labels the role rendered
  243. ansible.builtin.slurp:
  244. src: "{{ matrix_bridge_mautrix_meta_messenger_base_path }}/labels"
  245. register: mautrix_meta_messenger_labels
  246. # Traefik is not running here, so what is checked is what the role wrote,
  247. # not what a reverse-proxy would do with it. The port matters: it is
  248. # hardcoded in the role's templates rather than derived from a variable, so
  249. # nothing else in this scenario would catch it drifting from the port the
  250. # bridge actually listens on.
  251. - name: Assert the labels route the exposure hostname and path prefix to the appservice port
  252. ansible.builtin.assert:
  253. that:
  254. - "'traefik.enable=true' in mautrix_meta_messenger_labels_rendered"
  255. - "'traefik.http.services.matrix-mautrix-meta-messenger-appservice.loadbalancer.server.port=29319' in mautrix_meta_messenger_labels_rendered"
  256. - "'traefik.http.routers.matrix-mautrix-meta-messenger-exposure.rule=Host(`bridges.molecule.local`) && PathPrefix(`/bridges/meta-messenger`)' in mautrix_meta_messenger_labels_rendered"
  257. - "'traefik.http.middlewares.matrix-mautrix-meta-messenger-exposure-strip-prefix.stripprefix.prefixes=/bridges/meta-messenger' in mautrix_meta_messenger_labels_rendered"
  258. - "'traefik.docker.network=' + matrix_bridge_mautrix_meta_messenger_container_network in mautrix_meta_messenger_labels_rendered"
  259. fail_msg: "The rendered labels do not route the exposure hostname and path prefix to the appservice port"
  260. success_msg: "The rendered labels route the exposure hostname and path prefix to the appservice port"