Jason LaGuidice 1 week ago
committed by GitHub
parent
commit
e62d26f96a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
19 changed files with 1087 additions and 0 deletions
  1. +1
    -0
      README.md
  2. +105
    -0
      docs/configuring-playbook-bridge-rustpush.md
  3. +2
    -0
      docs/configuring-playbook.md
  4. +1
    -0
      docs/container-images.md
  5. +93
    -0
      group_vars/matrix_servers
  6. +247
    -0
      roles/custom/matrix-bridge-rustpush/defaults/main.yml
  7. +25
    -0
      roles/custom/matrix-bridge-rustpush/tasks/main.yml
  8. +130
    -0
      roles/custom/matrix-bridge-rustpush/tasks/setup_install.yml
  9. +24
    -0
      roles/custom/matrix-bridge-rustpush/tasks/setup_uninstall.yml
  10. +20
    -0
      roles/custom/matrix-bridge-rustpush/tasks/validate_config.yml
  11. +110
    -0
      roles/custom/matrix-bridge-rustpush/templates/Dockerfile.j2
  12. +4
    -0
      roles/custom/matrix-bridge-rustpush/templates/Dockerfile.j2.license
  13. +208
    -0
      roles/custom/matrix-bridge-rustpush/templates/config.yaml.j2
  14. +4
    -0
      roles/custom/matrix-bridge-rustpush/templates/config.yaml.j2.license
  15. +53
    -0
      roles/custom/matrix-bridge-rustpush/templates/labels.j2
  16. +4
    -0
      roles/custom/matrix-bridge-rustpush/templates/labels.j2.license
  17. +51
    -0
      roles/custom/matrix-bridge-rustpush/templates/systemd/matrix-rustpush-bridge.service.j2
  18. +4
    -0
      roles/custom/matrix-bridge-rustpush/templates/systemd/matrix-rustpush-bridge.service.j2.license
  19. +1
    -0
      setup.yml

+ 1
- 0
README.md View File

@@ -117,6 +117,7 @@ Bridges can be used to connect your Matrix installation with third-party communi
| [mautrix-gmessages](https://github.com/mautrix/gmessages) | ❌ | Bridge to [Google Messages](https://messages.google.com/) | [Link](docs/configuring-playbook-bridge-mautrix-gmessages.md) |
| [mautrix-whatsapp](https://github.com/mautrix/whatsapp) | ❌ | Bridge to [WhatsApp](https://www.whatsapp.com/) | [Link](docs/configuring-playbook-bridge-mautrix-whatsapp.md) |
| [mautrix-wsproxy](https://github.com/mautrix/wsproxy) | ❌ | Bridge to Android SMS or Apple iMessage | [Link](docs/configuring-playbook-bridge-mautrix-wsproxy.md) |
| [matrix-rustpush-bridge](https://github.com/jasonlaguidice/imessage) | ❌ | Bridge to [iMessage](https://support.apple.com/messages) via Apple Push Notification service | [Link](docs/configuring-playbook-bridge-rustpush.md) |
| [mautrix-bluesky](https://github.com/mautrix/bluesky) | ❌ | Bridge to [Bluesky](https://bsky.social/) | [Link](docs/configuring-playbook-bridge-mautrix-bluesky.md) |
| [mautrix-twitter](https://github.com/mautrix/twitter) | ❌ | Bridge to [Twitter](https://twitter.com/) | [Link](docs/configuring-playbook-bridge-mautrix-twitter.md) |
| [mautrix-googlechat](https://github.com/mautrix/googlechat) | ❌ | Bridge to [Google Chat](https://en.wikipedia.org/wiki/Google_Chat) | [Link](docs/configuring-playbook-bridge-mautrix-googlechat.md) |


+ 105
- 0
docs/configuring-playbook-bridge-rustpush.md View File

@@ -0,0 +1,105 @@
<!--
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Jason LaGuidice

SPDX-License-Identifier: AGPL-3.0-or-later
-->

# Setting up RustPush (iMessage) bridging (optional)
## \*\*\* THIS BRIDGE IS IN EARLY DEVELOPMENT \*\*\*
Be warned, here be ~~dragons~~ bugs. Your use, testing, and feedback of this bridge is appreciated. It may not be desirable to deploy this to a large number of users due to possible stability issues
<hr>
<sup>Refer the common guide for configuring mautrix bridges: [Setting up a Generic Mautrix Bridge](configuring-playbook-bridge-mautrix-bridges.md)</sup>

The playbook can install and configure [rustpush bridge to iMessage](https://github.com/jasonlaguidice/imessage) for you using Apple's push notification service.

See the project's [documentation](https://github.com/jasonlaguidice/imessage/blob/main/README.md) to learn what it does and why it might be useful to you.

## Prerequisites

### Hardware Key Extraction

To use this bridge on Linux (Docker), each user needs a **hardware key** extracted from a real Mac. This key contains hardware identifiers needed for iMessage registration. Hardware keys can be shared by a number of users (approximately 20) before causing issues with Apple.

The key is entered interactively through the bridge bot's login flow (not configured via Ansible variables). See the upstream [README](https://github.com/jasonlaguidice/imessage/blob/main/README.md) for instructions on extracting the key.

If extracted from an Intel Mac, the Mac does not need to remain running after the key is extracted for this bridge to work. Apple Silicon Macs must run a NAC relay and thus must remain running.

### Phone Number Registration (optional)

This bridge can **not** do phone number registration (PNR). The only way to have your phone number registered and used (instead of an Apple ID e-mail address) is to have an iPhone connected to your Apple account. Reference the [BlueBubbles Phone Number Registration Guide](https://docs.bluebubbles.app/server/advanced/registering-a-phone-number-with-your-imessage-account) for information on how to set this up.

### Enable Appservice Double Puppet (optional)

If you want to set up [Double Puppeting](https://docs.mau.fi/bridges/general/double-puppeting.html) (hint: you most likely do) for this bridge automatically, you need to have enabled [Appservice Double Puppet](configuring-playbook-appservice-double-puppet.md) service for this playbook.

See [this section](configuring-playbook-bridge-mautrix-bridges.md#set-up-double-puppeting-optional) on the [common guide for configuring mautrix bridges](configuring-playbook-bridge-mautrix-bridges.md) for details about setting up Double Puppeting.

## Adjusting the playbook configuration

To enable the bridge, add the following configuration to your `inventory/host_vars/matrix.example.com/vars.yml` file:

```yaml
matrix_rustpush_bridge_enabled: true
```

### Disable Backfill (optional)

Backfill can be disabled globally if desired via config. By default, the bridge will backfill from iCloud (CloudKit) and APNS if available. Backfill from `chat.db` is only possible when the bridge is running on MacOS.

```yaml
matrix_rustpush_bridge_backfill_enabled: false
```

### Extending the Configuration

There are some additional things you may wish to configure about the bridge.

See [this section](configuring-playbook-bridge-mautrix-bridges.md#extending-the-configuration) on the [common guide for configuring mautrix bridges](configuring-playbook-bridge-mautrix-bridges.md) for details about variables that you can customize and the bridge's default configuration, including [bridge permissions](configuring-playbook-bridge-mautrix-bridges.md#configure-bridge-permissions-optional), [encryption support](configuring-playbook-bridge-mautrix-bridges.md#enable-encryption-optional), [bot's username](configuring-playbook-bridge-mautrix-bridges.md#set-the-bots-username-optional), etc.

## Installing

After configuring the playbook, run it with [playbook tags](playbook-tags.md) as below:

<!-- NOTE: let this conservative command run (instead of install-all) to make it clear that failure of the command means something is clearly broken. -->
```sh
ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,start
```

**Notes**:

- The shortcut commands with the [`just` program](just.md) are also available: `just install-all` or `just setup-all`

`just install-all` is useful for maintaining your setup quickly ([2x-5x faster](../CHANGELOG.md#2x-5x-performance-improvements-in-playbook-runtime) than `just setup-all`) when its components remain unchanged. If you adjust your `vars.yml` to remove other components, you'd need to run `just setup-all`, or these components will still remain installed.

## Usage

To use the bridge, you need to start a chat with `@imessagebot:example.com` (where `example.com` is your base domain, not the `matrix.` domain).

After logging in, the bridge will start receiving iMessages and creating portal rooms.

## Interference With Mautrix-iMessage & WSproxy

By default, this bridge uses the same bot user name (`@imessagebot:example.com`) and same localpart for puppet users (`imessage_{{.}}`) as the mautrix-imessage bridge. If you use this bridge on your homeserver have a few options:

1. Change the bot and appservice name templates:

Set `matrix_rustpush_bridge_appservice_bot_username` and to a non-default value `matrix_rustpush_bridge_appservice_username_template`

2. After deactivating mautrix-imessage, delete the bot user and all its portal rooms from the database, then re-start the server (more difficult)

## Troubleshooting

As with all other services, you can find the logs in [systemd-journald](https://www.freedesktop.org/software/systemd/man/systemd-journald.service.html) by logging in to the server with SSH and running `journalctl -fu matrix-rustpush-bridge`.

### Increase logging verbosity

The default logging level for this component is `warn`. If you want to increase the verbosity, add the following configuration to your `vars.yml` file and re-run the playbook:

```yaml
# Valid values: fatal, error, warn, info, debug, trace
matrix_rustpush_bridge_logging_level: 'debug'

# Enable debug logging for RustPush
matrix_rustpush_bridge_rust_log: "warn,rustpushgo=info,openabsinthe=debug"
```

+ 2
- 0
docs/configuring-playbook.md View File

@@ -158,6 +158,8 @@ Bridges can be used to connect your Matrix installation with third-party communi

- [Setting up Mautrix wsproxy for bridging Android SMS or Apple iMessage](configuring-playbook-bridge-mautrix-wsproxy.md)

- [Setting up RustPush (iMessage) bridging](configuring-playbook-bridge-rustpush.md)

- [Setting up Appservice IRC bridging](configuring-playbook-bridge-appservice-irc.md)

- [Setting up Appservice Discord bridging](configuring-playbook-bridge-appservice-discord.md)


+ 1
- 0
docs/container-images.md View File

@@ -107,6 +107,7 @@ Bridges can be used to connect your Matrix installation with third-party communi
| [Heisenbridge](configuring-playbook-bridge-heisenbridge.md) | [hif1/heisenbridge](https://hub.docker.com/r/hif1/heisenbridge) | ❌ | Bouncer-style bridge to [IRC](https://wikipedia.org/wiki/Internet_Relay_Chat) |
| [mx-puppet-groupme](configuring-playbook-bridge-mx-puppet-groupme.md) | [xangelix/mx-puppet-groupme](https://hub.docker.com/r/xangelix/mx-puppet-groupme) | ❌ | Bridge to [GroupMe](https://groupme.com/) |
| [matrix-steam-bridge](configuring-playbook-bridge-steam.md) | [jasonlaguidice/matrix-steam-bridge](https://github.com/jasonlaguidice/matrix-steam-bridge/pkgs/container/matrix-steam-bridge) | ❌ | Bridge to [Steam](https://steampowered.com/) |
| [matrix-rustpush-bridge](configuring-playbook-bridge-rustpush.md) | [jasonlaguidice/imessage](https://github.com/jasonlaguidice/imessage/pkgs/container/imessage) | ❌ | Bridge to [iMessage](https://support.apple.com/messages) via Apple Push Notification service |
| [mx-puppet-steam](configuring-playbook-bridge-mx-puppet-steam.md) | [icewind1991/mx-puppet-steam](https://hub.docker.com/r/icewind1991/mx-puppet-steam) | ❌ | Bridge to [Steam](https://steamapp.com/) |
| [Postmoogle](configuring-playbook-bridge-postmoogle.md) | [etke.cc/postmoogle](https://github.com/etkecc/postmoogle/container_registry) | ❌ | Email to Matrix bridge |



+ 93
- 0
group_vars/matrix_servers View File

@@ -114,6 +114,8 @@ matrix_homeserver_container_extra_arguments_auto: |
+
(['--mount type=bind,src=' + matrix_mautrix_bluesky_config_path + '/registration.yaml,dst=/matrix-mautrix-bluesky-registration.yaml,ro'] if matrix_mautrix_bluesky_enabled else [])
+
(['--mount type=bind,src=' + matrix_rustpush_bridge_config_path + '/registration.yaml,dst=/matrix-rustpush-bridge-registration.yaml,ro'] if matrix_rustpush_bridge_enabled else [])
+
(['--mount type=bind,src=' + matrix_mautrix_discord_config_path + '/registration.yaml,dst=/matrix-mautrix-discord-registration.yaml,ro'] if matrix_mautrix_discord_enabled else [])
+
(['--mount type=bind,src=' + matrix_mautrix_slack_config_path + '/registration.yaml,dst=/matrix-mautrix-slack-registration.yaml,ro'] if matrix_mautrix_slack_enabled else [])
@@ -171,6 +173,8 @@ matrix_homeserver_app_service_config_files_auto: |
+
(['/matrix-mautrix-bluesky-registration.yaml'] if matrix_mautrix_bluesky_enabled else [])
+
(['/matrix-rustpush-bridge-registration.yaml'] if matrix_rustpush_bridge_enabled else [])
+
(['/matrix-mautrix-discord-registration.yaml'] if matrix_mautrix_discord_enabled else [])
+
(['/matrix-mautrix-slack-registration.yaml'] if matrix_mautrix_slack_enabled else [])
@@ -436,6 +440,13 @@ devture_systemd_service_manager_services_list_auto: |
'groups': ['matrix', 'bridges', 'mautrix-bluesky'],
}] if matrix_mautrix_bluesky_enabled else [])
+
([{
'name': 'matrix-rustpush-bridge.service',
'priority': 2000,
'restart_necessary': (matrix_rustpush_bridge_restart_necessary | bool),
'groups': ['matrix', 'bridges', 'matrix-rustpush-bridge'],
}] if matrix_rustpush_bridge_enabled else [])
+
([{
'name': 'matrix-mautrix-discord.service',
'priority': 2000,
@@ -1469,6 +1480,77 @@ matrix_mautrix_bluesky_database_password: "{{ (matrix_homeserver_generic_secret_
#
######################################################################


######################################################################
#
# matrix-bridge-rustpush
#
######################################################################

# We don't enable bridges by default.
matrix_rustpush_bridge_enabled: false

matrix_rustpush_bridge_systemd_required_services_list_auto: |
{{
matrix_addons_homeserver_systemd_services_list
+
([postgres_identifier ~ '.service'] if (postgres_enabled and matrix_rustpush_bridge_database_hostname == postgres_connection_hostname) else [])
}}

matrix_rustpush_bridge_container_network: "{{ matrix_addons_container_network }}"

matrix_rustpush_bridge_container_additional_networks_auto: |-
{{
(
([] if matrix_addons_homeserver_container_network == '' else [matrix_addons_homeserver_container_network])
+
([postgres_container_network] if (postgres_enabled and matrix_rustpush_bridge_database_hostname == postgres_connection_hostname and matrix_rustpush_bridge_container_network != postgres_container_network) else [])
+
([matrix_playbook_reverse_proxyable_services_additional_network] if matrix_playbook_reverse_proxyable_services_additional_network and matrix_rustpush_bridge_container_labels_traefik_enabled else [])
) | unique
}}

matrix_rustpush_bridge_container_labels_traefik_enabled: "{{ matrix_playbook_reverse_proxy_type in ['playbook-managed-traefik', 'other-traefik-container'] }}"
matrix_rustpush_bridge_container_labels_traefik_docker_network: "{{ matrix_playbook_reverse_proxyable_services_additional_network }}"
matrix_rustpush_bridge_container_labels_traefik_entrypoints: "{{ traefik_entrypoint_primary }}"
matrix_rustpush_bridge_container_labels_traefik_tls_certResolver: "{{ traefik_certResolver_primary }}"

matrix_rustpush_bridge_container_labels_metrics_middleware_basic_auth_enabled: "{{ matrix_metrics_exposure_http_basic_auth_enabled }}"
matrix_rustpush_bridge_container_labels_metrics_middleware_basic_auth_users: "{{ matrix_metrics_exposure_http_basic_auth_users }}"

matrix_rustpush_bridge_appservice_token: "{{ (matrix_homeserver_generic_secret_key + ':imsg.as.token') | hash('sha512') | to_uuid }}"

matrix_rustpush_bridge_homeserver_address: "{{ matrix_addons_homeserver_client_api_url }}"
matrix_rustpush_bridge_homeserver_token: "{{ (matrix_homeserver_generic_secret_key + ':imsg.hs.token') | hash('sha512') | to_uuid }}"

matrix_rustpush_bridge_homeserver_async_media: "{{ matrix_homeserver_implementation in ['synapse'] }}"

matrix_rustpush_bridge_provisioning_shared_secret: "{{ (matrix_homeserver_generic_secret_key + ':mau.imsg.prov') | hash('sha512') | to_uuid }}"

matrix_rustpush_bridge_double_puppet_secrets_auto: |-
{{
({
matrix_rustpush_bridge_homeserver_domain: ("as_token:" + matrix_appservice_double_puppet_registration_as_token)
})
if matrix_appservice_double_puppet_enabled
else {}
}}

matrix_rustpush_bridge_metrics_enabled: "{{ prometheus_enabled or matrix_metrics_exposure_enabled }}"

matrix_rustpush_bridge_metrics_proxying_enabled: "{{ matrix_rustpush_bridge_metrics_enabled and matrix_metrics_exposure_enabled }}"
matrix_rustpush_bridge_metrics_proxying_hostname: "{{ matrix_metrics_exposure_hostname }}"
matrix_rustpush_bridge_metrics_proxying_path_prefix: "{{ matrix_metrics_exposure_path_prefix }}/rustpush-bridge"

matrix_rustpush_bridge_database_hostname: "{{ postgres_connection_hostname if postgres_enabled else '' }}"
matrix_rustpush_bridge_database_password: "{{ (matrix_homeserver_generic_secret_key + ':mau.imsg.db') | hash('sha512') | to_uuid if postgres_enabled else '' }}"

######################################################################
#
# /matrix-bridge-rustpush
#
######################################################################

######################################################################
#
# matrix-bridge-mautrix-discord
@@ -4052,6 +4134,12 @@ postgres_managed_databases_auto: |
'password': matrix_mautrix_bluesky_database_password,
}] if (matrix_mautrix_bluesky_enabled and matrix_mautrix_bluesky_database_engine == 'postgres' and matrix_mautrix_bluesky_database_hostname == postgres_connection_hostname) else [])
+
([{
'name': matrix_rustpush_bridge_database_name,
'username': matrix_rustpush_bridge_database_username,
'password': matrix_rustpush_bridge_database_password,
}] if (matrix_rustpush_bridge_enabled and matrix_rustpush_bridge_database_engine == 'postgres' and matrix_rustpush_bridge_database_hostname == postgres_connection_hostname) else [])
+
([{
'name': matrix_mautrix_googlechat_database_name,
'username': matrix_mautrix_googlechat_database_username,
@@ -4992,6 +5080,11 @@ matrix_ketesa_config_asManagedUsers_auto: |
'^@bluesky_[a-zA-Z0-9]+:'+(matrix_domain | regex_escape)+'$',
] if matrix_mautrix_bluesky_enabled else [])
+
([
'^@'+(matrix_rustpush_bridge_appservice_bot_username | default('') | regex_escape)+':'+(matrix_domain | regex_escape)+'$',
'^@imessage_[a-zA-Z0-9_.+-]+:'+(matrix_domain | regex_escape)+'$',
] if matrix_rustpush_bridge_enabled else [])
+
([
'^@'+(matrix_mautrix_discord_appservice_bot_username | default('') | regex_escape)+':'+(matrix_domain | regex_escape)+'$',
'^@discord_[0-9]+:'+(matrix_domain | regex_escape)+'$',


+ 247
- 0
roles/custom/matrix-bridge-rustpush/defaults/main.yml View File

@@ -0,0 +1,247 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Jason LaGuidice
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---
# matrix-bridge-rustpush is a Matrix <-> iMessage bridge using RustPush
# Project source code URL: https://github.com/jasonlaguidice/imessage

matrix_rustpush_bridge_enabled: false

matrix_rustpush_bridge_container_image_self_build: false
matrix_rustpush_bridge_container_image_self_build_repo: "https://github.com/jasonlaguidice/imessage.git"
matrix_rustpush_bridge_container_image_self_build_repo_version: "{{ 'master' if matrix_rustpush_bridge_version == 'latest' else matrix_rustpush_bridge_version }}"

# Adjust to pin to releases
matrix_rustpush_bridge_version: v0.0.1
matrix_rustpush_bridge_docker_image: "{{ matrix_rustpush_bridge_docker_image_registry_prefix }}jasonlaguidice/imessage:{{ matrix_rustpush_bridge_version }}"
matrix_rustpush_bridge_docker_image_registry_prefix: "ghcr.io/"
matrix_rustpush_bridge_docker_image_force_pull: "{{ matrix_rustpush_bridge_docker_image.endswith(':latest') }}"

matrix_rustpush_bridge_base_path: "{{ matrix_base_data_path }}/matrix-rustpush-bridge"
matrix_rustpush_bridge_config_path: "{{ matrix_rustpush_bridge_base_path }}/config"
matrix_rustpush_bridge_data_path: "{{ matrix_rustpush_bridge_base_path }}/data"
matrix_rustpush_bridge_docker_src_files_path: "{{ matrix_rustpush_bridge_base_path }}/docker-src"

matrix_rustpush_bridge_homeserver_address: ""
# Whether asynchronous uploads via MSC2246 should be enabled for media.
matrix_rustpush_bridge_homeserver_async_media: false
matrix_rustpush_bridge_homeserver_domain: '{{ matrix_domain }}'
matrix_rustpush_bridge_appservice_address: 'http://matrix-rustpush-bridge:8081'

matrix_rustpush_bridge_msc4190_enabled: "{{ matrix_bridges_msc4190_enabled }}"
matrix_rustpush_bridge_self_sign_enabled: "{{ matrix_bridges_self_sign_enabled }}"

# A public address that external services can use to reach this appservice.
matrix_rustpush_bridge_appservice_public_address: ''

# Displayname template for iMessage contacts.
# Available variables: {{.FirstName}}, {{.LastName}}, {{.Nickname}},
# {{.Phone}}, {{.Email}}, {{.ID}}
matrix_rustpush_bridge_network_displayname_template: "{% raw %}{{if .FirstName}}{{.FirstName}}{{if .LastName}} {{.LastName}}{{end}}{{else if .Nickname}}{{.Nickname}}{{else if .Phone}}{{.Phone}}{{else if .Email}}{{.Email}}{{else}}{{.ID}}{{end}} (iMessage){% endraw %}"

matrix_rustpush_bridge_cloudkit_backfill: true
matrix_rustpush_bridge_video_transcoding: true
matrix_rustpush_bridge_heic_conversion: true
matrix_rustpush_bridge_disable_facetime: false
matrix_rustpush_bridge_statuskit_notifications: true
matrix_rustpush_bridge_statuskit_share_on_startup: true

matrix_rustpush_bridge_bridge_command_prefix: "!im"

matrix_rustpush_bridge_bridge_permissions: |
{{
{matrix_rustpush_bridge_homeserver_domain: 'user'}
| combine({matrix_admin: 'admin'} if matrix_admin else {})
}}

matrix_rustpush_bridge_container_network: ""

matrix_rustpush_bridge_container_additional_networks: "{{ matrix_rustpush_bridge_container_additional_networks_auto + matrix_rustpush_bridge_container_additional_networks_custom }}"
matrix_rustpush_bridge_container_additional_networks_auto: []
matrix_rustpush_bridge_container_additional_networks_custom: []

# matrix_rustpush_bridge_container_labels_traefik_enabled controls whether labels to assist a Traefik reverse-proxy will be attached to the container.
# See `../templates/labels.j2` for details.
#
# To inject your own other container labels, see `matrix_rustpush_bridge_container_labels_additional_labels`.
matrix_rustpush_bridge_container_labels_traefik_enabled: true
matrix_rustpush_bridge_container_labels_traefik_docker_network: "{{ matrix_rustpush_bridge_container_network }}"
matrix_rustpush_bridge_container_labels_traefik_entrypoints: web-secure
matrix_rustpush_bridge_container_labels_traefik_tls_certResolver: default # noqa var-naming

# Controls whether labels will be added that expose metrics
matrix_rustpush_bridge_container_labels_metrics_enabled: "{{ matrix_rustpush_bridge_metrics_enabled and matrix_rustpush_bridge_metrics_proxying_enabled }}"
matrix_rustpush_bridge_container_labels_metrics_traefik_rule: "Host(`{{ matrix_rustpush_bridge_metrics_proxying_hostname }}`) && PathPrefix(`{{ matrix_rustpush_bridge_metrics_proxying_path_prefix }}`)"
matrix_rustpush_bridge_container_labels_metrics_traefik_priority: 0
matrix_rustpush_bridge_container_labels_metrics_traefik_entrypoints: "{{ matrix_rustpush_bridge_container_labels_traefik_entrypoints }}"
matrix_rustpush_bridge_container_labels_metrics_traefik_tls: "{{ matrix_rustpush_bridge_container_labels_metrics_traefik_entrypoints != 'web' }}"
matrix_rustpush_bridge_container_labels_metrics_traefik_tls_certResolver: "{{ matrix_rustpush_bridge_container_labels_traefik_tls_certResolver }}" # noqa var-naming
matrix_rustpush_bridge_container_labels_metrics_middleware_basic_auth_enabled: false
# See: https://doc.traefik.io/traefik/middlewares/http/basicauth/#users
matrix_rustpush_bridge_container_labels_metrics_middleware_basic_auth_users: ''

# matrix_rustpush_bridge_container_labels_additional_labels contains a multiline string with additional labels to add to the container label file.
# See `../templates/labels.j2` for details.
#
# Example:
# matrix_rustpush_bridge_container_labels_additional_labels: |
# my.label=1
# another.label="here"
matrix_rustpush_bridge_container_labels_additional_labels: ''

# A list of extra arguments to pass to the container
matrix_rustpush_bridge_container_extra_arguments: []

# Override the Rust log filter passed to the bridge container via RUST_LOG.
# Leave empty to use the bridge's built-in default
# ("warn,rustpush=warn,rustpushgo=info,open_absinthe=info").
#
# Useful values:
# "warn,rustpushgo=info,open_absinthe=debug" # NAC emulator diagnostics (_enc field sizes, etc.)
# "warn,rustpushgo=info,open_absinthe=debug,rustpush=info" # + upstream rustpush internals
# "debug" # everything (very chatty)
#
# The open_absinthe crate logs NAC hardware-key diagnostics at INFO and emulator
# state at DEBUG. These are suppressed by default to reduce log noise.
matrix_rustpush_bridge_rust_log: ""

# List of systemd services that matrix-rustpush-bridge.service depends on.
matrix_rustpush_bridge_systemd_required_services_list: "{{ matrix_rustpush_bridge_systemd_required_services_list_default + matrix_rustpush_bridge_systemd_required_services_list_auto + matrix_rustpush_bridge_systemd_required_services_list_custom }}"
matrix_rustpush_bridge_systemd_required_services_list_default: "{{ [devture_systemd_docker_base_docker_service_name] if devture_systemd_docker_base_docker_service_name else [] }}"
matrix_rustpush_bridge_systemd_required_services_list_auto: []
matrix_rustpush_bridge_systemd_required_services_list_custom: []

# List of systemd services that matrix-rustpush-bridge.service wants
matrix_rustpush_bridge_systemd_wanted_services_list: []

matrix_rustpush_bridge_appservice_token: ''
matrix_rustpush_bridge_homeserver_token: ''

# Whether or not created rooms should have federation enabled.
# If false, created portal rooms will never be federated.
matrix_rustpush_bridge_matrix_federate_rooms: false

# Database-related configuration fields.
#
# To use Postgres:
# - adjust your database credentials via the `matrix_rustpush_bridge_postgres_*` variables
matrix_rustpush_bridge_database_engine: 'postgres'

matrix_rustpush_bridge_database_username: 'matrix_rustpush_bridge'
matrix_rustpush_bridge_database_password: 'some-password'
matrix_rustpush_bridge_database_hostname: ''
matrix_rustpush_bridge_database_port: 5432
matrix_rustpush_bridge_database_name: 'matrix_rustpush_bridge'
matrix_rustpush_bridge_database_sslmode: disable

matrix_rustpush_bridge_database_connection_string: 'postgres://{{ matrix_rustpush_bridge_database_username }}:{{ matrix_rustpush_bridge_database_password }}@{{ matrix_rustpush_bridge_database_hostname }}:{{ matrix_rustpush_bridge_database_port }}/{{ matrix_rustpush_bridge_database_name }}?sslmode={{ matrix_rustpush_bridge_database_sslmode }}'

matrix_rustpush_bridge_database_uri: "{{
{
'postgres': matrix_rustpush_bridge_database_connection_string,
}[matrix_rustpush_bridge_database_engine]
}}"

matrix_rustpush_bridge_double_puppet_secrets: "{{ matrix_rustpush_bridge_double_puppet_secrets_auto | combine(matrix_rustpush_bridge_double_puppet_secrets_custom) }}"
matrix_rustpush_bridge_double_puppet_secrets_auto: {}
matrix_rustpush_bridge_double_puppet_secrets_custom: {}

matrix_rustpush_bridge_appservice_bot_username: imessagebot
matrix_rustpush_bridge_appservice_bot_displayname: iMessage bridge bot
matrix_rustpush_bridge_appservice_bot_avatar: ''

# Localpart template for MXIDs of remote (iMessage) users.
# The `{{.}}` placeholder expands to the iMessage handle (phone/email).
matrix_rustpush_bridge_appservice_username_template: "{% raw %}imessage_{{.}}{% endraw %}"

# Backfill is disabled by default because Linux Docker cannot access chat.db.
# On macOS with Full Disk Access, this can be set to true.
matrix_rustpush_bridge_backfill_enabled: false
# Maximum number of messages to backfill in empty rooms
matrix_rustpush_bridge_backfill_max_initial_messages: 50

# Maximum number of missed messages to backfill after bridge restarts
matrix_rustpush_bridge_backfill_max_catchup_messages: 500

# How many days back to look for chats during initial sync.
# Default in upstream is 365 (1 year). Set to 0 to disable.
matrix_rustpush_bridge_initial_sync_days: 365

# Shared secret for authentication of provisioning API requests.
# If set to "disable", the provisioning API will be disabled.
matrix_rustpush_bridge_provisioning_shared_secret: disable

# Minimum severity of journal log messages.
# Valid values: fatal, error, warn, info, debug, trace
matrix_rustpush_bridge_logging_level: 'warn'

# Whether or not metrics endpoint should be enabled.
# Enabling them is usually enough for a local (in-container) Prometheus to consume them.
# If metrics need to be consumed by another (external) Prometheus server, consider exposing them via `matrix_rustpush_bridge_metrics_proxying_enabled`.
matrix_rustpush_bridge_metrics_enabled: false

# Controls whether metrics should be exposed on a public URL.
matrix_rustpush_bridge_metrics_proxying_enabled: false
matrix_rustpush_bridge_metrics_proxying_hostname: ''
matrix_rustpush_bridge_metrics_proxying_path_prefix: ''

# Default configuration template which covers the generic use case.
# You can customize it by controlling the various variables inside it.
#
# For a more advanced customization, you can extend the default (see `matrix_rustpush_bridge_configuration_extension_yaml`)
# or completely replace this variable with your own template.
matrix_rustpush_bridge_configuration_yaml: "{{ lookup('template', 'templates/config.yaml.j2') }}"

matrix_rustpush_bridge_configuration_extension_yaml: |
# Your custom YAML configuration goes here.
# This configuration extends the default starting configuration (`matrix_rustpush_bridge_configuration_yaml`).
#
# You can override individual variables from the default configuration, or introduce new ones.
#
# If you need something more special, you can take full control by
# completely redefining `matrix_rustpush_bridge_configuration_yaml`.

matrix_rustpush_bridge_configuration_extension: "{{ matrix_rustpush_bridge_configuration_extension_yaml | from_yaml if matrix_rustpush_bridge_configuration_extension_yaml | from_yaml is mapping else {} }}"

# Holds the final configuration (a combination of the default and its extension).
# You most likely don't need to touch this variable. Instead, see `matrix_rustpush_bridge_configuration_yaml`.
matrix_rustpush_bridge_configuration: "{{ matrix_rustpush_bridge_configuration_yaml | from_yaml | combine(matrix_rustpush_bridge_configuration_extension, recursive=True) }}"

matrix_rustpush_bridge_registration_yaml: |
id: rustpush-bridge
as_token: "{{ matrix_rustpush_bridge_appservice_token }}"
hs_token: "{{ matrix_rustpush_bridge_homeserver_token }}"
namespaces:
users:
- exclusive: true
regex: '^@imessage_.+:{{ matrix_rustpush_bridge_homeserver_domain | regex_escape }}$'
- exclusive: true
regex: '^@{{ matrix_rustpush_bridge_appservice_bot_username | regex_escape }}:{{ matrix_rustpush_bridge_homeserver_domain | regex_escape }}$'
url: {{ matrix_rustpush_bridge_appservice_address }}
sender_localpart: _bot_{{ matrix_rustpush_bridge_appservice_bot_username }}
rate_limited: false
de.sorunome.msc2409.push_ephemeral: true
receive_ephemeral: true
io.element.msc4190: {{ matrix_rustpush_bridge_msc4190_enabled | to_json }}

matrix_rustpush_bridge_registration: "{{ matrix_rustpush_bridge_registration_yaml | from_yaml }}"

# Enable End-to-bridge encryption
matrix_rustpush_bridge_bridge_encryption_allow: "{{ matrix_bridges_encryption_enabled }}"
matrix_rustpush_bridge_bridge_encryption_default: "{{ matrix_bridges_encryption_default }}"
matrix_rustpush_bridge_bridge_encryption_require: false
matrix_rustpush_bridge_bridge_encryption_appservice: false
matrix_rustpush_bridge_bridge_encryption_key_sharing_allow: "{{ matrix_rustpush_bridge_bridge_encryption_allow }}"
matrix_rustpush_bridge_bridge_encryption_pickle_key: mautrix.bridge.e2ee

# matrix_rustpush_bridge_restart_necessary controls whether the service
# will be restarted (when true) or merely started (when false) by the
# systemd service manager role (when conditional restart is enabled).
#
# This value is automatically computed during installation based on whether
# any configuration files, the systemd service file, or the container image changed.
# The default of `false` means "no restart needed" — appropriate when the role's
# installation tasks haven't run (e.g., due to --tags skipping them).
matrix_rustpush_bridge_restart_necessary: false

+ 25
- 0
roles/custom/matrix-bridge-rustpush/tasks/main.yml View File

@@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Jason LaGuidice
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- tags:
- setup-all
- setup-rustpush-bridge
- install-all
- install-rustpush-bridge
block:
- when: matrix_rustpush_bridge_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/validate_config.yml"

- when: matrix_rustpush_bridge_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_install.yml"

- tags:
- setup-all
- setup-rustpush-bridge
block:
- when: not matrix_rustpush_bridge_enabled | bool
ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_uninstall.yml"

+ 130
- 0
roles/custom/matrix-bridge-rustpush/tasks/setup_install.yml View File

@@ -0,0 +1,130 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Jason LaGuidice
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Ensure RustPush paths exist
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
mode: 0750
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
with_items:
- {path: "{{ matrix_rustpush_bridge_base_path }}", when: true}
- {path: "{{ matrix_rustpush_bridge_config_path }}", when: true}
- {path: "{{ matrix_rustpush_bridge_data_path }}", when: true}
- {path: "{{ matrix_rustpush_bridge_docker_src_files_path }}", when: "{{ matrix_rustpush_bridge_container_image_self_build }}"}
when: item.when | bool

- name: Ensure RustPush repository is present on self-build
ansible.builtin.git:
repo: "{{ matrix_rustpush_bridge_container_image_self_build_repo }}"
version: "{{ matrix_rustpush_bridge_container_image_self_build_repo_version }}"
dest: "{{ matrix_rustpush_bridge_docker_src_files_path }}"
force: "yes"
become: true
become_user: "{{ matrix_user_name }}"
register: matrix_rustpush_bridge_git_pull_results
when: "matrix_rustpush_bridge_enabled | bool and matrix_rustpush_bridge_container_image_self_build"

- name: Ensure RustPush Dockerfile is installed
ansible.builtin.template:
src: "{{ role_path }}/templates/Dockerfile.j2"
dest: "{{ matrix_rustpush_bridge_docker_src_files_path }}/Dockerfile"
mode: 0640
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
when: "matrix_rustpush_bridge_enabled | bool and matrix_rustpush_bridge_container_image_self_build | bool"

- name: Ensure RustPush Docker image is built
community.docker.docker_image:
name: "{{ matrix_rustpush_bridge_docker_image }}"
source: build
force_source: "{{ matrix_rustpush_bridge_git_pull_results.changed if matrix_rustpush_bridge_git_pull_results is defined else true }}"
build:
dockerfile: Dockerfile
path: "{{ matrix_rustpush_bridge_docker_src_files_path }}"
pull: true
args:
BUILD_VERSION: "{{ matrix_rustpush_bridge_container_image_self_build_repo_version }}"
BUILD_COMMIT: "{{ matrix_rustpush_bridge_git_pull_results.after[:8] if matrix_rustpush_bridge_git_pull_results is defined and matrix_rustpush_bridge_git_pull_results.after is defined else 'unknown' }}"
register: matrix_rustpush_bridge_container_image_build_result
when: "matrix_rustpush_bridge_enabled | bool and matrix_rustpush_bridge_container_image_self_build | bool"

- name: Ensure RustPush container image is pulled
community.docker.docker_image:
name: "{{ matrix_rustpush_bridge_docker_image }}"
source: pull
force_source: "{{ matrix_rustpush_bridge_container_image_force_pull if matrix_rustpush_bridge_container_image_force_pull is defined else true }}"
register: matrix_rustpush_bridge_container_image_pull_result
when: "matrix_rustpush_bridge_enabled | bool and not matrix_rustpush_bridge_container_image_self_build | bool"
ignore_errors: "{{ ansible_check_mode }}"

- name: Ensure rustpush-bridge config.yaml installed
ansible.builtin.copy:
content: >-
{{
matrix_rustpush_bridge_configuration
| combine({
'network': {
'displayname_template': matrix_rustpush_bridge_network_displayname_template_effective,
},
}, recursive=True)
| to_nice_yaml(indent=2, width=999999)
}}
dest: "{{ matrix_rustpush_bridge_config_path }}/config.yaml"
mode: 0644
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
vars:
matrix_rustpush_bridge_network_displayname_template_effective: "{{ (matrix_rustpush_bridge_configuration.network | default({}, true)).displayname_template | default(matrix_rustpush_bridge_network_displayname_template) }}"
register: matrix_rustpush_bridge_config_result

- name: Ensure rustpush-bridge registration.yaml installed
ansible.builtin.copy:
content: "{{ matrix_rustpush_bridge_registration | to_nice_yaml(indent=2, width=999999) }}"
dest: "{{ matrix_rustpush_bridge_config_path }}/registration.yaml"
mode: 0644
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
register: matrix_rustpush_bridge_registration_result

- name: Ensure rustpush-bridge support files installed
ansible.builtin.template:
src: "{{ role_path }}/templates/{{ item }}.j2"
dest: "{{ matrix_rustpush_bridge_base_path }}/{{ item }}"
mode: 0640
owner: "{{ matrix_user_name }}"
group: "{{ matrix_group_name }}"
with_items:
- labels
register: matrix_rustpush_bridge_support_files_result

- name: Ensure matrix-rustpush-bridge container network is created
community.general.docker_network:
enable_ipv6: "{{ devture_systemd_docker_base_ipv6_enabled }}"
name: "{{ matrix_rustpush_bridge_container_network }}"
driver: bridge
driver_options: "{{ devture_systemd_docker_base_container_networks_driver_options }}"

- name: Ensure matrix-rustpush-bridge.service installed
ansible.builtin.template:
src: "{{ role_path }}/templates/systemd/matrix-rustpush-bridge.service.j2"
dest: "{{ devture_systemd_docker_base_systemd_path }}/matrix-rustpush-bridge.service"
mode: 0644
register: matrix_rustpush_bridge_systemd_service_result

- name: Determine whether matrix-rustpush-bridge needs a restart
ansible.builtin.set_fact:
matrix_rustpush_bridge_restart_necessary: >-
{{
matrix_rustpush_bridge_config_result.changed | default(false)
or matrix_rustpush_bridge_registration_result.changed | default(false)
or matrix_rustpush_bridge_support_files_result.changed | default(false)
or matrix_rustpush_bridge_systemd_service_result.changed | default(false)
or matrix_rustpush_bridge_container_image_pull_result.changed | default(false)
or matrix_rustpush_bridge_container_image_build_result.changed | default(false)
}}

+ 24
- 0
roles/custom/matrix-bridge-rustpush/tasks/setup_uninstall.yml View File

@@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Jason LaGuidice
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Check existence of matrix-rustpush-bridge service
ansible.builtin.stat:
path: "{{ devture_systemd_docker_base_systemd_path }}/matrix-rustpush-bridge.service"
register: matrix_rustpush_bridge_service_stat

- when: matrix_rustpush_bridge_service_stat.stat.exists | bool
block:
- name: Ensure matrix-rustpush-bridge is stopped
ansible.builtin.service:
name: matrix-rustpush-bridge
state: stopped
daemon_reload: true

- name: Ensure matrix-rustpush-bridge.service doesn't exist
ansible.builtin.file:
path: "{{ devture_systemd_docker_base_systemd_path }}/matrix-rustpush-bridge.service"
state: absent

+ 20
- 0
roles/custom/matrix-bridge-rustpush/tasks/validate_config.yml View File

@@ -0,0 +1,20 @@
# SPDX-FileCopyrightText: 2026 MDAD project contributors
# SPDX-FileCopyrightText: 2026 Jason LaGuidice
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Fail if required RustPush settings not defined
ansible.builtin.fail:
msg: >-
You need to define a required configuration setting (`{{ item.name }}`).
when: "item.when | bool and lookup('vars', item.name, default='') | string | length == 0"
with_items:
- {'name': 'matrix_rustpush_bridge_appservice_token', when: true}
- {'name': 'matrix_rustpush_bridge_homeserver_address', when: true}
- {'name': 'matrix_rustpush_bridge_homeserver_token', when: true}
- {'name': 'matrix_rustpush_bridge_database_hostname', when: "{{ matrix_rustpush_bridge_database_engine == 'postgres' }}"}
- {'name': 'matrix_rustpush_bridge_container_network', when: true}
- {'name': 'matrix_rustpush_bridge_metrics_proxying_hostname', when: "{{ matrix_rustpush_bridge_metrics_proxying_enabled }}"}
- {'name': 'matrix_rustpush_bridge_metrics_proxying_path_prefix', when: "{{ matrix_rustpush_bridge_metrics_proxying_enabled }}"}

+ 110
- 0
roles/custom/matrix-bridge-rustpush/templates/Dockerfile.j2 View File

@@ -0,0 +1,110 @@
{#
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Jason LaGuidice

SPDX-License-Identifier: AGPL-3.0-or-later
#}

# ── Stage 1: builder ─────────────────────────────────────────────────────────
FROM ubuntu:24.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
cmake protobuf-compiler build-essential pkg-config \
git curl ca-certificates \
libolm-dev libclang-dev libssl-dev libunicorn-dev libheif-dev zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*

# Rust — install to default ~/.cargo so the Makefile's $(HOME)/.cargo/bin path resolves
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --default-toolchain stable
ENV PATH=/root/.cargo/bin:$PATH

# Go — arch-aware, fetches latest stable with fallback
ARG TARGETARCH
RUN set -e; \
GOARCH="${TARGETARCH:-amd64}"; \
GO_VERSION=$(curl -fsSL 'https://go.dev/dl/?mode=json' \
| grep -o '"version":"go[0-9.]*"' | head -1 \
| sed 's/"version":"//;s/"//'); \
: "${GO_VERSION:=go1.25.0}"; \
curl -fsSL "https://go.dev/dl/${GO_VERSION}.linux-${GOARCH}.tar.gz" \
| tar -C /usr/local -xz
ENV PATH=/usr/local/go/bin:$PATH \
GOTOOLCHAIN=local

WORKDIR /build

# ── Rust build layers ─────────────────────────────────────────────────────────
# Copy files that determine whether the clone+patch layer is valid.
# Changing the SHA pin, Makefile, or open-absinthe overlay invalidates this layer.
COPY third_party/rustpush-upstream.sha third_party/
COPY rustpush/ rustpush/
COPY Makefile .

# Clone upstream rustpush at the pinned SHA, apply all patches, overlay open-absinthe.
RUN make ensure-rustpush-source

# Copy Rust crate sources. Changing these invalidates only the Rust build layer,
# not the clone layer above.
COPY pkg/rustpushgo/ pkg/rustpushgo/
COPY nac-validation/ nac-validation/

# Build the Rust static library (~3 min; cached when Rust source is unchanged).
# hardware-key enables the unicorn-based x86 NAC emulator required on Linux
# (both amd64 and arm64 — unicorn supports cross-arch x86 emulation).
RUN cd pkg/rustpushgo && \
cargo build --release --features hardware-key && \
cp target/release/librustpushgo.a /build/librustpushgo.a

# ── Go build layers ───────────────────────────────────────────────────────────
# Download modules first so this layer is cached by go.mod/go.sum.
COPY go.mod go.sum ./
RUN go mod download

# Copy Go source.
COPY cmd/ cmd/
COPY pkg/connector/ pkg/connector/
COPY imessage/ imessage/
COPY ipc/ ipc/

# Build the bridge binary.
ARG BUILD_VERSION=dev
ARG BUILD_COMMIT=unknown
RUN BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) && \
CGO_LDFLAGS="-L/build" \
go build \
-ldflags "-X main.Tag=${BUILD_VERSION} -X main.Commit=${BUILD_COMMIT} -X main.BuildTime=${BUILD_TIME}" \
-o /build/matrix-rustpush \
./cmd/matrix-rustpush/

# ── Stage 2: runtime ─────────────────────────────────────────────────────────
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

# Runtime shared libraries the bridge binary needs at startup.
# libunicorn2 — unicorn-engine x86 NAC emulator (hardware-key feature)
# libheif1 — HEIC/HEIF conversion (linked at compile time even when disabled)
# libolm3 — Matrix OLM encryption (mautrix bridgev2 framework)
# libssl3 — OpenSSL (rustpush openssl crate dynamic link)
# ffmpeg — video transcoding
RUN apt-get update && apt-get install -y --no-install-recommends \
libunicorn2 libheif1 libolm3 libssl3 ffmpeg \
ca-certificates openssl curl \
&& curl -fsSL 'https://www.apple.com/appleca/AppleIncRootCertificate.cer' \
-o /tmp/AppleRootCA.cer \
&& openssl x509 -inform DER -in /tmp/AppleRootCA.cer \
-out /usr/local/share/ca-certificates/AppleRootCA.crt \
&& update-ca-certificates \
&& rm /tmp/AppleRootCA.cer \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /build/matrix-rustpush /usr/local/bin/matrix-rustpush

WORKDIR /data
VOLUME /data
EXPOSE 29332

ENTRYPOINT ["matrix-rustpush", "-c", "/data/config.yaml"]

+ 4
- 0
roles/custom/matrix-bridge-rustpush/templates/Dockerfile.j2.license View File

@@ -0,0 +1,4 @@
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Jason LaGuidice

SPDX-License-Identifier: AGPL-3.0-or-later

+ 208
- 0
roles/custom/matrix-bridge-rustpush/templates/config.yaml.j2 View File

@@ -0,0 +1,208 @@
#jinja2: lstrip_blocks: True
# Network-specific config options (iMessage via RustPush)
network:
# Displayname template for iMessage contacts.
# Available variables: {% raw %}{{.FirstName}}, {{.LastName}}, {{.Nickname}},
# {{.Phone}}, {{.Email}}, {{.ID}}{% endraw %}
displayname_template: {{ matrix_rustpush_bridge_network_displayname_template | to_json }}

# How many days back to look for chats during initial sync.
# Default is 365 (1 year). Set to 0 to use the default.
initial_sync_days: {{ matrix_rustpush_bridge_initial_sync_days | to_json }}

# Set to false to disable CloudKit backfill globally
cloudkit_backfill: {{ matrix_rustpush_bridge_cloudkit_backfill | to_json }}
backfill_source: cloudkit

# Enable or disable video transcoding
video_transcoding: {{ matrix_rustpush_bridge_video_transcoding | to_json }}

# Enable or disable HEIC conversion
heic_conversion: {{ matrix_rustpush_bridge_heic_conversion | to_json }}
heic_jpeg_quality: 95

# Set to true to disable Facetime support globally
disable_facetime: {{ matrix_rustpush_bridge_disable_facetime | to_json }}

# Set to false to disable Statuskit support globally
statuskit_notifications: {{ matrix_rustpush_bridge_statuskit_notifications | to_json }}
statuskit_share_on_startup: {{ matrix_rustpush_bridge_statuskit_share_on_startup | to_json }}

# Config options that affect the central bridge module.
bridge:
# The prefix for commands. Only required in non-management rooms.
command_prefix: {{ matrix_rustpush_bridge_bridge_command_prefix | to_json }}
# Should the bridge create a space for each login containing the rooms that account is in?
personal_filtering_spaces: true
# Whether the bridge should set names and avatars explicitly for DM portals.
private_chat_portal_meta: true
# Should events be handled asynchronously within portal rooms?
async_events: false
# Should every user have their own portals rather than sharing them?
split_portals: false
# Should the bridge resend `m.bridge` events to all portals on startup?
resend_bridge_info: false

# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
bridge_matrix_leave: false
# Should room tags only be synced when creating the portal?
tag_only_on_create: true
# List of tags to allow bridging.
only_bridge_tags: [m.favourite, m.lowpriority]
# Should room mute status only be synced when creating the portal?
mute_only_on_create: true

# What should be done to portal rooms when a user logs out or is logged out?
cleanup_on_logout:
enabled: false
manual:
private: nothing
relayed: nothing
shared_no_users: nothing
shared_has_users: nothing
bad_credentials:
private: nothing
relayed: nothing
shared_no_users: nothing
shared_has_users: nothing

# Settings for relay mode
relay:
enabled: false
admin_only: true
default_relays: []
message_formats:
m.text: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}{% endraw %}"
m.notice: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}{% endraw %}"
m.emote: "{% raw %}* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}{% endraw %}"
m.file: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}"
m.image: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}"
m.audio: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}"
m.video: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}"
m.location: "{% raw %}<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}{% endraw %}"
displayname_format: "{% raw %}{{ .DisambiguatedName }}{% endraw %}"

# Permissions for using the bridge.
permissions: {{ matrix_rustpush_bridge_bridge_permissions | to_json }}

# Config for the bridge's database.
database:
type: postgres
uri: {{ matrix_rustpush_bridge_database_uri | to_json }}
max_open_conns: 5
max_idle_conns: 1
max_conn_idle_time: null
max_conn_lifetime: null

# Homeserver details.
homeserver:
address: {{ matrix_rustpush_bridge_homeserver_address | to_json }}
domain: {{ matrix_rustpush_bridge_homeserver_domain | to_json }}
software: standard
status_endpoint:
message_send_checkpoint_endpoint:
async_media: {{ matrix_rustpush_bridge_homeserver_async_media | to_json }}
websocket: false
ping_interval_seconds: 0

# Application service host/registration related details.
appservice:
address: {{ matrix_rustpush_bridge_appservice_address | to_json }}
public_address: {{ matrix_rustpush_bridge_appservice_public_address | to_json }}

hostname: 0.0.0.0
port: 8081

id: rustpush-bridge
bot:
username: {{ matrix_rustpush_bridge_appservice_bot_username | to_json }}
displayname: {{ matrix_rustpush_bridge_appservice_bot_displayname | to_json(ensure_ascii=False) }}
avatar: {{ matrix_rustpush_bridge_appservice_bot_avatar | to_json }}

ephemeral_events: true
async_transactions: false

as_token: {{ matrix_rustpush_bridge_appservice_token | to_json }}
hs_token: {{ matrix_rustpush_bridge_homeserver_token | to_json }}

# Localpart template of MXIDs for remote users.
username_template: {{ matrix_rustpush_bridge_appservice_username_template | to_json }}

# Config options that affect the Matrix connector of the bridge.
matrix:
message_status_events: false
delivery_receipts: false
message_error_notices: true
sync_direct_chat_list: true
federate_rooms: {{ matrix_rustpush_bridge_matrix_federate_rooms | to_json }}
upload_file_threshold: 5242880

# Segment-compatible analytics endpoint for tracking some events.
analytics:
token: null
url: https://api.segment.io/v1/track
user_id: null

# Settings for provisioning API
provisioning:
prefix: /_matrix/provision
shared_secret: {{ matrix_rustpush_bridge_provisioning_shared_secret | to_json }}
allow_matrix_auth: true
debug_endpoints: false

# Settings for backfilling messages.
backfill:
enabled: {{ matrix_rustpush_bridge_backfill_enabled | to_json }}
max_initial_messages: {{ matrix_rustpush_bridge_backfill_max_initial_messages | to_json }}
max_catchup_messages: {{ matrix_rustpush_bridge_backfill_max_catchup_messages | to_json }}
unread_hours_threshold: 720
threads:
max_initial_messages: 50
queue:
enabled: false
batch_size: 100
batch_delay: 20
max_batches: -1
max_batches_override: {}

# Settings for enabling double puppeting
double_puppet:
servers: {}
allow_discovery: false
secrets: {{ matrix_rustpush_bridge_double_puppet_secrets | to_json }}

# End-to-bridge encryption support options.
encryption:
allow: {{ matrix_rustpush_bridge_bridge_encryption_allow | to_json }}
default: {{ matrix_rustpush_bridge_bridge_encryption_default | to_json }}
require: {{ matrix_rustpush_bridge_bridge_encryption_require | to_json }}
appservice: {{ matrix_rustpush_bridge_bridge_encryption_appservice | to_json }}
msc4190: {{ matrix_rustpush_bridge_msc4190_enabled | to_json }}
self_sign: {{ matrix_rustpush_bridge_self_sign_enabled | to_json }}
allow_key_sharing: {{ matrix_rustpush_bridge_bridge_encryption_key_sharing_allow | to_json }}
pickle_key: {{ matrix_rustpush_bridge_bridge_encryption_pickle_key | to_json }}
delete_keys:
delete_outbound_on_ack: false
dont_store_outbound: false
ratchet_on_decrypt: false
delete_fully_used_on_decrypt: false
delete_prev_on_new_session: false
delete_on_device_delete: false
periodically_delete_expired: false
delete_outdated_inbound: false
verification_levels:
receive: unverified
send: unverified
share: cross-signed-tofu
rotation:
enable_custom: false
milliseconds: 604800000
messages: 100
disable_device_change_key_rotation: false

# Logging config.
logging:
min_level: {{ matrix_rustpush_bridge_logging_level | to_json }}
writers:
- type: stdout
format: pretty-colored

+ 4
- 0
roles/custom/matrix-bridge-rustpush/templates/config.yaml.j2.license View File

@@ -0,0 +1,4 @@
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Jason LaGuidice

SPDX-License-Identifier: AGPL-3.0-or-later

+ 53
- 0
roles/custom/matrix-bridge-rustpush/templates/labels.j2 View File

@@ -0,0 +1,53 @@
{#
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Jason LaGuidice

SPDX-License-Identifier: AGPL-3.0-or-later
#}

{% if matrix_rustpush_bridge_container_labels_traefik_enabled %}
traefik.enable=true

{% if matrix_rustpush_bridge_container_labels_traefik_docker_network %}
traefik.docker.network={{ matrix_rustpush_bridge_container_labels_traefik_docker_network }}
{% endif %}

traefik.http.services.matrix-rustpush-bridge-metrics.loadbalancer.server.port=8000

{% if matrix_rustpush_bridge_container_labels_metrics_enabled %}
############################################################
# #
# Metrics #
# #
############################################################

{% if matrix_rustpush_bridge_container_labels_metrics_middleware_basic_auth_enabled %}
traefik.http.middlewares.matrix-rustpush-bridge-metrics-basic-auth.basicauth.users={{ matrix_rustpush_bridge_container_labels_metrics_middleware_basic_auth_users }}
traefik.http.routers.matrix-rustpush-bridge-metrics.middlewares=matrix-rustpush-bridge-metrics-basic-auth
{% endif %}

traefik.http.routers.matrix-rustpush-bridge-metrics.rule={{ matrix_rustpush_bridge_container_labels_metrics_traefik_rule }}

{% if matrix_rustpush_bridge_container_labels_metrics_traefik_priority | int > 0 %}
traefik.http.routers.matrix-rustpush-bridge-metrics.priority={{ matrix_rustpush_bridge_container_labels_metrics_traefik_priority }}
{% endif %}

traefik.http.routers.matrix-rustpush-bridge-metrics.service=matrix-rustpush-bridge-metrics
traefik.http.routers.matrix-rustpush-bridge-metrics.entrypoints={{ matrix_rustpush_bridge_container_labels_metrics_traefik_entrypoints }}

traefik.http.routers.matrix-rustpush-bridge-metrics.tls={{ matrix_rustpush_bridge_container_labels_metrics_traefik_tls | to_json }}
{% if matrix_rustpush_bridge_container_labels_metrics_traefik_tls %}
traefik.http.routers.matrix-rustpush-bridge-metrics.tls.certResolver={{ matrix_rustpush_bridge_container_labels_metrics_traefik_tls_certResolver }}
{% endif %}

############################################################
# #
# /Metrics #
# #
############################################################
{% endif %}


{% endif %}

{{ matrix_rustpush_bridge_container_labels_additional_labels }}

+ 4
- 0
roles/custom/matrix-bridge-rustpush/templates/labels.j2.license View File

@@ -0,0 +1,4 @@
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Jason LaGuidice

SPDX-License-Identifier: AGPL-3.0-or-later

+ 51
- 0
roles/custom/matrix-bridge-rustpush/templates/systemd/matrix-rustpush-bridge.service.j2 View File

@@ -0,0 +1,51 @@
#jinja2: lstrip_blocks: True
[Unit]
Description=Matrix RustPush bridge
{% for service in matrix_rustpush_bridge_systemd_required_services_list %}
Requires={{ service }}
After={{ service }}
{% endfor %}
{% for service in matrix_rustpush_bridge_systemd_wanted_services_list %}
Wants={{ service }}
{% endfor %}
DefaultDependencies=no

[Service]
Type=simple
Environment="HOME={{ devture_systemd_docker_base_systemd_unit_home_path }}"
ExecStartPre=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} stop -t {{ devture_systemd_docker_base_container_stop_grace_time_seconds }} matrix-rustpush-bridge 2>/dev/null || true'
ExecStartPre=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} rm matrix-rustpush-bridge 2>/dev/null || true'

ExecStartPre={{ devture_systemd_docker_base_host_command_docker }} create \
--rm \
--name=matrix-rustpush-bridge \
--log-driver=none \
--user={{ matrix_user_uid }}:{{ matrix_user_gid }} \
--cap-drop=ALL \
--network={{ matrix_rustpush_bridge_container_network }} \
--env HOME=/data \
{% if matrix_rustpush_bridge_rust_log %} --env RUST_LOG={{ matrix_rustpush_bridge_rust_log }} \
{% endif %} --mount type=bind,src={{ matrix_rustpush_bridge_config_path }},dst=/config \
--mount type=bind,src={{ matrix_rustpush_bridge_data_path }},dst=/data \
--label-file={{ matrix_rustpush_bridge_base_path }}/labels \
--entrypoint /usr/local/bin/matrix-rustpush \
{% for arg in matrix_rustpush_bridge_container_extra_arguments %}
{{ arg }} \
{% endfor %}
{{ matrix_rustpush_bridge_docker_image }} \
-c /config/config.yaml -r /config/registration.yaml

{% for network in matrix_rustpush_bridge_container_additional_networks %}
ExecStartPre={{ devture_systemd_docker_base_host_command_docker }} network connect {{ network }} matrix-rustpush-bridge
{% endfor %}

ExecStart={{ devture_systemd_docker_base_host_command_docker }} start --attach matrix-rustpush-bridge

ExecStop=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} stop -t {{ devture_systemd_docker_base_container_stop_grace_time_seconds }} matrix-rustpush-bridge 2>/dev/null || true'
ExecStop=-{{ devture_systemd_docker_base_host_command_sh }} -c '{{ devture_systemd_docker_base_host_command_docker }} rm matrix-rustpush-bridge 2>/dev/null || true'
Restart=always
RestartSec=30
SyslogIdentifier=matrix-rustpush-bridge

[Install]
WantedBy=multi-user.target

+ 4
- 0
roles/custom/matrix-bridge-rustpush/templates/systemd/matrix-rustpush-bridge.service.j2.license View File

@@ -0,0 +1,4 @@
SPDX-FileCopyrightText: 2026 MDAD project contributors
SPDX-FileCopyrightText: 2026 Jason LaGuidice

SPDX-License-Identifier: AGPL-3.0-or-later

+ 1
- 0
setup.yml View File

@@ -71,6 +71,7 @@
- custom/matrix-bridge-mautrix-discord
- custom/matrix-bridge-mautrix-slack
- custom/matrix-bridge-mautrix-bluesky
- custom/matrix-bridge-rustpush
- custom/matrix-bridge-mx-puppet-groupme
- custom/matrix-bridge-mx-puppet-steam
- custom/matrix-bridge-postmoogle


Loading…
Cancel
Save