mirror of
https://github.com/spantaleev/matrix-docker-ansible-deploy.git
synced 2024-12-12 08:43:55 +02:00
Make Facebook bridge configuration playbook-managed
Related to #193, but for the Facebook bridge. (other bridges can be changed to do the same later). This patch makes the bridge configuration entirely managed by the Ansible playbook. The bridge's `config.yaml` and `registration.yaml` configuration files are regenerated every time the playbook runs. This allows us to apply updates to those files and to avoid people having to manage the configuration files manually on the server. ------------------------------------------------------------- A deficiency of the current approach to dumping YAML configuration in `config.yaml` is that we strip all comments from it. Later on, when the bridge actually starts, it will load and redump (this time with comments), which will make the `config.yaml` file change. Subsequent playbook runs will report "changed" for the "Ensure mautrix-facebook config.yaml installed" task, which is a little strange. We might wish to improve this in the future, if possible. Still, it's better to have a (usually) somewhat meaningless "changed" task than to what we had -- never rebuilding the configuration.
This commit is contained in:
parent
04bc50a282
commit
330648a3e0
13
CHANGELOG.md
13
CHANGELOG.md
@ -1,3 +1,16 @@
|
|||||||
|
# 2019-06-07
|
||||||
|
|
||||||
|
## (BC Break) Facebook bridge configuration is now entirely managed by the playbook
|
||||||
|
|
||||||
|
Until now, configuration files for the [Facebook bridge](docs/configuring-playbook-bridge-mautrix-facebook.md) were created by the playbook initially, but never modified later on.
|
||||||
|
|
||||||
|
From now on, the playbook will keep those configuration in sync for you.
|
||||||
|
|
||||||
|
This means that if you were making manual changes to the `/matrix/mautrix-facebook/config.yaml` or `/matrix/mautrix-facebook/registration.yaml` configuration files, those would be lost the next time you run the playbook.
|
||||||
|
|
||||||
|
You need to migrate your manual changes over to the new `matrix_mautrix_facebook_configuration_extension_yaml` variable, so that the playbook would apply them for you.
|
||||||
|
|
||||||
|
|
||||||
# 2019-05-25
|
# 2019-05-25
|
||||||
|
|
||||||
## Support for exposing container ports publicly (not just to the host)
|
## Support for exposing container ports publicly (not just to the host)
|
||||||
|
@ -90,6 +90,10 @@ matrix_appservice_irc_systemd_required_services_list: |
|
|||||||
# We don't enable bridges by default.
|
# We don't enable bridges by default.
|
||||||
matrix_mautrix_facebook_enabled: false
|
matrix_mautrix_facebook_enabled: false
|
||||||
|
|
||||||
|
matrix_mautrix_facebook_appservice_token: "{{ matrix_synapse_macaroon_secret_key | password_hash('sha512', 'facebook-appservice-token') | to_uuid }}"
|
||||||
|
|
||||||
|
matrix_mautrix_facebook_homeserver_token: "{{ matrix_synapse_macaroon_secret_key | password_hash('sha512', 'facebook-homeserver-token') | to_uuid }}"
|
||||||
|
|
||||||
matrix_mautrix_facebook_systemd_required_services_list: |
|
matrix_mautrix_facebook_systemd_required_services_list: |
|
||||||
{{
|
{{
|
||||||
['docker.service']
|
['docker.service']
|
||||||
|
@ -7,7 +7,7 @@ matrix_mautrix_facebook_docker_image: "tulir/mautrix-facebook:latest"
|
|||||||
|
|
||||||
matrix_mautrix_facebook_base_path: "{{ matrix_base_data_path }}/mautrix-facebook"
|
matrix_mautrix_facebook_base_path: "{{ matrix_base_data_path }}/mautrix-facebook"
|
||||||
|
|
||||||
matrix_mautrix_facebook_homeserver_address: 'https://{{ matrix_server_fqn_matrix }}'
|
matrix_mautrix_facebook_homeserver_address: 'http://matrix-synapse:8008'
|
||||||
matrix_mautrix_facebook_homeserver_domain: '{{ matrix_domain }}'
|
matrix_mautrix_facebook_homeserver_domain: '{{ matrix_domain }}'
|
||||||
matrix_mautrix_facebook_appservice_address: 'http://matrix-mautrix-facebook:8080'
|
matrix_mautrix_facebook_appservice_address: 'http://matrix-mautrix-facebook:8080'
|
||||||
|
|
||||||
@ -19,3 +19,164 @@ matrix_mautrix_facebook_systemd_required_services_list: ['docker.service']
|
|||||||
|
|
||||||
# List of systemd services that matrix-mautrix-facebook.service wants
|
# List of systemd services that matrix-mautrix-facebook.service wants
|
||||||
matrix_mautrix_facebook_systemd_wanted_services_list: []
|
matrix_mautrix_facebook_systemd_wanted_services_list: []
|
||||||
|
|
||||||
|
matrix_mautrix_facebook_appservice_token: ''
|
||||||
|
matrix_mautrix_facebook_homeserver_token: ''
|
||||||
|
|
||||||
|
# Default mxisd 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_mautrix_facebook_configuration_extension_yaml`)
|
||||||
|
# or completely replace this variable with your own template.
|
||||||
|
matrix_mautrix_facebook_configuration_yaml: |
|
||||||
|
#jinja2: lstrip_blocks: "True"
|
||||||
|
# Homeserver details
|
||||||
|
homeserver:
|
||||||
|
# The address that this appservice can use to connect to the homeserver.
|
||||||
|
address: {{ matrix_mautrix_facebook_homeserver_address }}
|
||||||
|
# The domain of the homeserver (for MXIDs, etc).
|
||||||
|
domain: {{ matrix_mautrix_facebook_homeserver_domain }}
|
||||||
|
# Whether or not to verify the SSL certificate of the homeserver.
|
||||||
|
# Only applies if address starts with https://
|
||||||
|
verify_ssl: true
|
||||||
|
|
||||||
|
# Application service host/registration related details
|
||||||
|
# Changing these values requires regeneration of the registration.
|
||||||
|
appservice:
|
||||||
|
# The address that the homeserver can use to connect to this appservice.
|
||||||
|
address: {{ matrix_mautrix_facebook_appservice_address }}
|
||||||
|
|
||||||
|
# The hostname and port where this appservice should listen.
|
||||||
|
hostname: 0.0.0.0
|
||||||
|
port: 8080
|
||||||
|
# The maximum body size of appservice API requests (from the homeserver) in mebibytes
|
||||||
|
# Usually 1 is enough, but on high-traffic bridges you might need to increase this to avoid 413s
|
||||||
|
max_body_size: 1
|
||||||
|
|
||||||
|
# The full URI to the database. SQLite and Postgres are fully supported.
|
||||||
|
# Other DBMSes supported by SQLAlchemy may or may not work.
|
||||||
|
# Format examples:
|
||||||
|
# SQLite: sqlite:///filename.db
|
||||||
|
# Postgres: postgres://username:password@hostname/dbname
|
||||||
|
database: sqlite:////data/mautrix-facebook.db
|
||||||
|
|
||||||
|
# The unique ID of this appservice.
|
||||||
|
id: facebook
|
||||||
|
# Username of the appservice bot.
|
||||||
|
bot_username: facebookbot
|
||||||
|
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
||||||
|
# to leave display name/avatar as-is.
|
||||||
|
bot_displayname: Facebook bridge bot
|
||||||
|
bot_avatar: mxc://maunium.net/ddtNPZSKMNqaUzqrHuWvUADv
|
||||||
|
|
||||||
|
# Authentication tokens for AS <-> HS communication.
|
||||||
|
as_token: "{{ matrix_mautrix_facebook_appservice_token }}"
|
||||||
|
hs_token: "{{ matrix_mautrix_facebook_homeserver_token }}"
|
||||||
|
|
||||||
|
# Bridge config
|
||||||
|
bridge:
|
||||||
|
# Localpart template of MXIDs for Facebook users.
|
||||||
|
# {userid} is replaced with the user ID of the Facebook user.
|
||||||
|
username_template: "facebook_{userid}"
|
||||||
|
# Displayname template for Facebook users.
|
||||||
|
# {displayname} is replaced with the display name of the Facebook user
|
||||||
|
# as defined below in displayname_preference.
|
||||||
|
# Keys available for displayname_preference are also available here.
|
||||||
|
displayname_template: '{displayname} (FB)'
|
||||||
|
# Available keys:
|
||||||
|
# "name" (full name)
|
||||||
|
# "first_name"
|
||||||
|
# "last_name"
|
||||||
|
# "nickname"
|
||||||
|
# "own_nickname" (user-specific!)
|
||||||
|
displayname_preference:
|
||||||
|
- name
|
||||||
|
|
||||||
|
# The prefix for commands. Only required in non-management rooms.
|
||||||
|
command_prefix: "!fb"
|
||||||
|
|
||||||
|
# Number of chats to sync (and create portals for) on startup/login.
|
||||||
|
# Maximum 20, set 0 to disable automatic syncing.
|
||||||
|
initial_chat_sync: 10
|
||||||
|
# Whether or not the Facebook users of logged in Matrix users should be
|
||||||
|
# invited to private chats when the user sends a message from another client.
|
||||||
|
invite_own_puppet_to_pm: false
|
||||||
|
# Whether or not to use /sync to get presence, read receipts and typing notifications when using
|
||||||
|
# your own Matrix account as the Matrix puppet for your Facebook account.
|
||||||
|
sync_with_custom_puppets: true
|
||||||
|
# Whether or not to bridge presence in both directions. Facebook allows users not to broadcast
|
||||||
|
# presence, but then it won't send other users' presence to the client.
|
||||||
|
presence: true
|
||||||
|
|
||||||
|
# Permissions for using the bridge.
|
||||||
|
# Permitted values:
|
||||||
|
# user - Use the bridge with puppeting.
|
||||||
|
# admin - Use and administrate the bridge.
|
||||||
|
# Permitted keys:
|
||||||
|
# * - All Matrix users
|
||||||
|
# domain - All users on that homeserver
|
||||||
|
# mxid - Specific user
|
||||||
|
permissions:
|
||||||
|
'{{ matrix_mautrix_facebook_homeserver_domain }}': user
|
||||||
|
|
||||||
|
# Python logging configuration.
|
||||||
|
#
|
||||||
|
# See section 16.7.2 of the Python documentation for more info:
|
||||||
|
# https://docs.python.org/3.6/library/logging.config.html#configuration-dictionary-schema
|
||||||
|
logging:
|
||||||
|
version: 1
|
||||||
|
formatters:
|
||||||
|
colored:
|
||||||
|
(): mautrix_facebook.util.ColorFormatter
|
||||||
|
format: "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"
|
||||||
|
normal:
|
||||||
|
format: "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"
|
||||||
|
handlers:
|
||||||
|
file:
|
||||||
|
class: logging.handlers.RotatingFileHandler
|
||||||
|
formatter: normal
|
||||||
|
filename: /data/mautrix-facebook.log
|
||||||
|
maxBytes: 10485760
|
||||||
|
backupCount: 10
|
||||||
|
console:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: colored
|
||||||
|
loggers:
|
||||||
|
mau:
|
||||||
|
level: DEBUG
|
||||||
|
fbchat:
|
||||||
|
level: DEBUG
|
||||||
|
aiohttp:
|
||||||
|
level: INFO
|
||||||
|
root:
|
||||||
|
level: DEBUG
|
||||||
|
handlers: [file, console]
|
||||||
|
|
||||||
|
matrix_mautrix_facebook_configuration_extension_yaml: |
|
||||||
|
# Your custom YAML configuration goes here.
|
||||||
|
# This configuration extends the default starting configuration (`matrix_mautrix_facebook_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_mautrix_facebook_configuration_yaml`.
|
||||||
|
|
||||||
|
matrix_mautrix_facebook_configuration_extension: "{{ matrix_mautrix_facebook_configuration_extension_yaml|from_yaml if matrix_mautrix_facebook_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_mautrix_facebook_configuration_yaml`.
|
||||||
|
matrix_mautrix_facebook_configuration: "{{ matrix_mautrix_facebook_configuration_yaml|from_yaml|combine(matrix_mautrix_facebook_configuration_extension, recursive=True) }}"
|
||||||
|
|
||||||
|
matrix_mautrix_facebook_registration_yaml: |
|
||||||
|
id: facebook
|
||||||
|
as_token: "{{ matrix_mautrix_facebook_appservice_token }}"
|
||||||
|
hs_token: "{{ matrix_mautrix_facebook_homeserver_token }}"
|
||||||
|
namespaces:
|
||||||
|
users:
|
||||||
|
- exclusive: true
|
||||||
|
regex: '@facebook_.+:{{ matrix_mautrix_facebook_homeserver_domain }}'
|
||||||
|
url: {{ matrix_mautrix_facebook_appservice_address }}
|
||||||
|
sender_localpart: facebookbot
|
||||||
|
rate_limited: false
|
||||||
|
|
||||||
|
matrix_mautrix_facebook_registration: "{{ matrix_mautrix_facebook_registration_yaml|from_yaml }}"
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
tags:
|
tags:
|
||||||
- always
|
- always
|
||||||
|
|
||||||
|
- import_tasks: "{{ role_path }}/tasks/validate_config.yml"
|
||||||
|
when: "run_setup|bool and matrix_mautrix_facebook_enabled|bool"
|
||||||
|
tags:
|
||||||
|
- setup-all
|
||||||
|
- setup-mautrix-facebook
|
||||||
|
|
||||||
- import_tasks: "{{ role_path }}/tasks/setup_install.yml"
|
- import_tasks: "{{ role_path }}/tasks/setup_install.yml"
|
||||||
when: "run_setup|bool and matrix_mautrix_facebook_enabled|bool"
|
when: "run_setup|bool and matrix_mautrix_facebook_enabled|bool"
|
||||||
tags:
|
tags:
|
||||||
|
@ -21,19 +21,21 @@
|
|||||||
owner: "{{ matrix_user_username }}"
|
owner: "{{ matrix_user_username }}"
|
||||||
group: "{{ matrix_user_username }}"
|
group: "{{ matrix_user_username }}"
|
||||||
|
|
||||||
- name: Check if a mautrix-facebook configuration file exists
|
- name: Ensure mautrix-facebook config.yaml installed
|
||||||
stat:
|
copy:
|
||||||
path: "{{ matrix_mautrix_facebook_base_path }}/config.yaml"
|
content: "{{ matrix_mautrix_facebook_configuration|to_nice_yaml }}"
|
||||||
register: mautrix_facebook_config_file_stat
|
|
||||||
|
|
||||||
- name: Ensure Matrix Mautrix facebook config installed
|
|
||||||
template:
|
|
||||||
src: "{{ role_path }}/templates/config.yaml.j2"
|
|
||||||
dest: "{{ matrix_mautrix_facebook_base_path }}/config.yaml"
|
dest: "{{ matrix_mautrix_facebook_base_path }}/config.yaml"
|
||||||
mode: 0644
|
mode: 0644
|
||||||
owner: "{{ matrix_user_username }}"
|
owner: "{{ matrix_user_username }}"
|
||||||
group: "{{ matrix_user_username }}"
|
group: "{{ matrix_user_username }}"
|
||||||
when: "not mautrix_facebook_config_file_stat.stat.exists"
|
|
||||||
|
- name: Ensure mautrix-facebook registration.yaml installed
|
||||||
|
copy:
|
||||||
|
content: "{{ matrix_mautrix_facebook_registration|to_nice_yaml }}"
|
||||||
|
dest: "{{ matrix_mautrix_facebook_base_path }}/registration.yaml"
|
||||||
|
mode: 0644
|
||||||
|
owner: "{{ matrix_user_username }}"
|
||||||
|
group: "{{ matrix_user_username }}"
|
||||||
|
|
||||||
- name: Ensure matrix-mautrix-facebook.service installed
|
- name: Ensure matrix-mautrix-facebook.service installed
|
||||||
template:
|
template:
|
||||||
@ -47,24 +49,6 @@
|
|||||||
daemon_reload: yes
|
daemon_reload: yes
|
||||||
when: "matrix_mautrix_facebook_systemd_service_result.changed"
|
when: "matrix_mautrix_facebook_systemd_service_result.changed"
|
||||||
|
|
||||||
- name: Check if a mautrix-facebook registration file exists
|
|
||||||
stat:
|
|
||||||
path: "{{ matrix_mautrix_facebook_base_path }}/registration.yaml"
|
|
||||||
register: mautrix_facebook_registration_file_stat
|
|
||||||
|
|
||||||
- name: Generate matrix-mautrix-facebook registration.yaml if it doesn't exist
|
|
||||||
shell:
|
|
||||||
cmd: >-
|
|
||||||
/usr/bin/docker run
|
|
||||||
--rm
|
|
||||||
--user={{ matrix_user_uid }}:{{ matrix_user_gid }}
|
|
||||||
--cap-drop=ALL
|
|
||||||
--name matrix-mautrix-facebook-gen
|
|
||||||
-v {{ matrix_mautrix_facebook_base_path }}:/data:z
|
|
||||||
{{ matrix_mautrix_facebook_docker_image }}
|
|
||||||
python3 -m mautrix_facebook -g -c /data/config.yaml -r /data/registration.yaml
|
|
||||||
when: "not mautrix_facebook_registration_file_stat.stat.exists"
|
|
||||||
|
|
||||||
# If the matrix-synapse role is not used, these variables may not exist.
|
# If the matrix-synapse role is not used, these variables may not exist.
|
||||||
- set_fact:
|
- set_fact:
|
||||||
matrix_synapse_container_extra_arguments: >
|
matrix_synapse_container_extra_arguments: >
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
- name: Fail if required settings not defined
|
||||||
|
fail:
|
||||||
|
msg: >-
|
||||||
|
You need to define a required configuration setting (`{{ item }}`).
|
||||||
|
when: "vars[item] == ''"
|
||||||
|
with_items:
|
||||||
|
- "matrix_mautrix_facebook_appservice_token"
|
||||||
|
- "matrix_mautrix_facebook_homeserver_token"
|
@ -1,109 +0,0 @@
|
|||||||
#jinja2: lstrip_blocks: "True"
|
|
||||||
# Homeserver details
|
|
||||||
homeserver:
|
|
||||||
# The address that this appservice can use to connect to the homeserver.
|
|
||||||
address: {{ matrix_mautrix_facebook_homeserver_address }}
|
|
||||||
# The domain of the homeserver (for MXIDs, etc).
|
|
||||||
domain: {{ matrix_mautrix_facebook_homeserver_domain }}
|
|
||||||
# Whether or not to verify the SSL certificate of the homeserver.
|
|
||||||
# Only applies if address starts with https://
|
|
||||||
verify_ssl: true
|
|
||||||
|
|
||||||
# Application service host/registration related details
|
|
||||||
# Changing these values requires regeneration of the registration.
|
|
||||||
appservice:
|
|
||||||
# The address that the homeserver can use to connect to this appservice.
|
|
||||||
address: {{ matrix_mautrix_facebook_appservice_address }}
|
|
||||||
|
|
||||||
# The hostname and port where this appservice should listen.
|
|
||||||
hostname: 0.0.0.0
|
|
||||||
port: 8080
|
|
||||||
# The maximum body size of appservice API requests (from the homeserver) in mebibytes
|
|
||||||
# Usually 1 is enough, but on high-traffic bridges you might need to increase this to avoid 413s
|
|
||||||
max_body_size: 1
|
|
||||||
|
|
||||||
# The full URI to the database. SQLite and Postgres are fully supported.
|
|
||||||
# Other DBMSes supported by SQLAlchemy may or may not work.
|
|
||||||
# Format examples:
|
|
||||||
# SQLite: sqlite:///filename.db
|
|
||||||
# Postgres: postgres://username:password@hostname/dbname
|
|
||||||
database: sqlite:////data/mautrix-facebook.db
|
|
||||||
|
|
||||||
# The unique ID of this appservice.
|
|
||||||
id: facebook
|
|
||||||
# Username of the appservice bot.
|
|
||||||
bot_username: facebookbot
|
|
||||||
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
|
||||||
# to leave display name/avatar as-is.
|
|
||||||
bot_displayname: Facebook bridge bot
|
|
||||||
bot_avatar: mxc://maunium.net/ddtNPZSKMNqaUzqrHuWvUADv
|
|
||||||
|
|
||||||
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
|
|
||||||
as_token: "This value is generated when generating the registration"
|
|
||||||
hs_token: "This value is generated when generating the registration"
|
|
||||||
|
|
||||||
# Bridge config
|
|
||||||
bridge:
|
|
||||||
# Localpart template of MXIDs for Facebook users.
|
|
||||||
# {userid} is replaced with the user ID of the Facebook user.
|
|
||||||
username_template: "facebook_{userid}"
|
|
||||||
|
|
||||||
# The prefix for commands. Only required in non-management rooms.
|
|
||||||
command_prefix: "!fb"
|
|
||||||
|
|
||||||
# Number of chats to sync (and create portals for) on startup/login.
|
|
||||||
# Maximum 20, set 0 to disable automatic syncing.
|
|
||||||
initial_chat_sync: 10
|
|
||||||
# Whether or not the Facebook users of logged in Matrix users should be
|
|
||||||
# invited to private chats when the user sends a message from another client.
|
|
||||||
invite_own_puppet_to_pm: false
|
|
||||||
# Whether or not to use /sync to get presence, read receipts and typing notifications when using
|
|
||||||
# your own Matrix account as the Matrix puppet for your Facebook account.
|
|
||||||
sync_with_custom_puppets: true
|
|
||||||
# Whether or not to bridge presence in both directions. Facebook allows users not to broadcast
|
|
||||||
# presence, but then it won't send other users' presence to the client.
|
|
||||||
presence: true
|
|
||||||
|
|
||||||
# Permissions for using the bridge.
|
|
||||||
# Permitted values:
|
|
||||||
# user - Use the bridge with puppeting.
|
|
||||||
# admin - Use and administrate the bridge.
|
|
||||||
# Permitted keys:
|
|
||||||
# * - All Matrix users
|
|
||||||
# domain - All users on that homeserver
|
|
||||||
# mxid - Specific user
|
|
||||||
permissions:
|
|
||||||
'{{ matrix_mautrix_facebook_homeserver_domain }}': user
|
|
||||||
|
|
||||||
# Python logging configuration.
|
|
||||||
#
|
|
||||||
# See section 16.7.2 of the Python documentation for more info:
|
|
||||||
# https://docs.python.org/3.6/library/logging.config.html#configuration-dictionary-schema
|
|
||||||
logging:
|
|
||||||
version: 1
|
|
||||||
formatters:
|
|
||||||
colored:
|
|
||||||
(): mautrix_facebook.util.ColorFormatter
|
|
||||||
format: "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"
|
|
||||||
normal:
|
|
||||||
format: "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"
|
|
||||||
handlers:
|
|
||||||
file:
|
|
||||||
class: logging.handlers.RotatingFileHandler
|
|
||||||
formatter: normal
|
|
||||||
filename: /data/mautrix-facebook.log
|
|
||||||
maxBytes: 10485760
|
|
||||||
backupCount: 10
|
|
||||||
console:
|
|
||||||
class: logging.StreamHandler
|
|
||||||
formatter: colored
|
|
||||||
loggers:
|
|
||||||
mau:
|
|
||||||
level: DEBUG
|
|
||||||
fbchat:
|
|
||||||
level: DEBUG
|
|
||||||
aiohttp:
|
|
||||||
level: INFO
|
|
||||||
root:
|
|
||||||
level: DEBUG
|
|
||||||
handlers: [file, console]
|
|
Loading…
Reference in New Issue
Block a user