mirror of
https://github.com/spantaleev/matrix-docker-ansible-deploy.git
synced 2024-12-22 04:34:00 +00:00
Added Slack role
This commit is contained in:
parent
bd99dd05b4
commit
49766c5dac
81
docs/configuring-playbook-bridge-appservice-slack.md
Normal file
81
docs/configuring-playbook-bridge-appservice-slack.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Setting up Appservice Slack (optional)
|
||||
|
||||
The playbook can install and configure [matrix-appservice-slack](https://github.com/matrix-org/matrix-appservice-slack) for you.
|
||||
|
||||
See the project's [documentation](https://github.com/matrix-org/matrix-appservice-slack/blob/master/README.md) to learn what it does and why it might be useful to you.
|
||||
|
||||
Setup Instructions:
|
||||
|
||||
loosely based on [this](https://github.com/matrix-org/matrix-appservice-slack#Setup)
|
||||
|
||||
1. Create a new Matrix room to act as the administration control room. Note its internal room ID. This can
|
||||
be done in Riot by making a message, opening the options for that message and choosing "view source". The
|
||||
room ID will be displayed near the top.
|
||||
2. Enable the bridge with the following configuration in your `vars.yml` file:
|
||||
|
||||
```yaml
|
||||
matrix_appservice_slack_enabled: true
|
||||
matrix_appservice_slack_control_room_id: "Your matrix admin room id"
|
||||
```
|
||||
|
||||
3. If you've already installed Matrix services using the playbook before, you'll need to re-run it (`--tags=setup-all,start`). If not, proceed with [configuring other playbook services](configuring-playbook.md) and then with [Installing](installing.md). Get back to this guide once ready.
|
||||
4. Invite the bridge bot user into the admin room:
|
||||
|
||||
```
|
||||
/invite @slackbot:MY.DOMAIN
|
||||
```
|
||||
|
||||
Note that the bot's domain is your server's domain **without the `matrix.` prefix.**
|
||||
|
||||
5. Create a new Slack App [here](https://api.slack.com/apps).
|
||||
|
||||
Name the app & select the team/workspace this app will belong to.
|
||||
|
||||
Click on bot users and add a new bot user. We will use this account to bridge the the rooms.
|
||||
|
||||
6. Click on Event Subscriptions and enable them and use the request url `https://matrix.DOMAIN/appservice-slack`. Then add the following events and save:
|
||||
|
||||
Bot User Events:
|
||||
|
||||
- team_domain_change
|
||||
- message.channels
|
||||
- message.groups (if you want to bridge private channels)
|
||||
- team.info
|
||||
|
||||
7. Click on OAuth & Permissions and add the following scopes:
|
||||
|
||||
- chat:write:bot
|
||||
- users:read
|
||||
|
||||
If you want to bridge files, also add the following:
|
||||
|
||||
- files:write:user
|
||||
|
||||
Note: any media uploaded to matrix is currently accessible by anyone who knows the url. In order to make Slack files visible to matrix users, this bridge will make Slack files visible to anyone with the url (including files in private channels). This is different then the current behavior in Slack, which only allows authenticated access to media posted in private channels. See MSC701 for details.
|
||||
|
||||
8. Click on Install App and Install App to Workspace. Note the access tokens shown. You will need the Bot User OAuth Access Token and if you want to bridge files, the OAuth Access Token whenever you link a room.
|
||||
|
||||
9. For each channel you would like to bridge, perform the following steps:
|
||||
|
||||
* Create a Matrix room in the usual manner for your client. Take a note of its Matrix room ID - it will look something like !aBcDeF:example.com.
|
||||
|
||||
* Invite the bot user to both the Slack and Matrix channels you would like to bridge using `/invite @slackbot` for slack and `/invite @slackbot:MY.DOMAIN` for matrix.
|
||||
|
||||
* Determine the "channel ID" that Slack uses to identify the channel, which can be found in the url https://XXX.slack.com/messages/<channel id>/.
|
||||
|
||||
* Issue a link command in the administration control room with these collected values as arguments:
|
||||
|
||||
with file bridging:
|
||||
```
|
||||
link --channel_id CHANNELID --room !the-matrix:room.id --slack_bot_token xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx --slack_user_token xoxp-xxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxxxxxx
|
||||
```
|
||||
without file bridging:
|
||||
```
|
||||
link --channel_id CHANNELID --room !the-matrix:room.id --slack_bot_token xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
These arguments can be shortened to single-letter forms:
|
||||
```
|
||||
link -I CHANNELID -R !the-matrix:room.id -t xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
Other configuration options are available via the `matrix_appservice_slack_configuration_extension_yaml` variable.
|
@ -80,3 +80,5 @@ When you're done with all the configuration you'd like to do, continue with [Ins
|
||||
- [Setting up Appservice IRC bridging](configuring-playbook-bridge-appservice-irc.md) (optional)
|
||||
|
||||
- [Setting up Appservice Discord bridging](configuring-playbook-bridge-appservice-discord.md) (optional)
|
||||
|
||||
- [Setting up Appservice Slack bridging](configuring-playbook-bridge-appservice-slack.md) (optional)
|
||||
|
@ -60,6 +60,34 @@ matrix_appservice_discord_homeserver_token: "{{ matrix_synapse_macaroon_secret_k
|
||||
######################################################################
|
||||
|
||||
|
||||
######################################################################
|
||||
#
|
||||
# matrix-appservice-slack
|
||||
#
|
||||
######################################################################
|
||||
|
||||
# We don't enable bridges by default.
|
||||
matrix_appservice_slack_enabled: false
|
||||
|
||||
matrix_appservice_slack_appservice_token: "{{ matrix_synapse_macaroon_secret_key | password_hash('sha512', 'slack-appservice-token') | to_uuid }}"
|
||||
|
||||
matrix_appservice_slack_homeserver_token: "{{ matrix_synapse_macaroon_secret_key | password_hash('sha512', 'slack-homeserver-token') | to_uuid }}"
|
||||
|
||||
matrix_appservice_slack_id_token: "{{ matrix_synapse_macaroon_secret_key | password_hash('sha512', 'slack-id-token') | to_uuid }}"
|
||||
|
||||
matrix_appservice_slack_systemd_required_services_list: |
|
||||
{{
|
||||
['docker.service']
|
||||
+
|
||||
(['matrix-synapse.service'] if matrix_synapse_enabled else [])
|
||||
}}
|
||||
|
||||
######################################################################
|
||||
#
|
||||
# /matrix-bridge-appservice-slack
|
||||
#
|
||||
######################################################################
|
||||
|
||||
######################################################################
|
||||
#
|
||||
# matrix-bridge-appservice-irc
|
||||
|
104
roles/matrix-bridge-appservice-slack/defaults/main.yml
Normal file
104
roles/matrix-bridge-appservice-slack/defaults/main.yml
Normal file
@ -0,0 +1,104 @@
|
||||
# matrix-appservice-slack is a Matrix <-> Slack bridge
|
||||
# See: https://github.com/matrix-org/matrix-appservice-slack
|
||||
|
||||
matrix_appservice_slack_enabled: true
|
||||
|
||||
matrix_appservice_slack_docker_image: "cadair/matrix-appservice-slack:latest"
|
||||
|
||||
matrix_appservice_slack_base_path: "{{ matrix_base_data_path }}/appservice-slack"
|
||||
matrix_appservice_slack_config_path: "{{ matrix_appservice_slack_base_path }}/config"
|
||||
matrix_appservice_slack_data_path: "{{ matrix_appservice_slack_base_path }}/data"
|
||||
|
||||
matrix_appservice_slack_public_endpoint: /appservice-slack
|
||||
matrix_appservice_slack_inbound_uri_prefix: "{{ matrix_homeserver_url }}{{ matrix_appservice_slack_public_endpoint }}"
|
||||
|
||||
# Once you make a control room in Matrix, you can get its ID by typing any message and checking its source
|
||||
matrix_appservice_slack_control_room_id: ''
|
||||
matrix_appservice_slack_bot_name: 'slackbot'
|
||||
matrix_appservice_slack_user_prefix: 'slack_'
|
||||
|
||||
# Controls the SLACK_PORT and MATRIX_PORT of the installation
|
||||
matrix_appservice_slack_matrix_port: 9004
|
||||
matrix_appservice_slack_slack_port: 9003
|
||||
|
||||
# Takes an "<ip>:<port>" or "<port>" value (e.g. "127.0.0.1:9999"), or empty string to not expose.
|
||||
matrix_appservice_slack_container_http_host_bind_port: ''
|
||||
|
||||
matrix_appservice_slack_homeserver_media_url: "matrix.{{ matrix_domain }}"
|
||||
matrix_appservice_slack_homeserver_url: "http://matrix-synapse:8008"
|
||||
matrix_appservice_slack_homeserver_domain: "{{ matrix_domain }}"
|
||||
matrix_appservice_slack_appservice_url: 'http://matrix-appservice-slack'
|
||||
|
||||
# A list of extra arguments to pass to the container
|
||||
matrix_appservice_slack_container_extra_arguments: []
|
||||
|
||||
# List of systemd services that matrix-appservice-slack.service depends on.
|
||||
matrix_appservice_slack_systemd_required_services_list: ['docker.service']
|
||||
|
||||
# List of systemd services that matrix-appservice-slack.service wants
|
||||
matrix_appservice_slack_systemd_wanted_services_list: []
|
||||
|
||||
matrix_appservice_slack_appservice_token: ''
|
||||
matrix_appservice_slack_homeserver_token: ''
|
||||
matrix_appservice_slack_id_token: ''
|
||||
|
||||
matrix_appservice_slack_configuration_yaml: |
|
||||
slack_hook_port: {{ matrix_appservice_slack_slack_port }}
|
||||
inbound_uri_prefix: "{{ matrix_appservice_slack_inbound_uri_prefix }}"
|
||||
bot_username: "{{ matrix_appservice_slack_bot_name }}"
|
||||
username_prefix: {{ matrix_appservice_slack_user_prefix }}
|
||||
|
||||
homeserver:
|
||||
media_url: "{{ matrix_appservice_slack_homeserver_media_url }}"
|
||||
url: "{{ matrix_appservice_slack_homeserver_url }}"
|
||||
server_name: "{{ matrix_domain }}"
|
||||
|
||||
dbdir: "/data"
|
||||
|
||||
matrix_admin_room: "{{ matrix_appservice_slack_control_room_id }}"
|
||||
|
||||
|
||||
matrix_appservice_slack_configuration_extension_yaml: |
|
||||
#slack_hook_port: 9898
|
||||
#inbound_uri_prefix: "https://my.server.here:9898/"
|
||||
#bot_username: "slackbot"
|
||||
#username_prefix: "slack_"
|
||||
# Optional
|
||||
#slack_master_token: "abc-123-def"
|
||||
# Optional
|
||||
#matrix_admin_room: "!aBcDeF:matrix.org"
|
||||
#homeserver:
|
||||
# url: http://localhost:8008
|
||||
# server_name: my.server
|
||||
# Optional
|
||||
#tls:
|
||||
# key_file: /path/to/tls.key
|
||||
# crt_file: /path/to/tls.crt
|
||||
#logging:
|
||||
# console: "info"
|
||||
# files:
|
||||
# - "./debug.log": "info"
|
||||
#- "./error.log": "error"
|
||||
|
||||
matrix_appservice_slack_configuration_extension: "{{ matrix_appservice_slack_configuration_extension_yaml|from_yaml if matrix_appservice_slack_configuration_extension_yaml|from_yaml else {} }}"
|
||||
|
||||
matrix_appservice_slack_configuration: "{{ matrix_appservice_slack_configuration_yaml|from_yaml|combine(matrix_appservice_slack_configuration_extension, recursive=True) }}"
|
||||
|
||||
matrix_appservice_slack_registration_yaml: |
|
||||
id: "{{ matrix_appservice_slack_id_token }}"
|
||||
as_token: "{{ matrix_appservice_slack_appservice_token }}"
|
||||
hs_token: "{{ matrix_appservice_slack_homeserver_token }}"
|
||||
namespaces:
|
||||
users:
|
||||
- exclusive: true
|
||||
regex: '@{{ matrix_appservice_slack_user_prefix }}.*'
|
||||
aliases:
|
||||
- exclusive: false
|
||||
regex: '#{{ matrix_appservice_slack_user_prefix }}.*'
|
||||
rooms: []
|
||||
url: "{{matrix_appservice_slack_appservice_url}}:{{ matrix_appservice_slack_matrix_port }}"
|
||||
sender_localpart: slackbot
|
||||
rate_limited: true
|
||||
protocols: null
|
||||
|
||||
matrix_appservice_slack_registration: "{{ matrix_appservice_slack_registration_yaml|from_yaml }}"
|
79
roles/matrix-bridge-appservice-slack/tasks/init.yml
Normal file
79
roles/matrix-bridge-appservice-slack/tasks/init.yml
Normal file
@ -0,0 +1,79 @@
|
||||
# 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-bridge-appservice-slack role needs to execute before the matrix-synapse role.
|
||||
when: "matrix_synapse_role_executed|default(False)"
|
||||
|
||||
- set_fact:
|
||||
matrix_systemd_services_list: "{{ matrix_systemd_services_list + ['matrix-appservice-slack'] }}"
|
||||
when: matrix_appservice_slack_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_appservice_slack_config_path }}/slack-registration.yaml,dst=/matrix-appservice-slack-registration.yaml,ro"] }}
|
||||
|
||||
matrix_synapse_app_service_config_files: >
|
||||
{{ matrix_synapse_app_service_config_files|default([]) }}
|
||||
+
|
||||
{{ ["/matrix-appservice-slack-registration.yaml"] }}
|
||||
when: matrix_appservice_slack_enabled|bool
|
||||
|
||||
# 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-bridge-appservice-slack role needs to execute before the matrix-synapse role.
|
||||
when: "matrix_synapse_role_executed|default(False)"
|
||||
|
||||
- block:
|
||||
- name: Fail if matrix-nginx-proxy role already executed
|
||||
fail:
|
||||
msg: >-
|
||||
Trying to append Slack Appservice's reverse-proxying configuration to matrix-nginx-proxy,
|
||||
but it's pointless since the matrix-nginx-proxy role had already executed.
|
||||
To fix this, please change the order of roles in your plabook,
|
||||
so that the matrix-nginx-proxy role would run after the matrix-bridge-appservice-slack role.
|
||||
when: matrix_nginx_proxy_role_executed|default(False)|bool
|
||||
|
||||
- name: Generate Matrix Appservice Slack proxying configuration for matrix-nginx-proxy
|
||||
set_fact:
|
||||
matrix_appservice_slack_matrix_nginx_proxy_configuration: |
|
||||
location {{ matrix_appservice_slack_public_endpoint }} {
|
||||
{% if matrix_nginx_proxy_enabled|default(False) %}
|
||||
{# Use the embedded DNS resolver in Docker containers to discover the service #}
|
||||
resolver 127.0.0.11 valid=5s;
|
||||
set $backend "{{ matrix_appservice_slack_appservice_url }}:{{ matrix_appservice_slack_slack_port }}";
|
||||
proxy_pass $backend;
|
||||
{% else %}
|
||||
{# Generic configuration for use outside of our container setup #}
|
||||
proxy_pass http://127.0.0.1:{{ matrix_appservice_slack_slack_port }};
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
- name: Register Slack Appservice proxying configuration with matrix-nginx-proxy
|
||||
set_fact:
|
||||
matrix_nginx_proxy_proxy_matrix_additional_server_configuration_blocks: |
|
||||
{{
|
||||
matrix_nginx_proxy_proxy_matrix_additional_server_configuration_blocks|default([])
|
||||
+
|
||||
[matrix_appservice_slack_matrix_nginx_proxy_configuration]
|
||||
}}
|
||||
tags:
|
||||
- always
|
||||
when: matrix_appservice_slack_enabled|bool
|
||||
|
||||
- name: Warn about reverse-proxying if matrix-nginx-proxy not used
|
||||
debug:
|
||||
msg: >-
|
||||
NOTE: You've enabled the Matrix Slack bridge but are not using the matrix-nginx-proxy
|
||||
reverse proxy.
|
||||
Please make sure that you're proxying the `{{ something }}`
|
||||
URL endpoint to the matrix-appservice-slack container.
|
||||
You can expose the container's port using the `matrix_appservice_slack_container_http_host_bind_port` variable.
|
||||
when: "matrix_appservice_slack_enabled|bool and matrix_nginx_proxy_enabled is not defined"
|
21
roles/matrix-bridge-appservice-slack/tasks/main.yml
Normal file
21
roles/matrix-bridge-appservice-slack/tasks/main.yml
Normal file
@ -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_appservice_slack_enabled|bool"
|
||||
tags:
|
||||
- setup-all
|
||||
- setup-appservice-slack
|
||||
|
||||
- import_tasks: "{{ role_path }}/tasks/setup_install.yml"
|
||||
when: "run_setup|bool and matrix_appservice_slack_enabled|bool"
|
||||
tags:
|
||||
- setup-all
|
||||
- setup-appservice-slack
|
||||
|
||||
- import_tasks: "{{ role_path }}/tasks/setup_uninstall.yml"
|
||||
when: "run_setup|bool and not matrix_appservice_slack_enabled|bool"
|
||||
tags:
|
||||
- setup-all
|
||||
- setup-appservice-slack
|
46
roles/matrix-bridge-appservice-slack/tasks/setup_install.yml
Normal file
46
roles/matrix-bridge-appservice-slack/tasks/setup_install.yml
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
|
||||
- name: Ensure Appservice Slack image is pulled
|
||||
docker_image:
|
||||
name: "{{ matrix_appservice_slack_docker_image }}"
|
||||
source: "{{ 'pull' if ansible_version.major > 2 or ansible_version.minor > 7 else omit }}"
|
||||
|
||||
- name: Ensure AppService Slack paths exist
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: 0750
|
||||
owner: "{{ matrix_user_username }}"
|
||||
group: "{{ matrix_user_username }}"
|
||||
with_items:
|
||||
- "{{ matrix_appservice_slack_base_path }}"
|
||||
- "{{ matrix_appservice_slack_config_path }}"
|
||||
- "{{ matrix_appservice_slack_data_path }}"
|
||||
|
||||
- name: Ensure Matrix Appservice Slack config installed
|
||||
copy:
|
||||
content: "{{ matrix_appservice_slack_configuration|to_nice_yaml }}"
|
||||
dest: "{{ matrix_appservice_slack_config_path }}/config.yaml"
|
||||
mode: 0644
|
||||
owner: "{{ matrix_user_username }}"
|
||||
group: "{{ matrix_user_username }}"
|
||||
|
||||
- name: Ensure appservice-slack registration.yaml installed
|
||||
copy:
|
||||
content: "{{ matrix_appservice_slack_registration|to_nice_yaml }}"
|
||||
dest: "{{ matrix_appservice_slack_config_path }}/slack-registration.yaml"
|
||||
mode: 0644
|
||||
owner: "{{ matrix_user_username }}"
|
||||
group: "{{ matrix_user_username }}"
|
||||
|
||||
- name: Ensure matrix-appservice-slack.service installed
|
||||
template:
|
||||
src: "{{ role_path }}/templates/systemd/matrix-appservice-slack.service.j2"
|
||||
dest: "/etc/systemd/system/matrix-appservice-slack.service"
|
||||
mode: 0644
|
||||
register: matrix_appservice_slack_systemd_service_result
|
||||
|
||||
- name: Ensure systemd reloaded after matrix-appservice-slack.service installation
|
||||
service:
|
||||
daemon_reload: yes
|
||||
when: "matrix_appservice_slack_systemd_service_result.changed"
|
@ -0,0 +1,24 @@
|
||||
---
|
||||
|
||||
- name: Check existence of matrix-appservice-slack service
|
||||
stat:
|
||||
path: "/etc/systemd/system/matrix-appservice-slack.service"
|
||||
register: matrix_appservice_slack_service_stat
|
||||
|
||||
- name: Ensure matrix-appservice-slack is stopped
|
||||
service:
|
||||
name: matrix-appservice-slack
|
||||
state: stopped
|
||||
daemon_reload: yes
|
||||
when: "matrix_appservice_slack_service_stat.stat.exists"
|
||||
|
||||
- name: Ensure matrix-appservice-slack.service doesn't exist
|
||||
file:
|
||||
path: "/etc/systemd/system/matrix-appservice-slack.service"
|
||||
state: absent
|
||||
when: "matrix_appservice_slack_service_stat.stat.exists"
|
||||
|
||||
- name: Ensure systemd reloaded after matrix-appservice-slack.service removal
|
||||
service:
|
||||
daemon_reload: yes
|
||||
when: "matrix_appservice_slack_service_stat.stat.exists"
|
@ -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_appservice_slack_control_room_id"
|
||||
- "matrix_appservice_slack_appservice_token"
|
||||
- "matrix_appservice_slack_homeserver_token"
|
||||
- "matrix_appservice_slack_id_token"
|
@ -0,0 +1,38 @@
|
||||
#jinja2: lstrip_blocks: "True"
|
||||
[Unit]
|
||||
Description=Matrix Appservice Slack server
|
||||
{% for service in matrix_appservice_slack_systemd_required_services_list %}
|
||||
Requires={{ service }}
|
||||
After={{ service }}
|
||||
{% endfor %}
|
||||
{% for service in matrix_appservice_slack_systemd_wanted_services_list %}
|
||||
Wants={{ service }}
|
||||
{% endfor %}
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=-/usr/bin/docker kill matrix-appservice-slack
|
||||
ExecStartPre=-/usr/bin/docker rm matrix-appservice-slack
|
||||
|
||||
# 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-appservice-slack \
|
||||
--log-driver=none \
|
||||
--network={{ matrix_docker_network }} \
|
||||
-v {{ matrix_appservice_slack_config_path }}:/config:z \
|
||||
-v {{ matrix_appservice_slack_data_path }}:/data:z \
|
||||
{% for arg in matrix_appservice_slack_container_extra_arguments %}
|
||||
{{ arg }} \
|
||||
{% endfor %}
|
||||
{{ matrix_appservice_slack_docker_image }} \
|
||||
node app.js -c /config/config.yaml -f /config/slack-registration.yaml -p {{matrix_appservice_slack_matrix_port}}
|
||||
|
||||
ExecStop=-/usr/bin/docker kill matrix-appservice-slack
|
||||
ExecStop=-/usr/bin/docker rm matrix-appservice-slack
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
SyslogIdentifier=matrix-appservice-slack
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
Loading…
Reference in New Issue
Block a user