Просмотр исходного кода

Improve Synology DSM support based on maintainer feedback

- Add detect_platform.yml with a single `matrix_base_host_is_synology`
  boolean (null = autodetect, true/false = force); all Synology gates now
  use this variable instead of reaching into the raw stat result
- Make recursive chown conditional: stat the data path before any changes
  and only run `chown -R` (native binary, not Ansible file recurse) when
  uid/gid actually differs from expected
- Expose `matrix_base_synology_requests_version_constraint` so the
  requests<2.32 pin can be bumped without a code change
- Replace Python GID lookup with `synogroup --get` + regex_search,
  dropping the python3 dependency for that step
- Require `matrix_synology_user_password` (non-empty) and create the
  service account with expired=1 so it cannot be used to log in to DSM
- Apply `mount --make-shared /volume1` during setup so the first run
  no longer requires a manual step before starting services
- Use `devture_systemd_docker_base_host_command_docker` and
  `devture_systemd_docker_base_host_command_systemctl` in the boot-fix
  script instead of hardcoded paths
- Boot-fix now also restarts matrix-*.service units in `failed` state,
  not only `inactive`
- Update docs to reflect all of the above

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pull/5315/head
CKSit 1 месяц назад
Родитель
Сommit
3558618736
9 измененных файлов: 144 добавлений и 49 удалений
  1. +33
    -16
      docs/configuring-playbook-synology.md
  2. +15
    -0
      roles/custom/matrix-base/defaults/main.yml
  3. +16
    -0
      roles/custom/matrix-base/tasks/detect_platform.yml
  4. +6
    -1
      roles/custom/matrix-base/tasks/main.yml
  5. +21
    -7
      roles/custom/matrix-base/tasks/setup_matrix_base.yml
  6. +2
    -7
      roles/custom/matrix-base/tasks/setup_matrix_user.yml
  7. +32
    -9
      roles/custom/matrix-base/tasks/setup_matrix_user_synology.yml
  8. +8
    -2
      roles/custom/matrix-base/tasks/setup_synology_prerequisites.yml
  9. +11
    -7
      roles/custom/matrix-base/templates/bin/matrix-synology-boot-fix.j2

+ 33
- 16
docs/configuring-playbook-synology.md Просмотреть файл

@@ -23,20 +23,33 @@ This document is a guide for preparing Synology DSM for the installation of the
The playbook automatically detects Synology DSM by checking for `/etc/synoinfo.conf`. When detected, it:

- Uses `synouser` and `synogroup` (DSM-native tools) instead of standard Linux user management
- Pins the Python `requests` package to a version compatible with the Docker SDK
- Constrains the Python `requests` package to a version compatible with the Docker SDK
- Ensures `/volume1` has shared mount propagation so container bind mounts work correctly
- Deploys a `matrix-synology-boot-fix` service that runs on every boot after Docker is ready

You can override auto-detection by setting `matrix_base_host_is_synology: true` or `false` in your `vars.yml`.

### Matrix Service Account

The playbook creates a `matrix` system account using Synology's `synouser` tool. The account is secured as follows:

- **Expired** (`expired=1`) — the account cannot be used to log in to DSM or any application

You must set a password for this account via `matrix_synology_user_password` in your `vars.yml` (see [vars.yml Configuration](#varsyml-configuration)). The password cannot be used to log in because the account is expired, but a non-empty password is required as an additional security layer.

> If you pre-create the `matrix` user manually before running the playbook, the playbook will not modify the existing account's settings — you are responsible for securing it.

### Boot-fix Service

Synology DSM has two boot-time quirks that the boot-fix service addresses automatically:

**1. `/volume1` shared mount propagation**

Docker requires `/volume1` to be mounted as shared (`mount --make-shared /volume1`) for container bind mounts with `bind-propagation=slave` to work correctly (used by matrix-synapse for its media store). On Synology, this cannot be inserted into the systemd chain before Container Manager starts — doing so causes Container Manager to detect a broken dependency and prompt for repair on every boot. The boot-fix service runs this command after Docker is already up, safely outside Container Manager's dependency chain.
Docker requires `/volume1` to be mounted as shared (`mount --make-shared /volume1`) for container bind mounts with `bind-propagation=slave` to work correctly (used by matrix-synapse for its media store). On Synology, this cannot be inserted into the systemd chain before Container Manager starts — doing so causes Container Manager to detect a broken dependency and prompt for repair on every boot. The playbook applies this during setup, and the boot-fix service re-applies it on every subsequent reboot, safely outside Container Manager's dependency chain.

**2. Skipped services at boot**

Synology's systemd drops services with multi-level dependency chains from the boot activation queue (e.g. `matrix-traefik → matrix-container-socket-proxy → docker`). These services show as `inactive (dead)` after reboot even though they are enabled. The boot-fix service scans for any enabled `matrix-*.service` that is still inactive after boot and starts them automatically.
Synology's systemd drops services with multi-level dependency chains from the boot activation queue (e.g. `matrix-traefik → matrix-container-socket-proxy → docker`). These services show as `inactive` or `failed` after reboot even though they are enabled. The boot-fix service scans for any enabled `matrix-*.service` in either state and starts them automatically.

> **If you previously configured a Task Scheduler entry** (`Control Panel > Task Scheduler`) to run `mount --make-shared /volume1` at boot-up, you can remove it — the boot-fix service now handles this.

@@ -76,7 +89,8 @@ mkdir ~/path/to/your/project/folder
cd ~/path/to/your/project/folder

python3 -m venv ./myenv
source ./myenv/bin/activate
# (optional) activate python virtual environment
# source ./myenv/bin/activate
```

## Inventory Configuration
@@ -84,8 +98,8 @@ source ./myenv/bin/activate
In your `inventory/hosts` file, set the Python interpreter to your virtual environment:

```ini
# SSH key authentication example
matrix.example.com ansible_host=<your-dsm-ip> ansible_ssh_user=<dsm-ssh-user> become=true become_user=root ansible_python_interpreter=/absolute/path/to/myenv/bin/python ansible_sudo_pass='your-password'
# SSH key authentication with empty passphrase example
matrix.example.com ansible_host=<your-dsm-ip> ansible_ssh_user=<dsm-ssh-user> become=true become_user=root ansible_python_interpreter=/volume1/homes/path/to/your/project/folder/myenv/bin/python ansible_sudo_pass='your-password'
```

## vars.yml Configuration
@@ -95,6 +109,14 @@ Add the following Synology-specific variables to your `vars.yml`:
```yaml
# Synology-specific settings

# Controls Synology DSM-specific handling. `null` means autodetect (via /etc/synoinfo.conf).
# Set to `true`/`false` to force.
# matrix_base_host_is_synology: true

# Password for the Matrix service account created by the playbook.
# The account is created as expired so this password cannot be used to log in.
matrix_synology_user_password: "your-strong-password"

# User and group that will be created automatically by the playbook
matrix_user_name: "matrix"
matrix_group_name: "matrix"
@@ -104,7 +126,7 @@ matrix_base_data_path: "/volume1/docker/matrix"

# Use Synology Container Manager's Docker daemon instead of installing Docker
matrix_playbook_docker_installation_enabled: false
devture_systemd_docker_base_host_command_docker: "/usr/local/bin/docker"
devture_systemd_docker_base_host_command_docker: "/var/packages/ContainerManager/target/usr/bin/docker"
devture_systemd_docker_base_docker_service_name: "pkg-ContainerManager-dockerd.service"

# Use Synology's NTP service
@@ -128,17 +150,12 @@ matrix_playbook_public_matrix_federation_api_traefik_entrypoint_config_custom:

## Running the Playbook

Before running the playbook for the first time, run this once manually to ensure `/volume1` has shared mount propagation for the initial setup:

```shell
sudo mount --make-shared /volume1
```

After the playbook runs, this is handled automatically on every subsequent boot by the `matrix-synology-boot-fix` service.
# Full setup
ansible-playbook -i inventory/hosts setup.yml --tags=setup-all

```shell
# Full setup and start
ansible-playbook -i inventory/hosts setup.yml --tags=setup-all,start
# start
ansible-playbook -i inventory/hosts setup.yml --tags=install-all,start

# Stop all services
ansible-playbook -i inventory/hosts setup.yml --tags=stop


+ 15
- 0
roles/custom/matrix-base/defaults/main.yml Просмотреть файл

@@ -195,6 +195,21 @@ matrix_group_system: true
matrix_user_uid: ~
matrix_user_gid: ~

# Controls Synology DSM-specific handling. `null` means autodetect (via /etc/synoinfo.conf).
# Set to `true`/`false` to force.
matrix_base_host_is_synology: ~

# Password for the Matrix service account on Synology DSM.
# Must be set to a non-empty value in your vars.yml when running on Synology.
# The account is created as expired so the password cannot be used to log in.
matrix_synology_user_password: ""

# Version constraint for the requests Python package installed on Synology hosts.
# requests >= 2.32 dropped the http+docker URL scheme used by the Docker SDK,
# causing "Not supported URL scheme http+docker" errors. Installed into the
# system Python interpreter (ansible_python_interpreter) on the remote host.
matrix_base_synology_requests_version_constraint: "requests<2.32"

matrix_base_data_path: "/matrix"
matrix_base_data_path_mode: "750"



+ 16
- 0
roles/custom/matrix-base/tasks/detect_platform.yml Просмотреть файл

@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: 2026 Chiu Ki Sit
#
# SPDX-License-Identifier: AGPL-3.0-or-later

---

- name: Detect Synology DSM
ansible.builtin.stat:
path: /etc/synoinfo.conf
register: matrix_base_synoinfo_conf_stat
when: matrix_base_host_is_synology is none

- name: Set matrix_base_host_is_synology from detection
ansible.builtin.set_fact:
matrix_base_host_is_synology: "{{ matrix_base_synoinfo_conf_stat.stat.exists }}"
when: matrix_base_host_is_synology is none

+ 6
- 1
roles/custom/matrix-base/tasks/main.yml Просмотреть файл

@@ -16,6 +16,11 @@
block:
- ansible.builtin.include_tasks: "{{ role_path }}/tasks/validate_config.yml"

- tags:
- always
block:
- ansible.builtin.include_tasks: "{{ role_path }}/tasks/detect_platform.yml"

# This needs to always run, because it populates `matrix_user_uid` and `matrix_user_gid`,
# which are required by many other roles.
- tags:
@@ -30,7 +35,7 @@
- install-all
block:
- ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_synology_prerequisites.yml"
when: matrix_synoinfo_conf_stat.stat.exists | bool
when: matrix_base_host_is_synology

- tags:
- setup-all


+ 21
- 7
roles/custom/matrix-base/tasks/setup_matrix_base.yml Просмотреть файл

@@ -13,6 +13,14 @@

---

# Snapshot ownership before any changes so we can decide whether a recursive
# chown is needed (only when uid/gid actually differs from expected).
- name: Check current ownership of Matrix base path (Synology)
ansible.builtin.stat:
path: "{{ matrix_base_data_path }}"
register: matrix_base_data_path_stat
when: matrix_base_host_is_synology

- name: Ensure Matrix base paths exists
ansible.builtin.file:
path: "{{ item }}"
@@ -30,11 +38,17 @@
dest: "{{ matrix_bin_path }}/remove-all"
mode: '0750'

# On Synology, name-based chown works for directly-touched paths but leaves
# existing sub-paths with stale numeric ownership when uid/gid changes between
# runs. We recurse only when the pre-task uid/gid didn't match, so normal runs
# skip the expensive tree walk entirely. chown -R is used instead of the file
# module's recurse option to avoid Ansible iterating every entry in Python.
- name: Ensure Matrix base path ownership is correct using numeric UID/GID (Synology)
ansible.builtin.file:
path: "{{ matrix_base_data_path }}"
owner: "{{ matrix_user_uid }}"
group: "{{ matrix_user_gid }}"
recurse: true
state: directory
when: matrix_synoinfo_conf_stat.stat.exists | bool
ansible.builtin.command: chown -R {{ matrix_user_uid }}:{{ matrix_user_gid }} {{ matrix_base_data_path }}
changed_when: true
when: >-
matrix_base_host_is_synology and (
not matrix_base_data_path_stat.stat.exists or
matrix_base_data_path_stat.stat.uid | int != matrix_user_uid | int or
matrix_base_data_path_stat.stat.gid | int != matrix_user_gid | int
)

+ 2
- 7
roles/custom/matrix-base/tasks/setup_matrix_user.yml Просмотреть файл

@@ -6,13 +6,8 @@

---

- name: Check for Synology DSM
ansible.builtin.stat:
path: /etc/synoinfo.conf
register: matrix_synoinfo_conf_stat

- ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_matrix_user_synology.yml"
when: matrix_synoinfo_conf_stat.stat.exists | bool
when: matrix_base_host_is_synology

- ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_matrix_user_linux.yml"
when: not (matrix_synoinfo_conf_stat.stat.exists | bool)
when: not matrix_base_host_is_synology

+ 32
- 9
roles/custom/matrix-base/tasks/setup_matrix_user_synology.yml Просмотреть файл

@@ -4,16 +4,36 @@

---

- name: Fail if matrix_synology_user_password is not set
ansible.builtin.fail:
msg: >-
You must set `matrix_synology_user_password` to a non-empty value in your vars.yml.
This password secures the Matrix service account on Synology DSM.
The account is created as expired so the password cannot be used to log in.
when: matrix_synology_user_password == '' or matrix_synology_user_password is none

- name: Check if Matrix user exists (Synology)
ansible.builtin.command: id {{ matrix_user_name }}
register: matrix_user_check
changed_when: false
failed_when: false

# Created with expired=1 (cannot log in)
# as this is a service account. If you pre-create the user, you are responsible
# for securing it; the playbook will not modify an existing account's settings.
- name: Ensure Matrix user is created (Synology)
ansible.builtin.command: /usr/syno/sbin/synouser --add {{ matrix_user_name }} "" "{{ matrix_user_name }}" 0 "" ""
ansible.builtin.command: >
/usr/syno/sbin/synouser --add {{ matrix_user_name }}
"{{ matrix_synology_user_password }}" "{{ matrix_user_name }}" 1 "" 0
when: matrix_user_check.rc != 0
changed_when: true
no_log: true

- name: Ensure Matrix user password is up to date (Synology)
ansible.builtin.command: /usr/syno/sbin/synouser --setpw {{ matrix_user_name }} "{{ matrix_synology_user_password }}"
when: matrix_user_check.rc == 0
changed_when: false
no_log: true

- name: Check if Matrix group exists (Synology)
ansible.builtin.command: /usr/syno/sbin/synogroup --get {{ matrix_group_name }}
@@ -31,16 +51,19 @@
register: matrix_user_uid_result
changed_when: false

- name: Get Matrix group GID (Synology)
ansible.builtin.command:
argv:
- python3
- -c
- "import grp; print(grp.getgrnam('{{ matrix_group_name }}').gr_gid)"
register: matrix_user_gid_result
- name: Get Matrix group info (Synology)
ansible.builtin.command: /usr/syno/sbin/synogroup --get {{ matrix_group_name }}
register: matrix_synogroup_result
changed_when: false

- name: Initialize matrix_user_uid and matrix_user_gid
ansible.builtin.set_fact:
matrix_user_uid: "{{ matrix_user_uid_result.stdout }}"
matrix_user_gid: "{{ matrix_user_gid_result.stdout }}"
matrix_user_gid: >-
{{
matrix_synogroup_result.stdout_lines
| select('match', '^Group ID:')
| first
| regex_search('\[(\d+)\]', '\1')
| first
}}

+ 8
- 2
roles/custom/matrix-base/tasks/setup_synology_prerequisites.yml Просмотреть файл

@@ -4,9 +4,15 @@

---

- name: Ensure requests Python package is pinned for Docker SDK compatibility (Synology)
- name: Ensure requests Python package is constrained for Docker SDK compatibility (Synology)
ansible.builtin.pip:
name: requests==2.31.0
name: "{{ matrix_base_synology_requests_version_constraint }}"
state: present

# Run immediately during setup so matrix services can start without a manual
# step. The boot-fix service handles this on every subsequent reboot.
- name: Ensure /volume1 has shared mount propagation (Synology)
ansible.builtin.command: mount --make-shared /volume1
changed_when: false

- ansible.builtin.include_tasks: "{{ role_path }}/tasks/setup_synology_boot_fix.yml"

+ 11
- 7
roles/custom/matrix-base/templates/bin/matrix-synology-boot-fix.j2 Просмотреть файл

@@ -22,12 +22,12 @@
# Wait up to 120s for Docker to be ready
i=0
while [ "$i" -lt 60 ]; do
/usr/local/bin/docker info >/dev/null 2>&1 && break
{{ devture_systemd_docker_base_host_command_docker }} info >/dev/null 2>&1 && break
i=$((i + 1))
sleep 2
done

if ! /usr/local/bin/docker info >/dev/null 2>&1; then
if ! {{ devture_systemd_docker_base_host_command_docker }} info >/dev/null 2>&1; then
echo "matrix-synology-boot-fix: Docker not ready after 120s, aborting" >&2
exit 1
fi
@@ -39,12 +39,16 @@ fi
/bin/mount --make-shared /volume1
echo "matrix-synology-boot-fix: /volume1 set to shared mount propagation"

# Start any enabled matrix-*.service that is inactive (skipped at boot, not intentionally stopped)
/bin/systemctl list-unit-files 'matrix-*.service' --state=enabled --no-legend 2>/dev/null | \
# Start any enabled matrix-*.service that is inactive or failed.
# Both states indicate the service did not come up at boot — either skipped by
# Synology's boot ordering or failed due to Docker/mount-propagation not being
# ready yet (the conditions above now satisfy those prerequisites).
{{ devture_systemd_docker_base_host_command_systemctl }} list-unit-files 'matrix-*.service' --state=enabled --no-legend 2>/dev/null | \
while read -r unit _state; do
[ "$unit" = "matrix-synology-boot-fix.service" ] && continue
if [ "$(/bin/systemctl is-active "$unit" 2>/dev/null)" = "inactive" ]; then
echo "matrix-synology-boot-fix: starting $unit"
/bin/systemctl start "$unit"
status="$({{ devture_systemd_docker_base_host_command_systemctl }} is-active "$unit" 2>/dev/null)"
if [ "$status" = "inactive" ] || [ "$status" = "failed" ]; then
echo "matrix-synology-boot-fix: starting $unit (was $status)"
{{ devture_systemd_docker_base_host_command_systemctl }} start "$unit"
fi
done

Загрузка…
Отмена
Сохранить