diff --git a/group_vars/matrix_servers b/group_vars/matrix_servers index 6d9876acf..1fb1ea0f1 100755 --- a/group_vars/matrix_servers +++ b/group_vars/matrix_servers @@ -276,7 +276,6 @@ matrix_mautrix_telegram_login_shared_secret: "{{ matrix_synapse_ext_password_pro # ###################################################################### - ###################################################################### # # matrix-bridge-mautrix-whatsapp @@ -305,6 +304,33 @@ matrix_mautrix_whatsapp_login_shared_secret: "{{ matrix_synapse_ext_password_pro # ###################################################################### +###################################################################### +# +# matrix-sms-bridge +# +###################################################################### + +# We don't enable bridges by default. +matrix_sms_bridge_enabled: false + +matrix_sms_bridge_systemd_required_services_list: | + {{ + ['docker.service'] + + + (['matrix-synapse.service'] if matrix_synapse_enabled else []) + }} + +matrix_sms_bridge_appservice_token: "{{ matrix_synapse_macaroon_secret_key | password_hash('sha512', 'sms.as.token') | to_uuid }}" + +matrix_sms_bridge_homeserver_token: "{{ matrix_synapse_macaroon_secret_key | password_hash('sha512', 'sms.hs.token') | to_uuid }}" + +matrix_sms_bridge_database_password: "{{ matrix_synapse_macaroon_secret_key | password_hash('sha512', 'sms.db.password') | to_uuid }}" + +###################################################################### +# +# /matrix-sms-bridge +# +###################################################################### ###################################################################### # diff --git a/roles/matrix-sms-bridge/defaults/main.yml b/roles/matrix-sms-bridge/defaults/main.yml new file mode 100644 index 000000000..089b78eff --- /dev/null +++ b/roles/matrix-sms-bridge/defaults/main.yml @@ -0,0 +1,151 @@ +# matrix-sms-bridge is a Matrix <-> SMS bridge +# See: https://github.com/benkuly/matrix-sms-bridge + +matrix_sms_bridge_enabled: true + +matrix_sms_bridge_docker_image: "folivonet/matrix-sms-bridge:latest" +matrix_sms_bridge_docker_image_force_pull: "{{ matrix_sms_bridge_docker_image.endswith(':latest') }}" +matrix_sms_bridge_database_docker_image: "neo4j:latest" +matrix_sms_bridge_database_docker_image_force_pull: "{{ matrix_sms_bridge_docker_image.endswith(':latest') }}" + +matrix_sms_bridge_base_path: "{{ matrix_base_data_path }}/matrix-sms-bridge" +matrix_sms_bridge_config_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/config" +matrix_sms_bridge_data_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/data" +matrix_sms_bridge_data_spool_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/data/spool" +matrix_sms_bridge_data_spool_inbox_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/data/spool/inbox" +matrix_sms_bridge_data_spool_inbox_processed_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/data/spool/inbox_processed" +matrix_sms_bridge_data_spool_outbox_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/data/spool/outbox" +matrix_sms_bridge_data_spool_sent_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/data/spool/sent" +matrix_sms_bridge_data_spool_error_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/data/spool/error" +matrix_sms_bridge_database_path: "{{ matrix_base_data_path }}/matrix-sms-bridge/database" + +matrix_sms_bridge_appservice_token: '' +matrix_sms_bridge_homeserver_token: '' + +matrix_sms_bridge_database_username: 'matrix-sms-bridge' +matrix_sms_bridge_database_password: '' + +matrix_sms_bridge_container_http_host_bind_port: '' + +# A list of extra arguments to pass to the container +matrix_sms_bridge_container_extra_arguments: [] + +# List of systemd services that matrix-appservice-discord.service depends on. +matrix_sms_bridge_systemd_required_services_list: ['docker.service'] + +# List of systemd services that matrix-appservice-discord.service wants +matrix_sms_bridge_systemd_wanted_services_list: [] + +matrix_sms_bridge_appservice_url: 'http://matrix-sms-bridge:8080' +matrix_sms_bridge_database_url: 'bolt://matrix-sms-bridge-database:7687' +matrix_sms_bridge_homeserver_hostname: 'matrix-synapse' +matrix_sms_bridge_homeserver_port: '8008' + +matrix_sms_bridge_homserver_domain: "{{ matrix_domain }}" +matrix_sms_bridge_default_room: '' + +matrix_sms_bridge_gammu_modem: '' + + +matrix_sms_bridge_configuration_yaml: | + #jinja2: lstrip_blocks: "True" + + # Database connection + org: + neo4j: + driver: + uri: {{ matrix_sms_bridge_database_url }} + authentication: + username: {{ matrix_sms_bridge_database_username }} + password: {{ matrix_sms_bridge_database_password }} + + matrix: + bridge: + sms: + # (optional) SMS messages without a valid token a routed to this room. + # Note that you must invite @smsbot:yourHomeServer to this room. + defaultRoomId: "{{ matrix_sms_bridge_default_room }}" + templates: + # (optional) The message, that will be sent as SMS. Valid placeholders are {sender}, {body} and {token}. + outgoingMessage: "{sender} wrote:\n\n{body}\n\nTo answer to this message add this token to your message: {token}" + # (optional) The message, that will be sent as SMS, when an incoming SMS didn't contain a valid token + # and was routed to a default room. By default no answer will be sent. + answerInvalidTokenWithDefaultRoom: "Your token was invalid. The message will be sent to a default matrix room." + # (optional) The message, that will be sent as SMS, when an incoming SMS didn't contain a valid token + # and no default room is configured. + answerInvalidTokenWithoutDefaultRoom: "Your message did not contain any valid token. Nobody will read your message.", + # (optional) The message, that will be sent to a matrix room, when sending a bridged message via SMS failed. + sendSmsError: "Could not send SMS to this user. Please try it again later." + # (optional) The content of bridged SMS message into the default room. Valid placeholders are {sender} and {body}. + defaultRoomIncomingMessage: "{sender} wrote:\n{body}" + provider: + gammu: + # (optional) default is disabled + enabled: true + # (optional) Path to the Gammu-Inbox directory. Default is "/var/spool/gammu/inbox". + inboxPath: "{{ matrix_sms_bridge_data_path }}/spool/inbox" + # (optional) Path to the directory, where to put processed messages. Default is "/var/spool/gammu/inbox_processed". + inboxProcessedPath: "{{ matrix_sms_bridge_data_path }}/spool/inbox_processed" + bot: + # The domain-part of matrix-ids. E. g. example.org when your userIds look like @unicorn:example.org + serverName: {{ matrix_sms_bridge_homserver_domain }} + client: + homeServer: + # The hostname of your Homeserver. + hostname: {{ matrix_sms_bridge_homeserver_hostname }} + # (optional) The port of your Homeserver. Default is 443. + port: {{ matrix_sms_bridge_homeserver_port }} + # (optional) Use http or https. Default is true (so uses https). + secure: false + # The token to authenticate against the Homeserver. + token: {{ matrix_sms_bridge_appservice_token }} + appservice: + # A unique token for Homeservers to use to authenticate requests to this application service. + hsToken: {{ matrix_sms_bridge_homeserver_token }} + +matrix_sms_bridge_configuration_extension_yaml: | + # Your custom YAML configuration goes here. + # This configuration extends the default starting configuration (`matrix_sms_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_sms_bridge_configuration_yaml`. + +matrix_sms_bridge_configuration_extension: "{{ matrix_sms_bridge_configuration_extension_yaml|from_yaml if matrix_sms_bridge_configuration_extension_yaml|from_yaml is mapping else {} }}" + +matrix_sms_bridge_gammu_configuration: | + [gammu] + Device = /dev/ttyModem + LogFile = {{ matrix_sms_bridge_data_path }}/log/gammu.log + debugLevel = 1 + + [smsd] + Service = files + LoopSleep = 2 + InboxPath = {{ matrix_sms_bridge_data_path }}/spool/inbox/ + OutboxPath = {{ matrix_sms_bridge_data_path }}/spool/outbox/ + SentSMSPath = {{ matrix_sms_bridge_data_path }}/spool/sent/ + ErrorSMSPath = {{ matrix_sms_bridge_data_path }}/spool/error/ + InboxFormat = detail + OutboxFormat = detail + TransmitFormat = auto + debugLevel = 1 + LogFile = {{ matrix_sms_bridge_data_path }}/log/smsd.log + DeliveryReport = log + DeliveryReportDelay = 7200 + HangupCalls = 1 + CheckBattery = 0 + + +matrix_sms_bridge_registration_yaml: | + id: sms + as_token: "{{ matrix_sms_bridge_appservice_token }}" + hs_token: "{{ matrix_sms_bridge_homeserver_token }}" + namespaces: + users: + - exclusive: true + regex: '^@sms_.+:{{ matrix_sms_bridge_homserver_domain|regex_escape }}$' + url: {{ matrix_sms_bridge_appservice_url }} + sender_localpart: smsbot + rate_limited: false diff --git a/roles/matrix-sms-bridge/tasks/init.yml b/roles/matrix-sms-bridge/tasks/init.yml new file mode 100644 index 000000000..7a49e4b24 --- /dev/null +++ b/roles/matrix-sms-bridge/tasks/init.yml @@ -0,0 +1,24 @@ +# If the matrix-synapse role is not used, `matrix_synapse_role_executed` won't exist. +# We don't want to fail in such cases. +- name: Fail if matrix-synapse role already executed + fail: + msg: >- + The matrix-sms-bridge role needs to execute before the matrix-synapse role. + when: "matrix_sms_bridge_enabled and matrix_synapse_role_executed|default(False)" + +- set_fact: + matrix_systemd_services_list: "{{ matrix_systemd_services_list + ['matrix-sms-bridge','matrix-sms-bridge-database'] }}" + when: matrix_sms_bridge_enabled|bool + +# If the matrix-synapse role is not used, these variables may not exist. +- set_fact: + matrix_synapse_container_extra_arguments: > + {{ matrix_synapse_container_extra_arguments|default([]) }} + + + ["--mount type=bind,src={{ matrix_sms_bridge_config_path }}/registration.yaml,dst=/matrix-sms-bridge-registration.yaml,ro"] + + matrix_synapse_app_service_config_files: > + {{ matrix_synapse_app_service_config_files|default([]) }} + + + {{ ["/matrix-sms-bridge-registration.yaml"] }} + when: matrix_sms_bridge_enabled|bool diff --git a/roles/matrix-sms-bridge/tasks/main.yml b/roles/matrix-sms-bridge/tasks/main.yml new file mode 100644 index 000000000..c1c499dee --- /dev/null +++ b/roles/matrix-sms-bridge/tasks/main.yml @@ -0,0 +1,21 @@ +- import_tasks: "{{ role_path }}/tasks/init.yml" + tags: + - always + +- import_tasks: "{{ role_path }}/tasks/validate_config.yml" + when: "run_setup|bool and matrix_sms_bridge_enabled|bool" + tags: + - setup-all + - setup-matrix-sms-bridge + +- import_tasks: "{{ role_path }}/tasks/setup_install.yml" + when: "run_setup|bool and matrix_sms_bridge_enabled|bool" + tags: + - setup-all + - setup-matrix-sms-bridge + +- import_tasks: "{{ role_path }}/tasks/setup_uninstall.yml" + when: "run_setup|bool and not matrix_sms_bridge_enabled|bool" + tags: + - setup-all + - setup-matrix-sms-bridge diff --git a/roles/matrix-sms-bridge/tasks/setup_install.yml b/roles/matrix-sms-bridge/tasks/setup_install.yml new file mode 100644 index 000000000..1ceb371e6 --- /dev/null +++ b/roles/matrix-sms-bridge/tasks/setup_install.yml @@ -0,0 +1,71 @@ +--- + +- name: Ensure matrix-sms-bridge image is pulled + docker_image: + name: "{{ matrix_sms_bridge_docker_image }}" + source: "{{ 'pull' if ansible_version.major > 2 or ansible_version.minor > 7 else omit }}" + force_source: "{{ matrix_sms_bridge_docker_image_force_pull if ansible_version.major > 2 or ansible_version.minor >= 8 else omit }}" + force: "{{ omit if ansible_version.major > 2 or ansible_version.minor >= 8 else matrix_sms_bridge_docker_image_force_pull }}" + +- name: Ensure matrix-sms-bridge databse image is pulled + docker_image: + name: "{{ matrix_sms_bridge_database_docker_image }}" + source: "{{ 'pull' if ansible_version.major > 2 or ansible_version.minor > 7 else omit }}" + force_source: "{{ matrix_sms_bridge_database_docker_image_force_pull if ansible_version.major > 2 or ansible_version.minor >= 8 else omit }}" + force: "{{ omit if ansible_version.major > 2 or ansible_version.minor >= 8 else matrix_sms_bridge_database_docker_image_force_pull }}" + + +- name: Ensure matrix-sms-bridge paths exist + file: + path: "{{ item }}" + state: directory + mode: 0750 + owner: "{{ matrix_user_username }}" + group: "{{ matrix_user_groupname }}" + with_items: + - "{{ matrix_sms_bridge_base_path }}" + - "{{ matrix_sms_bridge_config_path }}" + - "{{ matrix_sms_bridge_data_path }}" + - "{{ matrix_sms_bridge_data_spool_path }}" + - "{{ matrix_sms_bridge_data_spool_inbox_path }}" + - "{{ matrix_sms_bridge_data_spool_inbox_processed_path }}" + - "{{ matrix_sms_bridge_data_spool_outbox_path }}" + - "{{ matrix_sms_bridge_data_spool_sent_path }}" + - "{{ matrix_sms_bridge_data_spool_error_path }}" + - "{{ matrix_sms_bridge_database_path }}" + +- name: Ensure matrix-sms-bridge application.yml installed + copy: + content: "{{ matrix_sms_bridge_configuration|to_nice_yaml }}" + dest: "{{ matrix_sms_bridge_config_path }}/application.yml" + mode: 0644 + owner: "{{ matrix_user_username }}" + group: "{{ matrix_user_groupname }}" + +- name: Ensure matrix-sms-bridge registration.yaml installed + copy: + content: "{{ matrix_sms_bridge_registration|to_nice_yaml }}" + dest: "{{ matrix_sms_bridge_config_path }}/registration.yaml" + mode: 0644 + owner: "{{ matrix_user_username }}" + group: "{{ matrix_user_groupname }}" + +- name: Ensure matrix-sms-bridge gammu-smsdrc installed + copy: + content: "{{ matrix_sms_bridge_registration }}" + dest: "{{ matrix_sms_bridge_config_path }}/gammu-smsdrc" + mode: 0644 + owner: "{{ matrix_user_username }}" + group: "{{ matrix_user_groupname }}" + +- name: Ensure matrix-sms-bridge.service installed + template: + src: "{{ role_path }}/templates/systemd/matrix-sms-bridge.service.j2" + dest: "{{ matrix_systemd_path }}/matrix-sms-bridge.service" + mode: 0644 + register: matrix_sms_bridge_systemd_service_result + +- name: Ensure systemd reloaded after matrix-sms-bridge.service installation + service: + daemon_reload: yes + when: "matrix_sms_bridge_systemd_service_result.changed" diff --git a/roles/matrix-sms-bridge/tasks/setup_uninstall.yml b/roles/matrix-sms-bridge/tasks/setup_uninstall.yml new file mode 100644 index 000000000..109127006 --- /dev/null +++ b/roles/matrix-sms-bridge/tasks/setup_uninstall.yml @@ -0,0 +1,24 @@ +--- + +- name: Check existence of matrix-sms-bridge service + stat: + path: "{{ matrix_systemd_path }}/matrix-sms-bridge.service" + register: matrix_sms_bridge_service_stat + +- name: Ensure matrix-sms-bridge is stopped + service: + name: matrix-sms-bridge + state: stopped + daemon_reload: yes + when: "matrix_sms_bridge_service_stat.stat.exists" + +- name: Ensure matrix-sms-bridge.service doesn't exist + file: + path: "{{ matrix_systemd_path }}/matrix-sms-bridge.service" + state: absent + when: "matrix_sms_bridge_service_stat.stat.exists" + +- name: Ensure systemd reloaded after matrix-sms-bridge.service removal + service: + daemon_reload: yes + when: "matrix_sms_bridge_service_stat.stat.exists" diff --git a/roles/matrix-sms-bridge/tasks/validate_config.yml b/roles/matrix-sms-bridge/tasks/validate_config.yml new file mode 100644 index 000000000..8eb7b63c5 --- /dev/null +++ b/roles/matrix-sms-bridge/tasks/validate_config.yml @@ -0,0 +1,12 @@ +--- + +- name: Fail if required settings not defined + fail: + msg: >- + You need to define a required configuration setting (`{{ item }}`). + when: "vars[item] == ''" + with_items: + - "matrix_sms_bridge_appservice_token" + - "matrix_sms_bridge_homeserver_token" + - "matrix_sms_bridge_database_password" + - "matrix_sms_bridge_gammu_modem" diff --git a/roles/matrix-sms-bridge/templates/systemd/matrix-sms-bridge.service.j2 b/roles/matrix-sms-bridge/templates/systemd/matrix-sms-bridge.service.j2 new file mode 100644 index 000000000..a97db7833 --- /dev/null +++ b/roles/matrix-sms-bridge/templates/systemd/matrix-sms-bridge.service.j2 @@ -0,0 +1,59 @@ +#jinja2: lstrip_blocks: "True" +[Unit] +Description=matrix-sms-bridge server +{% for service in matrix_sms_bridge_systemd_required_services_list %} +Requires={{ service }} +After={{ service }} +{% endfor %} +{% for service in matrix_sms_bridge_systemd_wanted_services_list %} +Wants={{ service }} +{% endfor %} + +[Service] +Type=simple +ExecStartPre=-/usr/bin/docker kill matrix-sms-bridge +ExecStartPre=-/usr/bin/docker rm matrix-sms-bridge +ExecStartPre=-/usr/bin/docker kill matrix-sms-bridge-database +ExecStartPre=-/usr/bin/docker rm matrix-sms-bridge-database + +ExecStartPre=/usr/bin/docker run --rm --name matrix-sms-bridge-database \ + --log-driver=none \ + --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ + --cap-drop=ALL \ + --network={{ matrix_docker_network }} \ + {% if matrix_sms_bridge_container_http_host_bind_port %} + -p {{ matrix_sms_bridge_container_http_host_bind_port }}:7687 \ + {% endif %} + -v {{ matrix_sms_bridge_database_path }}:/data:z \ + -e NEO4J_AUTH={{ matrix_sms_bridge_database_username }}/{{ matrix_sms_bridge_database_password }} + {{ matrix_sms_bridge_database_docker_image }} + +# Intentional delay, so that the homeserver (we likely depend on) can manage to start. +ExecStartPre=/bin/sleep 5 + +ExecStart=/usr/bin/docker run --rm --name matrix-sms-bridge \ + --log-driver=none \ + --user={{ matrix_user_uid }}:{{ matrix_user_gid }} \ + --cap-drop=ALL \ + --network={{ matrix_docker_network }} \ + {% if matrix_sms_bridge_container_http_host_bind_port %} + -p {{ matrix_sms_bridge_container_http_host_bind_port }}:8080 \ + {% endif %} + -v {{ matrix_sms_bridge_config_path }}:/config:z \ + -v {{ matrix_sms_bridge_data_path }}:/data:z \ + --device {{ matrix_sms_bridge_gammu_modem }}:/dev/ttyModem + {% for arg in matrix_sms_bridge_container_extra_arguments %} + {{ arg }} \ + {% endfor %} + {{ matrix_sms_bridge_docker_image }} + +ExecStop=-/usr/bin/docker kill matrix-sms-bridge +ExecStop=-/usr/bin/docker rm matrix-sms-bridge +ExecStop=-/usr/bin/docker kill matrix-sms-database +ExecStop=-/usr/bin/docker rm matrix-sms-database +Restart=always +RestartSec=30 +SyslogIdentifier=matrix-sms-bridge + + [Install] +WantedBy=multi-user.target